fixed bug with random calls
[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 \r
753     /* [HGM] time odds: set factor for each machine */\r
754     first.timeOdds  = appData.firstTimeOdds;\r
755     second.timeOdds = appData.secondTimeOdds;\r
756     { int norm = 1;\r
757         if(appData.timeOddsMode) {\r
758             norm = first.timeOdds;\r
759             if(norm > second.timeOdds) norm = second.timeOdds;\r
760         }\r
761         first.timeOdds /= norm;\r
762         second.timeOdds /= norm;\r
763     }\r
764 \r
765     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/\r
766     first.accumulateTC = appData.firstAccumulateTC;\r
767     second.accumulateTC = appData.secondAccumulateTC;\r
768     first.maxNrOfSessions = second.maxNrOfSessions = 1;\r
769 \r
770     /* [HGM] debug */\r
771     first.debug = second.debug = FALSE;\r
772     first.supportsNPS = second.supportsNPS = UNKNOWN;\r
773 \r
774     /* [HGM] options */\r
775     first.optionSettings  = appData.firstOptions;\r
776     second.optionSettings = appData.secondOptions;\r
777 \r
778     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
779     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
780     first.isUCI = appData.firstIsUCI; /* [AS] */\r
781     second.isUCI = appData.secondIsUCI; /* [AS] */\r
782     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
783     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
784 \r
785     if (appData.firstProtocolVersion > PROTOVER ||\r
786         appData.firstProtocolVersion < 1) {\r
787       char buf[MSG_SIZ];\r
788       sprintf(buf, _("protocol version %d not supported"),\r
789               appData.firstProtocolVersion);\r
790       DisplayFatalError(buf, 0, 2);\r
791     } else {\r
792       first.protocolVersion = appData.firstProtocolVersion;\r
793     }\r
794 \r
795     if (appData.secondProtocolVersion > PROTOVER ||\r
796         appData.secondProtocolVersion < 1) {\r
797       char buf[MSG_SIZ];\r
798       sprintf(buf, _("protocol version %d not supported"),\r
799               appData.secondProtocolVersion);\r
800       DisplayFatalError(buf, 0, 2);\r
801     } else {\r
802       second.protocolVersion = appData.secondProtocolVersion;\r
803     }\r
804 \r
805     if (appData.icsActive) {\r
806         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */\r
807     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
808         appData.clockMode = FALSE;\r
809         first.sendTime = second.sendTime = 0;\r
810     }\r
811     \r
812 #if ZIPPY\r
813     /* Override some settings from environment variables, for backward\r
814        compatibility.  Unfortunately it's not feasible to have the env\r
815        vars just set defaults, at least in xboard.  Ugh.\r
816     */\r
817     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
818       ZippyInit();\r
819     }\r
820 #endif\r
821     \r
822     if (appData.noChessProgram) {\r
823         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
824                                         + strlen(PATCHLEVEL));\r
825         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
826     } else {\r
827 #if 0\r
828         char *p, *q;\r
829         q = first.program;\r
830         while (*q != ' ' && *q != NULLCHAR) q++;\r
831         p = q;\r
832         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */\r
833         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
834                                         + strlen(PATCHLEVEL) + (q - p));\r
835         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
836         strncat(programVersion, p, q - p);\r
837 #else\r
838         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */\r
839         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
840                                         + strlen(PATCHLEVEL) + strlen(first.tidy));\r
841         sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);\r
842 #endif\r
843     }\r
844 \r
845     if (!appData.icsActive) {\r
846       char buf[MSG_SIZ];\r
847       /* Check for variants that are supported only in ICS mode,\r
848          or not at all.  Some that are accepted here nevertheless\r
849          have bugs; see comments below.\r
850       */\r
851       VariantClass variant = StringToVariant(appData.variant);\r
852       switch (variant) {\r
853       case VariantBughouse:     /* need four players and two boards */\r
854       case VariantKriegspiel:   /* need to hide pieces and move details */\r
855       /* case VariantFischeRandom: (Fabien: moved below) */\r
856         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);\r
857         DisplayFatalError(buf, 0, 2);\r
858         return;\r
859 \r
860       case VariantUnknown:\r
861       case VariantLoadable:\r
862       case Variant29:\r
863       case Variant30:\r
864       case Variant31:\r
865       case Variant32:\r
866       case Variant33:\r
867       case Variant34:\r
868       case Variant35:\r
869       case Variant36:\r
870       default:\r
871         sprintf(buf, _("Unknown variant name %s"), appData.variant);\r
872         DisplayFatalError(buf, 0, 2);\r
873         return;\r
874 \r
875       case VariantXiangqi:    /* [HGM] repetition rules not implemented */\r
876       case VariantFairy:      /* [HGM] TestLegality definitely off! */\r
877       case VariantGothic:     /* [HGM] should work */\r
878       case VariantCapablanca: /* [HGM] should work */\r
879       case VariantCourier:    /* [HGM] initial forced moves not implemented */\r
880       case VariantShogi:      /* [HGM] drops not tested for legality */\r
881       case VariantKnightmate: /* [HGM] should work */\r
882       case VariantCylinder:   /* [HGM] untested */\r
883       case VariantFalcon:     /* [HGM] untested */\r
884       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)\r
885                                  offboard interposition not understood */\r
886       case VariantNormal:     /* definitely works! */\r
887       case VariantWildCastle: /* pieces not automatically shuffled */\r
888       case VariantNoCastle:   /* pieces not automatically shuffled */\r
889       case VariantFischeRandom: /* [HGM] works and shuffles pieces */\r
890       case VariantLosers:     /* should work except for win condition,\r
891                                  and doesn't know captures are mandatory */\r
892       case VariantSuicide:    /* should work except for win condition,\r
893                                  and doesn't know captures are mandatory */\r
894       case VariantGiveaway:   /* should work except for win condition,\r
895                                  and doesn't know captures are mandatory */\r
896       case VariantTwoKings:   /* should work */\r
897       case VariantAtomic:     /* should work except for win condition */\r
898       case Variant3Check:     /* should work except for win condition */\r
899       case VariantShatranj:   /* should work except for all win conditions */\r
900       case VariantBerolina:   /* might work if TestLegality is off */\r
901       case VariantCapaRandom: /* should work */\r
902       case VariantJanus:      /* should work */\r
903       case VariantSuper:      /* experimental */\r
904       case VariantGreat:      /* experimental, requires legality testing to be off */\r
905         break;\r
906       }\r
907     }\r
908 \r
909     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard\r
910     InitEngineUCI( installDir, &second );\r
911 }\r
912 \r
913 int NextIntegerFromString( char ** str, long * value )\r
914 {\r
915     int result = -1;\r
916     char * s = *str;\r
917 \r
918     while( *s == ' ' || *s == '\t' ) {\r
919         s++;\r
920     }\r
921 \r
922     *value = 0;\r
923 \r
924     if( *s >= '0' && *s <= '9' ) {\r
925         while( *s >= '0' && *s <= '9' ) {\r
926             *value = *value * 10 + (*s - '0');\r
927             s++;\r
928         }\r
929 \r
930         result = 0;\r
931     }\r
932 \r
933     *str = s;\r
934 \r
935     return result;\r
936 }\r
937 \r
938 int NextTimeControlFromString( char ** str, long * value )\r
939 {\r
940     long temp;\r
941     int result = NextIntegerFromString( str, &temp );\r
942 \r
943     if( result == 0 ) {\r
944         *value = temp * 60; /* Minutes */\r
945         if( **str == ':' ) {\r
946             (*str)++;\r
947             result = NextIntegerFromString( str, &temp );\r
948             *value += temp; /* Seconds */\r
949         }\r
950     }\r
951 \r
952     return result;\r
953 }\r
954 \r
955 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)\r
956 {   /* [HGM] routine added to read '+moves/time' for secondary time control */\r
957     int result = -1; long temp, temp2;\r
958 \r
959     if(**str != '+') return -1; // old params remain in force!\r
960     (*str)++;\r
961     if( NextTimeControlFromString( str, &temp ) ) return -1;\r
962 \r
963     if(**str != '/') {\r
964         /* time only: incremental or sudden-death time control */\r
965         if(**str == '+') { /* increment follows; read it */\r
966             (*str)++;\r
967             if(result = NextIntegerFromString( str, &temp2)) return -1;\r
968             *inc = temp2 * 1000;\r
969         } else *inc = 0;\r
970         *moves = 0; *tc = temp * 1000; \r
971         return 0;\r
972     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */\r
973 \r
974     (*str)++; /* classical time control */\r
975     result = NextTimeControlFromString( str, &temp2);\r
976     if(result == 0) {\r
977         *moves = temp/60;\r
978         *tc    = temp2 * 1000;\r
979         *inc   = 0;\r
980     }\r
981     return result;\r
982 }\r
983 \r
984 int GetTimeQuota(int movenr)\r
985 {   /* [HGM] get time to add from the multi-session time-control string */\r
986     int moves=1; /* kludge to force reading of first session */\r
987     long time, increment;\r
988     char *s = fullTimeControlString;\r
989 \r
990     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);\r
991     do {\r
992         if(moves) NextSessionFromString(&s, &moves, &time, &increment);\r
993         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);\r
994         if(movenr == -1) return time;    /* last move before new session     */\r
995         if(!moves) return increment;     /* current session is incremental   */\r
996         if(movenr >= 0) movenr -= moves; /* we already finished this session */\r
997     } while(movenr >= -1);               /* try again for next session       */\r
998 \r
999     return 0; // no new time quota on this move\r
1000 }\r
1001 \r
1002 int\r
1003 ParseTimeControl(tc, ti, mps)\r
1004      char *tc;\r
1005      int ti;\r
1006      int mps;\r
1007 {\r
1008 #if 0\r
1009     int matched, min, sec;\r
1010 \r
1011     matched = sscanf(tc, "%d:%d", &min, &sec);\r
1012     if (matched == 1) {\r
1013         timeControl = min * 60 * 1000;\r
1014     } else if (matched == 2) {\r
1015         timeControl = (min * 60 + sec) * 1000;\r
1016     } else {\r
1017         return FALSE;\r
1018     }\r
1019 #else\r
1020     long tc1;\r
1021     long tc2;\r
1022     char buf[MSG_SIZ];\r
1023 \r
1024     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;\r
1025     if(ti > 0) {\r
1026         if(mps)\r
1027              sprintf(buf, "+%d/%s+%d", mps, tc, ti);\r
1028         else sprintf(buf, "+%s+%d", tc, ti);\r
1029     } else {\r
1030         if(mps)\r
1031              sprintf(buf, "+%d/%s", mps, tc);\r
1032         else sprintf(buf, "+%s", tc);\r
1033     }\r
1034     fullTimeControlString = StrSave(buf);\r
1035 \r
1036     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
1037         return FALSE;\r
1038     }\r
1039 \r
1040     if( *tc == '/' ) {\r
1041         /* Parse second time control */\r
1042         tc++;\r
1043 \r
1044         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
1045             return FALSE;\r
1046         }\r
1047 \r
1048         if( tc2 == 0 ) {\r
1049             return FALSE;\r
1050         }\r
1051 \r
1052         timeControl_2 = tc2 * 1000;\r
1053     }\r
1054     else {\r
1055         timeControl_2 = 0;\r
1056     }\r
1057 \r
1058     if( tc1 == 0 ) {\r
1059         return FALSE;\r
1060     }\r
1061 \r
1062     timeControl = tc1 * 1000;\r
1063 #endif\r
1064 \r
1065     if (ti >= 0) {\r
1066         timeIncrement = ti * 1000;  /* convert to ms */\r
1067         movesPerSession = 0;\r
1068     } else {\r
1069         timeIncrement = 0;\r
1070         movesPerSession = mps;\r
1071     }\r
1072     return TRUE;\r
1073 }\r
1074 \r
1075 void\r
1076 InitBackEnd2()\r
1077 {\r
1078     if (appData.debugMode) {\r
1079         fprintf(debugFP, "%s\n", programVersion);\r
1080     }\r
1081 \r
1082     if (appData.matchGames > 0) {\r
1083         appData.matchMode = TRUE;\r
1084     } else if (appData.matchMode) {\r
1085         appData.matchGames = 1;\r
1086     }\r
1087     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */\r
1088         appData.matchGames = appData.sameColorGames;\r
1089     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */\r
1090         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;\r
1091         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;\r
1092     }\r
1093     Reset(TRUE, FALSE);\r
1094     if (appData.noChessProgram || first.protocolVersion == 1) {\r
1095       InitBackEnd3();\r
1096     } else {\r
1097       /* kludge: allow timeout for initial "feature" commands */\r
1098       FreezeUI();\r
1099       DisplayMessage("", _("Starting chess program"));\r
1100       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
1101     }\r
1102 }\r
1103 \r
1104 void\r
1105 InitBackEnd3 P((void))\r
1106 {\r
1107     GameMode initialMode;\r
1108     char buf[MSG_SIZ];\r
1109     int err;\r
1110 \r
1111     InitChessProgram(&first, startedFromSetupPosition);\r
1112 \r
1113 \r
1114     if (appData.icsActive) {\r
1115 #ifdef WIN32\r
1116         /* [DM] Make a console window if needed [HGM] merged ifs */\r
1117         ConsoleCreate(); \r
1118 #endif\r
1119         err = establish();\r
1120         if (err != 0) {\r
1121             if (*appData.icsCommPort != NULLCHAR) {\r
1122                 sprintf(buf, _("Could not open comm port %s"),  \r
1123                         appData.icsCommPort);\r
1124             } else {\r
1125                 sprintf(buf, _("Could not connect to host %s, port %s"),  \r
1126                         appData.icsHost, appData.icsPort);\r
1127             }\r
1128             DisplayFatalError(buf, err, 1);\r
1129             return;\r
1130         }\r
1131         SetICSMode();\r
1132         telnetISR =\r
1133           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
1134         fromUserISR =\r
1135           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
1136     } else if (appData.noChessProgram) {\r
1137         SetNCPMode();\r
1138     } else {\r
1139         SetGNUMode();\r
1140     }\r
1141 \r
1142     if (*appData.cmailGameName != NULLCHAR) {\r
1143         SetCmailMode();\r
1144         OpenLoopback(&cmailPR);\r
1145         cmailISR =\r
1146           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
1147     }\r
1148     \r
1149     ThawUI();\r
1150     DisplayMessage("", "");\r
1151     if (StrCaseCmp(appData.initialMode, "") == 0) {\r
1152       initialMode = BeginningOfGame;\r
1153     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
1154       initialMode = TwoMachinesPlay;\r
1155     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
1156       initialMode = AnalyzeFile; \r
1157     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
1158       initialMode = AnalyzeMode;\r
1159     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
1160       initialMode = MachinePlaysWhite;\r
1161     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
1162       initialMode = MachinePlaysBlack;\r
1163     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
1164       initialMode = EditGame;\r
1165     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
1166       initialMode = EditPosition;\r
1167     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
1168       initialMode = Training;\r
1169     } else {\r
1170       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);\r
1171       DisplayFatalError(buf, 0, 2);\r
1172       return;\r
1173     }\r
1174 \r
1175     if (appData.matchMode) {\r
1176         /* Set up machine vs. machine match */\r
1177         if (appData.noChessProgram) {\r
1178             DisplayFatalError(_("Can't have a match with no chess programs"),\r
1179                               0, 2);\r
1180             return;\r
1181         }\r
1182         matchMode = TRUE;\r
1183         matchGame = 1;\r
1184         if (*appData.loadGameFile != NULLCHAR) {\r
1185             int index = appData.loadGameIndex; // [HGM] autoinc\r
1186             if(index<0) lastIndex = index = 1;\r
1187             if (!LoadGameFromFile(appData.loadGameFile,\r
1188                                   index,\r
1189                                   appData.loadGameFile, FALSE)) {\r
1190                 DisplayFatalError(_("Bad game file"), 0, 1);\r
1191                 return;\r
1192             }\r
1193         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1194             int index = appData.loadPositionIndex; // [HGM] autoinc\r
1195             if(index<0) lastIndex = index = 1;\r
1196             if (!LoadPositionFromFile(appData.loadPositionFile,\r
1197                                       index,\r
1198                                       appData.loadPositionFile)) {\r
1199                 DisplayFatalError(_("Bad position file"), 0, 1);\r
1200                 return;\r
1201             }\r
1202         }\r
1203         TwoMachinesEvent();\r
1204     } else if (*appData.cmailGameName != NULLCHAR) {\r
1205         /* Set up cmail mode */\r
1206         ReloadCmailMsgEvent(TRUE);\r
1207     } else {\r
1208         /* Set up other modes */\r
1209         if (initialMode == AnalyzeFile) {\r
1210           if (*appData.loadGameFile == NULLCHAR) {\r
1211             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);\r
1212             return;\r
1213           }\r
1214         }\r
1215         if (*appData.loadGameFile != NULLCHAR) {\r
1216             (void) LoadGameFromFile(appData.loadGameFile,\r
1217                                     appData.loadGameIndex,\r
1218                                     appData.loadGameFile, TRUE);\r
1219         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1220             (void) LoadPositionFromFile(appData.loadPositionFile,\r
1221                                         appData.loadPositionIndex,\r
1222                                         appData.loadPositionFile);\r
1223             /* [HGM] try to make self-starting even after FEN load */\r
1224             /* to allow automatic setup of fairy variants with wtm */\r
1225             if(initialMode == BeginningOfGame && !blackPlaysFirst) {\r
1226                 gameMode = BeginningOfGame;\r
1227                 setboardSpoiledMachineBlack = 1;\r
1228             }\r
1229             /* [HGM] loadPos: make that every new game uses the setup */\r
1230             /* from file as long as we do not switch variant          */\r
1231             if(!blackPlaysFirst) { int i;\r
1232                 startedFromPositionFile = TRUE;\r
1233                 CopyBoard(filePosition, boards[0]);\r
1234                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];\r
1235             }\r
1236         }\r
1237         if (initialMode == AnalyzeMode) {\r
1238           if (appData.noChessProgram) {\r
1239             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);\r
1240             return;\r
1241           }\r
1242           if (appData.icsActive) {\r
1243             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);\r
1244             return;\r
1245           }\r
1246           AnalyzeModeEvent();\r
1247         } else if (initialMode == AnalyzeFile) {\r
1248           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent\r
1249           ShowThinkingEvent();\r
1250           AnalyzeFileEvent();\r
1251           AnalysisPeriodicEvent(1);\r
1252         } else if (initialMode == MachinePlaysWhite) {\r
1253           if (appData.noChessProgram) {\r
1254             DisplayFatalError(_("MachineWhite mode requires a chess engine"),\r
1255                               0, 2);\r
1256             return;\r
1257           }\r
1258           if (appData.icsActive) {\r
1259             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),\r
1260                               0, 2);\r
1261             return;\r
1262           }\r
1263           MachineWhiteEvent();\r
1264         } else if (initialMode == MachinePlaysBlack) {\r
1265           if (appData.noChessProgram) {\r
1266             DisplayFatalError(_("MachineBlack mode requires a chess engine"),\r
1267                               0, 2);\r
1268             return;\r
1269           }\r
1270           if (appData.icsActive) {\r
1271             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),\r
1272                               0, 2);\r
1273             return;\r
1274           }\r
1275           MachineBlackEvent();\r
1276         } else if (initialMode == TwoMachinesPlay) {\r
1277           if (appData.noChessProgram) {\r
1278             DisplayFatalError(_("TwoMachines mode requires a chess engine"),\r
1279                               0, 2);\r
1280             return;\r
1281           }\r
1282           if (appData.icsActive) {\r
1283             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),\r
1284                               0, 2);\r
1285             return;\r
1286           }\r
1287           TwoMachinesEvent();\r
1288         } else if (initialMode == EditGame) {\r
1289           EditGameEvent();\r
1290         } else if (initialMode == EditPosition) {\r
1291           EditPositionEvent();\r
1292         } else if (initialMode == Training) {\r
1293           if (*appData.loadGameFile == NULLCHAR) {\r
1294             DisplayFatalError(_("Training mode requires a game file"), 0, 2);\r
1295             return;\r
1296           }\r
1297           TrainingEvent();\r
1298         }\r
1299     }\r
1300 }\r
1301 \r
1302 /*\r
1303  * Establish will establish a contact to a remote host.port.\r
1304  * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
1305  *  used to talk to the host.\r
1306  * Returns 0 if okay, error code if not.\r
1307  */\r
1308 int\r
1309 establish()\r
1310 {\r
1311     char buf[MSG_SIZ];\r
1312 \r
1313     if (*appData.icsCommPort != NULLCHAR) {\r
1314         /* Talk to the host through a serial comm port */\r
1315         return OpenCommPort(appData.icsCommPort, &icsPR);\r
1316 \r
1317     } else if (*appData.gateway != NULLCHAR) {\r
1318         if (*appData.remoteShell == NULLCHAR) {\r
1319             /* Use the rcmd protocol to run telnet program on a gateway host */\r
1320             sprintf(buf, "%s %s %s",\r
1321                     appData.telnetProgram, appData.icsHost, appData.icsPort);\r
1322             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
1323 \r
1324         } else {\r
1325             /* Use the rsh program to run telnet program on a gateway host */\r
1326             if (*appData.remoteUser == NULLCHAR) {\r
1327                 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,\r
1328                         appData.gateway, appData.telnetProgram,\r
1329                         appData.icsHost, appData.icsPort);\r
1330             } else {\r
1331                 sprintf(buf, "%s %s -l %s %s %s %s",\r
1332                         appData.remoteShell, appData.gateway, \r
1333                         appData.remoteUser, appData.telnetProgram,\r
1334                         appData.icsHost, appData.icsPort);\r
1335             }\r
1336             return StartChildProcess(buf, "", &icsPR);\r
1337 \r
1338         }\r
1339     } else if (appData.useTelnet) {\r
1340         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
1341 \r
1342     } else {\r
1343         /* TCP socket interface differs somewhat between\r
1344            Unix and NT; handle details in the front end.\r
1345            */\r
1346         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
1347     }\r
1348 }\r
1349 \r
1350 void\r
1351 show_bytes(fp, buf, count)\r
1352      FILE *fp;\r
1353      char *buf;\r
1354      int count;\r
1355 {\r
1356     while (count--) {\r
1357         if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
1358             fprintf(fp, "\\%03o", *buf & 0xff);\r
1359         } else {\r
1360             putc(*buf, fp);\r
1361         }\r
1362         buf++;\r
1363     }\r
1364     fflush(fp);\r
1365 }\r
1366 \r
1367 /* Returns an errno value */\r
1368 int\r
1369 OutputMaybeTelnet(pr, message, count, outError)\r
1370      ProcRef pr;\r
1371      char *message;\r
1372      int count;\r
1373      int *outError;\r
1374 {\r
1375     char buf[8192], *p, *q, *buflim;\r
1376     int left, newcount, outcount;\r
1377 \r
1378     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
1379         *appData.gateway != NULLCHAR) {\r
1380         if (appData.debugMode) {\r
1381             fprintf(debugFP, ">ICS: ");\r
1382             show_bytes(debugFP, message, count);\r
1383             fprintf(debugFP, "\n");\r
1384         }\r
1385         return OutputToProcess(pr, message, count, outError);\r
1386     }\r
1387 \r
1388     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
1389     p = message;\r
1390     q = buf;\r
1391     left = count;\r
1392     newcount = 0;\r
1393     while (left) {\r
1394         if (q >= buflim) {\r
1395             if (appData.debugMode) {\r
1396                 fprintf(debugFP, ">ICS: ");\r
1397                 show_bytes(debugFP, buf, newcount);\r
1398                 fprintf(debugFP, "\n");\r
1399             }\r
1400             outcount = OutputToProcess(pr, buf, newcount, outError);\r
1401             if (outcount < newcount) return -1; /* to be sure */\r
1402             q = buf;\r
1403             newcount = 0;\r
1404         }\r
1405         if (*p == '\n') {\r
1406             *q++ = '\r';\r
1407             newcount++;\r
1408         } else if (((unsigned char) *p) == TN_IAC) {\r
1409             *q++ = (char) TN_IAC;\r
1410             newcount ++;\r
1411         }\r
1412         *q++ = *p++;\r
1413         newcount++;\r
1414         left--;\r
1415     }\r
1416     if (appData.debugMode) {\r
1417         fprintf(debugFP, ">ICS: ");\r
1418         show_bytes(debugFP, buf, newcount);\r
1419         fprintf(debugFP, "\n");\r
1420     }\r
1421     outcount = OutputToProcess(pr, buf, newcount, outError);\r
1422     if (outcount < newcount) return -1; /* to be sure */\r
1423     return count;\r
1424 }\r
1425 \r
1426 void\r
1427 read_from_player(isr, closure, message, count, error)\r
1428      InputSourceRef isr;\r
1429      VOIDSTAR closure;\r
1430      char *message;\r
1431      int count;\r
1432      int error;\r
1433 {\r
1434     int outError, outCount;\r
1435     static int gotEof = 0;\r
1436 \r
1437     /* Pass data read from player on to ICS */\r
1438     if (count > 0) {\r
1439         gotEof = 0;\r
1440         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
1441         if (outCount < count) {\r
1442             DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1443         }\r
1444     } else if (count < 0) {\r
1445         RemoveInputSource(isr);\r
1446         DisplayFatalError(_("Error reading from keyboard"), error, 1);\r
1447     } else if (gotEof++ > 0) {\r
1448         RemoveInputSource(isr);\r
1449         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);\r
1450     }\r
1451 }\r
1452 \r
1453 void\r
1454 SendToICS(s)\r
1455      char *s;\r
1456 {\r
1457     int count, outCount, outError;\r
1458 \r
1459     if (icsPR == NULL) return;\r
1460 \r
1461     count = strlen(s);\r
1462     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
1463     if (outCount < count) {\r
1464         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1465     }\r
1466 }\r
1467 \r
1468 /* This is used for sending logon scripts to the ICS. Sending\r
1469    without a delay causes problems when using timestamp on ICC\r
1470    (at least on my machine). */\r
1471 void\r
1472 SendToICSDelayed(s,msdelay)\r
1473      char *s;\r
1474      long msdelay;\r
1475 {\r
1476     int count, outCount, outError;\r
1477 \r
1478     if (icsPR == NULL) return;\r
1479 \r
1480     count = strlen(s);\r
1481     if (appData.debugMode) {\r
1482         fprintf(debugFP, ">ICS: ");\r
1483         show_bytes(debugFP, s, count);\r
1484         fprintf(debugFP, "\n");\r
1485     }\r
1486     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
1487                                       msdelay);\r
1488     if (outCount < count) {\r
1489         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1490     }\r
1491 }\r
1492 \r
1493 \r
1494 /* Remove all highlighting escape sequences in s\r
1495    Also deletes any suffix starting with '(' \r
1496    */\r
1497 char *\r
1498 StripHighlightAndTitle(s)\r
1499      char *s;\r
1500 {\r
1501     static char retbuf[MSG_SIZ];\r
1502     char *p = retbuf;\r
1503 \r
1504     while (*s != NULLCHAR) {\r
1505         while (*s == '\033') {\r
1506             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1507             if (*s != NULLCHAR) s++;\r
1508         }\r
1509         while (*s != NULLCHAR && *s != '\033') {\r
1510             if (*s == '(' || *s == '[') {\r
1511                 *p = NULLCHAR;\r
1512                 return retbuf;\r
1513             }\r
1514             *p++ = *s++;\r
1515         }\r
1516     }\r
1517     *p = NULLCHAR;\r
1518     return retbuf;\r
1519 }\r
1520 \r
1521 /* Remove all highlighting escape sequences in s */\r
1522 char *\r
1523 StripHighlight(s)\r
1524      char *s;\r
1525 {\r
1526     static char retbuf[MSG_SIZ];\r
1527     char *p = retbuf;\r
1528 \r
1529     while (*s != NULLCHAR) {\r
1530         while (*s == '\033') {\r
1531             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1532             if (*s != NULLCHAR) s++;\r
1533         }\r
1534         while (*s != NULLCHAR && *s != '\033') {\r
1535             *p++ = *s++;\r
1536         }\r
1537     }\r
1538     *p = NULLCHAR;\r
1539     return retbuf;\r
1540 }\r
1541 \r
1542 char *variantNames[] = VARIANT_NAMES;\r
1543 char *\r
1544 VariantName(v)\r
1545      VariantClass v;\r
1546 {\r
1547     return variantNames[v];\r
1548 }\r
1549 \r
1550 \r
1551 /* Identify a variant from the strings the chess servers use or the\r
1552    PGN Variant tag names we use. */\r
1553 VariantClass\r
1554 StringToVariant(e)\r
1555      char *e;\r
1556 {\r
1557     char *p;\r
1558     int wnum = -1;\r
1559     VariantClass v = VariantNormal;\r
1560     int i, found = FALSE;\r
1561     char buf[MSG_SIZ];\r
1562 \r
1563     if (!e) return v;\r
1564 \r
1565     /* [HGM] skip over optional board-size prefixes */\r
1566     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||\r
1567         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
1568         while( *e++ != '_');\r
1569     }\r
1570 \r
1571     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1572       if (StrCaseStr(e, variantNames[i])) {\r
1573         v = (VariantClass) i;\r
1574         found = TRUE;\r
1575         break;\r
1576       }\r
1577     }\r
1578 \r
1579     if (!found) {\r
1580       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1581           || StrCaseStr(e, "wild/fr") \r
1582           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {\r
1583         v = VariantFischeRandom;\r
1584       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1585                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1586         p += i;\r
1587         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1588         if (isdigit(*p)) {\r
1589           wnum = atoi(p);\r
1590         } else {\r
1591           wnum = -1;\r
1592         }\r
1593         switch (wnum) {\r
1594         case 0: /* FICS only, actually */\r
1595         case 1:\r
1596           /* Castling legal even if K starts on d-file */\r
1597           v = VariantWildCastle;\r
1598           break;\r
1599         case 2:\r
1600         case 3:\r
1601         case 4:\r
1602           /* Castling illegal even if K & R happen to start in\r
1603              normal positions. */\r
1604           v = VariantNoCastle;\r
1605           break;\r
1606         case 5:\r
1607         case 7:\r
1608         case 8:\r
1609         case 10:\r
1610         case 11:\r
1611         case 12:\r
1612         case 13:\r
1613         case 14:\r
1614         case 15:\r
1615         case 18:\r
1616         case 19:\r
1617           /* Castling legal iff K & R start in normal positions */\r
1618           v = VariantNormal;\r
1619           break;\r
1620         case 6:\r
1621         case 20:\r
1622         case 21:\r
1623           /* Special wilds for position setup; unclear what to do here */\r
1624           v = VariantLoadable;\r
1625           break;\r
1626         case 9:\r
1627           /* Bizarre ICC game */\r
1628           v = VariantTwoKings;\r
1629           break;\r
1630         case 16:\r
1631           v = VariantKriegspiel;\r
1632           break;\r
1633         case 17:\r
1634           v = VariantLosers;\r
1635           break;\r
1636         case 22:\r
1637           v = VariantFischeRandom;\r
1638           break;\r
1639         case 23:\r
1640           v = VariantCrazyhouse;\r
1641           break;\r
1642         case 24:\r
1643           v = VariantBughouse;\r
1644           break;\r
1645         case 25:\r
1646           v = Variant3Check;\r
1647           break;\r
1648         case 26:\r
1649           /* Not quite the same as FICS suicide! */\r
1650           v = VariantGiveaway;\r
1651           break;\r
1652         case 27:\r
1653           v = VariantAtomic;\r
1654           break;\r
1655         case 28:\r
1656           v = VariantShatranj;\r
1657           break;\r
1658 \r
1659         /* Temporary names for future ICC types.  The name *will* change in \r
1660            the next xboard/WinBoard release after ICC defines it. */\r
1661         case 29:\r
1662           v = Variant29;\r
1663           break;\r
1664         case 30:\r
1665           v = Variant30;\r
1666           break;\r
1667         case 31:\r
1668           v = Variant31;\r
1669           break;\r
1670         case 32:\r
1671           v = Variant32;\r
1672           break;\r
1673         case 33:\r
1674           v = Variant33;\r
1675           break;\r
1676         case 34:\r
1677           v = Variant34;\r
1678           break;\r
1679         case 35:\r
1680           v = Variant35;\r
1681           break;\r
1682         case 36:\r
1683           v = Variant36;\r
1684           break;\r
1685         case 37:\r
1686           v = VariantShogi;\r
1687           break;\r
1688         case 38:\r
1689           v = VariantXiangqi;\r
1690           break;\r
1691         case 39:\r
1692           v = VariantCourier;\r
1693           break;\r
1694         case 40:\r
1695           v = VariantGothic;\r
1696           break;\r
1697         case 41:\r
1698           v = VariantCapablanca;\r
1699           break;\r
1700         case 42:\r
1701           v = VariantKnightmate;\r
1702           break;\r
1703         case 43:\r
1704           v = VariantFairy;\r
1705           break;\r
1706         case 44:\r
1707           v = VariantCylinder;\r
1708           break;\r
1709         case 45:\r
1710           v = VariantFalcon;\r
1711           break;\r
1712         case 46:\r
1713           v = VariantCapaRandom;\r
1714           break;\r
1715         case 47:\r
1716           v = VariantBerolina;\r
1717           break;\r
1718         case 48:\r
1719           v = VariantJanus;\r
1720           break;\r
1721         case 49:\r
1722           v = VariantSuper;\r
1723           break;\r
1724         case 50:\r
1725           v = VariantGreat;\r
1726           break;\r
1727         case -1:\r
1728           /* Found "wild" or "w" in the string but no number;\r
1729              must assume it's normal chess. */\r
1730           v = VariantNormal;\r
1731           break;\r
1732         default:\r
1733           sprintf(buf, _("Unknown wild type %d"), wnum);\r
1734           DisplayError(buf, 0);\r
1735           v = VariantUnknown;\r
1736           break;\r
1737         }\r
1738       }\r
1739     }\r
1740     if (appData.debugMode) {\r
1741       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),\r
1742               e, wnum, VariantName(v));\r
1743     }\r
1744     return v;\r
1745 }\r
1746 \r
1747 static int leftover_start = 0, leftover_len = 0;\r
1748 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1749 \r
1750 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1751    advance *index beyond it, and set leftover_start to the new value of\r
1752    *index; else return FALSE.  If pattern contains the character '*', it\r
1753    matches any sequence of characters not containing '\r', '\n', or the\r
1754    character following the '*' (if any), and the matched sequence(s) are\r
1755    copied into star_match.\r
1756    */\r
1757 int\r
1758 looking_at(buf, index, pattern)\r
1759      char *buf;\r
1760      int *index;\r
1761      char *pattern;\r
1762 {\r
1763     char *bufp = &buf[*index], *patternp = pattern;\r
1764     int star_count = 0;\r
1765     char *matchp = star_match[0];\r
1766     \r
1767     for (;;) {\r
1768         if (*patternp == NULLCHAR) {\r
1769             *index = leftover_start = bufp - buf;\r
1770             *matchp = NULLCHAR;\r
1771             return TRUE;\r
1772         }\r
1773         if (*bufp == NULLCHAR) return FALSE;\r
1774         if (*patternp == '*') {\r
1775             if (*bufp == *(patternp + 1)) {\r
1776                 *matchp = NULLCHAR;\r
1777                 matchp = star_match[++star_count];\r
1778                 patternp += 2;\r
1779                 bufp++;\r
1780                 continue;\r
1781             } else if (*bufp == '\n' || *bufp == '\r') {\r
1782                 patternp++;\r
1783                 if (*patternp == NULLCHAR)\r
1784                   continue;\r
1785                 else\r
1786                   return FALSE;\r
1787             } else {\r
1788                 *matchp++ = *bufp++;\r
1789                 continue;\r
1790             }\r
1791         }\r
1792         if (*patternp != *bufp) return FALSE;\r
1793         patternp++;\r
1794         bufp++;\r
1795     }\r
1796 }\r
1797 \r
1798 void\r
1799 SendToPlayer(data, length)\r
1800      char *data;\r
1801      int length;\r
1802 {\r
1803     int error, outCount;\r
1804     outCount = OutputToProcess(NoProc, data, length, &error);\r
1805     if (outCount < length) {\r
1806         DisplayFatalError(_("Error writing to display"), error, 1);\r
1807     }\r
1808 }\r
1809 \r
1810 void\r
1811 PackHolding(packed, holding)\r
1812      char packed[];\r
1813      char *holding;\r
1814 {\r
1815     char *p = holding;\r
1816     char *q = packed;\r
1817     int runlength = 0;\r
1818     int curr = 9999;\r
1819     do {\r
1820         if (*p == curr) {\r
1821             runlength++;\r
1822         } else {\r
1823             switch (runlength) {\r
1824               case 0:\r
1825                 break;\r
1826               case 1:\r
1827                 *q++ = curr;\r
1828                 break;\r
1829               case 2:\r
1830                 *q++ = curr;\r
1831                 *q++ = curr;\r
1832                 break;\r
1833               default:\r
1834                 sprintf(q, "%d", runlength);\r
1835                 while (*q) q++;\r
1836                 *q++ = curr;\r
1837                 break;\r
1838             }\r
1839             runlength = 1;\r
1840             curr = *p;\r
1841         }\r
1842     } while (*p++);\r
1843     *q = NULLCHAR;\r
1844 }\r
1845 \r
1846 /* Telnet protocol requests from the front end */\r
1847 void\r
1848 TelnetRequest(ddww, option)\r
1849      unsigned char ddww, option;\r
1850 {\r
1851     unsigned char msg[3];\r
1852     int outCount, outError;\r
1853 \r
1854     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1855 \r
1856     if (appData.debugMode) {\r
1857         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1858         switch (ddww) {\r
1859           case TN_DO:\r
1860             ddwwStr = "DO";\r
1861             break;\r
1862           case TN_DONT:\r
1863             ddwwStr = "DONT";\r
1864             break;\r
1865           case TN_WILL:\r
1866             ddwwStr = "WILL";\r
1867             break;\r
1868           case TN_WONT:\r
1869             ddwwStr = "WONT";\r
1870             break;\r
1871           default:\r
1872             ddwwStr = buf1;\r
1873             sprintf(buf1, "%d", ddww);\r
1874             break;\r
1875         }\r
1876         switch (option) {\r
1877           case TN_ECHO:\r
1878             optionStr = "ECHO";\r
1879             break;\r
1880           default:\r
1881             optionStr = buf2;\r
1882             sprintf(buf2, "%d", option);\r
1883             break;\r
1884         }\r
1885         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1886     }\r
1887     msg[0] = TN_IAC;\r
1888     msg[1] = ddww;\r
1889     msg[2] = option;\r
1890     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1891     if (outCount < 3) {\r
1892         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1893     }\r
1894 }\r
1895 \r
1896 void\r
1897 DoEcho()\r
1898 {\r
1899     if (!appData.icsActive) return;\r
1900     TelnetRequest(TN_DO, TN_ECHO);\r
1901 }\r
1902 \r
1903 void\r
1904 DontEcho()\r
1905 {\r
1906     if (!appData.icsActive) return;\r
1907     TelnetRequest(TN_DONT, TN_ECHO);\r
1908 }\r
1909 \r
1910 void\r
1911 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
1912 {\r
1913     /* put the holdings sent to us by the server on the board holdings area */\r
1914     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
1915     char p;\r
1916     ChessSquare piece;\r
1917 \r
1918     if(gameInfo.holdingsWidth < 2)  return;\r
1919 \r
1920     if( (int)lowestPiece >= BlackPawn ) {\r
1921         holdingsColumn = 0;\r
1922         countsColumn = 1;\r
1923         holdingsStartRow = BOARD_HEIGHT-1;\r
1924         direction = -1;\r
1925     } else {\r
1926         holdingsColumn = BOARD_WIDTH-1;\r
1927         countsColumn = BOARD_WIDTH-2;\r
1928         holdingsStartRow = 0;\r
1929         direction = 1;\r
1930     }\r
1931 \r
1932     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
1933         board[i][holdingsColumn] = EmptySquare;\r
1934         board[i][countsColumn]   = (ChessSquare) 0;\r
1935     }\r
1936     while( (p=*holdings++) != NULLCHAR ) {\r
1937         piece = CharToPiece( ToUpper(p) );\r
1938         if(piece == EmptySquare) continue;\r
1939         /*j = (int) piece - (int) WhitePawn;*/\r
1940         j = PieceToNumber(piece);\r
1941         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1942         if(j < 0) continue;               /* should not happen */\r
1943         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );\r
1944         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
1945         board[holdingsStartRow+j*direction][countsColumn]++;\r
1946     }\r
1947 \r
1948 }\r
1949 \r
1950 \r
1951 void\r
1952 VariantSwitch(Board board, VariantClass newVariant)\r
1953 {\r
1954    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;\r
1955    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;\r
1956 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;\r
1957 \r
1958    startedFromPositionFile = FALSE;\r
1959    if(gameInfo.variant == newVariant) return;\r
1960 \r
1961    /* [HGM] This routine is called each time an assignment is made to\r
1962     * gameInfo.variant during a game, to make sure the board sizes\r
1963     * are set to match the new variant. If that means adding or deleting\r
1964     * holdings, we shift the playing board accordingly\r
1965     * This kludge is needed because in ICS observe mode, we get boards\r
1966     * of an ongoing game without knowing the variant, and learn about the\r
1967     * latter only later. This can be because of the move list we requested,\r
1968     * in which case the game history is refilled from the beginning anyway,\r
1969     * but also when receiving holdings of a crazyhouse game. In the latter\r
1970     * case we want to add those holdings to the already received position.\r
1971     */\r
1972 \r
1973 \r
1974   if (appData.debugMode) {\r
1975     fprintf(debugFP, "Switch board from %s to %s\n",\r
1976                VariantName(gameInfo.variant), VariantName(newVariant));\r
1977     setbuf(debugFP, NULL);\r
1978   }\r
1979     shuffleOpenings = 0;       /* [HGM] shuffle */\r
1980     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
1981     switch(newVariant) {\r
1982             case VariantShogi:\r
1983               newWidth = 9;  newHeight = 9;\r
1984               gameInfo.holdingsSize = 7;\r
1985             case VariantBughouse:\r
1986             case VariantCrazyhouse:\r
1987               newHoldingsWidth = 2; break;\r
1988             default:\r
1989               newHoldingsWidth = gameInfo.holdingsSize = 0;\r
1990     }\r
1991 \r
1992     if(newWidth  != gameInfo.boardWidth  ||\r
1993        newHeight != gameInfo.boardHeight ||\r
1994        newHoldingsWidth != gameInfo.holdingsWidth ) {\r
1995 \r
1996         /* shift position to new playing area, if needed */\r
1997         if(newHoldingsWidth > gameInfo.holdingsWidth) {\r
1998            for(i=0; i<BOARD_HEIGHT; i++) \r
1999                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)\r
2000                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
2001                                                      board[i][j];\r
2002            for(i=0; i<newHeight; i++) {\r
2003                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;\r
2004                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;\r
2005            }\r
2006         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {\r
2007            for(i=0; i<BOARD_HEIGHT; i++)\r
2008                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
2009                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
2010                                                  board[i][j];\r
2011         }\r
2012 \r
2013         gameInfo.boardWidth  = newWidth;\r
2014         gameInfo.boardHeight = newHeight;\r
2015         gameInfo.holdingsWidth = newHoldingsWidth;\r
2016         gameInfo.variant = newVariant;\r
2017         InitDrawingSizes(-2, 0);\r
2018 \r
2019         /* [HGM] The following should definitely be solved in a better way */\r
2020 #if 0\r
2021         CopyBoard(board, tempBoard); /* save position in case it is board[0] */\r
2022         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];\r
2023         saveEP = epStatus[0];\r
2024 #endif\r
2025         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */\r
2026 #if 0\r
2027         epStatus[0] = saveEP;\r
2028         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];\r
2029         CopyBoard(tempBoard, board); /* restore position received from ICS   */\r
2030 #endif\r
2031     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }\r
2032 \r
2033     forwardMostMove = oldForwardMostMove;\r
2034     backwardMostMove = oldBackwardMostMove;\r
2035     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */\r
2036 }\r
2037 \r
2038 static int loggedOn = FALSE;\r
2039 \r
2040 /*-- Game start info cache: --*/\r
2041 int gs_gamenum;\r
2042 char gs_kind[MSG_SIZ];\r
2043 static char player1Name[128] = "";\r
2044 static char player2Name[128] = "";\r
2045 static int player1Rating = -1;\r
2046 static int player2Rating = -1;\r
2047 /*----------------------------*/\r
2048 \r
2049 ColorClass curColor = ColorNormal;\r
2050 int suppressKibitz = 0;\r
2051 \r
2052 void\r
2053 read_from_ics(isr, closure, data, count, error)\r
2054      InputSourceRef isr;\r
2055      VOIDSTAR closure;\r
2056      char *data;\r
2057      int count;\r
2058      int error;\r
2059 {\r
2060 #define BUF_SIZE 8192\r
2061 #define STARTED_NONE 0\r
2062 #define STARTED_MOVES 1\r
2063 #define STARTED_BOARD 2\r
2064 #define STARTED_OBSERVE 3\r
2065 #define STARTED_HOLDINGS 4\r
2066 #define STARTED_CHATTER 5\r
2067 #define STARTED_COMMENT 6\r
2068 #define STARTED_MOVES_NOHIDE 7\r
2069     \r
2070     static int started = STARTED_NONE;\r
2071     static char parse[20000];\r
2072     static int parse_pos = 0;\r
2073     static char buf[BUF_SIZE + 1];\r
2074     static int firstTime = TRUE, intfSet = FALSE;\r
2075     static ColorClass prevColor = ColorNormal;\r
2076     static int savingComment = FALSE;\r
2077     char str[500];\r
2078     int i, oldi;\r
2079     int buf_len;\r
2080     int next_out;\r
2081     int tkind;\r
2082     int backup;    /* [DM] For zippy color lines */\r
2083     char *p;\r
2084 \r
2085     if (appData.debugMode) {\r
2086       if (!error) {\r
2087         fprintf(debugFP, "<ICS: ");\r
2088         show_bytes(debugFP, data, count);\r
2089         fprintf(debugFP, "\n");\r
2090       }\r
2091     }\r
2092 \r
2093     if (appData.debugMode) { int f = forwardMostMove;\r
2094         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,\r
2095                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
2096     }\r
2097     if (count > 0) {\r
2098         /* If last read ended with a partial line that we couldn't parse,\r
2099            prepend it to the new read and try again. */\r
2100         if (leftover_len > 0) {\r
2101             for (i=0; i<leftover_len; i++)\r
2102               buf[i] = buf[leftover_start + i];\r
2103         }\r
2104 \r
2105         /* Copy in new characters, removing nulls and \r's */\r
2106         buf_len = leftover_len;\r
2107         for (i = 0; i < count; i++) {\r
2108             if (data[i] != NULLCHAR && data[i] != '\r')\r
2109               buf[buf_len++] = data[i];\r
2110             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && \r
2111                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') \r
2112                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous\r
2113         }\r
2114 \r
2115         buf[buf_len] = NULLCHAR;\r
2116         next_out = leftover_len;\r
2117         leftover_start = 0;\r
2118         \r
2119         i = 0;\r
2120         while (i < buf_len) {\r
2121             /* Deal with part of the TELNET option negotiation\r
2122                protocol.  We refuse to do anything beyond the\r
2123                defaults, except that we allow the WILL ECHO option,\r
2124                which ICS uses to turn off password echoing when we are\r
2125                directly connected to it.  We reject this option\r
2126                if localLineEditing mode is on (always on in xboard)\r
2127                and we are talking to port 23, which might be a real\r
2128                telnet server that will try to keep WILL ECHO on permanently.\r
2129              */\r
2130             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
2131                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
2132                 unsigned char option;\r
2133                 oldi = i;\r
2134                 switch ((unsigned char) buf[++i]) {\r
2135                   case TN_WILL:\r
2136                     if (appData.debugMode)\r
2137                       fprintf(debugFP, "\n<WILL ");\r
2138                     switch (option = (unsigned char) buf[++i]) {\r
2139                       case TN_ECHO:\r
2140                         if (appData.debugMode)\r
2141                           fprintf(debugFP, "ECHO ");\r
2142                         /* Reply only if this is a change, according\r
2143                            to the protocol rules. */\r
2144                         if (remoteEchoOption) break;\r
2145                         if (appData.localLineEditing &&\r
2146                             atoi(appData.icsPort) == TN_PORT) {\r
2147                             TelnetRequest(TN_DONT, TN_ECHO);\r
2148                         } else {\r
2149                             EchoOff();\r
2150                             TelnetRequest(TN_DO, TN_ECHO);\r
2151                             remoteEchoOption = TRUE;\r
2152                         }\r
2153                         break;\r
2154                       default:\r
2155                         if (appData.debugMode)\r
2156                           fprintf(debugFP, "%d ", option);\r
2157                         /* Whatever this is, we don't want it. */\r
2158                         TelnetRequest(TN_DONT, option);\r
2159                         break;\r
2160                     }\r
2161                     break;\r
2162                   case TN_WONT:\r
2163                     if (appData.debugMode)\r
2164                       fprintf(debugFP, "\n<WONT ");\r
2165                     switch (option = (unsigned char) buf[++i]) {\r
2166                       case TN_ECHO:\r
2167                         if (appData.debugMode)\r
2168                           fprintf(debugFP, "ECHO ");\r
2169                         /* Reply only if this is a change, according\r
2170                            to the protocol rules. */\r
2171                         if (!remoteEchoOption) break;\r
2172                         EchoOn();\r
2173                         TelnetRequest(TN_DONT, TN_ECHO);\r
2174                         remoteEchoOption = FALSE;\r
2175                         break;\r
2176                       default:\r
2177                         if (appData.debugMode)\r
2178                           fprintf(debugFP, "%d ", (unsigned char) option);\r
2179                         /* Whatever this is, it must already be turned\r
2180                            off, because we never agree to turn on\r
2181                            anything non-default, so according to the\r
2182                            protocol rules, we don't reply. */\r
2183                         break;\r
2184                     }\r
2185                     break;\r
2186                   case TN_DO:\r
2187                     if (appData.debugMode)\r
2188                       fprintf(debugFP, "\n<DO ");\r
2189                     switch (option = (unsigned char) buf[++i]) {\r
2190                       default:\r
2191                         /* Whatever this is, we refuse to do it. */\r
2192                         if (appData.debugMode)\r
2193                           fprintf(debugFP, "%d ", option);\r
2194                         TelnetRequest(TN_WONT, option);\r
2195                         break;\r
2196                     }\r
2197                     break;\r
2198                   case TN_DONT:\r
2199                     if (appData.debugMode)\r
2200                       fprintf(debugFP, "\n<DONT ");\r
2201                     switch (option = (unsigned char) buf[++i]) {\r
2202                       default:\r
2203                         if (appData.debugMode)\r
2204                           fprintf(debugFP, "%d ", option);\r
2205                         /* Whatever this is, we are already not doing\r
2206                            it, because we never agree to do anything\r
2207                            non-default, so according to the protocol\r
2208                            rules, we don't reply. */\r
2209                         break;\r
2210                     }\r
2211                     break;\r
2212                   case TN_IAC:\r
2213                     if (appData.debugMode)\r
2214                       fprintf(debugFP, "\n<IAC ");\r
2215                     /* Doubled IAC; pass it through */\r
2216                     i--;\r
2217                     break;\r
2218                   default:\r
2219                     if (appData.debugMode)\r
2220                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
2221                     /* Drop all other telnet commands on the floor */\r
2222                     break;\r
2223                 }\r
2224                 if (oldi > next_out)\r
2225                   SendToPlayer(&buf[next_out], oldi - next_out);\r
2226                 if (++i > next_out)\r
2227                   next_out = i;\r
2228                 continue;\r
2229             }\r
2230                 \r
2231             /* OK, this at least will *usually* work */\r
2232             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
2233                 loggedOn = TRUE;\r
2234             }\r
2235             \r
2236             if (loggedOn && !intfSet) {\r
2237                 if (ics_type == ICS_ICC) {\r
2238                   sprintf(str,\r
2239                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
2240                           programVersion);\r
2241 \r
2242                 } else if (ics_type == ICS_CHESSNET) {\r
2243                   sprintf(str, "/style 12\n");\r
2244                 } else {\r
2245                   strcpy(str, "alias $ @\n$set interface ");\r
2246                   strcat(str, programVersion);\r
2247                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
2248 #ifdef WIN32\r
2249                   strcat(str, "$iset nohighlight 1\n");\r
2250 #endif\r
2251                   strcat(str, "$iset lock 1\n$style 12\n");\r
2252                 }\r
2253                 SendToICS(str);\r
2254                 intfSet = TRUE;\r
2255             }\r
2256 \r
2257             if (started == STARTED_COMMENT) {\r
2258                 /* Accumulate characters in comment */\r
2259                 parse[parse_pos++] = buf[i];\r
2260                 if (buf[i] == '\n') {\r
2261                     parse[parse_pos] = NULLCHAR;\r
2262                     if(!suppressKibitz) // [HGM] kibitz\r
2263                         AppendComment(forwardMostMove, StripHighlight(parse));\r
2264                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window\r
2265                         int nrDigit = 0, nrAlph = 0, i;\r
2266                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input\r
2267                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }\r
2268                         parse[parse_pos] = NULLCHAR;\r
2269                         // try to be smart: if it does not look like search info, it should go to\r
2270                         // ICS interaction window after all, not to engine-output window.\r
2271                         for(i=0; i<parse_pos; i++) { // count letters and digits\r
2272                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');\r
2273                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');\r
2274                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');\r
2275                         }\r
2276                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info\r
2277                             OutputKibitz(suppressKibitz, parse);\r
2278                         } else {\r
2279                             char tmp[MSG_SIZ];\r
2280                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);\r
2281                             SendToPlayer(tmp, strlen(tmp));\r
2282                         }\r
2283                     }\r
2284                     started = STARTED_NONE;\r
2285                 } else {\r
2286                     /* Don't match patterns against characters in chatter */\r
2287                     i++;\r
2288                     continue;\r
2289                 }\r
2290             }\r
2291             if (started == STARTED_CHATTER) {\r
2292                 if (buf[i] != '\n') {\r
2293                     /* Don't match patterns against characters in chatter */\r
2294                     i++;\r
2295                     continue;\r
2296                 }\r
2297                 started = STARTED_NONE;\r
2298             }\r
2299 \r
2300             /* Kludge to deal with rcmd protocol */\r
2301             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2302                 DisplayFatalError(&buf[1], 0, 1);\r
2303                 continue;\r
2304             } else {\r
2305                 firstTime = FALSE;\r
2306             }\r
2307 \r
2308             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2309                 ics_type = ICS_ICC;\r
2310                 ics_prefix = "/";\r
2311                 if (appData.debugMode)\r
2312                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2313                 continue;\r
2314             }\r
2315             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2316                 ics_type = ICS_FICS;\r
2317                 ics_prefix = "$";\r
2318                 if (appData.debugMode)\r
2319                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2320                 continue;\r
2321             }\r
2322             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2323                 ics_type = ICS_CHESSNET;\r
2324                 ics_prefix = "/";\r
2325                 if (appData.debugMode)\r
2326                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2327                 continue;\r
2328             }\r
2329 \r
2330             if (!loggedOn &&\r
2331                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2332                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2333                  looking_at(buf, &i, "will be \"*\""))) {\r
2334               strcpy(ics_handle, star_match[0]);\r
2335               continue;\r
2336             }\r
2337 \r
2338             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2339               char buf[MSG_SIZ];\r
2340               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2341               DisplayIcsInteractionTitle(buf);\r
2342               have_set_title = TRUE;\r
2343             }\r
2344 \r
2345             /* skip finger notes */\r
2346             if (started == STARTED_NONE &&\r
2347                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2348                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2349                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2350               started = STARTED_CHATTER;\r
2351               i += 3;\r
2352               continue;\r
2353             }\r
2354 \r
2355             /* skip formula vars */\r
2356             if (started == STARTED_NONE &&\r
2357                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2358               started = STARTED_CHATTER;\r
2359               i += 3;\r
2360               continue;\r
2361             }\r
2362 \r
2363             oldi = i;\r
2364             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window\r
2365             if (appData.autoKibitz && started == STARTED_NONE && \r
2366                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze\r
2367                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {\r
2368                 if(looking_at(buf, &i, "* kibitzes: ") &&\r
2369                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || \r
2370                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent\r
2371                         suppressKibitz = TRUE;\r
2372                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]\r
2373                                 && (gameMode == IcsPlayingWhite)) ||\r
2374                            (StrStr(star_match[0], gameInfo.black) == star_match[0]\r
2375                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz\r
2376                             started = STARTED_CHATTER; // own kibitz we simply discard\r
2377                         else {\r
2378                             started = STARTED_COMMENT; // make sure it will be collected in parse[]\r
2379                             parse_pos = 0; parse[0] = NULLCHAR;\r
2380                             savingComment = TRUE;\r
2381                             suppressKibitz = gameMode != IcsObserving ? 2 :\r
2382                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;\r
2383                         } \r
2384                         continue;\r
2385                 } else\r
2386                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz\r
2387                     started = STARTED_CHATTER;\r
2388                     suppressKibitz = TRUE;\r
2389                 }\r
2390             } // [HGM] kibitz: end of patch\r
2391 \r
2392             if (appData.zippyTalk || appData.zippyPlay) {\r
2393                 /* [DM] Backup address for color zippy lines */\r
2394                 backup = i;\r
2395 #if ZIPPY\r
2396        #ifdef WIN32\r
2397                if (loggedOn == TRUE)\r
2398                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||\r
2399                           (appData.zippyPlay && ZippyMatch(buf, &backup)));\r
2400        #else\r
2401                 if (ZippyControl(buf, &i) ||\r
2402                     ZippyConverse(buf, &i) ||\r
2403                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2404                       loggedOn = TRUE;\r
2405                       if (!appData.colorize) continue;\r
2406                 }\r
2407        #endif\r
2408 #endif\r
2409             } // [DM] 'else { ' deleted\r
2410                 if (/* Don't color "message" or "messages" output */\r
2411                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2412                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2413                     looking_at(buf, &i, "--* (*:*): ") ||\r
2414                     /* Regular tells and says */\r
2415                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2416                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2417                     looking_at(buf, &i, "* says: ") ||\r
2418                     /* Message notifications (same color as tells) */\r
2419                     looking_at(buf, &i, "* has left a message ") ||\r
2420                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2421                     /* Whispers and kibitzes */\r
2422                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2423                     looking_at(buf, &i, "* kibitzes: ") ||\r
2424                     /* Channel tells */\r
2425                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2426 \r
2427                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2428                       /* Avoid "tells you:" spoofs in channels */\r
2429                      tkind = 3;\r
2430                   }\r
2431                   if (star_match[0][0] == NULLCHAR ||\r
2432                       strchr(star_match[0], ' ') ||\r
2433                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2434                     /* Reject bogus matches */\r
2435                     i = oldi;\r
2436                   } else {\r
2437                     if (appData.colorize) {\r
2438                       if (oldi > next_out) {\r
2439                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2440                         next_out = oldi;\r
2441                       }\r
2442                       switch (tkind) {\r
2443                       case 1:\r
2444                         Colorize(ColorTell, FALSE);\r
2445                         curColor = ColorTell;\r
2446                         break;\r
2447                       case 2:\r
2448                         Colorize(ColorKibitz, FALSE);\r
2449                         curColor = ColorKibitz;\r
2450                         break;\r
2451                       case 3:\r
2452                         p = strrchr(star_match[1], '(');\r
2453                         if (p == NULL) {\r
2454                           p = star_match[1];\r
2455                         } else {\r
2456                           p++;\r
2457                         }\r
2458                         if (atoi(p) == 1) {\r
2459                           Colorize(ColorChannel1, FALSE);\r
2460                           curColor = ColorChannel1;\r
2461                         } else {\r
2462                           Colorize(ColorChannel, FALSE);\r
2463                           curColor = ColorChannel;\r
2464                         }\r
2465                         break;\r
2466                       case 5:\r
2467                         curColor = ColorNormal;\r
2468                         break;\r
2469                       }\r
2470                     }\r
2471                     if (started == STARTED_NONE && appData.autoComment &&\r
2472                         (gameMode == IcsObserving ||\r
2473                          gameMode == IcsPlayingWhite ||\r
2474                          gameMode == IcsPlayingBlack)) {\r
2475                       parse_pos = i - oldi;\r
2476                       memcpy(parse, &buf[oldi], parse_pos);\r
2477                       parse[parse_pos] = NULLCHAR;\r
2478                       started = STARTED_COMMENT;\r
2479                       savingComment = TRUE;\r
2480                     } else {\r
2481                       started = STARTED_CHATTER;\r
2482                       savingComment = FALSE;\r
2483                     }\r
2484                     loggedOn = TRUE;\r
2485                     continue;\r
2486                   }\r
2487                 }\r
2488 \r
2489                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2490                     looking_at(buf, &i, "* c-shouts: ")) {\r
2491                     if (appData.colorize) {\r
2492                         if (oldi > next_out) {\r
2493                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2494                             next_out = oldi;\r
2495                         }\r
2496                         Colorize(ColorSShout, FALSE);\r
2497                         curColor = ColorSShout;\r
2498                     }\r
2499                     loggedOn = TRUE;\r
2500                     started = STARTED_CHATTER;\r
2501                     continue;\r
2502                 }\r
2503 \r
2504                 if (looking_at(buf, &i, "--->")) {\r
2505                     loggedOn = TRUE;\r
2506                     continue;\r
2507                 }\r
2508 \r
2509                 if (looking_at(buf, &i, "* shouts: ") ||\r
2510                     looking_at(buf, &i, "--> ")) {\r
2511                     if (appData.colorize) {\r
2512                         if (oldi > next_out) {\r
2513                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2514                             next_out = oldi;\r
2515                         }\r
2516                         Colorize(ColorShout, FALSE);\r
2517                         curColor = ColorShout;\r
2518                     }\r
2519                     loggedOn = TRUE;\r
2520                     started = STARTED_CHATTER;\r
2521                     continue;\r
2522                 }\r
2523 \r
2524                 if (looking_at( buf, &i, "Challenge:")) {\r
2525                     if (appData.colorize) {\r
2526                         if (oldi > next_out) {\r
2527                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2528                             next_out = oldi;\r
2529                         }\r
2530                         Colorize(ColorChallenge, FALSE);\r
2531                         curColor = ColorChallenge;\r
2532                     }\r
2533                     loggedOn = TRUE;\r
2534                     continue;\r
2535                 }\r
2536 \r
2537                 if (looking_at(buf, &i, "* offers you") ||\r
2538                     looking_at(buf, &i, "* offers to be") ||\r
2539                     looking_at(buf, &i, "* would like to") ||\r
2540                     looking_at(buf, &i, "* requests to") ||\r
2541                     looking_at(buf, &i, "Your opponent offers") ||\r
2542                     looking_at(buf, &i, "Your opponent requests")) {\r
2543 \r
2544                     if (appData.colorize) {\r
2545                         if (oldi > next_out) {\r
2546                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2547                             next_out = oldi;\r
2548                         }\r
2549                         Colorize(ColorRequest, FALSE);\r
2550                         curColor = ColorRequest;\r
2551                     }\r
2552                     continue;\r
2553                 }\r
2554 \r
2555                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2556                     if (appData.colorize) {\r
2557                         if (oldi > next_out) {\r
2558                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2559                             next_out = oldi;\r
2560                         }\r
2561                         Colorize(ColorSeek, FALSE);\r
2562                         curColor = ColorSeek;\r
2563                     }\r
2564                     continue;\r
2565             }\r
2566 \r
2567             if (looking_at(buf, &i, "\\   ")) {\r
2568                 if (prevColor != ColorNormal) {\r
2569                     if (oldi > next_out) {\r
2570                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2571                         next_out = oldi;\r
2572                     }\r
2573                     Colorize(prevColor, TRUE);\r
2574                     curColor = prevColor;\r
2575                 }\r
2576                 if (savingComment) {\r
2577                     parse_pos = i - oldi;\r
2578                     memcpy(parse, &buf[oldi], parse_pos);\r
2579                     parse[parse_pos] = NULLCHAR;\r
2580                     started = STARTED_COMMENT;\r
2581                 } else {\r
2582                     started = STARTED_CHATTER;\r
2583                 }\r
2584                 continue;\r
2585             }\r
2586 \r
2587             if (looking_at(buf, &i, "Black Strength :") ||\r
2588                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2589                 looking_at(buf, &i, "<10>") ||\r
2590                 looking_at(buf, &i, "#@#")) {\r
2591                 /* Wrong board style */\r
2592                 loggedOn = TRUE;\r
2593                 SendToICS(ics_prefix);\r
2594                 SendToICS("set style 12\n");\r
2595                 SendToICS(ics_prefix);\r
2596                 SendToICS("refresh\n");\r
2597                 continue;\r
2598             }\r
2599             \r
2600             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2601                 ICSInitScript();\r
2602                 have_sent_ICS_logon = 1;\r
2603                 continue;\r
2604             }\r
2605               \r
2606             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2607                 (looking_at(buf, &i, "\n<12> ") ||\r
2608                  looking_at(buf, &i, "<12> "))) {\r
2609                 loggedOn = TRUE;\r
2610                 if (oldi > next_out) {\r
2611                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2612                 }\r
2613                 next_out = i;\r
2614                 started = STARTED_BOARD;\r
2615                 parse_pos = 0;\r
2616                 continue;\r
2617             }\r
2618 \r
2619             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2620                 looking_at(buf, &i, "<b1> ")) {\r
2621                 if (oldi > next_out) {\r
2622                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2623                 }\r
2624                 next_out = i;\r
2625                 started = STARTED_HOLDINGS;\r
2626                 parse_pos = 0;\r
2627                 continue;\r
2628             }\r
2629 \r
2630             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2631                 loggedOn = TRUE;\r
2632                 /* Header for a move list -- first line */\r
2633 \r
2634                 switch (ics_getting_history) {\r
2635                   case H_FALSE:\r
2636                     switch (gameMode) {\r
2637                       case IcsIdle:\r
2638                       case BeginningOfGame:\r
2639                         /* User typed "moves" or "oldmoves" while we\r
2640                            were idle.  Pretend we asked for these\r
2641                            moves and soak them up so user can step\r
2642                            through them and/or save them.\r
2643                            */\r
2644                         Reset(FALSE, TRUE);\r
2645                         gameMode = IcsObserving;\r
2646                         ModeHighlight();\r
2647                         ics_gamenum = -1;\r
2648                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2649                         break;\r
2650                       case EditGame: /*?*/\r
2651                       case EditPosition: /*?*/\r
2652                         /* Should above feature work in these modes too? */\r
2653                         /* For now it doesn't */\r
2654                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2655                         break;\r
2656                       default:\r
2657                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2658                         break;\r
2659                     }\r
2660                     break;\r
2661                   case H_REQUESTED:\r
2662                     /* Is this the right one? */\r
2663                     if (gameInfo.white && gameInfo.black &&\r
2664                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2665                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2666                         /* All is well */\r
2667                         ics_getting_history = H_GOT_REQ_HEADER;\r
2668                     }\r
2669                     break;\r
2670                   case H_GOT_REQ_HEADER:\r
2671                   case H_GOT_UNREQ_HEADER:\r
2672                   case H_GOT_UNWANTED_HEADER:\r
2673                   case H_GETTING_MOVES:\r
2674                     /* Should not happen */\r
2675                     DisplayError(_("Error gathering move list: two headers"), 0);\r
2676                     ics_getting_history = H_FALSE;\r
2677                     break;\r
2678                 }\r
2679 \r
2680                 /* Save player ratings into gameInfo if needed */\r
2681                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2682                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2683                     (gameInfo.whiteRating == -1 ||\r
2684                      gameInfo.blackRating == -1)) {\r
2685 \r
2686                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2687                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2688                     if (appData.debugMode)\r
2689                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), \r
2690                               gameInfo.whiteRating, gameInfo.blackRating);\r
2691                 }\r
2692                 continue;\r
2693             }\r
2694 \r
2695             if (looking_at(buf, &i,\r
2696               "* * match, initial time: * minute*, increment: * second")) {\r
2697                 /* Header for a move list -- second line */\r
2698                 /* Initial board will follow if this is a wild game */\r
2699                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2700                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2701                 gameInfo.event = StrSave(str);\r
2702                 /* [HGM] we switched variant. Translate boards if needed. */\r
2703                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2704                 continue;\r
2705             }\r
2706 \r
2707             if (looking_at(buf, &i, "Move  ")) {\r
2708                 /* Beginning of a move list */\r
2709                 switch (ics_getting_history) {\r
2710                   case H_FALSE:\r
2711                     /* Normally should not happen */\r
2712                     /* Maybe user hit reset while we were parsing */\r
2713                     break;\r
2714                   case H_REQUESTED:\r
2715                     /* Happens if we are ignoring a move list that is not\r
2716                      * the one we just requested.  Common if the user\r
2717                      * tries to observe two games without turning off\r
2718                      * getMoveList */\r
2719                     break;\r
2720                   case H_GETTING_MOVES:\r
2721                     /* Should not happen */\r
2722                     DisplayError(_("Error gathering move list: nested"), 0);\r
2723                     ics_getting_history = H_FALSE;\r
2724                     break;\r
2725                   case H_GOT_REQ_HEADER:\r
2726                     ics_getting_history = H_GETTING_MOVES;\r
2727                     started = STARTED_MOVES;\r
2728                     parse_pos = 0;\r
2729                     if (oldi > next_out) {\r
2730                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2731                     }\r
2732                     break;\r
2733                   case H_GOT_UNREQ_HEADER:\r
2734                     ics_getting_history = H_GETTING_MOVES;\r
2735                     started = STARTED_MOVES_NOHIDE;\r
2736                     parse_pos = 0;\r
2737                     break;\r
2738                   case H_GOT_UNWANTED_HEADER:\r
2739                     ics_getting_history = H_FALSE;\r
2740                     break;\r
2741                 }\r
2742                 continue;\r
2743             }                           \r
2744             \r
2745             if (looking_at(buf, &i, "% ") ||\r
2746                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2747                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book\r
2748                 savingComment = FALSE;\r
2749                 switch (started) {\r
2750                   case STARTED_MOVES:\r
2751                   case STARTED_MOVES_NOHIDE:\r
2752                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2753                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2754                     ParseGameHistory(parse);\r
2755 #if ZIPPY\r
2756                     if (appData.zippyPlay && first.initDone) {\r
2757                         FeedMovesToProgram(&first, forwardMostMove);\r
2758                         if (gameMode == IcsPlayingWhite) {\r
2759                             if (WhiteOnMove(forwardMostMove)) {\r
2760                                 if (first.sendTime) {\r
2761                                   if (first.useColors) {\r
2762                                     SendToProgram("black\n", &first); \r
2763                                   }\r
2764                                   SendTimeRemaining(&first, TRUE);\r
2765                                 }\r
2766 #if 0\r
2767                                 if (first.useColors) {\r
2768                                   SendToProgram("white\ngo\n", &first);\r
2769                                 } else {\r
2770                                   SendToProgram("go\n", &first);\r
2771                                 }\r
2772 #else\r
2773                                 if (first.useColors) {\r
2774                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent\r
2775                                 }\r
2776                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos\r
2777 #endif\r
2778                                 first.maybeThinking = TRUE;\r
2779                             } else {\r
2780                                 if (first.usePlayother) {\r
2781                                   if (first.sendTime) {\r
2782                                     SendTimeRemaining(&first, TRUE);\r
2783                                   }\r
2784                                   SendToProgram("playother\n", &first);\r
2785                                   firstMove = FALSE;\r
2786                                 } else {\r
2787                                   firstMove = TRUE;\r
2788                                 }\r
2789                             }\r
2790                         } else if (gameMode == IcsPlayingBlack) {\r
2791                             if (!WhiteOnMove(forwardMostMove)) {\r
2792                                 if (first.sendTime) {\r
2793                                   if (first.useColors) {\r
2794                                     SendToProgram("white\n", &first);\r
2795                                   }\r
2796                                   SendTimeRemaining(&first, FALSE);\r
2797                                 }\r
2798 #if 0\r
2799                                 if (first.useColors) {\r
2800                                   SendToProgram("black\ngo\n", &first);\r
2801                                 } else {\r
2802                                   SendToProgram("go\n", &first);\r
2803                                 }\r
2804 #else\r
2805                                 if (first.useColors) {\r
2806                                   SendToProgram("black\n", &first);\r
2807                                 }\r
2808                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);\r
2809 #endif\r
2810                                 first.maybeThinking = TRUE;\r
2811                             } else {\r
2812                                 if (first.usePlayother) {\r
2813                                   if (first.sendTime) {\r
2814                                     SendTimeRemaining(&first, FALSE);\r
2815                                   }\r
2816                                   SendToProgram("playother\n", &first);\r
2817                                   firstMove = FALSE;\r
2818                                 } else {\r
2819                                   firstMove = TRUE;\r
2820                                 }\r
2821                             }\r
2822                         }                       \r
2823                     }\r
2824 #endif\r
2825                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2826                         /* Moves came from oldmoves or moves command\r
2827                            while we weren't doing anything else.\r
2828                            */\r
2829                         currentMove = forwardMostMove;\r
2830                         ClearHighlights();/*!!could figure this out*/\r
2831                         flipView = appData.flipView;\r
2832                         DrawPosition(FALSE, boards[currentMove]);\r
2833                         DisplayBothClocks();\r
2834                         sprintf(str, "%s vs. %s",\r
2835                                 gameInfo.white, gameInfo.black);\r
2836                         DisplayTitle(str);\r
2837                         gameMode = IcsIdle;\r
2838                     } else {\r
2839                         /* Moves were history of an active game */\r
2840                         if (gameInfo.resultDetails != NULL) {\r
2841                             free(gameInfo.resultDetails);\r
2842                             gameInfo.resultDetails = NULL;\r
2843                         }\r
2844                     }\r
2845                     HistorySet(parseList, backwardMostMove,\r
2846                                forwardMostMove, currentMove-1);\r
2847                     DisplayMove(currentMove - 1);\r
2848                     if (started == STARTED_MOVES) next_out = i;\r
2849                     started = STARTED_NONE;\r
2850                     ics_getting_history = H_FALSE;\r
2851                     break;\r
2852 \r
2853                   case STARTED_OBSERVE:\r
2854                     started = STARTED_NONE;\r
2855                     SendToICS(ics_prefix);\r
2856                     SendToICS("refresh\n");\r
2857                     break;\r
2858 \r
2859                   default:\r
2860                     break;\r
2861                 }\r
2862                 if(bookHit) { // [HGM] book: simulate book reply\r
2863                     static char bookMove[MSG_SIZ]; // a bit generous?\r
2864 \r
2865                     programStats.nodes = programStats.depth = programStats.time = \r
2866                     programStats.score = programStats.got_only_move = 0;\r
2867                     sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
2868 \r
2869                     strcpy(bookMove, "move ");\r
2870                     strcat(bookMove, bookHit);\r
2871                     HandleMachineMove(bookMove, &first);\r
2872                 }\r
2873                 continue;\r
2874             }\r
2875             \r
2876             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2877                  started == STARTED_HOLDINGS ||\r
2878                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2879                 /* Accumulate characters in move list or board */\r
2880                 parse[parse_pos++] = buf[i];\r
2881             }\r
2882             \r
2883             /* Start of game messages.  Mostly we detect start of game\r
2884                when the first board image arrives.  On some versions\r
2885                of the ICS, though, we need to do a "refresh" after starting\r
2886                to observe in order to get the current board right away. */\r
2887             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2888                 started = STARTED_OBSERVE;\r
2889                 continue;\r
2890             }\r
2891 \r
2892             /* Handle auto-observe */\r
2893             if (appData.autoObserve &&\r
2894                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2895                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2896                 char *player;\r
2897                 /* Choose the player that was highlighted, if any. */\r
2898                 if (star_match[0][0] == '\033' ||\r
2899                     star_match[1][0] != '\033') {\r
2900                     player = star_match[0];\r
2901                 } else {\r
2902                     player = star_match[2];\r
2903                 }\r
2904                 sprintf(str, "%sobserve %s\n",\r
2905                         ics_prefix, StripHighlightAndTitle(player));\r
2906                 SendToICS(str);\r
2907 \r
2908                 /* Save ratings from notify string */\r
2909                 strcpy(player1Name, star_match[0]);\r
2910                 player1Rating = string_to_rating(star_match[1]);\r
2911                 strcpy(player2Name, star_match[2]);\r
2912                 player2Rating = string_to_rating(star_match[3]);\r
2913 \r
2914                 if (appData.debugMode)\r
2915                   fprintf(debugFP, \r
2916                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2917                           player1Name, player1Rating,\r
2918                           player2Name, player2Rating);\r
2919 \r
2920                 continue;\r
2921             }\r
2922 \r
2923             /* Deal with automatic examine mode after a game,\r
2924                and with IcsObserving -> IcsExamining transition */\r
2925             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2926                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2927 \r
2928                 int gamenum = atoi(star_match[0]);\r
2929                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2930                     gamenum == ics_gamenum) {\r
2931                     /* We were already playing or observing this game;\r
2932                        no need to refetch history */\r
2933                     gameMode = IcsExamining;\r
2934                     if (pausing) {\r
2935                         pauseExamForwardMostMove = forwardMostMove;\r
2936                     } else if (currentMove < forwardMostMove) {\r
2937                         ForwardInner(forwardMostMove);\r
2938                     }\r
2939                 } else {\r
2940                     /* I don't think this case really can happen */\r
2941                     SendToICS(ics_prefix);\r
2942                     SendToICS("refresh\n");\r
2943                 }\r
2944                 continue;\r
2945             }    \r
2946             \r
2947             /* Error messages */\r
2948             if (ics_user_moved) {\r
2949                 if (looking_at(buf, &i, "Illegal move") ||\r
2950                     looking_at(buf, &i, "Not a legal move") ||\r
2951                     looking_at(buf, &i, "Your king is in check") ||\r
2952                     looking_at(buf, &i, "It isn't your turn") ||\r
2953                     looking_at(buf, &i, "It is not your move")) {\r
2954                     /* Illegal move */\r
2955                     ics_user_moved = 0;\r
2956                     if (forwardMostMove > backwardMostMove) {\r
2957                         currentMove = --forwardMostMove;\r
2958                         DisplayMove(currentMove - 1); /* before DMError */\r
2959                         DisplayMoveError(_("Illegal move (rejected by ICS)"));\r
2960                         DrawPosition(FALSE, boards[currentMove]);\r
2961                         SwitchClocks();\r
2962                         DisplayBothClocks();\r
2963                     }\r
2964                     continue;\r
2965                 }\r
2966             }\r
2967 \r
2968             if (looking_at(buf, &i, "still have time") ||\r
2969                 looking_at(buf, &i, "not out of time") ||\r
2970                 looking_at(buf, &i, "either player is out of time") ||\r
2971                 looking_at(buf, &i, "has timeseal; checking")) {\r
2972                 /* We must have called his flag a little too soon */\r
2973                 whiteFlag = blackFlag = FALSE;\r
2974                 continue;\r
2975             }\r
2976 \r
2977             if (looking_at(buf, &i, "added * seconds to") ||\r
2978                 looking_at(buf, &i, "seconds were added to")) {\r
2979                 /* Update the clocks */\r
2980                 SendToICS(ics_prefix);\r
2981                 SendToICS("refresh\n");\r
2982                 continue;\r
2983             }\r
2984 \r
2985             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2986                 ics_clock_paused = TRUE;\r
2987                 StopClocks();\r
2988                 continue;\r
2989             }\r
2990 \r
2991             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2992                 ics_clock_paused = FALSE;\r
2993                 StartClocks();\r
2994                 continue;\r
2995             }\r
2996 \r
2997             /* Grab player ratings from the Creating: message.\r
2998                Note we have to check for the special case when\r
2999                the ICS inserts things like [white] or [black]. */\r
3000             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
3001                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
3002                 /* star_matches:\r
3003                    0    player 1 name (not necessarily white)\r
3004                    1    player 1 rating\r
3005                    2    empty, white, or black (IGNORED)\r
3006                    3    player 2 name (not necessarily black)\r
3007                    4    player 2 rating\r
3008                    \r
3009                    The names/ratings are sorted out when the game\r
3010                    actually starts (below).\r
3011                 */\r
3012                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
3013                 player1Rating = string_to_rating(star_match[1]);\r
3014                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
3015                 player2Rating = string_to_rating(star_match[4]);\r
3016 \r
3017                 if (appData.debugMode)\r
3018                   fprintf(debugFP, \r
3019                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
3020                           player1Name, player1Rating,\r
3021                           player2Name, player2Rating);\r
3022 \r
3023                 continue;\r
3024             }\r
3025             \r
3026             /* Improved generic start/end-of-game messages */\r
3027             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
3028                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
3029                 /* If tkind == 0: */\r
3030                 /* star_match[0] is the game number */\r
3031                 /*           [1] is the white player's name */\r
3032                 /*           [2] is the black player's name */\r
3033                 /* For end-of-game: */\r
3034                 /*           [3] is the reason for the game end */\r
3035                 /*           [4] is a PGN end game-token, preceded by " " */\r
3036                 /* For start-of-game: */\r
3037                 /*           [3] begins with "Creating" or "Continuing" */\r
3038                 /*           [4] is " *" or empty (don't care). */\r
3039                 int gamenum = atoi(star_match[0]);\r
3040                 char *whitename, *blackname, *why, *endtoken;\r
3041                 ChessMove endtype = (ChessMove) 0;\r
3042 \r
3043                 if (tkind == 0) {\r
3044                   whitename = star_match[1];\r
3045                   blackname = star_match[2];\r
3046                   why = star_match[3];\r
3047                   endtoken = star_match[4];\r
3048                 } else {\r
3049                   whitename = star_match[1];\r
3050                   blackname = star_match[3];\r
3051                   why = star_match[5];\r
3052                   endtoken = star_match[6];\r
3053                 }\r
3054 \r
3055                 /* Game start messages */\r
3056                 if (strncmp(why, "Creating ", 9) == 0 ||\r
3057                     strncmp(why, "Continuing ", 11) == 0) {\r
3058                     gs_gamenum = gamenum;\r
3059                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
3060 #if ZIPPY\r
3061                     if (appData.zippyPlay) {\r
3062                         ZippyGameStart(whitename, blackname);\r
3063                     }\r
3064 #endif /*ZIPPY*/\r
3065                     continue;\r
3066                 }\r
3067 \r
3068                 /* Game end messages */\r
3069                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
3070                     ics_gamenum != gamenum) {\r
3071                     continue;\r
3072                 }\r
3073                 while (endtoken[0] == ' ') endtoken++;\r
3074                 switch (endtoken[0]) {\r
3075                   case '*':\r
3076                   default:\r
3077                     endtype = GameUnfinished;\r
3078                     break;\r
3079                   case '0':\r
3080                     endtype = BlackWins;\r
3081                     break;\r
3082                   case '1':\r
3083                     if (endtoken[1] == '/')\r
3084                       endtype = GameIsDrawn;\r
3085                     else\r
3086                       endtype = WhiteWins;\r
3087                     break;\r
3088                 }\r
3089                 GameEnds(endtype, why, GE_ICS);\r
3090 #if ZIPPY\r
3091                 if (appData.zippyPlay && first.initDone) {\r
3092                     ZippyGameEnd(endtype, why);\r
3093                     if (first.pr == NULL) {\r
3094                       /* Start the next process early so that we'll\r
3095                          be ready for the next challenge */\r
3096                       StartChessProgram(&first);\r
3097                     }\r
3098                     /* Send "new" early, in case this command takes\r
3099                        a long time to finish, so that we'll be ready\r
3100                        for the next challenge. */\r
3101                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'\r
3102                     Reset(TRUE, TRUE);\r
3103                 }\r
3104 #endif /*ZIPPY*/\r
3105                 continue;\r
3106             }\r
3107 \r
3108             if (looking_at(buf, &i, "Removing game * from observation") ||\r
3109                 looking_at(buf, &i, "no longer observing game *") ||\r
3110                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
3111                 if (gameMode == IcsObserving &&\r
3112                     atoi(star_match[0]) == ics_gamenum)\r
3113                   {\r
3114                       /* icsEngineAnalyze */\r
3115                       if (appData.icsEngineAnalyze) {\r
3116                             ExitAnalyzeMode();\r
3117                             ModeHighlight();\r
3118                       }\r
3119                       StopClocks();\r
3120                       gameMode = IcsIdle;\r
3121                       ics_gamenum = -1;\r
3122                       ics_user_moved = FALSE;\r
3123                   }\r
3124                 continue;\r
3125             }\r
3126 \r
3127             if (looking_at(buf, &i, "no longer examining game *")) {\r
3128                 if (gameMode == IcsExamining &&\r
3129                     atoi(star_match[0]) == ics_gamenum)\r
3130                   {\r
3131                       gameMode = IcsIdle;\r
3132                       ics_gamenum = -1;\r
3133                       ics_user_moved = FALSE;\r
3134                   }\r
3135                 continue;\r
3136             }\r
3137 \r
3138             /* Advance leftover_start past any newlines we find,\r
3139                so only partial lines can get reparsed */\r
3140             if (looking_at(buf, &i, "\n")) {\r
3141                 prevColor = curColor;\r
3142                 if (curColor != ColorNormal) {\r
3143                     if (oldi > next_out) {\r
3144                         SendToPlayer(&buf[next_out], oldi - next_out);\r
3145                         next_out = oldi;\r
3146                     }\r
3147                     Colorize(ColorNormal, FALSE);\r
3148                     curColor = ColorNormal;\r
3149                 }\r
3150                 if (started == STARTED_BOARD) {\r
3151                     started = STARTED_NONE;\r
3152                     parse[parse_pos] = NULLCHAR;\r
3153                     ParseBoard12(parse);\r
3154                     ics_user_moved = 0;\r
3155 \r
3156                     /* Send premove here */\r
3157                     if (appData.premove) {\r
3158                       char str[MSG_SIZ];\r
3159                       if (currentMove == 0 &&\r
3160                           gameMode == IcsPlayingWhite &&\r
3161                           appData.premoveWhite) {\r
3162                         sprintf(str, "%s%s\n", ics_prefix,\r
3163                                 appData.premoveWhiteText);\r
3164                         if (appData.debugMode)\r
3165                           fprintf(debugFP, "Sending premove:\n");\r
3166                         SendToICS(str);\r
3167                       } else if (currentMove == 1 &&\r
3168                                  gameMode == IcsPlayingBlack &&\r
3169                                  appData.premoveBlack) {\r
3170                         sprintf(str, "%s%s\n", ics_prefix,\r
3171                                 appData.premoveBlackText);\r
3172                         if (appData.debugMode)\r
3173                           fprintf(debugFP, "Sending premove:\n");\r
3174                         SendToICS(str);\r
3175                       } else if (gotPremove) {\r
3176                         gotPremove = 0;\r
3177                         ClearPremoveHighlights();\r
3178                         if (appData.debugMode)\r
3179                           fprintf(debugFP, "Sending premove:\n");\r
3180                           UserMoveEvent(premoveFromX, premoveFromY, \r
3181                                         premoveToX, premoveToY, \r
3182                                         premovePromoChar);\r
3183                       }\r
3184                     }\r
3185 \r
3186                     /* Usually suppress following prompt */\r
3187                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
3188                         if (looking_at(buf, &i, "*% ")) {\r
3189                             savingComment = FALSE;\r
3190                         }\r
3191                     }\r
3192                     next_out = i;\r
3193                 } else if (started == STARTED_HOLDINGS) {\r
3194                     int gamenum;\r
3195                     char new_piece[MSG_SIZ];\r
3196                     started = STARTED_NONE;\r
3197                     parse[parse_pos] = NULLCHAR;\r
3198                     if (appData.debugMode)\r
3199                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
3200                                                         parse, currentMove);\r
3201                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
3202                         gamenum == ics_gamenum) {\r
3203                         if (gameInfo.variant == VariantNormal) {\r
3204                           /* [HGM] We seem to switch variant during a game!\r
3205                            * Presumably no holdings were displayed, so we have\r
3206                            * to move the position two files to the right to\r
3207                            * create room for them!\r
3208                            */\r
3209                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
3210                           /* Get a move list just to see the header, which\r
3211                              will tell us whether this is really bug or zh */\r
3212                           if (ics_getting_history == H_FALSE) {\r
3213                             ics_getting_history = H_REQUESTED;\r
3214                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3215                             SendToICS(str);\r
3216                           }\r
3217                         }\r
3218                         new_piece[0] = NULLCHAR;\r
3219                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
3220                                &gamenum, white_holding, black_holding,\r
3221                                new_piece);\r
3222                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
3223                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
3224                         /* [HGM] copy holdings to board holdings area */\r
3225                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
3226                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
3227 #if ZIPPY\r
3228                         if (appData.zippyPlay && first.initDone) {\r
3229                             ZippyHoldings(white_holding, black_holding,\r
3230                                           new_piece);\r
3231                         }\r
3232 #endif /*ZIPPY*/\r
3233                         if (tinyLayout || smallLayout) {\r
3234                             char wh[16], bh[16];\r
3235                             PackHolding(wh, white_holding);\r
3236                             PackHolding(bh, black_holding);\r
3237                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
3238                                     gameInfo.white, gameInfo.black);\r
3239                         } else {\r
3240                             sprintf(str, "%s [%s] vs. %s [%s]",\r
3241                                     gameInfo.white, white_holding,\r
3242                                     gameInfo.black, black_holding);\r
3243                         }\r
3244 \r
3245                         DrawPosition(FALSE, boards[currentMove]);\r
3246                         DisplayTitle(str);\r
3247                     }\r
3248                     /* Suppress following prompt */\r
3249                     if (looking_at(buf, &i, "*% ")) {\r
3250                         savingComment = FALSE;\r
3251                     }\r
3252                     next_out = i;\r
3253                 }\r
3254                 continue;\r
3255             }\r
3256 \r
3257             i++;                /* skip unparsed character and loop back */\r
3258         }\r
3259         \r
3260         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window\r
3261             started != STARTED_HOLDINGS && i > next_out) {\r
3262             SendToPlayer(&buf[next_out], i - next_out);\r
3263             next_out = i;\r
3264         }\r
3265         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above\r
3266         \r
3267         leftover_len = buf_len - leftover_start;\r
3268         /* if buffer ends with something we couldn't parse,\r
3269            reparse it after appending the next read */\r
3270         \r
3271     } else if (count == 0) {\r
3272         RemoveInputSource(isr);\r
3273         DisplayFatalError(_("Connection closed by ICS"), 0, 0);\r
3274     } else {\r
3275         DisplayFatalError(_("Error reading from ICS"), error, 1);\r
3276     }\r
3277 }\r
3278 \r
3279 \r
3280 /* Board style 12 looks like this:\r
3281    \r
3282    <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
3283    \r
3284  * The "<12> " is stripped before it gets to this routine.  The two\r
3285  * trailing 0's (flip state and clock ticking) are later addition, and\r
3286  * some chess servers may not have them, or may have only the first.\r
3287  * Additional trailing fields may be added in the future.  \r
3288  */\r
3289 \r
3290 #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
3291 \r
3292 #define RELATION_OBSERVING_PLAYED    0\r
3293 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
3294 #define RELATION_PLAYING_MYMOVE      1\r
3295 #define RELATION_PLAYING_NOTMYMOVE  -1\r
3296 #define RELATION_EXAMINING           2\r
3297 #define RELATION_ISOLATED_BOARD     -3\r
3298 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
3299 \r
3300 void\r
3301 ParseBoard12(string)\r
3302      char *string;\r
3303\r
3304     GameMode newGameMode;\r
3305     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;\r
3306     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;\r
3307     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
3308     char to_play, board_chars[200];\r
3309     char move_str[500], str[500], elapsed_time[500];\r
3310     char black[32], white[32];\r
3311     Board board;\r
3312     int prevMove = currentMove;\r
3313     int ticking = 2;\r
3314     ChessMove moveType;\r
3315     int fromX, fromY, toX, toY;\r
3316     char promoChar;\r
3317     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */\r
3318     char *bookHit = NULL; // [HGM] book\r
3319 \r
3320     fromX = fromY = toX = toY = -1;\r
3321     \r
3322     newGame = FALSE;\r
3323 \r
3324     if (appData.debugMode)\r
3325       fprintf(debugFP, _("Parsing board: %s\n"), string);\r
3326 \r
3327     move_str[0] = NULLCHAR;\r
3328     elapsed_time[0] = NULLCHAR;\r
3329     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */\r
3330         int  i = 0, j;\r
3331         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {\r
3332             if(string[i] == ' ') { ranks++; files = 0; }\r
3333             else files++;\r
3334             i++;\r
3335         }\r
3336         for(j = 0; j <i; j++) board_chars[j] = string[j];\r
3337         board_chars[i] = '\0';\r
3338         string += i + 1;\r
3339     }\r
3340     n = sscanf(string, PATTERN, &to_play, &double_push,\r
3341                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
3342                &gamenum, white, black, &relation, &basetime, &increment,\r
3343                &white_stren, &black_stren, &white_time, &black_time,\r
3344                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
3345                &ticking);\r
3346 \r
3347     if (n < 21) {\r
3348         sprintf(str, _("Failed to parse board string:\n\"%s\""), string);\r
3349         DisplayError(str, 0);\r
3350         return;\r
3351     }\r
3352 \r
3353     /* Convert the move number to internal form */\r
3354     moveNum = (moveNum - 1) * 2;\r
3355     if (to_play == 'B') moveNum++;\r
3356     if (moveNum >= MAX_MOVES) {\r
3357       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
3358                         0, 1);\r
3359       return;\r
3360     }\r
3361     \r
3362     switch (relation) {\r
3363       case RELATION_OBSERVING_PLAYED:\r
3364       case RELATION_OBSERVING_STATIC:\r
3365         if (gamenum == -1) {\r
3366             /* Old ICC buglet */\r
3367             relation = RELATION_OBSERVING_STATIC;\r
3368         }\r
3369         newGameMode = IcsObserving;\r
3370         break;\r
3371       case RELATION_PLAYING_MYMOVE:\r
3372       case RELATION_PLAYING_NOTMYMOVE:\r
3373         newGameMode =\r
3374           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3375             IcsPlayingWhite : IcsPlayingBlack;\r
3376         break;\r
3377       case RELATION_EXAMINING:\r
3378         newGameMode = IcsExamining;\r
3379         break;\r
3380       case RELATION_ISOLATED_BOARD:\r
3381       default:\r
3382         /* Just display this board.  If user was doing something else,\r
3383            we will forget about it until the next board comes. */ \r
3384         newGameMode = IcsIdle;\r
3385         break;\r
3386       case RELATION_STARTING_POSITION:\r
3387         newGameMode = gameMode;\r
3388         break;\r
3389     }\r
3390     \r
3391     /* Modify behavior for initial board display on move listing\r
3392        of wild games.\r
3393        */\r
3394     switch (ics_getting_history) {\r
3395       case H_FALSE:\r
3396       case H_REQUESTED:\r
3397         break;\r
3398       case H_GOT_REQ_HEADER:\r
3399       case H_GOT_UNREQ_HEADER:\r
3400         /* This is the initial position of the current game */\r
3401         gamenum = ics_gamenum;\r
3402         moveNum = 0;            /* old ICS bug workaround */\r
3403         if (to_play == 'B') {\r
3404           startedFromSetupPosition = TRUE;\r
3405           blackPlaysFirst = TRUE;\r
3406           moveNum = 1;\r
3407           if (forwardMostMove == 0) forwardMostMove = 1;\r
3408           if (backwardMostMove == 0) backwardMostMove = 1;\r
3409           if (currentMove == 0) currentMove = 1;\r
3410         }\r
3411         newGameMode = gameMode;\r
3412         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3413         break;\r
3414       case H_GOT_UNWANTED_HEADER:\r
3415         /* This is an initial board that we don't want */\r
3416         return;\r
3417       case H_GETTING_MOVES:\r
3418         /* Should not happen */\r
3419         DisplayError(_("Error gathering move list: extra board"), 0);\r
3420         ics_getting_history = H_FALSE;\r
3421         return;\r
3422     }\r
3423     \r
3424     /* Take action if this is the first board of a new game, or of a\r
3425        different game than is currently being displayed.  */\r
3426     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3427         relation == RELATION_ISOLATED_BOARD) {\r
3428         \r
3429         /* Forget the old game and get the history (if any) of the new one */\r
3430         if (gameMode != BeginningOfGame) {\r
3431           Reset(FALSE, TRUE);\r
3432         }\r
3433         newGame = TRUE;\r
3434         if (appData.autoRaiseBoard) BoardToTop();\r
3435         prevMove = -3;\r
3436         if (gamenum == -1) {\r
3437             newGameMode = IcsIdle;\r
3438         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3439                    appData.getMoveList) {\r
3440             /* Need to get game history */\r
3441             ics_getting_history = H_REQUESTED;\r
3442             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3443             SendToICS(str);\r
3444         }\r
3445         \r
3446         /* Initially flip the board to have black on the bottom if playing\r
3447            black or if the ICS flip flag is set, but let the user change\r
3448            it with the Flip View button. */\r
3449         flipView = appData.autoFlipView ? \r
3450           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3451           appData.flipView;\r
3452         \r
3453         /* Done with values from previous mode; copy in new ones */\r
3454         gameMode = newGameMode;\r
3455         ModeHighlight();\r
3456         ics_gamenum = gamenum;\r
3457         if (gamenum == gs_gamenum) {\r
3458             int klen = strlen(gs_kind);\r
3459             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3460             sprintf(str, "ICS %s", gs_kind);\r
3461             gameInfo.event = StrSave(str);\r
3462         } else {\r
3463             gameInfo.event = StrSave("ICS game");\r
3464         }\r
3465         gameInfo.site = StrSave(appData.icsHost);\r
3466         gameInfo.date = PGNDate();\r
3467         gameInfo.round = StrSave("-");\r
3468         gameInfo.white = StrSave(white);\r
3469         gameInfo.black = StrSave(black);\r
3470         timeControl = basetime * 60 * 1000;\r
3471         timeControl_2 = 0;\r
3472         timeIncrement = increment * 1000;\r
3473         movesPerSession = 0;\r
3474         gameInfo.timeControl = TimeControlTagValue();\r
3475         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3476   if (appData.debugMode) {\r
3477     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3478     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3479     setbuf(debugFP, NULL);\r
3480   }\r
3481 \r
3482         gameInfo.outOfBook = NULL;\r
3483         \r
3484         /* Do we have the ratings? */\r
3485         if (strcmp(player1Name, white) == 0 &&\r
3486             strcmp(player2Name, black) == 0) {\r
3487             if (appData.debugMode)\r
3488               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3489                       player1Rating, player2Rating);\r
3490             gameInfo.whiteRating = player1Rating;\r
3491             gameInfo.blackRating = player2Rating;\r
3492         } else if (strcmp(player2Name, white) == 0 &&\r
3493                    strcmp(player1Name, black) == 0) {\r
3494             if (appData.debugMode)\r
3495               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3496                       player2Rating, player1Rating);\r
3497             gameInfo.whiteRating = player2Rating;\r
3498             gameInfo.blackRating = player1Rating;\r
3499         }\r
3500         player1Name[0] = player2Name[0] = NULLCHAR;\r
3501 \r
3502         /* Silence shouts if requested */\r
3503         if (appData.quietPlay &&\r
3504             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3505             SendToICS(ics_prefix);\r
3506             SendToICS("set shout 0\n");\r
3507         }\r
3508     }\r
3509     \r
3510     /* Deal with midgame name changes */\r
3511     if (!newGame) {\r
3512         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3513             if (gameInfo.white) free(gameInfo.white);\r
3514             gameInfo.white = StrSave(white);\r
3515         }\r
3516         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3517             if (gameInfo.black) free(gameInfo.black);\r
3518             gameInfo.black = StrSave(black);\r
3519         }\r
3520     }\r
3521     \r
3522     /* Throw away game result if anything actually changes in examine mode */\r
3523     if (gameMode == IcsExamining && !newGame) {\r
3524         gameInfo.result = GameUnfinished;\r
3525         if (gameInfo.resultDetails != NULL) {\r
3526             free(gameInfo.resultDetails);\r
3527             gameInfo.resultDetails = NULL;\r
3528         }\r
3529     }\r
3530     \r
3531     /* In pausing && IcsExamining mode, we ignore boards coming\r
3532        in if they are in a different variation than we are. */\r
3533     if (pauseExamInvalid) return;\r
3534     if (pausing && gameMode == IcsExamining) {\r
3535         if (moveNum <= pauseExamForwardMostMove) {\r
3536             pauseExamInvalid = TRUE;\r
3537             forwardMostMove = pauseExamForwardMostMove;\r
3538             return;\r
3539         }\r
3540     }\r
3541     \r
3542   if (appData.debugMode) {\r
3543     fprintf(debugFP, "load %dx%d board\n", files, ranks);\r
3544   }\r
3545     /* Parse the board */\r
3546     for (k = 0; k < ranks; k++) {\r
3547       for (j = 0; j < files; j++)\r
3548         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);\r
3549       if(gameInfo.holdingsWidth > 1) {\r
3550            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3551            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3552       }\r
3553     }\r
3554     CopyBoard(boards[moveNum], board);\r
3555     if (moveNum == 0) {\r
3556         startedFromSetupPosition =\r
3557           !CompareBoards(board, initialPosition);\r
3558         if(startedFromSetupPosition)\r
3559             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */\r
3560     }\r
3561 \r
3562     /* [HGM] Set castling rights. Take the outermost Rooks,\r
3563        to make it also work for FRC opening positions. Note that board12\r
3564        is really defective for later FRC positions, as it has no way to\r
3565        indicate which Rook can castle if they are on the same side of King.\r
3566        For the initial position we grant rights to the outermost Rooks,\r
3567        and remember thos rights, and we then copy them on positions\r
3568        later in an FRC game. This means WB might not recognize castlings with\r
3569        Rooks that have moved back to their original position as illegal,\r
3570        but in ICS mode that is not its job anyway.\r
3571     */\r
3572     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)\r
3573     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;\r
3574 \r
3575         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3576             if(board[0][i] == WhiteRook) j = i;\r
3577         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3578         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3579             if(board[0][i] == WhiteRook) j = i;\r
3580         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3581         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3582             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3583         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3584         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3585             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3586         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3587 \r
3588         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }\r
3589         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3590             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;\r
3591         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3592             if(board[BOARD_HEIGHT-1][k] == bKing)\r
3593                 initialRights[5] = castlingRights[moveNum][5] = k;\r
3594     } else { int r;\r
3595         r = castlingRights[moveNum][0] = initialRights[0];\r
3596         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;\r
3597         r = castlingRights[moveNum][1] = initialRights[1];\r
3598         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;\r
3599         r = castlingRights[moveNum][3] = initialRights[3];\r
3600         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;\r
3601         r = castlingRights[moveNum][4] = initialRights[4];\r
3602         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;\r
3603         /* wildcastle kludge: always assume King has rights */\r
3604         r = castlingRights[moveNum][2] = initialRights[2];\r
3605         r = castlingRights[moveNum][5] = initialRights[5];\r
3606     }\r
3607     /* [HGM] e.p. rights. Assume that ICS sends file number here? */\r
3608     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;\r
3609 \r
3610     \r
3611     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3612         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3613         /* This was an initial position from a move list, not\r
3614            the current position */\r
3615         return;\r
3616     }\r
3617     \r
3618     /* Update currentMove and known move number limits */\r
3619     newMove = newGame || moveNum > forwardMostMove;\r
3620 \r
3621     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */\r
3622     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {\r
3623         takeback = forwardMostMove - moveNum;\r
3624         for (i = 0; i < takeback; i++) {\r
3625              if (appData.debugMode) fprintf(debugFP, "take back move\n");\r
3626              SendToProgram("undo\n", &first);\r
3627         }\r
3628     }\r
3629 \r
3630     if (newGame) {\r
3631         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3632         if (gameMode == IcsExamining && moveNum == 0) {\r
3633           /* Workaround for ICS limitation: we are not told the wild\r
3634              type when starting to examine a game.  But if we ask for\r
3635              the move list, the move list header will tell us */\r
3636             ics_getting_history = H_REQUESTED;\r
3637             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3638             SendToICS(str);\r
3639         }\r
3640     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3641                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3642         forwardMostMove = moveNum;\r
3643         if (!pausing || currentMove > forwardMostMove)\r
3644           currentMove = forwardMostMove;\r
3645     } else {\r
3646         /* New part of history that is not contiguous with old part */ \r
3647         if (pausing && gameMode == IcsExamining) {\r
3648             pauseExamInvalid = TRUE;\r
3649             forwardMostMove = pauseExamForwardMostMove;\r
3650             return;\r
3651         }\r
3652         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3653         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3654             ics_getting_history = H_REQUESTED;\r
3655             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3656             SendToICS(str);\r
3657         }\r
3658     }\r
3659     \r
3660     /* Update the clocks */\r
3661     if (strchr(elapsed_time, '.')) {\r
3662       /* Time is in ms */\r
3663       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3664       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3665     } else {\r
3666       /* Time is in seconds */\r
3667       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3668       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3669     }\r
3670       \r
3671 \r
3672 #if ZIPPY\r
3673     if (appData.zippyPlay && newGame &&\r
3674         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3675         gameMode != IcsExamining)\r
3676       ZippyFirstBoard(moveNum, basetime, increment);\r
3677 #endif\r
3678     \r
3679     /* Put the move on the move list, first converting\r
3680        to canonical algebraic form. */\r
3681     if (moveNum > 0) {\r
3682   if (appData.debugMode) {\r
3683     if (appData.debugMode) { int f = forwardMostMove;\r
3684         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,\r
3685                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
3686     }\r
3687     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3688     fprintf(debugFP, "moveNum = %d\n", moveNum);\r
3689     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3690     setbuf(debugFP, NULL);\r
3691   }\r
3692         if (moveNum <= backwardMostMove) {\r
3693             /* We don't know what the board looked like before\r
3694                this move.  Punt. */\r
3695             strcpy(parseList[moveNum - 1], move_str);\r
3696             strcat(parseList[moveNum - 1], " ");\r
3697             strcat(parseList[moveNum - 1], elapsed_time);\r
3698             moveList[moveNum - 1][0] = NULLCHAR;\r
3699         } else if (strcmp(move_str, "none") == 0) {\r
3700             // [HGM] long SAN: swapped order; test for 'none' before parsing move\r
3701             /* Again, we don't know what the board looked like;\r
3702                this is really the start of the game. */\r
3703             parseList[moveNum - 1][0] = NULLCHAR;\r
3704             moveList[moveNum - 1][0] = NULLCHAR;\r
3705             backwardMostMove = moveNum;\r
3706             startedFromSetupPosition = TRUE;\r
3707             fromX = fromY = toX = toY = -1;\r
3708         } else {\r
3709           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. \r
3710           //                 So we parse the long-algebraic move string in stead of the SAN move\r
3711           int valid; char buf[MSG_SIZ], *prom;\r
3712 \r
3713           // str looks something like "Q/a1-a2"; kill the slash\r
3714           if(str[1] == '/') \r
3715                 sprintf(buf, "%c%s", str[0], str+2);\r
3716           else  strcpy(buf, str); // might be castling\r
3717           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) \r
3718                 strcat(buf, prom); // long move lacks promo specification!\r
3719           if(!appData.testLegality) {\r
3720                 if(appData.debugMode) \r
3721                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);\r
3722                 strcpy(move_str, buf);\r
3723           }\r
3724           valid = ParseOneMove(move_str, moveNum - 1, &moveType,\r
3725                                 &fromX, &fromY, &toX, &toY, &promoChar)\r
3726                || ParseOneMove(buf, moveNum - 1, &moveType,\r
3727                                 &fromX, &fromY, &toX, &toY, &promoChar);\r
3728           // end of long SAN patch\r
3729           if (valid) {\r
3730             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3731                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3732                                      fromY, fromX, toY, toX, promoChar,\r
3733                                      parseList[moveNum-1]);\r
3734             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3735                              castlingRights[moveNum]) ) {\r
3736               case MT_NONE:\r
3737               case MT_STALEMATE:\r
3738               default:\r
3739                 break;\r
3740               case MT_CHECK:\r
3741                 if(gameInfo.variant != VariantShogi)\r
3742                     strcat(parseList[moveNum - 1], "+");\r
3743                 break;\r
3744               case MT_CHECKMATE:\r
3745                 strcat(parseList[moveNum - 1], "#");\r
3746                 break;\r
3747             }\r
3748             strcat(parseList[moveNum - 1], " ");\r
3749             strcat(parseList[moveNum - 1], elapsed_time);\r
3750             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3751             strcpy(moveList[moveNum - 1], currentMoveString);\r
3752             strcat(moveList[moveNum - 1], "\n");\r
3753           } else {\r
3754             /* Move from ICS was illegal!?  Punt. */\r
3755   if (appData.debugMode) {\r
3756     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3757     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3758   }\r
3759 #if 0\r
3760             if (appData.testLegality && appData.debugMode) {\r
3761                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3762                 DisplayError(str, 0);\r
3763             }\r
3764 #endif\r
3765             strcpy(parseList[moveNum - 1], move_str);\r
3766             strcat(parseList[moveNum - 1], " ");\r
3767             strcat(parseList[moveNum - 1], elapsed_time);\r
3768             moveList[moveNum - 1][0] = NULLCHAR;\r
3769             fromX = fromY = toX = toY = -1;\r
3770           }\r
3771         }\r
3772   if (appData.debugMode) {\r
3773     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3774     setbuf(debugFP, NULL);\r
3775   }\r
3776 \r
3777 #if ZIPPY\r
3778         /* Send move to chess program (BEFORE animating it). */\r
3779         if (appData.zippyPlay && !newGame && newMove && \r
3780            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3781 \r
3782             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3783                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3784                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3785                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),\r
3786                             move_str);\r
3787                     DisplayError(str, 0);\r
3788                 } else {\r
3789                     if (first.sendTime) {\r
3790                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3791                     }\r
3792                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book\r
3793                     if (firstMove && !bookHit) {\r
3794                         firstMove = FALSE;\r
3795                         if (first.useColors) {\r
3796                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3797                                         "white\ngo\n" :\r
3798                                         "black\ngo\n", &first);\r
3799                         } else {\r
3800                           SendToProgram("go\n", &first);\r
3801                         }\r
3802                         first.maybeThinking = TRUE;\r
3803                     }\r
3804                 }\r
3805             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3806               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3807                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);\r
3808                 DisplayError(str, 0);\r
3809               } else {\r
3810                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!\r
3811                 SendMoveToProgram(moveNum - 1, &first);\r
3812               }\r
3813             }\r
3814         }\r
3815 #endif\r
3816     }\r
3817 \r
3818     if (moveNum > 0 && !gotPremove) {\r
3819         /* If move comes from a remote source, animate it.  If it\r
3820            isn't remote, it will have already been animated. */\r
3821         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3822             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3823         }\r
3824         if (!pausing && appData.highlightLastMove) {\r
3825             SetHighlights(fromX, fromY, toX, toY);\r
3826         }\r
3827     }\r
3828     \r
3829     /* Start the clocks */\r
3830     whiteFlag = blackFlag = FALSE;\r
3831     appData.clockMode = !(basetime == 0 && increment == 0);\r
3832     if (ticking == 0) {\r
3833       ics_clock_paused = TRUE;\r
3834       StopClocks();\r
3835     } else if (ticking == 1) {\r
3836       ics_clock_paused = FALSE;\r
3837     }\r
3838     if (gameMode == IcsIdle ||\r
3839         relation == RELATION_OBSERVING_STATIC ||\r
3840         relation == RELATION_EXAMINING ||\r
3841         ics_clock_paused)\r
3842       DisplayBothClocks();\r
3843     else\r
3844       StartClocks();\r
3845     \r
3846     /* Display opponents and material strengths */\r
3847     if (gameInfo.variant != VariantBughouse &&\r
3848         gameInfo.variant != VariantCrazyhouse) {\r
3849         if (tinyLayout || smallLayout) {\r
3850             if(gameInfo.variant == VariantNormal)\r
3851                 sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3852                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3853                     basetime, increment);\r
3854             else\r
3855                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", \r
3856                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3857                     basetime, increment, (int) gameInfo.variant);\r
3858         } else {\r
3859             if(gameInfo.variant == VariantNormal)\r
3860                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3861                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3862                     basetime, increment);\r
3863             else\r
3864                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", \r
3865                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3866                     basetime, increment, VariantName(gameInfo.variant));\r
3867         }\r
3868         DisplayTitle(str);\r
3869   if (appData.debugMode) {\r
3870     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);\r
3871   }\r
3872     }\r
3873 \r
3874    \r
3875     /* Display the board */\r
3876     if (!pausing) {\r
3877       \r
3878       if (appData.premove)\r
3879           if (!gotPremove || \r
3880              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3881              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3882               ClearPremoveHighlights();\r
3883 \r
3884       DrawPosition(FALSE, boards[currentMove]);\r
3885       DisplayMove(moveNum - 1);\r
3886       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3887         RingBell();\r
3888     }\r
3889 \r
3890     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3891 #if ZIPPY\r
3892     if(bookHit) { // [HGM] book: simulate book reply\r
3893         static char bookMove[MSG_SIZ]; // a bit generous?\r
3894 \r
3895         programStats.nodes = programStats.depth = programStats.time = \r
3896         programStats.score = programStats.got_only_move = 0;\r
3897         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
3898 \r
3899         strcpy(bookMove, "move ");\r
3900         strcat(bookMove, bookHit);\r
3901         HandleMachineMove(bookMove, &first);\r
3902     }\r
3903 #endif\r
3904 }\r
3905 \r
3906 void\r
3907 GetMoveListEvent()\r
3908 {\r
3909     char buf[MSG_SIZ];\r
3910     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3911         ics_getting_history = H_REQUESTED;\r
3912         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3913         SendToICS(buf);\r
3914     }\r
3915 }\r
3916 \r
3917 void\r
3918 AnalysisPeriodicEvent(force)\r
3919      int force;\r
3920 {\r
3921     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3922          && !force) || !appData.periodicUpdates)\r
3923       return;\r
3924 \r
3925     /* Send . command to Crafty to collect stats */\r
3926     SendToProgram(".\n", &first);\r
3927 \r
3928     /* Don't send another until we get a response (this makes\r
3929        us stop sending to old Crafty's which don't understand\r
3930        the "." command (sending illegal cmds resets node count & time,\r
3931        which looks bad)) */\r
3932     programStats.ok_to_send = 0;\r
3933 }\r
3934 \r
3935 void\r
3936 SendMoveToProgram(moveNum, cps)\r
3937      int moveNum;\r
3938      ChessProgramState *cps;\r
3939 {\r
3940     char buf[MSG_SIZ];\r
3941 \r
3942     if (cps->useUsermove) {\r
3943       SendToProgram("usermove ", cps);\r
3944     }\r
3945     if (cps->useSAN) {\r
3946       char *space;\r
3947       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3948         int len = space - parseList[moveNum];\r
3949         memcpy(buf, parseList[moveNum], len);\r
3950         buf[len++] = '\n';\r
3951         buf[len] = NULLCHAR;\r
3952       } else {\r
3953         sprintf(buf, "%s\n", parseList[moveNum]);\r
3954       }\r
3955       SendToProgram(buf, cps);\r
3956     } else {\r
3957       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */\r
3958         AlphaRank(moveList[moveNum], 4);\r
3959         SendToProgram(moveList[moveNum], cps);\r
3960         AlphaRank(moveList[moveNum], 4); // and back\r
3961       } else\r
3962       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3963        * the engine. It would be nice to have a better way to identify castle \r
3964        * moves here. */\r
3965       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)\r
3966                                                                          && cps->useOOCastle) {\r
3967         int fromX = moveList[moveNum][0] - AAA; \r
3968         int fromY = moveList[moveNum][1] - ONE;\r
3969         int toX = moveList[moveNum][2] - AAA; \r
3970         int toY = moveList[moveNum][3] - ONE;\r
3971         if((boards[moveNum][fromY][fromX] == WhiteKing \r
3972             && boards[moveNum][toY][toX] == WhiteRook)\r
3973            || (boards[moveNum][fromY][fromX] == BlackKing \r
3974                && boards[moveNum][toY][toX] == BlackRook)) {\r
3975           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3976           else SendToProgram("O-O-O\n", cps);\r
3977         }\r
3978         else SendToProgram(moveList[moveNum], cps);\r
3979       }\r
3980       else SendToProgram(moveList[moveNum], cps);\r
3981       /* End of additions by Tord */\r
3982     }\r
3983 \r
3984     /* [HGM] setting up the opening has brought engine in force mode! */\r
3985     /*       Send 'go' if we are in a mode where machine should play. */\r
3986     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
3987         (gameMode == TwoMachinesPlay   ||\r
3988 #ifdef ZIPPY\r
3989          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||\r
3990 #endif\r
3991          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
3992         SendToProgram("go\n", cps);\r
3993   if (appData.debugMode) {\r
3994     fprintf(debugFP, "(extra)\n");\r
3995   }\r
3996     }\r
3997     setboardSpoiledMachineBlack = 0;\r
3998 }\r
3999 \r
4000 void\r
4001 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
4002      ChessMove moveType;\r
4003      int fromX, fromY, toX, toY;\r
4004 {\r
4005     char user_move[MSG_SIZ];\r
4006 \r
4007     switch (moveType) {\r
4008       default:\r
4009         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),\r
4010                 (int)moveType, fromX, fromY, toX, toY);\r
4011         DisplayError(user_move + strlen("say "), 0);\r
4012         break;\r
4013       case WhiteKingSideCastle:\r
4014       case BlackKingSideCastle:\r
4015       case WhiteQueenSideCastleWild:\r
4016       case BlackQueenSideCastleWild:\r
4017       /* PUSH Fabien */\r
4018       case WhiteHSideCastleFR:\r
4019       case BlackHSideCastleFR:\r
4020       /* POP Fabien */\r
4021         sprintf(user_move, "o-o\n");\r
4022         break;\r
4023       case WhiteQueenSideCastle:\r
4024       case BlackQueenSideCastle:\r
4025       case WhiteKingSideCastleWild:\r
4026       case BlackKingSideCastleWild:\r
4027       /* PUSH Fabien */\r
4028       case WhiteASideCastleFR:\r
4029       case BlackASideCastleFR:\r
4030       /* POP Fabien */\r
4031         sprintf(user_move, "o-o-o\n");\r
4032         break;\r
4033       case WhitePromotionQueen:\r
4034       case BlackPromotionQueen:\r
4035       case WhitePromotionRook:\r
4036       case BlackPromotionRook:\r
4037       case WhitePromotionBishop:\r
4038       case BlackPromotionBishop:\r
4039       case WhitePromotionKnight:\r
4040       case BlackPromotionKnight:\r
4041       case WhitePromotionKing:\r
4042       case BlackPromotionKing:\r
4043       case WhitePromotionChancellor:\r
4044       case BlackPromotionChancellor:\r
4045       case WhitePromotionArchbishop:\r
4046       case BlackPromotionArchbishop:\r
4047         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)\r
4048             sprintf(user_move, "%c%c%c%c=%c\n",\r
4049                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4050                 PieceToChar(WhiteFerz));\r
4051         else if(gameInfo.variant == VariantGreat)\r
4052             sprintf(user_move, "%c%c%c%c=%c\n",\r
4053                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4054                 PieceToChar(WhiteMan));\r
4055         else\r
4056             sprintf(user_move, "%c%c%c%c=%c\n",\r
4057                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4058                 PieceToChar(PromoPiece(moveType)));\r
4059         break;\r
4060       case WhiteDrop:\r
4061       case BlackDrop:\r
4062         sprintf(user_move, "%c@%c%c\n",\r
4063                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
4064                 AAA + toX, ONE + toY);\r
4065         break;\r
4066       case NormalMove:\r
4067       case WhiteCapturesEnPassant:\r
4068       case BlackCapturesEnPassant:\r
4069       case IllegalMove:  /* could be a variant we don't quite understand */\r
4070         sprintf(user_move, "%c%c%c%c\n",\r
4071                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
4072         break;\r
4073     }\r
4074     SendToICS(user_move);\r
4075 }\r
4076 \r
4077 void\r
4078 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
4079      int rf, ff, rt, ft;\r
4080      char promoChar;\r
4081      char move[7];\r
4082 {\r
4083     if (rf == DROP_RANK) {\r
4084         sprintf(move, "%c@%c%c\n",\r
4085                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
4086     } else {\r
4087         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
4088             sprintf(move, "%c%c%c%c\n",\r
4089                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
4090         } else {\r
4091             sprintf(move, "%c%c%c%c%c\n",\r
4092                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
4093         }\r
4094     }\r
4095 }\r
4096 \r
4097 void\r
4098 ProcessICSInitScript(f)\r
4099      FILE *f;\r
4100 {\r
4101     char buf[MSG_SIZ];\r
4102 \r
4103     while (fgets(buf, MSG_SIZ, f)) {\r
4104         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
4105     }\r
4106 \r
4107     fclose(f);\r
4108 }\r
4109 \r
4110 \r
4111 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
4112 void\r
4113 AlphaRank(char *move, int n)\r
4114 {\r
4115 //    char *p = move, c; int x, y;\r
4116 \r
4117     if (appData.debugMode) {\r
4118         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
4119     }\r
4120 \r
4121     if(move[1]=='*' && \r
4122        move[2]>='0' && move[2]<='9' &&\r
4123        move[3]>='a' && move[3]<='x'    ) {\r
4124         move[1] = '@';\r
4125         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4126         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4127     } else\r
4128     if(move[0]>='0' && move[0]<='9' &&\r
4129        move[1]>='a' && move[1]<='x' &&\r
4130        move[2]>='0' && move[2]<='9' &&\r
4131        move[3]>='a' && move[3]<='x'    ) {\r
4132         /* input move, Shogi -> normal */\r
4133         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;\r
4134         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\r
4135         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4136         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4137     } else\r
4138     if(move[1]=='@' &&\r
4139        move[3]>='0' && move[3]<='9' &&\r
4140        move[2]>='a' && move[2]<='x'    ) {\r
4141         move[1] = '*';\r
4142         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4143         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4144     } else\r
4145     if(\r
4146        move[0]>='a' && move[0]<='x' &&\r
4147        move[3]>='0' && move[3]<='9' &&\r
4148        move[2]>='a' && move[2]<='x'    ) {\r
4149          /* output move, normal -> Shogi */\r
4150         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
4151         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
4152         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4153         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4154         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
4155     }\r
4156     if (appData.debugMode) {\r
4157         fprintf(debugFP, "   out = '%s'\n", move);\r
4158     }\r
4159 }\r
4160 \r
4161 /* Parser for moves from gnuchess, ICS, or user typein box */\r
4162 Boolean\r
4163 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
4164      char *move;\r
4165      int moveNum;\r
4166      ChessMove *moveType;\r
4167      int *fromX, *fromY, *toX, *toY;\r
4168      char *promoChar;\r
4169 {       \r
4170     if (appData.debugMode) {\r
4171         fprintf(debugFP, "move to parse: %s\n", move);\r
4172     }\r
4173     *moveType = yylexstr(moveNum, move);\r
4174 \r
4175     switch (*moveType) {\r
4176       case WhitePromotionChancellor:\r
4177       case BlackPromotionChancellor:\r
4178       case WhitePromotionArchbishop:\r
4179       case BlackPromotionArchbishop:\r
4180       case WhitePromotionQueen:\r
4181       case BlackPromotionQueen:\r
4182       case WhitePromotionRook:\r
4183       case BlackPromotionRook:\r
4184       case WhitePromotionBishop:\r
4185       case BlackPromotionBishop:\r
4186       case WhitePromotionKnight:\r
4187       case BlackPromotionKnight:\r
4188       case WhitePromotionKing:\r
4189       case BlackPromotionKing:\r
4190       case NormalMove:\r
4191       case WhiteCapturesEnPassant:\r
4192       case BlackCapturesEnPassant:\r
4193       case WhiteKingSideCastle:\r
4194       case WhiteQueenSideCastle:\r
4195       case BlackKingSideCastle:\r
4196       case BlackQueenSideCastle:\r
4197       case WhiteKingSideCastleWild:\r
4198       case WhiteQueenSideCastleWild:\r
4199       case BlackKingSideCastleWild:\r
4200       case BlackQueenSideCastleWild:\r
4201       /* Code added by Tord: */\r
4202       case WhiteHSideCastleFR:\r
4203       case WhiteASideCastleFR:\r
4204       case BlackHSideCastleFR:\r
4205       case BlackASideCastleFR:\r
4206       /* End of code added by Tord */\r
4207       case IllegalMove:         /* bug or odd chess variant */\r
4208         *fromX = currentMoveString[0] - AAA;\r
4209         *fromY = currentMoveString[1] - ONE;\r
4210         *toX = currentMoveString[2] - AAA;\r
4211         *toY = currentMoveString[3] - ONE;\r
4212         *promoChar = currentMoveString[4];\r
4213         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
4214             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
4215     if (appData.debugMode) {\r
4216         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
4217     }\r
4218             *fromX = *fromY = *toX = *toY = 0;\r
4219             return FALSE;\r
4220         }\r
4221         if (appData.testLegality) {\r
4222           return (*moveType != IllegalMove);\r
4223         } else {\r
4224           return !(fromX == fromY && toX == toY);\r
4225         }\r
4226 \r
4227       case WhiteDrop:\r
4228       case BlackDrop:\r
4229         *fromX = *moveType == WhiteDrop ?\r
4230           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
4231           (int) CharToPiece(ToLower(currentMoveString[0]));\r
4232         *fromY = DROP_RANK;\r
4233         *toX = currentMoveString[2] - AAA;\r
4234         *toY = currentMoveString[3] - ONE;\r
4235         *promoChar = NULLCHAR;\r
4236         return TRUE;\r
4237 \r
4238       case AmbiguousMove:\r
4239       case ImpossibleMove:\r
4240       case (ChessMove) 0:       /* end of file */\r
4241       case ElapsedTime:\r
4242       case Comment:\r
4243       case PGNTag:\r
4244       case NAG:\r
4245       case WhiteWins:\r
4246       case BlackWins:\r
4247       case GameIsDrawn:\r
4248       default:\r
4249     if (appData.debugMode) {\r
4250         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
4251     }\r
4252         /* bug? */\r
4253         *fromX = *fromY = *toX = *toY = 0;\r
4254         *promoChar = NULLCHAR;\r
4255         return FALSE;\r
4256     }\r
4257 }\r
4258 \r
4259 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.\r
4260 // All positions will have equal probability, but the current method will not provide a unique\r
4261 // numbering scheme for arrays that contain 3 or more pieces of the same kind.\r
4262 #define DARK 1\r
4263 #define LITE 2\r
4264 #define ANY 3\r
4265 \r
4266 int squaresLeft[4];\r
4267 int piecesLeft[(int)BlackPawn];\r
4268 int seed, nrOfShuffles;\r
4269 \r
4270 void GetPositionNumber()\r
4271 {       // sets global variable seed\r
4272         int i;\r
4273 \r
4274         seed = appData.defaultFrcPosition;\r
4275         if(seed < 0) { // randomize based on time for negative FRC position numbers\r
4276                 for(i=0; i<50; i++) seed += random();\r
4277                 seed = random() ^ random() >> 8 ^ random() << 8;\r
4278                 if(seed<0) seed = -seed;\r
4279         }\r
4280 }\r
4281 \r
4282 int put(Board board, int pieceType, int rank, int n, int shade)\r
4283 // put the piece on the (n-1)-th empty squares of the given shade\r
4284 {\r
4285         int i;\r
4286 \r
4287         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
4288                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {\r
4289                         board[rank][i] = (ChessSquare) pieceType;\r
4290                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;\r
4291                         squaresLeft[ANY]--;\r
4292                         piecesLeft[pieceType]--; \r
4293                         return i;\r
4294                 }\r
4295         }\r
4296         return -1;\r
4297 }\r
4298 \r
4299 \r
4300 void AddOnePiece(Board board, int pieceType, int rank, int shade)\r
4301 // calculate where the next piece goes, (any empty square), and put it there\r
4302 {\r
4303         int i;\r
4304 \r
4305         i = seed % squaresLeft[shade];\r
4306         nrOfShuffles *= squaresLeft[shade];\r
4307         seed /= squaresLeft[shade];\r
4308         put(board, pieceType, rank, i, shade);\r
4309 }\r
4310 \r
4311 void AddTwoPieces(Board board, int pieceType, int rank)\r
4312 // calculate where the next 2 identical pieces go, (any empty square), and put it there\r
4313 {\r
4314         int i, n=squaresLeft[ANY], j=n-1, k;\r
4315 \r
4316         k = n*(n-1)/2; // nr of possibilities, not counting permutations\r
4317         i = seed % k;  // pick one\r
4318         nrOfShuffles *= k;\r
4319         seed /= k;\r
4320         while(i >= j) i -= j--;\r
4321         j = n - 1 - j; i += j;\r
4322         put(board, pieceType, rank, j, ANY);\r
4323         put(board, pieceType, rank, i, ANY);\r
4324 }\r
4325 \r
4326 void SetUpShuffle(Board board, int number)\r
4327 {\r
4328         int i, p, first=1;\r
4329 \r
4330         GetPositionNumber(); nrOfShuffles = 1;\r
4331 \r
4332         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;\r
4333         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;\r
4334         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];\r
4335 \r
4336         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;\r
4337 \r
4338         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board\r
4339             p = (int) board[0][i];\r
4340             if(p < (int) BlackPawn) piecesLeft[p] ++;\r
4341             board[0][i] = EmptySquare;\r
4342         }\r
4343 \r
4344         if(PosFlags(0) & F_ALL_CASTLE_OK) {\r
4345             // shuffles restricted to allow normal castling put KRR first\r
4346             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle\r
4347                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4348             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles\r
4349                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4350             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling\r
4351                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);\r
4352             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling\r
4353                 put(board, WhiteRook, 0, 0, ANY);\r
4354             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle\r
4355         }\r
4356 \r
4357         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)\r
4358             // only for even boards make effort to put pairs of colorbound pieces on opposite colors\r
4359             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {\r
4360                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;\r
4361                 while(piecesLeft[p] >= 2) {\r
4362                     AddOnePiece(board, p, 0, LITE);\r
4363                     AddOnePiece(board, p, 0, DARK);\r
4364                 }\r
4365                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)\r
4366             }\r
4367 \r
4368         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {\r
4369             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere\r
4370             // but we leave King and Rooks for last, to possibly obey FRC restriction\r
4371             if(p == (int)WhiteRook) continue;\r
4372             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations\r
4373             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece\r
4374         }\r
4375 \r
4376         // now everything is placed, except perhaps King (Unicorn) and Rooks\r
4377 \r
4378         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {\r
4379             // Last King gets castling rights\r
4380             while(piecesLeft[(int)WhiteUnicorn]) {\r
4381                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4382                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4383             }\r
4384 \r
4385             while(piecesLeft[(int)WhiteKing]) {\r
4386                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4387                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4388             }\r
4389 \r
4390 \r
4391         } else {\r
4392             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);\r
4393             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);\r
4394         }\r
4395 \r
4396         // Only Rooks can be left; simply place them all\r
4397         while(piecesLeft[(int)WhiteRook]) {\r
4398                 i = put(board, WhiteRook, 0, 0, ANY);\r
4399                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights\r
4400                         if(first) {\r
4401                                 first=0;\r
4402                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;\r
4403                         }\r
4404                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;\r
4405                 }\r
4406         }\r
4407         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white\r
4408             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;\r
4409         }\r
4410 \r
4411         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize\r
4412 }\r
4413 \r
4414 int SetCharTable( char *table, const char * map )\r
4415 /* [HGM] moved here from winboard.c because of its general usefulness */\r
4416 /*       Basically a safe strcpy that uses the last character as King */\r
4417 {\r
4418     int result = FALSE; int NrPieces;\r
4419 \r
4420     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
4421                     && NrPieces >= 12 && !(NrPieces&1)) {\r
4422         int i; /* [HGM] Accept even length from 12 to 34 */\r
4423 \r
4424         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
4425         for( i=0; i<NrPieces/2-1; i++ ) {\r
4426             table[i] = map[i];\r
4427             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
4428         }\r
4429         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
4430         table[(int) BlackKing]  = map[NrPieces-1];\r
4431 \r
4432         result = TRUE;\r
4433     }\r
4434 \r
4435     return result;\r
4436 }\r
4437 \r
4438 void Prelude(Board board)\r
4439 {       // [HGM] superchess: random selection of exo-pieces\r
4440         int i, j, k; ChessSquare p; \r
4441         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };\r
4442 \r
4443         GetPositionNumber(); // use FRC position number\r
4444 \r
4445         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table\r
4446             SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4447             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) \r
4448                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;\r
4449         }\r
4450 \r
4451         j = seed%4;                 seed /= 4; \r
4452         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4453         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4454         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4455         j = seed%3 + (seed%3 >= j); seed /= 3; \r
4456         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4457         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4458         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4459         j = seed%3;                 seed /= 3; \r
4460         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4461         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4462         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4463         j = seed%2 + (seed%2 >= j); seed /= 2; \r
4464         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4465         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4466         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4467         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);\r
4468         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);\r
4469         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);\r
4470         put(board, exoPieces[0],    0, 0, ANY);\r
4471         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];\r
4472 }\r
4473 \r
4474 void\r
4475 InitPosition(redraw)\r
4476      int redraw;\r
4477 {\r
4478     ChessSquare (* pieces)[BOARD_SIZE];\r
4479     int i, j, pawnRow, overrule,\r
4480     oldx = gameInfo.boardWidth,\r
4481     oldy = gameInfo.boardHeight,\r
4482     oldh = gameInfo.holdingsWidth,\r
4483     oldv = gameInfo.variant;\r
4484 \r
4485     currentMove = forwardMostMove = backwardMostMove = 0;\r
4486     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request\r
4487 \r
4488     /* [AS] Initialize pv info list [HGM] and game status */\r
4489     {\r
4490         for( i=0; i<MAX_MOVES; i++ ) {\r
4491             pvInfoList[i].depth = 0;\r
4492             epStatus[i]=EP_NONE;\r
4493             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
4494         }\r
4495 \r
4496         initialRulePlies = 0; /* 50-move counter start */\r
4497 \r
4498         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4499         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4500     }\r
4501 \r
4502     \r
4503     /* [HGM] logic here is completely changed. In stead of full positions */\r
4504     /* the initialized data only consist of the two backranks. The switch */\r
4505     /* selects which one we will use, which is than copied to the Board   */\r
4506     /* initialPosition, which for the rest is initialized by Pawns and    */\r
4507     /* empty squares. This initial position is then copied to boards[0],  */\r
4508     /* possibly after shuffling, so that it remains available.            */\r
4509 \r
4510     gameInfo.holdingsWidth = 0; /* default board sizes */\r
4511     gameInfo.boardWidth    = 8;\r
4512     gameInfo.boardHeight   = 8;\r
4513     gameInfo.holdingsSize  = 0;\r
4514     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
4515     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
4516     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
4517 \r
4518     switch (gameInfo.variant) {\r
4519     case VariantFischeRandom:\r
4520       shuffleOpenings = TRUE;\r
4521     default:\r
4522       pieces = FIDEArray;\r
4523       break;\r
4524     case VariantShatranj:\r
4525       pieces = ShatranjArray;\r
4526       nrCastlingRights = 0;\r
4527       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
4528       break;\r
4529     case VariantTwoKings:\r
4530       pieces = twoKingsArray;\r
4531       break;\r
4532     case VariantCapaRandom:\r
4533       shuffleOpenings = TRUE;\r
4534     case VariantCapablanca:\r
4535       pieces = CapablancaArray;\r
4536       gameInfo.boardWidth = 10;\r
4537       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4538       break;\r
4539     case VariantGothic:\r
4540       pieces = GothicArray;\r
4541       gameInfo.boardWidth = 10;\r
4542       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4543       break;\r
4544     case VariantJanus:\r
4545       pieces = JanusArray;\r
4546       gameInfo.boardWidth = 10;\r
4547       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
4548       nrCastlingRights = 6;\r
4549         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4550         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4551         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;\r
4552         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4553         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4554         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;\r
4555       break;\r
4556     case VariantFalcon:\r
4557       pieces = FalconArray;\r
4558       gameInfo.boardWidth = 10;\r
4559       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4560       break;\r
4561     case VariantXiangqi:\r
4562       pieces = XiangqiArray;\r
4563       gameInfo.boardWidth  = 9;\r
4564       gameInfo.boardHeight = 10;\r
4565       nrCastlingRights = 0;\r
4566       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4567       break;\r
4568     case VariantShogi:\r
4569       pieces = ShogiArray;\r
4570       gameInfo.boardWidth  = 9;\r
4571       gameInfo.boardHeight = 9;\r
4572       gameInfo.holdingsSize = 7;\r
4573       nrCastlingRights = 0;\r
4574       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4575       break;\r
4576     case VariantCourier:\r
4577       pieces = CourierArray;\r
4578       gameInfo.boardWidth  = 12;\r
4579       nrCastlingRights = 0;\r
4580       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4581       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4582       break;\r
4583     case VariantKnightmate:\r
4584       pieces = KnightmateArray;\r
4585       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4586       break;\r
4587     case VariantFairy:\r
4588       pieces = fairyArray;\r
4589       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4590       break;\r
4591     case VariantGreat:\r
4592       pieces = GreatArray;\r
4593       gameInfo.boardWidth = 10;\r
4594       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");\r
4595       gameInfo.holdingsSize = 8;\r
4596       break;\r
4597     case VariantSuper:\r
4598       pieces = FIDEArray;\r
4599       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
4600       gameInfo.holdingsSize = 8;\r
4601       startedFromSetupPosition = TRUE;\r
4602       break;\r
4603     case VariantCrazyhouse:\r
4604     case VariantBughouse:\r
4605       pieces = FIDEArray;\r
4606       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4607       gameInfo.holdingsSize = 5;\r
4608       break;\r
4609     case VariantWildCastle:\r
4610       pieces = FIDEArray;\r
4611       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4612       shuffleOpenings = 1;\r
4613       break;\r
4614     case VariantNoCastle:\r
4615       pieces = FIDEArray;\r
4616       nrCastlingRights = 0;\r
4617       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4618       /* !!?unconstrained back-rank shuffle */\r
4619       shuffleOpenings = 1;\r
4620       break;\r
4621     }\r
4622 \r
4623     overrule = 0;\r
4624     if(appData.NrFiles >= 0) {\r
4625         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4626         gameInfo.boardWidth = appData.NrFiles;\r
4627     }\r
4628     if(appData.NrRanks >= 0) {\r
4629         gameInfo.boardHeight = appData.NrRanks;\r
4630     }\r
4631     if(appData.holdingsSize >= 0) {\r
4632         i = appData.holdingsSize;\r
4633         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4634         gameInfo.holdingsSize = i;\r
4635     }\r
4636     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4637     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4638         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);\r
4639 \r
4640     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4641     if(pawnRow < 1) pawnRow = 1;\r
4642 \r
4643     /* User pieceToChar list overrules defaults */\r
4644     if(appData.pieceToCharTable != NULL)\r
4645         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4646 \r
4647     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4648 \r
4649         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4650             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4651         for( i=0; i<BOARD_HEIGHT; i++ )\r
4652             initialPosition[i][j] = s;\r
4653 \r
4654         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4655         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4656         initialPosition[pawnRow][j] = WhitePawn;\r
4657         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4658         if(gameInfo.variant == VariantXiangqi) {\r
4659             if(j&1) {\r
4660                 initialPosition[pawnRow][j] = \r
4661                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4662                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4663                    initialPosition[2][j] = WhiteCannon;\r
4664                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4665                 }\r
4666             }\r
4667         }\r
4668         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4669     }\r
4670     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4671 \r
4672             j=BOARD_LEFT+1;\r
4673             initialPosition[1][j] = WhiteBishop;\r
4674             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4675             j=BOARD_RGHT-2;\r
4676             initialPosition[1][j] = WhiteRook;\r
4677             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4678     }\r
4679 \r
4680     if( nrCastlingRights == -1) {\r
4681         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4682         /*       This sets default castling rights from none to normal corners   */\r
4683         /* Variants with other castling rights must set them themselves above    */\r
4684         nrCastlingRights = 6;\r
4685        \r
4686         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4687         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4688         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4689         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4690         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4691         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4692      }\r
4693 \r
4694      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
4695      if(gameInfo.variant == VariantGreat) { // promotion commoners\r
4696         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;\r
4697         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;\r
4698         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;\r
4699         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;\r
4700      }\r
4701 #if 0\r
4702     if(gameInfo.variant == VariantFischeRandom) {\r
4703       if( appData.defaultFrcPosition < 0 ) {\r
4704         ShuffleFRC( initialPosition );\r
4705       }\r
4706       else {\r
4707         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4708       }\r
4709       startedFromSetupPosition = TRUE;\r
4710     } else \r
4711 #else\r
4712   if (appData.debugMode) {\r
4713     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
4714   }\r
4715     if(shuffleOpenings) {\r
4716         SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
4717         startedFromSetupPosition = TRUE;\r
4718     }\r
4719 #endif\r
4720     if(startedFromPositionFile) {\r
4721       /* [HGM] loadPos: use PositionFile for every new game */\r
4722       CopyBoard(initialPosition, filePosition);\r
4723       for(i=0; i<nrCastlingRights; i++)\r
4724           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4725       startedFromSetupPosition = TRUE;\r
4726     }\r
4727 \r
4728     CopyBoard(boards[0], initialPosition);\r
4729 \r
4730     if(oldx != gameInfo.boardWidth ||\r
4731        oldy != gameInfo.boardHeight ||\r
4732        oldh != gameInfo.holdingsWidth\r
4733 #ifdef GOTHIC\r
4734        || oldv == VariantGothic ||        // For licensing popups\r
4735        gameInfo.variant == VariantGothic\r
4736 #endif\r
4737 #ifdef FALCON\r
4738        || oldv == VariantFalcon ||\r
4739        gameInfo.variant == VariantFalcon\r
4740 #endif\r
4741                                          )\r
4742             InitDrawingSizes(-2 ,0);\r
4743 \r
4744     if (redraw)\r
4745       DrawPosition(TRUE, boards[currentMove]);\r
4746 }\r
4747 \r
4748 void\r
4749 SendBoard(cps, moveNum)\r
4750      ChessProgramState *cps;\r
4751      int moveNum;\r
4752 {\r
4753     char message[MSG_SIZ];\r
4754     \r
4755     if (cps->useSetboard) {\r
4756       char* fen = PositionToFEN(moveNum, cps->useFEN960);\r
4757       sprintf(message, "setboard %s\n", fen);\r
4758       SendToProgram(message, cps);\r
4759       free(fen);\r
4760 \r
4761     } else {\r
4762       ChessSquare *bp;\r
4763       int i, j;\r
4764       /* Kludge to set black to move, avoiding the troublesome and now\r
4765        * deprecated "black" command.\r
4766        */\r
4767       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4768 \r
4769       SendToProgram("edit\n", cps);\r
4770       SendToProgram("#\n", cps);\r
4771       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4772         bp = &boards[moveNum][i][BOARD_LEFT];\r
4773         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4774           if ((int) *bp < (int) BlackPawn) {\r
4775             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4776                     AAA + j, ONE + i);\r
4777             if(message[0] == '+' || message[0] == '~') {\r
4778                 sprintf(message, "%c%c%c+\n",\r
4779                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4780                         AAA + j, ONE + i);\r
4781             }\r
4782             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4783                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4784                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4785             }\r
4786             SendToProgram(message, cps);\r
4787           }\r
4788         }\r
4789       }\r
4790     \r
4791       SendToProgram("c\n", cps);\r
4792       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4793         bp = &boards[moveNum][i][BOARD_LEFT];\r
4794         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4795           if (((int) *bp != (int) EmptySquare)\r
4796               && ((int) *bp >= (int) BlackPawn)) {\r
4797             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4798                     AAA + j, ONE + i);\r
4799             if(message[0] == '+' || message[0] == '~') {\r
4800                 sprintf(message, "%c%c%c+\n",\r
4801                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4802                         AAA + j, ONE + i);\r
4803             }\r
4804             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4805                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4806                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4807             }\r
4808             SendToProgram(message, cps);\r
4809           }\r
4810         }\r
4811       }\r
4812     \r
4813       SendToProgram(".\n", cps);\r
4814     }\r
4815     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4816 }\r
4817 \r
4818 int\r
4819 IsPromotion(fromX, fromY, toX, toY)\r
4820      int fromX, fromY, toX, toY;\r
4821 {\r
4822     /* [HGM] add Shogi promotions */\r
4823     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4824     ChessSquare piece;\r
4825 \r
4826     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4827       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4828    /* [HGM] Note to self: line above also weeds out drops */\r
4829     piece = boards[currentMove][fromY][fromX];\r
4830     if(gameInfo.variant == VariantShogi) {\r
4831         promotionZoneSize = 3;\r
4832         highestPromotingPiece = (int)WhiteKing;\r
4833         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4834            and if in normal chess we then allow promotion to King, why not\r
4835            allow promotion of other piece in Shogi?                         */\r
4836     }\r
4837     if((int)piece >= BlackPawn) {\r
4838         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4839              return FALSE;\r
4840         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4841     } else {\r
4842         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4843            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4844     }\r
4845     return ( (int)piece <= highestPromotingPiece );\r
4846 }\r
4847 \r
4848 int\r
4849 InPalace(row, column)\r
4850      int row, column;\r
4851 {   /* [HGM] for Xiangqi */\r
4852     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4853          column < (BOARD_WIDTH + 4)/2 &&\r
4854          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4855     return FALSE;\r
4856 }\r
4857 \r
4858 int\r
4859 PieceForSquare (x, y)\r
4860      int x;\r
4861      int y;\r
4862 {\r
4863   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
4864      return -1;\r
4865   else\r
4866      return boards[currentMove][y][x];\r
4867 }\r
4868 \r
4869 int\r
4870 OKToStartUserMove(x, y)\r
4871      int x, y;\r
4872 {\r
4873     ChessSquare from_piece;\r
4874     int white_piece;\r
4875 \r
4876     if (matchMode) return FALSE;\r
4877     if (gameMode == EditPosition) return TRUE;\r
4878 \r
4879     if (x >= 0 && y >= 0)\r
4880       from_piece = boards[currentMove][y][x];\r
4881     else\r
4882       from_piece = EmptySquare;\r
4883 \r
4884     if (from_piece == EmptySquare) return FALSE;\r
4885 \r
4886     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4887       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4888 \r
4889     switch (gameMode) {\r
4890       case PlayFromGameFile:\r
4891       case AnalyzeFile:\r
4892       case TwoMachinesPlay:\r
4893       case EndOfGame:\r
4894         return FALSE;\r
4895 \r
4896       case IcsObserving:\r
4897       case IcsIdle:\r
4898         return FALSE;\r
4899 \r
4900       case MachinePlaysWhite:\r
4901       case IcsPlayingBlack:\r
4902         if (appData.zippyPlay) return FALSE;\r
4903         if (white_piece) {\r
4904             DisplayMoveError(_("You are playing Black"));\r
4905             return FALSE;\r
4906         }\r
4907         break;\r
4908 \r
4909       case MachinePlaysBlack:\r
4910       case IcsPlayingWhite:\r
4911         if (appData.zippyPlay) return FALSE;\r
4912         if (!white_piece) {\r
4913             DisplayMoveError(_("You are playing White"));\r
4914             return FALSE;\r
4915         }\r
4916         break;\r
4917 \r
4918       case EditGame:\r
4919         if (!white_piece && WhiteOnMove(currentMove)) {\r
4920             DisplayMoveError(_("It is White's turn"));\r
4921             return FALSE;\r
4922         }           \r
4923         if (white_piece && !WhiteOnMove(currentMove)) {\r
4924             DisplayMoveError(_("It is Black's turn"));\r
4925             return FALSE;\r
4926         }           \r
4927         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
4928             /* Editing correspondence game history */\r
4929             /* Could disallow this or prompt for confirmation */\r
4930             cmailOldMove = -1;\r
4931         }\r
4932         if (currentMove < forwardMostMove) {\r
4933             /* Discarding moves */\r
4934             /* Could prompt for confirmation here,\r
4935                but I don't think that's such a good idea */\r
4936             forwardMostMove = currentMove;\r
4937         }\r
4938         break;\r
4939 \r
4940       case BeginningOfGame:\r
4941         if (appData.icsActive) return FALSE;\r
4942         if (!appData.noChessProgram) {\r
4943             if (!white_piece) {\r
4944                 DisplayMoveError(_("You are playing White"));\r
4945                 return FALSE;\r
4946             }\r
4947         }\r
4948         break;\r
4949         \r
4950       case Training:\r
4951         if (!white_piece && WhiteOnMove(currentMove)) {\r
4952             DisplayMoveError(_("It is White's turn"));\r
4953             return FALSE;\r
4954         }           \r
4955         if (white_piece && !WhiteOnMove(currentMove)) {\r
4956             DisplayMoveError(_("It is Black's turn"));\r
4957             return FALSE;\r
4958         }           \r
4959         break;\r
4960 \r
4961       default:\r
4962       case IcsExamining:\r
4963         break;\r
4964     }\r
4965     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
4966         && gameMode != AnalyzeFile && gameMode != Training) {\r
4967         DisplayMoveError(_("Displayed position is not current"));\r
4968         return FALSE;\r
4969     }\r
4970     return TRUE;\r
4971 }\r
4972 \r
4973 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
4974 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
4975 int lastLoadGameUseList = FALSE;\r
4976 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
4977 ChessMove lastLoadGameStart = (ChessMove) 0;\r
4978 \r
4979 \r
4980 ChessMove\r
4981 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
4982      int fromX, fromY, toX, toY;\r
4983      int promoChar;\r
4984 {\r
4985     ChessMove moveType;\r
4986     ChessSquare pdown, pup;\r
4987 \r
4988     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
4989     if ((fromX == toX) && (fromY == toY)) {\r
4990         return ImpossibleMove;\r
4991     }\r
4992 \r
4993     /* [HGM] suppress all moves into holdings area and guard band */\r
4994     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
4995             return ImpossibleMove;\r
4996 \r
4997     /* [HGM] <sameColor> moved to here from winboard.c */\r
4998     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
4999     pdown = boards[currentMove][fromY][fromX];\r
5000     pup = boards[currentMove][toY][toX];\r
5001     if (    gameMode != EditPosition &&\r
5002             (WhitePawn <= pdown && pdown < BlackPawn &&\r
5003              WhitePawn <= pup && pup < BlackPawn  ||\r
5004              BlackPawn <= pdown && pdown < EmptySquare &&\r
5005              BlackPawn <= pup && pup < EmptySquare \r
5006             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
5007                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
5008                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
5009         )           )\r
5010          return ImpossibleMove;\r
5011 \r
5012     /* Check if the user is playing in turn.  This is complicated because we\r
5013        let the user "pick up" a piece before it is his turn.  So the piece he\r
5014        tried to pick up may have been captured by the time he puts it down!\r
5015        Therefore we use the color the user is supposed to be playing in this\r
5016        test, not the color of the piece that is currently on the starting\r
5017        square---except in EditGame mode, where the user is playing both\r
5018        sides; fortunately there the capture race can't happen.  (It can\r
5019        now happen in IcsExamining mode, but that's just too bad.  The user\r
5020        will get a somewhat confusing message in that case.)\r
5021        */\r
5022 \r
5023     switch (gameMode) {\r
5024       case PlayFromGameFile:\r
5025       case AnalyzeFile:\r
5026       case TwoMachinesPlay:\r
5027       case EndOfGame:\r
5028       case IcsObserving:\r
5029       case IcsIdle:\r
5030         /* We switched into a game mode where moves are not accepted,\r
5031            perhaps while the mouse button was down. */\r
5032         return ImpossibleMove;\r
5033 \r
5034       case MachinePlaysWhite:\r
5035         /* User is moving for Black */\r
5036         if (WhiteOnMove(currentMove)) {\r
5037             DisplayMoveError(_("It is White's turn"));\r
5038             return ImpossibleMove;\r
5039         }\r
5040         break;\r
5041 \r
5042       case MachinePlaysBlack:\r
5043         /* User is moving for White */\r
5044         if (!WhiteOnMove(currentMove)) {\r
5045             DisplayMoveError(_("It is Black's turn"));\r
5046             return ImpossibleMove;\r
5047         }\r
5048         break;\r
5049 \r
5050       case EditGame:\r
5051       case IcsExamining:\r
5052       case BeginningOfGame:\r
5053       case AnalyzeMode:\r
5054       case Training:\r
5055         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
5056             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
5057             /* User is moving for Black */\r
5058             if (WhiteOnMove(currentMove)) {\r
5059                 DisplayMoveError(_("It is White's turn"));\r
5060                 return ImpossibleMove;\r
5061             }\r
5062         } else {\r
5063             /* User is moving for White */\r
5064             if (!WhiteOnMove(currentMove)) {\r
5065                 DisplayMoveError(_("It is Black's turn"));\r
5066                 return ImpossibleMove;\r
5067             }\r
5068         }\r
5069         break;\r
5070 \r
5071       case IcsPlayingBlack:\r
5072         /* User is moving for Black */\r
5073         if (WhiteOnMove(currentMove)) {\r
5074             if (!appData.premove) {\r
5075                 DisplayMoveError(_("It is White's turn"));\r
5076             } else if (toX >= 0 && toY >= 0) {\r
5077                 premoveToX = toX;\r
5078                 premoveToY = toY;\r
5079                 premoveFromX = fromX;\r
5080                 premoveFromY = fromY;\r
5081                 premovePromoChar = promoChar;\r
5082                 gotPremove = 1;\r
5083                 if (appData.debugMode) \r
5084                     fprintf(debugFP, "Got premove: fromX %d,"\r
5085                             "fromY %d, toX %d, toY %d\n",\r
5086                             fromX, fromY, toX, toY);\r
5087             }\r
5088             return ImpossibleMove;\r
5089         }\r
5090         break;\r
5091 \r
5092       case IcsPlayingWhite:\r
5093         /* User is moving for White */\r
5094         if (!WhiteOnMove(currentMove)) {\r
5095             if (!appData.premove) {\r
5096                 DisplayMoveError(_("It is Black's turn"));\r
5097             } else if (toX >= 0 && toY >= 0) {\r
5098                 premoveToX = toX;\r
5099                 premoveToY = toY;\r
5100                 premoveFromX = fromX;\r
5101                 premoveFromY = fromY;\r
5102                 premovePromoChar = promoChar;\r
5103                 gotPremove = 1;\r
5104                 if (appData.debugMode) \r
5105                     fprintf(debugFP, "Got premove: fromX %d,"\r
5106                             "fromY %d, toX %d, toY %d\n",\r
5107                             fromX, fromY, toX, toY);\r
5108             }\r
5109             return ImpossibleMove;\r
5110         }\r
5111         break;\r
5112 \r
5113       default:\r
5114         break;\r
5115 \r
5116       case EditPosition:\r
5117         /* EditPosition, empty square, or different color piece;\r
5118            click-click move is possible */\r
5119         if (toX == -2 || toY == -2) {\r
5120             boards[0][fromY][fromX] = EmptySquare;\r
5121             return AmbiguousMove;\r
5122         } else if (toX >= 0 && toY >= 0) {\r
5123             boards[0][toY][toX] = boards[0][fromY][fromX];\r
5124             boards[0][fromY][fromX] = EmptySquare;\r
5125             return AmbiguousMove;\r
5126         }\r
5127         return ImpossibleMove;\r
5128     }\r
5129 \r
5130     /* [HGM] If move started in holdings, it means a drop */\r
5131     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
5132          if( pup != EmptySquare ) return ImpossibleMove;\r
5133          if(appData.testLegality) {\r
5134              /* it would be more logical if LegalityTest() also figured out\r
5135               * which drops are legal. For now we forbid pawns on back rank.\r
5136               * Shogi is on its own here...\r
5137               */\r
5138              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
5139                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
5140                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
5141          }\r
5142          return WhiteDrop; /* Not needed to specify white or black yet */\r
5143     }\r
5144 \r
5145     userOfferedDraw = FALSE;\r
5146         \r
5147     /* [HGM] always test for legality, to get promotion info */\r
5148     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
5149                           epStatus[currentMove], castlingRights[currentMove],\r
5150                                          fromY, fromX, toY, toX, promoChar);\r
5151 \r
5152     /* [HGM] but possibly ignore an IllegalMove result */\r
5153     if (appData.testLegality) {\r
5154         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
5155             DisplayMoveError(_("Illegal move"));\r
5156             return ImpossibleMove;\r
5157         }\r
5158     }\r
5159 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);\r
5160     return moveType;\r
5161     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
5162        function is made into one that returns an OK move type if FinishMove\r
5163        should be called. This to give the calling driver routine the\r
5164        opportunity to finish the userMove input with a promotion popup,\r
5165        without bothering the user with this for invalid or illegal moves */\r
5166 \r
5167 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
5168 }\r
5169 \r
5170 /* Common tail of UserMoveEvent and DropMenuEvent */\r
5171 int\r
5172 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
5173      ChessMove moveType;\r
5174      int fromX, fromY, toX, toY;\r
5175      /*char*/int promoChar;\r
5176 {\r
5177     char *bookHit = 0;\r
5178 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);\r
5179     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { \r
5180         // [HGM] superchess: suppress promotions to non-available piece\r
5181         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5182         if(WhiteOnMove(currentMove)) {\r
5183             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;\r
5184         } else {\r
5185             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;\r
5186         }\r
5187     }\r
5188 \r
5189     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion\r
5190        move type in caller when we know the move is a legal promotion */\r
5191     if(moveType == NormalMove && promoChar)\r
5192         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
5193 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);\r
5194     /* [HGM] convert drag-and-drop piece drops to standard form */\r
5195     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
5196          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
5197          fromX = boards[currentMove][fromY][fromX];\r
5198          fromY = DROP_RANK;\r
5199     }\r
5200 \r
5201     /* [HGM] <popupFix> The following if has been moved here from\r
5202        UserMoveEvent(). Because it seemed to belon here (why not allow\r
5203        piece drops in training games?), and because it can only be\r
5204        performed after it is known to what we promote. */\r
5205     if (gameMode == Training) {\r
5206       /* compare the move played on the board to the next move in the\r
5207        * game. If they match, display the move and the opponent's response. \r
5208        * If they don't match, display an error message.\r
5209        */\r
5210       int saveAnimate;\r
5211       Board testBoard;\r
5212       CopyBoard(testBoard, boards[currentMove]);\r
5213       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
5214 \r
5215       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
5216         ForwardInner(currentMove+1);\r
5217 \r
5218         /* Autoplay the opponent's response.\r
5219          * if appData.animate was TRUE when Training mode was entered,\r
5220          * the response will be animated.\r
5221          */\r
5222         saveAnimate = appData.animate;\r
5223         appData.animate = animateTraining;\r
5224         ForwardInner(currentMove+1);\r
5225         appData.animate = saveAnimate;\r
5226 \r
5227         /* check for the end of the game */\r
5228         if (currentMove >= forwardMostMove) {\r
5229           gameMode = PlayFromGameFile;\r
5230           ModeHighlight();\r
5231           SetTrainingModeOff();\r
5232           DisplayInformation(_("End of game"));\r
5233         }\r
5234       } else {\r
5235         DisplayError(_("Incorrect move"), 0);\r
5236       }\r
5237       return 1;\r
5238     }\r
5239 \r
5240   /* Ok, now we know that the move is good, so we can kill\r
5241      the previous line in Analysis Mode */\r
5242   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
5243     forwardMostMove = currentMove;\r
5244   }\r
5245 \r
5246   /* If we need the chess program but it's dead, restart it */\r
5247   ResurrectChessProgram();\r
5248 \r
5249   /* A user move restarts a paused game*/\r
5250   if (pausing)\r
5251     PauseEvent();\r
5252 \r
5253   thinkOutput[0] = NULLCHAR;\r
5254 \r
5255   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
5256 \r
5257     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) \r
5258                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
5259         // [HGM] superchess: take promotion piece out of holdings\r
5260         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5261         if(WhiteOnMove(forwardMostMove-1)) {\r
5262             if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])\r
5263                 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;\r
5264         } else {\r
5265             if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])\r
5266                 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;\r
5267         }\r
5268     }\r
5269 \r
5270   if (gameMode == BeginningOfGame) {\r
5271     if (appData.noChessProgram) {\r
5272       gameMode = EditGame;\r
5273       SetGameInfo();\r
5274     } else {\r
5275       char buf[MSG_SIZ];\r
5276       gameMode = MachinePlaysBlack;\r
5277       StartClocks();\r
5278       SetGameInfo();\r
5279       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
5280       DisplayTitle(buf);\r
5281       if (first.sendName) {\r
5282         sprintf(buf, "name %s\n", gameInfo.white);\r
5283         SendToProgram(buf, &first);\r
5284       }\r
5285       StartClocks();\r
5286     }\r
5287     ModeHighlight();\r
5288   }\r
5289 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);\r
5290   /* Relay move to ICS or chess engine */\r
5291   if (appData.icsActive) {\r
5292     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
5293         gameMode == IcsExamining) {\r
5294       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5295       ics_user_moved = 1;\r
5296     }\r
5297   } else {\r
5298     if (first.sendTime && (gameMode == BeginningOfGame ||\r
5299                            gameMode == MachinePlaysWhite ||\r
5300                            gameMode == MachinePlaysBlack)) {\r
5301       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
5302     }\r
5303     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
5304          // [HGM] book: if program might be playing, let it use book\r
5305         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);\r
5306         first.maybeThinking = TRUE;\r
5307     } else SendMoveToProgram(forwardMostMove-1, &first);\r
5308     if (currentMove == cmailOldMove + 1) {\r
5309       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
5310     }\r
5311   }\r
5312 \r
5313   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5314 \r
5315   switch (gameMode) {\r
5316   case EditGame:\r
5317     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
5318                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
5319     case MT_NONE:\r
5320     case MT_CHECK:\r
5321       break;\r
5322     case MT_CHECKMATE:\r
5323       if (WhiteOnMove(currentMove)) {\r
5324         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
5325       } else {\r
5326         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
5327       }\r
5328       break;\r
5329     case MT_STALEMATE:\r
5330       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
5331       break;\r
5332     }\r
5333     break;\r
5334     \r
5335   case MachinePlaysBlack:\r
5336   case MachinePlaysWhite:\r
5337     /* disable certain menu options while machine is thinking */\r
5338     SetMachineThinkingEnables();\r
5339     break;\r
5340 \r
5341   default:\r
5342     break;\r
5343   }\r
5344 \r
5345   if(bookHit) { // [HGM] book: simulate book reply\r
5346         static char bookMove[MSG_SIZ]; // a bit generous?\r
5347 \r
5348         programStats.nodes = programStats.depth = programStats.time = \r
5349         programStats.score = programStats.got_only_move = 0;\r
5350         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
5351 \r
5352         strcpy(bookMove, "move ");\r
5353         strcat(bookMove, bookHit);\r
5354         HandleMachineMove(bookMove, &first);\r
5355   }\r
5356   return 1;\r
5357 }\r
5358 \r
5359 void\r
5360 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
5361      int fromX, fromY, toX, toY;\r
5362      int promoChar;\r
5363 {\r
5364     /* [HGM] This routine was added to allow calling of its two logical\r
5365        parts from other modules in the old way. Before, UserMoveEvent()\r
5366        automatically called FinishMove() if the move was OK, and returned\r
5367        otherwise. I separated the two, in order to make it possible to\r
5368        slip a promotion popup in between. But that it always needs two\r
5369        calls, to the first part, (now called UserMoveTest() ), and to\r
5370        FinishMove if the first part succeeded. Calls that do not need\r
5371        to do anything in between, can call this routine the old way. \r
5372     */\r
5373     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
5374 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);\r
5375     if(moveType != ImpossibleMove)\r
5376         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
5377 }\r
5378 \r
5379 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
5380 {\r
5381 //    char * hint = lastHint;\r
5382     FrontEndProgramStats stats;\r
5383 \r
5384     stats.which = cps == &first ? 0 : 1;\r
5385     stats.depth = cpstats->depth;\r
5386     stats.nodes = cpstats->nodes;\r
5387     stats.score = cpstats->score;\r
5388     stats.time = cpstats->time;\r
5389     stats.pv = cpstats->movelist;\r
5390     stats.hint = lastHint;\r
5391     stats.an_move_index = 0;\r
5392     stats.an_move_count = 0;\r
5393 \r
5394     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
5395         stats.hint = cpstats->move_name;\r
5396         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
5397         stats.an_move_count = cpstats->nr_moves;\r
5398     }\r
5399 \r
5400     SetProgramStats( &stats );\r
5401 }\r
5402 \r
5403 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)\r
5404 {   // [HGM] book: this routine intercepts moves to simulate book replies\r
5405     char *bookHit = NULL;\r
5406 \r
5407     //first determine if the incoming move brings opponent into his book\r
5408     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
5409         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
5410     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");\r
5411     if(bookHit != NULL && !cps->bookSuspend) {\r
5412         // make sure opponent is not going to reply after receiving move to book position\r
5413         SendToProgram("force\n", cps);\r
5414         cps->bookSuspend = TRUE; // flag indicating it has to be restarted\r
5415     }\r
5416     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move\r
5417     // now arrange restart after book miss\r
5418     if(bookHit) {\r
5419         // after a book hit we never send 'go', and the code after the call to this routine\r
5420         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').\r
5421         char buf[MSG_SIZ];\r
5422         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(\r
5423         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it\r
5424         SendToProgram(buf, cps);\r
5425         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'\r
5426     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine\r
5427         SendToProgram("go\n", cps);\r
5428         cps->bookSuspend = FALSE; // after a 'go' we are never suspended\r
5429     } else { // 'go' might be sent based on 'firstMove' after this routine returns\r
5430         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return\r
5431             SendToProgram("go\n", cps); \r
5432         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss\r
5433     }\r
5434     return bookHit; // notify caller of hit, so it can take action to send move to opponent\r
5435 }\r
5436 \r
5437 char *savedMessage;\r
5438 ChessProgramState *savedState;\r
5439 void DeferredBookMove(void)\r
5440 {\r
5441         if(savedState->lastPing != savedState->lastPong)\r
5442                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
5443         else\r
5444         HandleMachineMove(savedMessage, savedState);\r
5445 }\r
5446 \r
5447 void\r
5448 HandleMachineMove(message, cps)\r
5449      char *message;\r
5450      ChessProgramState *cps;\r
5451 {\r
5452     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
5453     char realname[MSG_SIZ];\r
5454     int fromX, fromY, toX, toY;\r
5455     ChessMove moveType;\r
5456     char promoChar;\r
5457     char *p;\r
5458     int machineWhite;\r
5459     char *bookHit;\r
5460 \r
5461 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit\r
5462     /*\r
5463      * Kludge to ignore BEL characters\r
5464      */\r
5465     while (*message == '\007') message++;\r
5466 \r
5467     /*\r
5468      * [HGM] engine debug message: ignore lines starting with '#' character\r
5469      */\r
5470     if(cps->debug && *message == '#') return;\r
5471 \r
5472     /*\r
5473      * Look for book output\r
5474      */\r
5475     if (cps == &first && bookRequested) {\r
5476         if (message[0] == '\t' || message[0] == ' ') {\r
5477             /* Part of the book output is here; append it */\r
5478             strcat(bookOutput, message);\r
5479             strcat(bookOutput, "  \n");\r
5480             return;\r
5481         } else if (bookOutput[0] != NULLCHAR) {\r
5482             /* All of book output has arrived; display it */\r
5483             char *p = bookOutput;\r
5484             while (*p != NULLCHAR) {\r
5485                 if (*p == '\t') *p = ' ';\r
5486                 p++;\r
5487             }\r
5488             DisplayInformation(bookOutput);\r
5489             bookRequested = FALSE;\r
5490             /* Fall through to parse the current output */\r
5491         }\r
5492     }\r
5493 \r
5494     /*\r
5495      * Look for machine move.\r
5496      */\r
5497     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
5498         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
5499     {\r
5500         /* This method is only useful on engines that support ping */\r
5501         if (cps->lastPing != cps->lastPong) {\r
5502           if (gameMode == BeginningOfGame) {\r
5503             /* Extra move from before last new; ignore */\r
5504             if (appData.debugMode) {\r
5505                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5506             }\r
5507           } else {\r
5508             if (appData.debugMode) {\r
5509                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5510                         cps->which, gameMode);\r
5511             }\r
5512 \r
5513             SendToProgram("undo\n", cps);\r
5514           }\r
5515           return;\r
5516         }\r
5517 \r
5518         switch (gameMode) {\r
5519           case BeginningOfGame:\r
5520             /* Extra move from before last reset; ignore */\r
5521             if (appData.debugMode) {\r
5522                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5523             }\r
5524             return;\r
5525 \r
5526           case EndOfGame:\r
5527           case IcsIdle:\r
5528           default:\r
5529             /* Extra move after we tried to stop.  The mode test is\r
5530                not a reliable way of detecting this problem, but it's\r
5531                the best we can do on engines that don't support ping.\r
5532             */\r
5533             if (appData.debugMode) {\r
5534                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5535                         cps->which, gameMode);\r
5536             }\r
5537             SendToProgram("undo\n", cps);\r
5538             return;\r
5539 \r
5540           case MachinePlaysWhite:\r
5541           case IcsPlayingWhite:\r
5542             machineWhite = TRUE;\r
5543             break;\r
5544 \r
5545           case MachinePlaysBlack:\r
5546           case IcsPlayingBlack:\r
5547             machineWhite = FALSE;\r
5548             break;\r
5549 \r
5550           case TwoMachinesPlay:\r
5551             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5552             break;\r
5553         }\r
5554         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5555             if (appData.debugMode) {\r
5556                 fprintf(debugFP,\r
5557                         "Ignoring move out of turn by %s, gameMode %d"\r
5558                         ", forwardMost %d\n",\r
5559                         cps->which, gameMode, forwardMostMove);\r
5560             }\r
5561             return;\r
5562         }\r
5563 \r
5564     if (appData.debugMode) { int f = forwardMostMove;\r
5565         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5566                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5567     }\r
5568         if(cps->alphaRank) AlphaRank(machineMove, 4);\r
5569         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5570                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5571             /* Machine move could not be parsed; ignore it. */\r
5572             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),\r
5573                     machineMove, cps->which);\r
5574             DisplayError(buf1, 0);\r
5575             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",\r
5576                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);\r
5577             if (gameMode == TwoMachinesPlay) {\r
5578               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5579                        buf1, GE_XBOARD);\r
5580             }\r
5581             return;\r
5582         }\r
5583 \r
5584         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5585         /* So we have to redo legality test with true e.p. status here,  */\r
5586         /* to make sure an illegal e.p. capture does not slip through,   */\r
5587         /* to cause a forfeit on a justified illegal-move complaint      */\r
5588         /* of the opponent.                                              */\r
5589         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5590             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5591                                                               ) {\r
5592            ChessMove moveType;\r
5593            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5594                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5595                              fromY, fromX, toY, toX, promoChar);\r
5596             if (appData.debugMode) {\r
5597                 int i;\r
5598                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5599                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5600                 fprintf(debugFP, "castling rights\n");\r
5601             }\r
5602             if(moveType == IllegalMove) {\r
5603                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5604                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5605                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5606                            buf1, GE_XBOARD);\r
5607            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
5608            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5609               when they shouldn't (like TSCP-Gothic) */\r
5610            switch(moveType) {\r
5611              case WhiteASideCastleFR:\r
5612              case BlackASideCastleFR:\r
5613                toX+=2;\r
5614                currentMoveString[2]++;\r
5615                break;\r
5616              case WhiteHSideCastleFR:\r
5617              case BlackHSideCastleFR:\r
5618                toX--;\r
5619                currentMoveString[2]--;\r
5620                break;\r
5621              default: ; // nothing to do, but suppresses warning of pedantic compilers\r
5622            }\r
5623         }\r
5624         hintRequested = FALSE;\r
5625         lastHint[0] = NULLCHAR;\r
5626         bookRequested = FALSE;\r
5627         /* Program may be pondering now */\r
5628         cps->maybeThinking = TRUE;\r
5629         if (cps->sendTime == 2) cps->sendTime = 1;\r
5630         if (cps->offeredDraw) cps->offeredDraw--;\r
5631 \r
5632 #if ZIPPY\r
5633         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5634             first.initDone) {\r
5635           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5636           ics_user_moved = 1;\r
5637           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
5638                 char buf[3*MSG_SIZ];\r
5639 \r
5640                 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",\r
5641                         programStats.depth,\r
5642                         programStats.score / 100.,\r
5643                         programStats.time / 100.,\r
5644                         u64ToDouble(programStats.nodes),\r
5645                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),\r
5646                         programStats.movelist);\r
5647                 SendToICS(buf);\r
5648           }\r
5649         }\r
5650 #endif\r
5651         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5652         strcpy(machineMove, currentMoveString);\r
5653         strcat(machineMove, "\n");\r
5654         strcpy(moveList[forwardMostMove], machineMove);\r
5655 \r
5656         /* [AS] Save move info and clear stats for next move */\r
5657         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5658         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5659         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5660         ClearProgramStats();\r
5661         thinkOutput[0] = NULLCHAR;\r
5662         hiddenThinkOutputState = 0;\r
5663 \r
5664         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5665 \r
5666         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5667         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5668             int count = 0;\r
5669 \r
5670             while( count < adjudicateLossPlies ) {\r
5671                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5672 \r
5673                 if( count & 1 ) {\r
5674                     score = -score; /* Flip score for winning side */\r
5675                 }\r
5676 \r
5677                 if( score > adjudicateLossThreshold ) {\r
5678                     break;\r
5679                 }\r
5680 \r
5681                 count++;\r
5682             }\r
5683 \r
5684             if( count >= adjudicateLossPlies ) {\r
5685                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5686 \r
5687                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5688                     "Xboard adjudication", \r
5689                     GE_XBOARD );\r
5690 \r
5691                 return;\r
5692             }\r
5693         }\r
5694 \r
5695         if( gameMode == TwoMachinesPlay ) {\r
5696           // [HGM] some adjudications useful with buggy engines\r
5697             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
5698           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {\r
5699 \r
5700             if(appData.testLegality)\r
5701             // don't wait for engine to announce game end if we can judge ourselves\r
5702             switch (MateTest(boards[forwardMostMove],\r
5703                                  PosFlags(forwardMostMove), epFile,\r
5704                                        castlingRights[forwardMostMove]) ) {\r
5705               case MT_NONE:\r
5706               case MT_CHECK:\r
5707               default:\r
5708                 break;\r
5709               case MT_STALEMATE:\r
5710                 epStatus[forwardMostMove] = EP_STALEMATE;\r
5711                 if(appData.checkMates) {\r
5712                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5713                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5714                     if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantSuicide\r
5715                                                          || gameInfo.variant == VariantGiveaway) // [HGM] losers:\r
5716                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, // stalemated side wins!\r
5717                                 "Xboard adjudication: Stalemate", GE_XBOARD );\r
5718                     else\r
5719                         GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate", GE_XBOARD );\r
5720                     return;\r
5721                 }\r
5722                 break;\r
5723               case MT_CHECKMATE:\r
5724                 epStatus[forwardMostMove] = EP_CHECKMATE;\r
5725                 if(appData.checkMates) {\r
5726                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5727                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5728                     GameEnds( WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers) // [HGM] losers:\r
5729                              ? BlackWins : WhiteWins,            // reverse the result ( A!=1 is !A for a boolean)\r
5730                              "Xboard adjudication: Checkmate", GE_XBOARD );\r
5731                     return;\r
5732                 }\r
5733                 break;\r
5734             }\r
5735 \r
5736             if( appData.testLegality )\r
5737             {   /* [HGM] Some more adjudications for obstinate engines */\r
5738                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5739                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,\r
5740                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;\r
5741                 static int moveCount = 6;\r
5742 \r
5743                 /* First absolutely insufficient mating material. Count what is on board. */\r
5744                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5745                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5746                     int m=i;\r
5747 \r
5748                     switch((int) p)\r
5749                     {   /* count B,N,R and other of each side */\r
5750                         case WhiteKing:\r
5751                         case BlackKing:\r
5752                              NrK++; break; // [HGM] atomic: count Kings\r
5753                         case WhiteKnight:\r
5754                              NrWN++; break;\r
5755                         case WhiteBishop:\r
5756                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5757                              bishopsColor |= 1 << ((i^j)&1);\r
5758                              NrWB++; break;\r
5759                         case BlackKnight:\r
5760                              NrBN++; break;\r
5761                         case BlackBishop:\r
5762                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5763                              bishopsColor |= 1 << ((i^j)&1);\r
5764                              NrBB++; break;\r
5765                         case WhiteRook:\r
5766                              NrWR++; break;\r
5767                         case BlackRook:\r
5768                              NrBR++; break;\r
5769                         case WhiteQueen:\r
5770                              NrWQ++; break;\r
5771                         case BlackQueen:\r
5772                              NrBQ++; break;\r
5773                         case EmptySquare: \r
5774                              break;\r
5775                         case BlackPawn:\r
5776                              m = 7-i;\r
5777                         case WhitePawn:\r
5778                              PawnAdvance += m; NrPawns++;\r
5779                     }\r
5780                     NrPieces += (p != EmptySquare);\r
5781                     NrW += ((int)p < (int)BlackPawn);\r
5782                     if(gameInfo.variant == VariantXiangqi && \r
5783                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {\r
5784                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces\r
5785                         NrW -= ((int)p < (int)BlackPawn);\r
5786                     }\r
5787                 }\r
5788 \r
5789                 if(gameInfo.variant == VariantAtomic && NrK < 2) {\r
5790                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal\r
5791                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated\r
5792                      if(appData.checkMates) {\r
5793                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move\r
5794                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5795                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5796                                                         "Xboard adjudication: King destroyed", GE_XBOARD );\r
5797                          return;\r
5798                      }\r
5799                 }\r
5800 \r
5801                 /* Bare King in Shatranj (loses) or Losers (wins) */\r
5802                 if( NrW == 1 || NrPieces - NrW == 1) {\r
5803                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)\r
5804                      epStatus[forwardMostMove] = EP_STALEMATE; // kludge to make position claimable as win\r
5805                      if(appData.checkMates) {\r
5806                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5807                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5808                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5809                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5810                          return;\r
5811                      }\r
5812                   } else\r
5813                   if( gameInfo.variant == VariantShatranj && --bare < 0)\r
5814                   {    /* bare King */\r
5815                         epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as win for stm\r
5816                         if(appData.checkMates) {\r
5817                             /* but only adjudicate if adjudication enabled */\r
5818                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move\r
5819                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5820                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
5821                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5822                             return;\r
5823                         }\r
5824                   }\r
5825                 } else bare = 1;\r
5826 \r
5827 \r
5828                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && \r
5829                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible\r
5830                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||\r
5831                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color\r
5832                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5833 \r
5834                      /* always flag draws, for judging claims */\r
5835                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5836 \r
5837                      if(appData.materialDraws) {\r
5838                          /* but only adjudicate them if adjudication enabled */\r
5839                          SendToProgram("force\n", cps->other); // suppress reply\r
5840                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */\r
5841                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5842                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5843                          return;\r
5844                      }\r
5845                 }\r
5846 \r
5847                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5848                 if(NrPieces == 4 && \r
5849                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5850                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5851                    || NrWN==2 || NrBN==2     /* KNNK */\r
5852                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5853                   ) ) {\r
5854                      if(--moveCount < 0 && appData.trivialDraws)\r
5855                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5856                           SendToProgram("force\n", cps->other); // suppress reply\r
5857                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5858                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5859                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5860                           return;\r
5861                      }\r
5862                 } else moveCount = 6;\r
5863             }\r
5864           }\r
5865 #if 1\r
5866     if (appData.debugMode) { int i;\r
5867       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5868               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5869               appData.drawRepeats);\r
5870       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5871            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5872 \r
5873     }\r
5874 #endif\r
5875                 /* Check for rep-draws */\r
5876                 count = 0;\r
5877                 for(k = forwardMostMove-2;\r
5878                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5879                         epStatus[k] < EP_UNKNOWN &&\r
5880                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5881                     k-=2)\r
5882                 {   int rights=0;\r
5883 #if 0\r
5884     if (appData.debugMode) {\r
5885       fprintf(debugFP, " loop\n");\r
5886     }\r
5887 #endif\r
5888                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5889 #if 0\r
5890     if (appData.debugMode) {\r
5891       fprintf(debugFP, "match\n");\r
5892     }\r
5893 #endif\r
5894                         /* compare castling rights */\r
5895                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5896                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5897                                 rights++; /* King lost rights, while rook still had them */\r
5898                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5899                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5900                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5901                                    rights++; /* but at least one rook lost them */\r
5902                         }\r
5903                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5904                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5905                                 rights++; \r
5906                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5907                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5908                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5909                                    rights++;\r
5910                         }\r
5911 #if 0\r
5912     if (appData.debugMode) {\r
5913       for(i=0; i<nrCastlingRights; i++)\r
5914       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5915     }\r
5916 \r
5917     if (appData.debugMode) {\r
5918       fprintf(debugFP, " %d %d\n", rights, k);\r
5919     }\r
5920 #endif\r
5921                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5922                             && appData.drawRepeats > 1) {\r
5923                              /* adjudicate after user-specified nr of repeats */\r
5924                              SendToProgram("force\n", cps->other); // suppress reply\r
5925                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5926                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5927                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { \r
5928                                 // [HGM] xiangqi: check for forbidden perpetuals\r
5929                                 int m, ourPerpetual = 1, hisPerpetual = 1;\r
5930                                 for(m=forwardMostMove; m>k; m-=2) {\r
5931                                     if(MateTest(boards[m], PosFlags(m), \r
5932                                                         EP_NONE, castlingRights[m]) != MT_CHECK)\r
5933                                         ourPerpetual = 0; // the current mover did not always check\r
5934                                     if(MateTest(boards[m-1], PosFlags(m-1), \r
5935                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)\r
5936                                         hisPerpetual = 0; // the opponent did not always check\r
5937                                 }\r
5938                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",\r
5939                                                                         ourPerpetual, hisPerpetual);\r
5940                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
5941                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5942                                            "Xboard adjudication: perpetual checking", GE_XBOARD );\r
5943                                     return;\r
5944                                 }\r
5945                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet\r
5946                                     break; // (or we would have caught him before). Abort repetition-checking loop.\r
5947                                 // Now check for perpetual chases\r
5948                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase\r
5949                                     hisPerpetual = PerpetualChase(k, forwardMostMove);\r
5950                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);\r
5951                                     if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
5952                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5953                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );\r
5954                                         return;\r
5955                                     }\r
5956                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet\r
5957                                         break; // Abort repetition-checking loop.\r
5958                                 }\r
5959                                 // if neither of us is checking or chasing all the time, or both are, it is draw\r
5960                              }\r
5961                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
5962                              return;\r
5963                         }\r
5964                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
5965                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
5966                     }\r
5967                 }\r
5968 \r
5969                 /* Now we test for 50-move draws. Determine ply count */\r
5970                 count = forwardMostMove;\r
5971                 /* look for last irreversble move */\r
5972                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
5973                     count--;\r
5974                 /* if we hit starting position, add initial plies */\r
5975                 if( count == backwardMostMove )\r
5976                     count -= initialRulePlies;\r
5977                 count = forwardMostMove - count; \r
5978                 if( count >= 100)\r
5979                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
5980                          /* this is used to judge if draw claims are legal */\r
5981                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
5982                          SendToProgram("force\n", cps->other); // suppress reply\r
5983                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5984                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5985                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
5986                          return;\r
5987                 }\r
5988 \r
5989                 /* if draw offer is pending, treat it as a draw claim\r
5990                  * when draw condition present, to allow engines a way to\r
5991                  * claim draws before making their move to avoid a race\r
5992                  * condition occurring after their move\r
5993                  */\r
5994                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
5995                          char *p = NULL;\r
5996                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
5997                              p = "Draw claim: 50-move rule";\r
5998                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
5999                              p = "Draw claim: 3-fold repetition";\r
6000                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
6001                              p = "Draw claim: insufficient mating material";\r
6002                          if( p != NULL ) {\r
6003                              SendToProgram("force\n", cps->other); // suppress reply\r
6004                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6005                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
6006                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6007                              return;\r
6008                          }\r
6009                 }\r
6010 \r
6011 \r
6012                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
6013                     SendToProgram("force\n", cps->other); // suppress reply\r
6014                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6015                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6016 \r
6017                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
6018 \r
6019                     return;\r
6020                 }\r
6021         }\r
6022 \r
6023         bookHit = NULL;\r
6024         if (gameMode == TwoMachinesPlay) {\r
6025             /* [HGM] relaying draw offers moved to after reception of move */\r
6026             /* and interpreting offer as claim if it brings draw condition */\r
6027             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
6028                 SendToProgram("draw\n", cps->other);\r
6029             }\r
6030             if (cps->other->sendTime) {\r
6031                 SendTimeRemaining(cps->other,\r
6032                                   cps->other->twoMachinesColor[0] == 'w');\r
6033             }\r
6034             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);\r
6035             if (firstMove && !bookHit) {\r
6036                 firstMove = FALSE;\r
6037                 if (cps->other->useColors) {\r
6038                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
6039                 }\r
6040                 SendToProgram("go\n", cps->other);\r
6041             }\r
6042             cps->other->maybeThinking = TRUE;\r
6043         }\r
6044 \r
6045         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6046         \r
6047         if (!pausing && appData.ringBellAfterMoves) {\r
6048             RingBell();\r
6049         }\r
6050 \r
6051         /* \r
6052          * Reenable menu items that were disabled while\r
6053          * machine was thinking\r
6054          */\r
6055         if (gameMode != TwoMachinesPlay)\r
6056             SetUserThinkingEnables();\r
6057 \r
6058         // [HGM] book: after book hit opponent has received move and is now in force mode\r
6059         // force the book reply into it, and then fake that it outputted this move by jumping\r
6060         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move\r
6061         if(bookHit) {\r
6062                 static char bookMove[MSG_SIZ]; // a bit generous?\r
6063 \r
6064                 strcpy(bookMove, "move ");\r
6065                 strcat(bookMove, bookHit);\r
6066                 message = bookMove;\r
6067                 cps = cps->other;\r
6068                 programStats.nodes = programStats.depth = programStats.time = \r
6069                 programStats.score = programStats.got_only_move = 0;\r
6070                 sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
6071 \r
6072                 if(cps->lastPing != cps->lastPong) {\r
6073                     savedMessage = message; // args for deferred call\r
6074                     savedState = cps;\r
6075                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
6076                     return;\r
6077                 }\r
6078                 goto FakeBookMove;\r
6079         }\r
6080 \r
6081         return;\r
6082     }\r
6083 \r
6084     /* Set special modes for chess engines.  Later something general\r
6085      *  could be added here; for now there is just one kludge feature,\r
6086      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
6087      *  when "xboard" is given as an interactive command.\r
6088      */\r
6089     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
6090         cps->useSigint = FALSE;\r
6091         cps->useSigterm = FALSE;\r
6092     }\r
6093 \r
6094     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
6095      * want this, I was asked to put it in, and obliged.\r
6096      */\r
6097     if (!strncmp(message, "setboard ", 9)) {\r
6098         Board initial_position; int i;\r
6099 \r
6100         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
6101 \r
6102         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
6103             DisplayError(_("Bad FEN received from engine"), 0);\r
6104             return ;\r
6105         } else {\r
6106            Reset(FALSE, FALSE);\r
6107            CopyBoard(boards[0], initial_position);\r
6108            initialRulePlies = FENrulePlies;\r
6109            epStatus[0] = FENepStatus;\r
6110            for( i=0; i<nrCastlingRights; i++ )\r
6111                 castlingRights[0][i] = FENcastlingRights[i];\r
6112            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
6113            else gameMode = MachinePlaysBlack;                 \r
6114            DrawPosition(FALSE, boards[currentMove]);\r
6115         }\r
6116         return;\r
6117     }\r
6118 \r
6119     /*\r
6120      * Look for communication commands\r
6121      */\r
6122     if (!strncmp(message, "telluser ", 9)) {\r
6123         DisplayNote(message + 9);\r
6124         return;\r
6125     }\r
6126     if (!strncmp(message, "tellusererror ", 14)) {\r
6127         DisplayError(message + 14, 0);\r
6128         return;\r
6129     }\r
6130     if (!strncmp(message, "tellopponent ", 13)) {\r
6131       if (appData.icsActive) {\r
6132         if (loggedOn) {\r
6133           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
6134           SendToICS(buf1);\r
6135         }\r
6136       } else {\r
6137         DisplayNote(message + 13);\r
6138       }\r
6139       return;\r
6140     }\r
6141     if (!strncmp(message, "tellothers ", 11)) {\r
6142       if (appData.icsActive) {\r
6143         if (loggedOn) {\r
6144           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
6145           SendToICS(buf1);\r
6146         }\r
6147       }\r
6148       return;\r
6149     }\r
6150     if (!strncmp(message, "tellall ", 8)) {\r
6151       if (appData.icsActive) {\r
6152         if (loggedOn) {\r
6153           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
6154           SendToICS(buf1);\r
6155         }\r
6156       } else {\r
6157         DisplayNote(message + 8);\r
6158       }\r
6159       return;\r
6160     }\r
6161     if (strncmp(message, "warning", 7) == 0) {\r
6162         /* Undocumented feature, use tellusererror in new code */\r
6163         DisplayError(message, 0);\r
6164         return;\r
6165     }\r
6166     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
6167         strcpy(realname, cps->tidy);\r
6168         strcat(realname, " query");\r
6169         AskQuestion(realname, buf2, buf1, cps->pr);\r
6170         return;\r
6171     }\r
6172     /* Commands from the engine directly to ICS.  We don't allow these to be \r
6173      *  sent until we are logged on. Crafty kibitzes have been known to \r
6174      *  interfere with the login process.\r
6175      */\r
6176     if (loggedOn) {\r
6177         if (!strncmp(message, "tellics ", 8)) {\r
6178             SendToICS(message + 8);\r
6179             SendToICS("\n");\r
6180             return;\r
6181         }\r
6182         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
6183             SendToICS(ics_prefix);\r
6184             SendToICS(message + 15);\r
6185             SendToICS("\n");\r
6186             return;\r
6187         }\r
6188         /* The following are for backward compatibility only */\r
6189         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
6190             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
6191             SendToICS(ics_prefix);\r
6192             SendToICS(message);\r
6193             SendToICS("\n");\r
6194             return;\r
6195         }\r
6196     }\r
6197     if (strncmp(message, "feature ", 8) == 0) {\r
6198       ParseFeatures(message+8, cps);\r
6199     }\r
6200     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
6201         return;\r
6202     }\r
6203     /*\r
6204      * If the move is illegal, cancel it and redraw the board.\r
6205      * Also deal with other error cases.  Matching is rather loose\r
6206      * here to accommodate engines written before the spec.\r
6207      */\r
6208     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
6209         strncmp(message, "Error", 5) == 0) {\r
6210         if (StrStr(message, "name") || \r
6211             StrStr(message, "rating") || StrStr(message, "?") ||\r
6212             StrStr(message, "result") || StrStr(message, "board") ||\r
6213             StrStr(message, "bk") || StrStr(message, "computer") ||\r
6214             StrStr(message, "variant") || StrStr(message, "hint") ||\r
6215             StrStr(message, "random") || StrStr(message, "depth") ||\r
6216             StrStr(message, "accepted")) {\r
6217             return;\r
6218         }\r
6219         if (StrStr(message, "protover")) {\r
6220           /* Program is responding to input, so it's apparently done\r
6221              initializing, and this error message indicates it is\r
6222              protocol version 1.  So we don't need to wait any longer\r
6223              for it to initialize and send feature commands. */\r
6224           FeatureDone(cps, 1);\r
6225           cps->protocolVersion = 1;\r
6226           return;\r
6227         }\r
6228         cps->maybeThinking = FALSE;\r
6229 \r
6230         if (StrStr(message, "draw")) {\r
6231             /* Program doesn't have "draw" command */\r
6232             cps->sendDrawOffers = 0;\r
6233             return;\r
6234         }\r
6235         if (cps->sendTime != 1 &&\r
6236             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
6237           /* Program apparently doesn't have "time" or "otim" command */\r
6238           cps->sendTime = 0;\r
6239           return;\r
6240         }\r
6241         if (StrStr(message, "analyze")) {\r
6242             cps->analysisSupport = FALSE;\r
6243             cps->analyzing = FALSE;\r
6244             Reset(FALSE, TRUE);\r
6245             sprintf(buf2, _("%s does not support analysis"), cps->tidy);\r
6246             DisplayError(buf2, 0);\r
6247             return;\r
6248         }\r
6249         if (StrStr(message, "(no matching move)st")) {\r
6250           /* Special kludge for GNU Chess 4 only */\r
6251           cps->stKludge = TRUE;\r
6252           SendTimeControl(cps, movesPerSession, timeControl,\r
6253                           timeIncrement, appData.searchDepth,\r
6254                           searchTime);\r
6255           return;\r
6256         }\r
6257         if (StrStr(message, "(no matching move)sd")) {\r
6258           /* Special kludge for GNU Chess 4 only */\r
6259           cps->sdKludge = TRUE;\r
6260           SendTimeControl(cps, movesPerSession, timeControl,\r
6261                           timeIncrement, appData.searchDepth,\r
6262                           searchTime);\r
6263           return;\r
6264         }\r
6265         if (!StrStr(message, "llegal")) {\r
6266             return;\r
6267         }\r
6268         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6269             gameMode == IcsIdle) return;\r
6270         if (forwardMostMove <= backwardMostMove) return;\r
6271 #if 0\r
6272         /* Following removed: it caused a bug where a real illegal move\r
6273            message in analyze mored would be ignored. */\r
6274         if (cps == &first && programStats.ok_to_send == 0) {\r
6275             /* Bogus message from Crafty responding to "."  This filtering\r
6276                can miss some of the bad messages, but fortunately the bug \r
6277                is fixed in current Crafty versions, so it doesn't matter. */\r
6278             return;\r
6279         }\r
6280 #endif\r
6281         if (pausing) PauseEvent();\r
6282         if (gameMode == PlayFromGameFile) {\r
6283             /* Stop reading this game file */\r
6284             gameMode = EditGame;\r
6285             ModeHighlight();\r
6286         }\r
6287         currentMove = --forwardMostMove;\r
6288         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
6289         SwitchClocks();\r
6290         DisplayBothClocks();\r
6291         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),\r
6292                 parseList[currentMove], cps->which);\r
6293         DisplayMoveError(buf1);\r
6294         DrawPosition(FALSE, boards[currentMove]);\r
6295 \r
6296         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
6297         /* only passes fully legal moves                            */\r
6298         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
6299             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
6300                                 "False illegal-move claim", GE_XBOARD );\r
6301         }\r
6302         return;\r
6303     }\r
6304     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
6305         /* Program has a broken "time" command that\r
6306            outputs a string not ending in newline.\r
6307            Don't use it. */\r
6308         cps->sendTime = 0;\r
6309     }\r
6310     \r
6311     /*\r
6312      * If chess program startup fails, exit with an error message.\r
6313      * Attempts to recover here are futile.\r
6314      */\r
6315     if ((StrStr(message, "unknown host") != NULL)\r
6316         || (StrStr(message, "No remote directory") != NULL)\r
6317         || (StrStr(message, "not found") != NULL)\r
6318         || (StrStr(message, "No such file") != NULL)\r
6319         || (StrStr(message, "can't alloc") != NULL)\r
6320         || (StrStr(message, "Permission denied") != NULL)) {\r
6321 \r
6322         cps->maybeThinking = FALSE;\r
6323         sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),\r
6324                 cps->which, cps->program, cps->host, message);\r
6325         RemoveInputSource(cps->isr);\r
6326         DisplayFatalError(buf1, 0, 1);\r
6327         return;\r
6328     }\r
6329     \r
6330     /* \r
6331      * Look for hint output\r
6332      */\r
6333     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
6334         if (cps == &first && hintRequested) {\r
6335             hintRequested = FALSE;\r
6336             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
6337                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
6338                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
6339                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
6340                                     fromY, fromX, toY, toX, promoChar, buf1);\r
6341                 sprintf(buf2, _("Hint: %s"), buf1);\r
6342                 DisplayInformation(buf2);\r
6343             } else {\r
6344                 /* Hint move could not be parsed!? */\r
6345                 sprintf(buf2,\r
6346                         _("Illegal hint move \"%s\"\nfrom %s chess program"),\r
6347                         buf1, cps->which);\r
6348                 DisplayError(buf2, 0);\r
6349             }\r
6350         } else {\r
6351             strcpy(lastHint, buf1);\r
6352         }\r
6353         return;\r
6354     }\r
6355 \r
6356     /*\r
6357      * Ignore other messages if game is not in progress\r
6358      */\r
6359     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6360         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
6361 \r
6362     /*\r
6363      * look for win, lose, draw, or draw offer\r
6364      */\r
6365     if (strncmp(message, "1-0", 3) == 0) {\r
6366         char *p, *q, *r = "";\r
6367         p = strchr(message, '{');\r
6368         if (p) {\r
6369             q = strchr(p, '}');\r
6370             if (q) {\r
6371                 *q = NULLCHAR;\r
6372                 r = p + 1;\r
6373             }\r
6374         }\r
6375         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
6376         return;\r
6377     } else if (strncmp(message, "0-1", 3) == 0) {\r
6378         char *p, *q, *r = "";\r
6379         p = strchr(message, '{');\r
6380         if (p) {\r
6381             q = strchr(p, '}');\r
6382             if (q) {\r
6383                 *q = NULLCHAR;\r
6384                 r = p + 1;\r
6385             }\r
6386         }\r
6387         /* Kludge for Arasan 4.1 bug */\r
6388         if (strcmp(r, "Black resigns") == 0) {\r
6389             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
6390             return;\r
6391         }\r
6392         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
6393         return;\r
6394     } else if (strncmp(message, "1/2", 3) == 0) {\r
6395         char *p, *q, *r = "";\r
6396         p = strchr(message, '{');\r
6397         if (p) {\r
6398             q = strchr(p, '}');\r
6399             if (q) {\r
6400                 *q = NULLCHAR;\r
6401                 r = p + 1;\r
6402             }\r
6403         }\r
6404             \r
6405         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
6406         return;\r
6407 \r
6408     } else if (strncmp(message, "White resign", 12) == 0) {\r
6409         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6410         return;\r
6411     } else if (strncmp(message, "Black resign", 12) == 0) {\r
6412         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6413         return;\r
6414     } else if (strncmp(message, "White matches", 13) == 0 ||\r
6415                strncmp(message, "Black matches", 13) == 0   ) {\r
6416         /* [HGM] ignore GNUShogi noises */\r
6417         return;\r
6418     } else if (strncmp(message, "White", 5) == 0 &&\r
6419                message[5] != '(' &&\r
6420                StrStr(message, "Black") == NULL) {\r
6421         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6422         return;\r
6423     } else if (strncmp(message, "Black", 5) == 0 &&\r
6424                message[5] != '(') {\r
6425         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6426         return;\r
6427     } else if (strcmp(message, "resign") == 0 ||\r
6428                strcmp(message, "computer resigns") == 0) {\r
6429         switch (gameMode) {\r
6430           case MachinePlaysBlack:\r
6431           case IcsPlayingBlack:\r
6432             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
6433             break;\r
6434           case MachinePlaysWhite:\r
6435           case IcsPlayingWhite:\r
6436             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
6437             break;\r
6438           case TwoMachinesPlay:\r
6439             if (cps->twoMachinesColor[0] == 'w')\r
6440               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6441             else\r
6442               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6443             break;\r
6444           default:\r
6445             /* can't happen */\r
6446             break;\r
6447         }\r
6448         return;\r
6449     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
6450         switch (gameMode) {\r
6451           case MachinePlaysBlack:\r
6452           case IcsPlayingBlack:\r
6453             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6454             break;\r
6455           case MachinePlaysWhite:\r
6456           case IcsPlayingWhite:\r
6457             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
6458             break;\r
6459           case TwoMachinesPlay:\r
6460             if (cps->twoMachinesColor[0] == 'w')\r
6461               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6462             else\r
6463               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6464             break;\r
6465           default:\r
6466             /* can't happen */\r
6467             break;\r
6468         }\r
6469         return;\r
6470     } else if (strncmp(message, "computer mates", 14) == 0) {\r
6471         switch (gameMode) {\r
6472           case MachinePlaysBlack:\r
6473           case IcsPlayingBlack:\r
6474             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
6475             break;\r
6476           case MachinePlaysWhite:\r
6477           case IcsPlayingWhite:\r
6478             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6479             break;\r
6480           case TwoMachinesPlay:\r
6481             if (cps->twoMachinesColor[0] == 'w')\r
6482               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6483             else\r
6484               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6485             break;\r
6486           default:\r
6487             /* can't happen */\r
6488             break;\r
6489         }\r
6490         return;\r
6491     } else if (strncmp(message, "checkmate", 9) == 0) {\r
6492         if (WhiteOnMove(forwardMostMove)) {\r
6493             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6494         } else {\r
6495             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6496         }\r
6497         return;\r
6498     } else if (strstr(message, "Draw") != NULL ||\r
6499                strstr(message, "game is a draw") != NULL) {\r
6500         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
6501         return;\r
6502     } else if (strstr(message, "offer") != NULL &&\r
6503                strstr(message, "draw") != NULL) {\r
6504 #if ZIPPY\r
6505         if (appData.zippyPlay && first.initDone) {\r
6506             /* Relay offer to ICS */\r
6507             SendToICS(ics_prefix);\r
6508             SendToICS("draw\n");\r
6509         }\r
6510 #endif\r
6511         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
6512         if (gameMode == TwoMachinesPlay) {\r
6513             if (cps->other->offeredDraw) {\r
6514                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6515             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
6516             /* until after we also have move, to see if it is really claim */\r
6517             }\r
6518 #if 0\r
6519               else {\r
6520                 if (cps->other->sendDrawOffers) {\r
6521                     SendToProgram("draw\n", cps->other);\r
6522                 }\r
6523             }\r
6524 #endif\r
6525         } else if (gameMode == MachinePlaysWhite ||\r
6526                    gameMode == MachinePlaysBlack) {\r
6527           if (userOfferedDraw) {\r
6528             DisplayInformation(_("Machine accepts your draw offer"));\r
6529             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6530           } else {\r
6531             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));\r
6532           }\r
6533         }\r
6534     }\r
6535 \r
6536     \r
6537     /*\r
6538      * Look for thinking output\r
6539      */\r
6540     if ( appData.showThinking // [HGM] thinking: test all options that cause this output\r
6541           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
6542                                 ) {\r
6543         int plylev, mvleft, mvtot, curscore, time;\r
6544         char mvname[MOVE_LEN];\r
6545         u64 nodes; // [DM]\r
6546         char plyext;\r
6547         int ignore = FALSE;\r
6548         int prefixHint = FALSE;\r
6549         mvname[0] = NULLCHAR;\r
6550 \r
6551         switch (gameMode) {\r
6552           case MachinePlaysBlack:\r
6553           case IcsPlayingBlack:\r
6554             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6555             break;\r
6556           case MachinePlaysWhite:\r
6557           case IcsPlayingWhite:\r
6558             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6559             break;\r
6560           case AnalyzeMode:\r
6561           case AnalyzeFile:\r
6562             break;\r
6563           case IcsObserving: /* [DM] icsEngineAnalyze */\r
6564             if (!appData.icsEngineAnalyze) ignore = TRUE;\r
6565             break;\r
6566           case TwoMachinesPlay:\r
6567             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
6568                 ignore = TRUE;\r
6569             }\r
6570             break;\r
6571           default:\r
6572             ignore = TRUE;\r
6573             break;\r
6574         }\r
6575 \r
6576         if (!ignore) {\r
6577             buf1[0] = NULLCHAR;\r
6578             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6579                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
6580 \r
6581                 if (plyext != ' ' && plyext != '\t') {\r
6582                     time *= 100;\r
6583                 }\r
6584 \r
6585                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6586                 if( cps->scoreIsAbsolute && \r
6587                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
6588                 {\r
6589                     curscore = -curscore;\r
6590                 }\r
6591 \r
6592 \r
6593                 programStats.depth = plylev;\r
6594                 programStats.nodes = nodes;\r
6595                 programStats.time = time;\r
6596                 programStats.score = curscore;\r
6597                 programStats.got_only_move = 0;\r
6598 \r
6599                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
6600                         int ticklen;\r
6601 \r
6602                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time\r
6603                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time\r
6604                         if(WhiteOnMove(forwardMostMove)) \r
6605                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
6606                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
6607                 }\r
6608 \r
6609                 /* Buffer overflow protection */\r
6610                 if (buf1[0] != NULLCHAR) {\r
6611                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
6612                         && appData.debugMode) {\r
6613                         fprintf(debugFP,\r
6614                                 "PV is too long; using the first %d bytes.\n",\r
6615                                 sizeof(programStats.movelist) - 1);\r
6616                     }\r
6617 \r
6618                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
6619                 } else {\r
6620                     sprintf(programStats.movelist, " no PV\n");\r
6621                 }\r
6622 \r
6623                 if (programStats.seen_stat) {\r
6624                     programStats.ok_to_send = 1;\r
6625                 }\r
6626 \r
6627                 if (strchr(programStats.movelist, '(') != NULL) {\r
6628                     programStats.line_is_book = 1;\r
6629                     programStats.nr_moves = 0;\r
6630                     programStats.moves_left = 0;\r
6631                 } else {\r
6632                     programStats.line_is_book = 0;\r
6633                 }\r
6634 \r
6635                 SendProgramStatsToFrontend( cps, &programStats );\r
6636 \r
6637                 /* \r
6638                     [AS] Protect the thinkOutput buffer from overflow... this\r
6639                     is only useful if buf1 hasn't overflowed first!\r
6640                 */\r
6641                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
6642                         plylev, \r
6643                         (gameMode == TwoMachinesPlay ?\r
6644                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
6645                         ((double) curscore) / 100.0,\r
6646                         prefixHint ? lastHint : "",\r
6647                         prefixHint ? " " : "" );\r
6648 \r
6649                 if( buf1[0] != NULLCHAR ) {\r
6650                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
6651 \r
6652                     if( strlen(buf1) > max_len ) {\r
6653                         if( appData.debugMode) {\r
6654                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
6655                         }\r
6656                         buf1[max_len+1] = '\0';\r
6657                     }\r
6658 \r
6659                     strcat( thinkOutput, buf1 );\r
6660                 }\r
6661 \r
6662                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode\r
6663                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6664                     DisplayMove(currentMove - 1);\r
6665                     DisplayAnalysis();\r
6666                 }\r
6667                 return;\r
6668 \r
6669             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6670                 /* crafty (9.25+) says "(only move) <move>"\r
6671                  * if there is only 1 legal move\r
6672                  */\r
6673                 sscanf(p, "(only move) %s", buf1);\r
6674                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6675                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6676                 programStats.depth = 1;\r
6677                 programStats.nr_moves = 1;\r
6678                 programStats.moves_left = 1;\r
6679                 programStats.nodes = 1;\r
6680                 programStats.time = 1;\r
6681                 programStats.got_only_move = 1;\r
6682 \r
6683                 /* Not really, but we also use this member to\r
6684                    mean "line isn't going to change" (Crafty\r
6685                    isn't searching, so stats won't change) */\r
6686                 programStats.line_is_book = 1;\r
6687 \r
6688                 SendProgramStatsToFrontend( cps, &programStats );\r
6689                 \r
6690                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || \r
6691                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6692                     DisplayMove(currentMove - 1);\r
6693                     DisplayAnalysis();\r
6694                 }\r
6695                 return;\r
6696             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",\r
6697                               &time, &nodes, &plylev, &mvleft,\r
6698                               &mvtot, mvname) >= 5) {\r
6699                 /* The stat01: line is from Crafty (9.29+) in response\r
6700                    to the "." command */\r
6701                 programStats.seen_stat = 1;\r
6702                 cps->maybeThinking = TRUE;\r
6703 \r
6704                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6705                   return;\r
6706 \r
6707                 programStats.depth = plylev;\r
6708                 programStats.time = time;\r
6709                 programStats.nodes = nodes;\r
6710                 programStats.moves_left = mvleft;\r
6711                 programStats.nr_moves = mvtot;\r
6712                 strcpy(programStats.move_name, mvname);\r
6713                 programStats.ok_to_send = 1;\r
6714                 programStats.movelist[0] = '\0';\r
6715 \r
6716                 SendProgramStatsToFrontend( cps, &programStats );\r
6717 \r
6718                 DisplayAnalysis();\r
6719                 return;\r
6720 \r
6721             } else if (strncmp(message,"++",2) == 0) {\r
6722                 /* Crafty 9.29+ outputs this */\r
6723                 programStats.got_fail = 2;\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 = 1;\r
6729                 return;\r
6730 \r
6731             } else if (thinkOutput[0] != NULLCHAR &&\r
6732                        strncmp(message, "    ", 4) == 0) {\r
6733                 unsigned message_len;\r
6734 \r
6735                 p = message;\r
6736                 while (*p && *p == ' ') p++;\r
6737 \r
6738                 message_len = strlen( p );\r
6739 \r
6740                 /* [AS] Avoid buffer overflow */\r
6741                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6742                     strcat(thinkOutput, " ");\r
6743                     strcat(thinkOutput, p);\r
6744                 }\r
6745 \r
6746                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6747                     strcat(programStats.movelist, " ");\r
6748                     strcat(programStats.movelist, p);\r
6749                 }\r
6750 \r
6751                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||\r
6752                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6753                     DisplayMove(currentMove - 1);\r
6754                     DisplayAnalysis();\r
6755                 }\r
6756                 return;\r
6757             }\r
6758         }\r
6759         else {\r
6760             buf1[0] = NULLCHAR;\r
6761 \r
6762             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6763                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6764             {\r
6765                 ChessProgramStats cpstats;\r
6766 \r
6767                 if (plyext != ' ' && plyext != '\t') {\r
6768                     time *= 100;\r
6769                 }\r
6770 \r
6771                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6772                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6773                     curscore = -curscore;\r
6774                 }\r
6775 \r
6776                 cpstats.depth = plylev;\r
6777                 cpstats.nodes = nodes;\r
6778                 cpstats.time = time;\r
6779                 cpstats.score = curscore;\r
6780                 cpstats.got_only_move = 0;\r
6781                 cpstats.movelist[0] = '\0';\r
6782 \r
6783                 if (buf1[0] != NULLCHAR) {\r
6784                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6785                 }\r
6786 \r
6787                 cpstats.ok_to_send = 0;\r
6788                 cpstats.line_is_book = 0;\r
6789                 cpstats.nr_moves = 0;\r
6790                 cpstats.moves_left = 0;\r
6791 \r
6792                 SendProgramStatsToFrontend( cps, &cpstats );\r
6793             }\r
6794         }\r
6795     }\r
6796 }\r
6797 \r
6798 \r
6799 /* Parse a game score from the character string "game", and\r
6800    record it as the history of the current game.  The game\r
6801    score is NOT assumed to start from the standard position. \r
6802    The display is not updated in any way.\r
6803    */\r
6804 void\r
6805 ParseGameHistory(game)\r
6806      char *game;\r
6807 {\r
6808     ChessMove moveType;\r
6809     int fromX, fromY, toX, toY, boardIndex;\r
6810     char promoChar;\r
6811     char *p, *q;\r
6812     char buf[MSG_SIZ];\r
6813 \r
6814     if (appData.debugMode)\r
6815       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6816 \r
6817     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6818     gameInfo.site = StrSave(appData.icsHost);\r
6819     gameInfo.date = PGNDate();\r
6820     gameInfo.round = StrSave("-");\r
6821 \r
6822     /* Parse out names of players */\r
6823     while (*game == ' ') game++;\r
6824     p = buf;\r
6825     while (*game != ' ') *p++ = *game++;\r
6826     *p = NULLCHAR;\r
6827     gameInfo.white = StrSave(buf);\r
6828     while (*game == ' ') game++;\r
6829     p = buf;\r
6830     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6831     *p = NULLCHAR;\r
6832     gameInfo.black = StrSave(buf);\r
6833 \r
6834     /* Parse moves */\r
6835     boardIndex = blackPlaysFirst ? 1 : 0;\r
6836     yynewstr(game);\r
6837     for (;;) {\r
6838         yyboardindex = boardIndex;\r
6839         moveType = (ChessMove) yylex();\r
6840         switch (moveType) {\r
6841           case IllegalMove:             /* maybe suicide chess, etc. */\r
6842   if (appData.debugMode) {\r
6843     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6844     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6845     setbuf(debugFP, NULL);\r
6846   }\r
6847           case WhitePromotionChancellor:\r
6848           case BlackPromotionChancellor:\r
6849           case WhitePromotionArchbishop:\r
6850           case BlackPromotionArchbishop:\r
6851           case WhitePromotionQueen:\r
6852           case BlackPromotionQueen:\r
6853           case WhitePromotionRook:\r
6854           case BlackPromotionRook:\r
6855           case WhitePromotionBishop:\r
6856           case BlackPromotionBishop:\r
6857           case WhitePromotionKnight:\r
6858           case BlackPromotionKnight:\r
6859           case WhitePromotionKing:\r
6860           case BlackPromotionKing:\r
6861           case NormalMove:\r
6862           case WhiteCapturesEnPassant:\r
6863           case BlackCapturesEnPassant:\r
6864           case WhiteKingSideCastle:\r
6865           case WhiteQueenSideCastle:\r
6866           case BlackKingSideCastle:\r
6867           case BlackQueenSideCastle:\r
6868           case WhiteKingSideCastleWild:\r
6869           case WhiteQueenSideCastleWild:\r
6870           case BlackKingSideCastleWild:\r
6871           case BlackQueenSideCastleWild:\r
6872           /* PUSH Fabien */\r
6873           case WhiteHSideCastleFR:\r
6874           case WhiteASideCastleFR:\r
6875           case BlackHSideCastleFR:\r
6876           case BlackASideCastleFR:\r
6877           /* POP Fabien */\r
6878             fromX = currentMoveString[0] - AAA;\r
6879             fromY = currentMoveString[1] - ONE;\r
6880             toX = currentMoveString[2] - AAA;\r
6881             toY = currentMoveString[3] - ONE;\r
6882             promoChar = currentMoveString[4];\r
6883             break;\r
6884           case WhiteDrop:\r
6885           case BlackDrop:\r
6886             fromX = moveType == WhiteDrop ?\r
6887               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6888             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6889             fromY = DROP_RANK;\r
6890             toX = currentMoveString[2] - AAA;\r
6891             toY = currentMoveString[3] - ONE;\r
6892             promoChar = NULLCHAR;\r
6893             break;\r
6894           case AmbiguousMove:\r
6895             /* bug? */\r
6896             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);\r
6897   if (appData.debugMode) {\r
6898     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
6899     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6900     setbuf(debugFP, NULL);\r
6901   }\r
6902             DisplayError(buf, 0);\r
6903             return;\r
6904           case ImpossibleMove:\r
6905             /* bug? */\r
6906             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);\r
6907   if (appData.debugMode) {\r
6908     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
6909     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6910     setbuf(debugFP, NULL);\r
6911   }\r
6912             DisplayError(buf, 0);\r
6913             return;\r
6914           case (ChessMove) 0:   /* end of file */\r
6915             if (boardIndex < backwardMostMove) {\r
6916                 /* Oops, gap.  How did that happen? */\r
6917                 DisplayError(_("Gap in move list"), 0);\r
6918                 return;\r
6919             }\r
6920             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
6921             if (boardIndex > forwardMostMove) {\r
6922                 forwardMostMove = boardIndex;\r
6923             }\r
6924             return;\r
6925           case ElapsedTime:\r
6926             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
6927                 strcat(parseList[boardIndex-1], " ");\r
6928                 strcat(parseList[boardIndex-1], yy_text);\r
6929             }\r
6930             continue;\r
6931           case Comment:\r
6932           case PGNTag:\r
6933           case NAG:\r
6934           default:\r
6935             /* ignore */\r
6936             continue;\r
6937           case WhiteWins:\r
6938           case BlackWins:\r
6939           case GameIsDrawn:\r
6940           case GameUnfinished:\r
6941             if (gameMode == IcsExamining) {\r
6942                 if (boardIndex < backwardMostMove) {\r
6943                     /* Oops, gap.  How did that happen? */\r
6944                     return;\r
6945                 }\r
6946                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6947                 return;\r
6948             }\r
6949             gameInfo.result = moveType;\r
6950             p = strchr(yy_text, '{');\r
6951             if (p == NULL) p = strchr(yy_text, '(');\r
6952             if (p == NULL) {\r
6953                 p = yy_text;\r
6954                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
6955             } else {\r
6956                 q = strchr(p, *p == '{' ? '}' : ')');\r
6957                 if (q != NULL) *q = NULLCHAR;\r
6958                 p++;\r
6959             }\r
6960             gameInfo.resultDetails = StrSave(p);\r
6961             continue;\r
6962         }\r
6963         if (boardIndex >= forwardMostMove &&\r
6964             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
6965             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6966             return;\r
6967         }\r
6968         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
6969                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
6970                                  parseList[boardIndex]);\r
6971         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
6972         /* currentMoveString is set as a side-effect of yylex */\r
6973         strcpy(moveList[boardIndex], currentMoveString);\r
6974         strcat(moveList[boardIndex], "\n");\r
6975         boardIndex++;\r
6976         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
6977         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
6978                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
6979           case MT_NONE:\r
6980           case MT_STALEMATE:\r
6981           default:\r
6982             break;\r
6983           case MT_CHECK:\r
6984             if(gameInfo.variant != VariantShogi)\r
6985                 strcat(parseList[boardIndex - 1], "+");\r
6986             break;\r
6987           case MT_CHECKMATE:\r
6988             strcat(parseList[boardIndex - 1], "#");\r
6989             break;\r
6990         }\r
6991     }\r
6992 }\r
6993 \r
6994 \r
6995 /* Apply a move to the given board  */\r
6996 void\r
6997 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
6998      int fromX, fromY, toX, toY;\r
6999      int promoChar;\r
7000      Board board;\r
7001 {\r
7002   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
7003 \r
7004     /* [HGM] compute & store e.p. status and castling rights for new position */\r
7005     /* if we are updating a board for which those exist (i.e. in boards[])    */\r
7006     if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)\r
7007     { int i;\r
7008 \r
7009       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
7010       oldEP = epStatus[p-1];\r
7011       epStatus[p] = EP_NONE;\r
7012 \r
7013       if( board[toY][toX] != EmptySquare ) \r
7014            epStatus[p] = EP_CAPTURE;  \r
7015 \r
7016       if( board[fromY][fromX] == WhitePawn ) {\r
7017            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7018                epStatus[p] = EP_PAWN_MOVE;\r
7019            if( toY-fromY==2) {\r
7020                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
7021                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7022                       epStatus[p] = toX | berolina;\r
7023                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
7024                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7025                       epStatus[p] = toX;\r
7026            }\r
7027       } else \r
7028       if( board[fromY][fromX] == BlackPawn ) {\r
7029            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7030                epStatus[p] = EP_PAWN_MOVE; \r
7031            if( toY-fromY== -2) {\r
7032                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
7033                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7034                       epStatus[p] = toX | berolina;\r
7035                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
7036                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7037                       epStatus[p] = toX;\r
7038            }\r
7039        }\r
7040 \r
7041        for(i=0; i<nrCastlingRights; i++) {\r
7042            castlingRights[p][i] = castlingRights[p-1][i];\r
7043            if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||\r
7044               castlingRights[p][i] == toX   && castlingRank[i] == toY   \r
7045              ) castlingRights[p][i] = -1; // revoke for moved or captured piece\r
7046        }\r
7047 \r
7048     }\r
7049 \r
7050   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
7051   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
7052        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
7053          \r
7054   if (fromX == toX && fromY == toY) return;\r
7055 \r
7056   if (fromY == DROP_RANK) {\r
7057         /* must be first */\r
7058         piece = board[toY][toX] = (ChessSquare) fromX;\r
7059   } else {\r
7060      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
7061      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
7062      if(gameInfo.variant == VariantKnightmate)\r
7063          king += (int) WhiteUnicorn - (int) WhiteKing;\r
7064 \r
7065     /* Code added by Tord: */\r
7066     /* FRC castling assumed when king captures friendly rook. */\r
7067     if (board[fromY][fromX] == WhiteKing &&\r
7068              board[toY][toX] == WhiteRook) {\r
7069       board[fromY][fromX] = EmptySquare;\r
7070       board[toY][toX] = EmptySquare;\r
7071       if(toX > fromX) {\r
7072         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
7073       } else {\r
7074         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
7075       }\r
7076     } else if (board[fromY][fromX] == BlackKing &&\r
7077                board[toY][toX] == BlackRook) {\r
7078       board[fromY][fromX] = EmptySquare;\r
7079       board[toY][toX] = EmptySquare;\r
7080       if(toX > fromX) {\r
7081         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
7082       } else {\r
7083         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
7084       }\r
7085     /* End of code added by Tord */\r
7086 \r
7087     } else if (board[fromY][fromX] == king\r
7088         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7089         && toY == fromY && toX > fromX+1) {\r
7090         board[fromY][fromX] = EmptySquare;\r
7091         board[toY][toX] = king;\r
7092         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7093         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7094     } else if (board[fromY][fromX] == king\r
7095         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7096                && toY == fromY && toX < fromX-1) {\r
7097         board[fromY][fromX] = EmptySquare;\r
7098         board[toY][toX] = king;\r
7099         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7100         board[fromY][BOARD_LEFT] = EmptySquare;\r
7101     } else if (board[fromY][fromX] == WhitePawn\r
7102                && toY == BOARD_HEIGHT-1\r
7103                && gameInfo.variant != VariantXiangqi\r
7104                ) {\r
7105         /* white pawn promotion */\r
7106         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
7107         if (board[toY][toX] == EmptySquare) {\r
7108             board[toY][toX] = WhiteQueen;\r
7109         }\r
7110         if(gameInfo.variant==VariantBughouse ||\r
7111            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7112             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7113         board[fromY][fromX] = EmptySquare;\r
7114     } else if ((fromY == BOARD_HEIGHT-4)\r
7115                && (toX != fromX)\r
7116                && gameInfo.variant != VariantXiangqi\r
7117                && gameInfo.variant != VariantBerolina\r
7118                && (board[fromY][fromX] == WhitePawn)\r
7119                && (board[toY][toX] == EmptySquare)) {\r
7120         board[fromY][fromX] = EmptySquare;\r
7121         board[toY][toX] = WhitePawn;\r
7122         captured = board[toY - 1][toX];\r
7123         board[toY - 1][toX] = EmptySquare;\r
7124     } else if ((fromY == BOARD_HEIGHT-4)\r
7125                && (toX == fromX)\r
7126                && gameInfo.variant == VariantBerolina\r
7127                && (board[fromY][fromX] == WhitePawn)\r
7128                && (board[toY][toX] == EmptySquare)) {\r
7129         board[fromY][fromX] = EmptySquare;\r
7130         board[toY][toX] = WhitePawn;\r
7131         if(oldEP & EP_BEROLIN_A) {\r
7132                 captured = board[fromY][fromX-1];\r
7133                 board[fromY][fromX-1] = EmptySquare;\r
7134         }else{  captured = board[fromY][fromX+1];\r
7135                 board[fromY][fromX+1] = EmptySquare;\r
7136         }\r
7137     } else if (board[fromY][fromX] == king\r
7138         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7139                && toY == fromY && toX > fromX+1) {\r
7140         board[fromY][fromX] = EmptySquare;\r
7141         board[toY][toX] = king;\r
7142         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7143         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7144     } else if (board[fromY][fromX] == king\r
7145         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7146                && toY == fromY && toX < fromX-1) {\r
7147         board[fromY][fromX] = EmptySquare;\r
7148         board[toY][toX] = king;\r
7149         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7150         board[fromY][BOARD_LEFT] = EmptySquare;\r
7151     } else if (fromY == 7 && fromX == 3\r
7152                && board[fromY][fromX] == BlackKing\r
7153                && toY == 7 && toX == 5) {\r
7154         board[fromY][fromX] = EmptySquare;\r
7155         board[toY][toX] = BlackKing;\r
7156         board[fromY][7] = EmptySquare;\r
7157         board[toY][4] = BlackRook;\r
7158     } else if (fromY == 7 && fromX == 3\r
7159                && board[fromY][fromX] == BlackKing\r
7160                && toY == 7 && toX == 1) {\r
7161         board[fromY][fromX] = EmptySquare;\r
7162         board[toY][toX] = BlackKing;\r
7163         board[fromY][0] = EmptySquare;\r
7164         board[toY][2] = BlackRook;\r
7165     } else if (board[fromY][fromX] == BlackPawn\r
7166                && toY == 0\r
7167                && gameInfo.variant != VariantXiangqi\r
7168                ) {\r
7169         /* black pawn promotion */\r
7170         board[0][toX] = CharToPiece(ToLower(promoChar));\r
7171         if (board[0][toX] == EmptySquare) {\r
7172             board[0][toX] = BlackQueen;\r
7173         }\r
7174         if(gameInfo.variant==VariantBughouse ||\r
7175            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7176             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7177         board[fromY][fromX] = EmptySquare;\r
7178     } else if ((fromY == 3)\r
7179                && (toX != fromX)\r
7180                && gameInfo.variant != VariantXiangqi\r
7181                && gameInfo.variant != VariantBerolina\r
7182                && (board[fromY][fromX] == BlackPawn)\r
7183                && (board[toY][toX] == EmptySquare)) {\r
7184         board[fromY][fromX] = EmptySquare;\r
7185         board[toY][toX] = BlackPawn;\r
7186         captured = board[toY + 1][toX];\r
7187         board[toY + 1][toX] = EmptySquare;\r
7188     } else if ((fromY == 3)\r
7189                && (toX == fromX)\r
7190                && gameInfo.variant == VariantBerolina\r
7191                && (board[fromY][fromX] == BlackPawn)\r
7192                && (board[toY][toX] == EmptySquare)) {\r
7193         board[fromY][fromX] = EmptySquare;\r
7194         board[toY][toX] = BlackPawn;\r
7195         if(oldEP & EP_BEROLIN_A) {\r
7196                 captured = board[fromY][fromX-1];\r
7197                 board[fromY][fromX-1] = EmptySquare;\r
7198         }else{  captured = board[fromY][fromX+1];\r
7199                 board[fromY][fromX+1] = EmptySquare;\r
7200         }\r
7201     } else {\r
7202         board[toY][toX] = board[fromY][fromX];\r
7203         board[fromY][fromX] = EmptySquare;\r
7204     }\r
7205 \r
7206     /* [HGM] now we promote for Shogi, if needed */\r
7207     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
7208         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7209   }\r
7210 \r
7211     if (gameInfo.holdingsWidth != 0) {\r
7212 \r
7213       /* !!A lot more code needs to be written to support holdings  */\r
7214       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
7215       /* penultimate board files, so they are automaticlly stored   */\r
7216       /* in the game history.                                       */\r
7217       if (fromY == DROP_RANK) {\r
7218         /* Delete from holdings, by decreasing count */\r
7219         /* and erasing image if necessary            */\r
7220         p = (int) fromX;\r
7221         if(p < (int) BlackPawn) { /* white drop */\r
7222              p -= (int)WhitePawn;\r
7223              if(p >= gameInfo.holdingsSize) p = 0;\r
7224              if(--board[p][BOARD_WIDTH-2] == 0)\r
7225                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
7226         } else {                  /* black drop */\r
7227              p -= (int)BlackPawn;\r
7228              if(p >= gameInfo.holdingsSize) p = 0;\r
7229              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
7230                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
7231         }\r
7232       }\r
7233       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
7234           && gameInfo.variant != VariantBughouse        ) {\r
7235         /* [HGM] holdings: Add to holdings, if holdings exist */\r
7236         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { \r
7237                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
7238                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
7239         }\r
7240         p = (int) captured;\r
7241         if (p >= (int) BlackPawn) {\r
7242           p -= (int)BlackPawn;\r
7243           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7244                   /* in Shogi restore piece to its original  first */\r
7245                   captured = (ChessSquare) (DEMOTED captured);\r
7246                   p = DEMOTED p;\r
7247           }\r
7248           p = PieceToNumber((ChessSquare)p);\r
7249           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
7250           board[p][BOARD_WIDTH-2]++;\r
7251           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;\r
7252         } else {\r
7253           p -= (int)WhitePawn;\r
7254           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7255                   captured = (ChessSquare) (DEMOTED captured);\r
7256                   p = DEMOTED p;\r
7257           }\r
7258           p = PieceToNumber((ChessSquare)p);\r
7259           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
7260           board[BOARD_HEIGHT-1-p][1]++;\r
7261           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;\r
7262         }\r
7263       }\r
7264 \r
7265     } else if (gameInfo.variant == VariantAtomic) {\r
7266       if (captured != EmptySquare) {\r
7267         int y, x;\r
7268         for (y = toY-1; y <= toY+1; y++) {\r
7269           for (x = toX-1; x <= toX+1; x++) {\r
7270             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
7271                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
7272               board[y][x] = EmptySquare;\r
7273             }\r
7274           }\r
7275         }\r
7276         board[toY][toX] = EmptySquare;\r
7277       }\r
7278     }\r
7279     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
7280         /* [HGM] Shogi promotions */\r
7281         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7282     }\r
7283 \r
7284 }\r
7285 \r
7286 /* Updates forwardMostMove */\r
7287 void\r
7288 MakeMove(fromX, fromY, toX, toY, promoChar)\r
7289      int fromX, fromY, toX, toY;\r
7290      int promoChar;\r
7291 {\r
7292 //    forwardMostMove++; // [HGM] bare: moved downstream\r
7293 \r
7294     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */\r
7295         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
7296         piece = boards[forwardMostMove][fromY][fromX];\r
7297         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
7298         if(gameInfo.variant == VariantKnightmate)\r
7299             king += (int) WhiteUnicorn - (int) WhiteKing;\r
7300         if(forwardMostMove == 0) {\r
7301             if(blackPlaysFirst) \r
7302                 fprintf(serverMoves, "%s;", second.tidy);\r
7303             fprintf(serverMoves, "%s;", first.tidy);\r
7304             if(!blackPlaysFirst) \r
7305                 fprintf(serverMoves, "%s;", second.tidy);\r
7306         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
7307         lastLoadFlag = loadFlag;\r
7308         // print base move\r
7309         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
7310         // print castling suffix\r
7311         if( toY == fromY && piece == king ) {\r
7312             if(toX-fromX > 1)\r
7313                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
7314             if(fromX-toX >1)\r
7315                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
7316         }\r
7317         // e.p. suffix\r
7318         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||\r
7319              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&\r
7320              boards[forwardMostMove][toY][toX] == EmptySquare\r
7321              && fromX != toX )\r
7322                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
7323         // promotion suffix\r
7324         if(promoChar != NULLCHAR)\r
7325                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
7326         if(!loadFlag) {\r
7327             fprintf(serverMoves, "/%d/%d",\r
7328                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);\r
7329             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;\r
7330             else                      timeLeft = blackTimeRemaining/1000;\r
7331             fprintf(serverMoves, "/%d", timeLeft);\r
7332         }\r
7333         fflush(serverMoves);\r
7334     }\r
7335 \r
7336     if (forwardMostMove+1 >= MAX_MOVES) {\r
7337       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
7338                         0, 1);\r
7339       return;\r
7340     }\r
7341     SwitchClocks();\r
7342     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;\r
7343     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;\r
7344     if (commentList[forwardMostMove+1] != NULL) {\r
7345         free(commentList[forwardMostMove+1]);\r
7346         commentList[forwardMostMove+1] = NULL;\r
7347     }\r
7348     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);\r
7349     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);\r
7350     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board\r
7351     gameInfo.result = GameUnfinished;\r
7352     if (gameInfo.resultDetails != NULL) {\r
7353         free(gameInfo.resultDetails);\r
7354         gameInfo.resultDetails = NULL;\r
7355     }\r
7356     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
7357                               moveList[forwardMostMove - 1]);\r
7358     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
7359                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
7360                              fromY, fromX, toY, toX, promoChar,\r
7361                              parseList[forwardMostMove - 1]);\r
7362     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
7363                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
7364                             castlingRights[forwardMostMove]) ) {\r
7365       case MT_NONE:\r
7366       case MT_STALEMATE:\r
7367       default:\r
7368         break;\r
7369       case MT_CHECK:\r
7370         if(gameInfo.variant != VariantShogi)\r
7371             strcat(parseList[forwardMostMove - 1], "+");\r
7372         break;\r
7373       case MT_CHECKMATE:\r
7374         strcat(parseList[forwardMostMove - 1], "#");\r
7375         break;\r
7376     }\r
7377     if (appData.debugMode) {\r
7378         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
7379     }\r
7380 \r
7381 }\r
7382 \r
7383 /* Updates currentMove if not pausing */\r
7384 void\r
7385 ShowMove(fromX, fromY, toX, toY)\r
7386 {\r
7387     int instant = (gameMode == PlayFromGameFile) ?\r
7388         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
7389     if(appData.noGUI) return;\r
7390     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
7391         if (!instant) {\r
7392             if (forwardMostMove == currentMove + 1) {\r
7393                 AnimateMove(boards[forwardMostMove - 1],\r
7394                             fromX, fromY, toX, toY);\r
7395             }\r
7396             if (appData.highlightLastMove) {\r
7397                 SetHighlights(fromX, fromY, toX, toY);\r
7398             }\r
7399         }\r
7400         currentMove = forwardMostMove;\r
7401     }\r
7402 \r
7403     if (instant) return;\r
7404 \r
7405     DisplayMove(currentMove - 1);\r
7406     DrawPosition(FALSE, boards[currentMove]);\r
7407     DisplayBothClocks();\r
7408     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
7409 }\r
7410 \r
7411 void SendEgtPath(ChessProgramState *cps)\r
7412 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */\r
7413         char buf[MSG_SIZ], name[MSG_SIZ], *p;\r
7414 \r
7415         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;\r
7416 \r
7417         while(*p) {\r
7418             char c, *q = name+1, *r, *s;\r
7419 \r
7420             name[0] = ','; // extract next format name from feature and copy with prefixed ','\r
7421             while(*p && *p != ',') *q++ = *p++;\r
7422             *q++ = ':'; *q = 0;\r
7423             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && \r
7424                 strcmp(name, ",nalimov:") == 0 ) {\r
7425                 // take nalimov path from the menu-changeable option first, if it is defined\r
7426                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);\r
7427                 SendToProgram(buf,cps);     // send egtbpath command for nalimov\r
7428             } else\r
7429             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||\r
7430                 (s = StrStr(appData.egtFormats, name)) != NULL) {\r
7431                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma\r
7432                 s = r = StrStr(s, ":") + 1; // beginning of path info\r
7433                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string\r
7434                 c = *r; *r = 0;             // temporarily null-terminate path info\r
7435                     *--q = 0;               // strip of trailig ':' from name\r
7436                     sprintf(buf, "egtbpath %s %s\n", name+1, s);\r
7437                 *r = c;\r
7438                 SendToProgram(buf,cps);     // send egtbpath command for this format\r
7439             }\r
7440             if(*p == ',') p++; // read away comma to position for next format name\r
7441         }\r
7442 }\r
7443 \r
7444 void\r
7445 InitChessProgram(cps, setup)\r
7446      ChessProgramState *cps;\r
7447      int setup; /* [HGM] needed to setup FRC opening position */\r
7448 {\r
7449     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
7450     if (appData.noChessProgram) return;\r
7451     hintRequested = FALSE;\r
7452     bookRequested = FALSE;\r
7453 \r
7454     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */\r
7455     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */\r
7456     if(cps->memSize) { /* [HGM] memory */\r
7457         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);\r
7458         SendToProgram(buf, cps);\r
7459     }\r
7460     SendEgtPath(cps); /* [HGM] EGT */\r
7461     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */\r
7462         sprintf(buf, "cores %d\n", appData.smpCores);\r
7463         SendToProgram(buf, cps);\r
7464     }\r
7465 \r
7466     SendToProgram(cps->initString, cps);\r
7467     if (gameInfo.variant != VariantNormal &&\r
7468         gameInfo.variant != VariantLoadable\r
7469         /* [HGM] also send variant if board size non-standard */\r
7470         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
7471                                             ) {\r
7472       char *v = VariantName(gameInfo.variant);\r
7473       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
7474         /* [HGM] in protocol 1 we have to assume all variants valid */\r
7475         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);\r
7476         DisplayFatalError(buf, 0, 1);\r
7477         return;\r
7478       }\r
7479 \r
7480       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
7481       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7482       if( gameInfo.variant == VariantXiangqi )\r
7483            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
7484       if( gameInfo.variant == VariantShogi )\r
7485            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
7486       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
7487            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
7488       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
7489                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
7490            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7491       if( gameInfo.variant == VariantCourier )\r
7492            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7493       if( gameInfo.variant == VariantSuper )\r
7494            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7495       if( gameInfo.variant == VariantGreat )\r
7496            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7497 \r
7498       if(overruled) {\r
7499            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
7500                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
7501            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
7502            if(StrStr(cps->variants, b) == NULL) { \r
7503                // specific sized variant not known, check if general sizing allowed\r
7504                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
7505                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
7506                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
7507                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
7508                        DisplayFatalError(buf, 0, 1);\r
7509                        return;\r
7510                    }\r
7511                    /* [HGM] here we really should compare with the maximum supported board size */\r
7512                }\r
7513            }\r
7514       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
7515       sprintf(buf, "variant %s\n", b);\r
7516       SendToProgram(buf, cps);\r
7517     }\r
7518     currentlyInitializedVariant = gameInfo.variant;\r
7519 \r
7520     /* [HGM] send opening position in FRC to first engine */\r
7521     if(setup) {\r
7522           SendToProgram("force\n", cps);\r
7523           SendBoard(cps, 0);\r
7524           /* engine is now in force mode! Set flag to wake it up after first move. */\r
7525           setboardSpoiledMachineBlack = 1;\r
7526     }\r
7527 \r
7528     if (cps->sendICS) {\r
7529       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
7530       SendToProgram(buf, cps);\r
7531     }\r
7532     cps->maybeThinking = FALSE;\r
7533     cps->offeredDraw = 0;\r
7534     if (!appData.icsActive) {\r
7535         SendTimeControl(cps, movesPerSession, timeControl,\r
7536                         timeIncrement, appData.searchDepth,\r
7537                         searchTime);\r
7538     }\r
7539     if (appData.showThinking \r
7540         // [HGM] thinking: four options require thinking output to be sent\r
7541         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
7542                                 ) {\r
7543         SendToProgram("post\n", cps);\r
7544     }\r
7545     SendToProgram("hard\n", cps);\r
7546     if (!appData.ponderNextMove) {\r
7547         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
7548            it without being sure what state we are in first.  "hard"\r
7549            is not a toggle, so that one is OK.\r
7550          */\r
7551         SendToProgram("easy\n", cps);\r
7552     }\r
7553     if (cps->usePing) {\r
7554       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
7555       SendToProgram(buf, cps);\r
7556     }\r
7557     cps->initDone = TRUE;\r
7558 }   \r
7559 \r
7560 \r
7561 void\r
7562 StartChessProgram(cps)\r
7563      ChessProgramState *cps;\r
7564 {\r
7565     char buf[MSG_SIZ];\r
7566     int err;\r
7567 \r
7568     if (appData.noChessProgram) return;\r
7569     cps->initDone = FALSE;\r
7570 \r
7571     if (strcmp(cps->host, "localhost") == 0) {\r
7572         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
7573     } else if (*appData.remoteShell == NULLCHAR) {\r
7574         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
7575     } else {\r
7576         if (*appData.remoteUser == NULLCHAR) {\r
7577             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
7578                     cps->program);\r
7579         } else {\r
7580             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
7581                     cps->host, appData.remoteUser, cps->program);\r
7582         }\r
7583         err = StartChildProcess(buf, "", &cps->pr);\r
7584     }\r
7585     \r
7586     if (err != 0) {\r
7587         sprintf(buf, _("Startup failure on '%s'"), cps->program);\r
7588         DisplayFatalError(buf, err, 1);\r
7589         cps->pr = NoProc;\r
7590         cps->isr = NULL;\r
7591         return;\r
7592     }\r
7593     \r
7594     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
7595     if (cps->protocolVersion > 1) {\r
7596       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
7597       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options\r
7598       cps->comboCnt = 0;  //                and values of combo boxes\r
7599       SendToProgram(buf, cps);\r
7600     } else {\r
7601       SendToProgram("xboard\n", cps);\r
7602     }\r
7603 }\r
7604 \r
7605 \r
7606 void\r
7607 TwoMachinesEventIfReady P((void))\r
7608 {\r
7609   if (first.lastPing != first.lastPong) {\r
7610     DisplayMessage("", _("Waiting for first chess program"));\r
7611     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7612     return;\r
7613   }\r
7614   if (second.lastPing != second.lastPong) {\r
7615     DisplayMessage("", _("Waiting for second chess program"));\r
7616     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7617     return;\r
7618   }\r
7619   ThawUI();\r
7620   TwoMachinesEvent();\r
7621 }\r
7622 \r
7623 void\r
7624 NextMatchGame P((void))\r
7625 {\r
7626     int index; /* [HGM] autoinc: step lod index during match */\r
7627     Reset(FALSE, TRUE);\r
7628     if (*appData.loadGameFile != NULLCHAR) {\r
7629         index = appData.loadGameIndex;\r
7630         if(index < 0) { // [HGM] autoinc\r
7631             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7632             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7633         } \r
7634         LoadGameFromFile(appData.loadGameFile,\r
7635                          index,\r
7636                          appData.loadGameFile, FALSE);\r
7637     } else if (*appData.loadPositionFile != NULLCHAR) {\r
7638         index = appData.loadPositionIndex;\r
7639         if(index < 0) { // [HGM] autoinc\r
7640             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7641             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7642         } \r
7643         LoadPositionFromFile(appData.loadPositionFile,\r
7644                              index,\r
7645                              appData.loadPositionFile);\r
7646     }\r
7647     TwoMachinesEventIfReady();\r
7648 }\r
7649 \r
7650 void UserAdjudicationEvent( int result )\r
7651 {\r
7652     ChessMove gameResult = GameIsDrawn;\r
7653 \r
7654     if( result > 0 ) {\r
7655         gameResult = WhiteWins;\r
7656     }\r
7657     else if( result < 0 ) {\r
7658         gameResult = BlackWins;\r
7659     }\r
7660 \r
7661     if( gameMode == TwoMachinesPlay ) {\r
7662         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
7663     }\r
7664 }\r
7665 \r
7666 \r
7667 void\r
7668 GameEnds(result, resultDetails, whosays)\r
7669      ChessMove result;\r
7670      char *resultDetails;\r
7671      int whosays;\r
7672 {\r
7673     GameMode nextGameMode;\r
7674     int isIcsGame;\r
7675     char buf[MSG_SIZ];\r
7676 \r
7677     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
7678     endingGame = 1;\r
7679 \r
7680     if (appData.debugMode) {\r
7681       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
7682               result, resultDetails ? resultDetails : "(null)", whosays);\r
7683     }\r
7684 \r
7685     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
7686         /* If we are playing on ICS, the server decides when the\r
7687            game is over, but the engine can offer to draw, claim \r
7688            a draw, or resign. \r
7689          */\r
7690 #if ZIPPY\r
7691         if (appData.zippyPlay && first.initDone) {\r
7692             if (result == GameIsDrawn) {\r
7693                 /* In case draw still needs to be claimed */\r
7694                 SendToICS(ics_prefix);\r
7695                 SendToICS("draw\n");\r
7696             } else if (StrCaseStr(resultDetails, "resign")) {\r
7697                 SendToICS(ics_prefix);\r
7698                 SendToICS("resign\n");\r
7699             }\r
7700         }\r
7701 #endif\r
7702         endingGame = 0; /* [HGM] crash */\r
7703         return;\r
7704     }\r
7705 \r
7706     /* If we're loading the game from a file, stop */\r
7707     if (whosays == GE_FILE) {\r
7708       (void) StopLoadGameTimer();\r
7709       gameFileFP = NULL;\r
7710     }\r
7711 \r
7712     /* Cancel draw offers */\r
7713     first.offeredDraw = second.offeredDraw = 0;\r
7714 \r
7715     /* If this is an ICS game, only ICS can really say it's done;\r
7716        if not, anyone can. */\r
7717     isIcsGame = (gameMode == IcsPlayingWhite || \r
7718                  gameMode == IcsPlayingBlack || \r
7719                  gameMode == IcsObserving    || \r
7720                  gameMode == IcsExamining);\r
7721 \r
7722     if (!isIcsGame || whosays == GE_ICS) {\r
7723         /* OK -- not an ICS game, or ICS said it was done */\r
7724         StopClocks();\r
7725         if (!isIcsGame && !appData.noChessProgram) \r
7726           SetUserThinkingEnables();\r
7727     \r
7728         /* [HGM] if a machine claims the game end we verify this claim */\r
7729         if(gameMode == TwoMachinesPlay && appData.testClaims) {\r
7730             if(appData.testLegality && whosays >= GE_ENGINE1 ) {\r
7731                 char claimer;\r
7732                 ChessMove trueResult = (ChessMove) -1;\r
7733 \r
7734                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
7735                                             first.twoMachinesColor[0] :\r
7736                                             second.twoMachinesColor[0] ;\r
7737 \r
7738                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first\r
7739                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {\r
7740                     /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7741                     trueResult = WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers)\r
7742                         ? BlackWins : WhiteWins; // [HGM] losers: reverse the result in VariantLosers!\r
7743                 } else\r
7744                 if(epStatus[forwardMostMove] == EP_STALEMATE) {\r
7745                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE\r
7746                     if(gameInfo.variant == VariantGiveaway || gameInfo.variant == VariantSuicide || \r
7747                        gameInfo.variant == VariantLosers)  // [HGM] losers: in giveaway variants stalemate wins\r
7748                         trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;\r
7749                 }\r
7750 \r
7751                 // now verify win claims, but not in drop games, as we don't understand those yet\r
7752                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper\r
7753                                                  || gameInfo.variant == VariantGreat) &&\r
7754                     (result == WhiteWins && claimer == 'w' ||\r
7755                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win\r
7756                       if (appData.debugMode) {\r
7757                         fprintf(debugFP, "result=%d sp=%d move=%d\n",\r
7758                                 result, epStatus[forwardMostMove], forwardMostMove);\r
7759                       }\r
7760                       if(result != trueResult) {\r
7761                               sprintf(buf, "False win claim: '%s'", resultDetails);\r
7762                               result = claimer == 'w' ? BlackWins : WhiteWins;\r
7763                               resultDetails = buf;\r
7764                       }\r
7765                 } else\r
7766                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
7767                     && (forwardMostMove <= backwardMostMove ||\r
7768                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
7769                         (claimer=='b')==(forwardMostMove&1))\r
7770                                                                                   ) {\r
7771                       /* [HGM] verify: draws that were not flagged are false claims */\r
7772                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
7773                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7774                       resultDetails = buf;\r
7775                 }\r
7776                 /* (Claiming a loss is accepted no questions asked!) */\r
7777             }\r
7778             /* [HGM] bare: don't allow bare King to win */\r
7779             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)\r
7780                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway \r
7781                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...\r
7782                && result != GameIsDrawn)\r
7783             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
7784                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
7785                         int p = (int)boards[forwardMostMove][i][j] - color;\r
7786                         if(p >= 0 && p <= (int)WhiteKing) k++;\r
7787                 }\r
7788                 if (appData.debugMode) {\r
7789                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",\r
7790                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);\r
7791                 }\r
7792                 if(k <= 1) {\r
7793                         result = GameIsDrawn;\r
7794                         sprintf(buf, "%s but bare king", resultDetails);\r
7795                         resultDetails = buf;\r
7796                 }\r
7797             }\r
7798         }\r
7799 \r
7800 \r
7801         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
7802             if(result==WhiteWins) c = '+';\r
7803             if(result==BlackWins) c = '-';\r
7804             if(resultDetails != NULL)\r
7805                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
7806         }\r
7807         if (resultDetails != NULL) {\r
7808             gameInfo.result = result;\r
7809             gameInfo.resultDetails = StrSave(resultDetails);\r
7810 \r
7811             /* display last move only if game was not loaded from file */\r
7812             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7813                 DisplayMove(currentMove - 1);\r
7814     \r
7815             if (forwardMostMove != 0) {\r
7816                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7817                     if (*appData.saveGameFile != NULLCHAR) {\r
7818                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7819                     } else if (appData.autoSaveGames) {\r
7820                         AutoSaveGame();\r
7821                     }\r
7822                     if (*appData.savePositionFile != NULLCHAR) {\r
7823                         SavePositionToFile(appData.savePositionFile);\r
7824                     }\r
7825                 }\r
7826             }\r
7827 \r
7828             /* Tell program how game ended in case it is learning */\r
7829             /* [HGM] Moved this to after saving the PGN, just in case */\r
7830             /* engine died and we got here through time loss. In that */\r
7831             /* case we will get a fatal error writing the pipe, which */\r
7832             /* would otherwise lose us the PGN.                       */\r
7833             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7834             /* output during GameEnds should never be fatal anymore   */\r
7835             if (gameMode == MachinePlaysWhite ||\r
7836                 gameMode == MachinePlaysBlack ||\r
7837                 gameMode == TwoMachinesPlay ||\r
7838                 gameMode == IcsPlayingWhite ||\r
7839                 gameMode == IcsPlayingBlack ||\r
7840                 gameMode == BeginningOfGame) {\r
7841                 char buf[MSG_SIZ];\r
7842                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7843                         resultDetails);\r
7844                 if (first.pr != NoProc) {\r
7845                     SendToProgram(buf, &first);\r
7846                 }\r
7847                 if (second.pr != NoProc &&\r
7848                     gameMode == TwoMachinesPlay) {\r
7849                     SendToProgram(buf, &second);\r
7850                 }\r
7851             }\r
7852         }\r
7853 \r
7854         if (appData.icsActive) {\r
7855             if (appData.quietPlay &&\r
7856                 (gameMode == IcsPlayingWhite ||\r
7857                  gameMode == IcsPlayingBlack)) {\r
7858                 SendToICS(ics_prefix);\r
7859                 SendToICS("set shout 1\n");\r
7860             }\r
7861             nextGameMode = IcsIdle;\r
7862             ics_user_moved = FALSE;\r
7863             /* clean up premove.  It's ugly when the game has ended and the\r
7864              * premove highlights are still on the board.\r
7865              */\r
7866             if (gotPremove) {\r
7867               gotPremove = FALSE;\r
7868               ClearPremoveHighlights();\r
7869               DrawPosition(FALSE, boards[currentMove]);\r
7870             }\r
7871             if (whosays == GE_ICS) {\r
7872                 switch (result) {\r
7873                 case WhiteWins:\r
7874                     if (gameMode == IcsPlayingWhite)\r
7875                         PlayIcsWinSound();\r
7876                     else if(gameMode == IcsPlayingBlack)\r
7877                         PlayIcsLossSound();\r
7878                     break;\r
7879                 case BlackWins:\r
7880                     if (gameMode == IcsPlayingBlack)\r
7881                         PlayIcsWinSound();\r
7882                     else if(gameMode == IcsPlayingWhite)\r
7883                         PlayIcsLossSound();\r
7884                     break;\r
7885                 case GameIsDrawn:\r
7886                     PlayIcsDrawSound();\r
7887                     break;\r
7888                 default:\r
7889                     PlayIcsUnfinishedSound();\r
7890                 }\r
7891             }\r
7892         } else if (gameMode == EditGame ||\r
7893                    gameMode == PlayFromGameFile || \r
7894                    gameMode == AnalyzeMode || \r
7895                    gameMode == AnalyzeFile) {\r
7896             nextGameMode = gameMode;\r
7897         } else {\r
7898             nextGameMode = EndOfGame;\r
7899         }\r
7900         pausing = FALSE;\r
7901         ModeHighlight();\r
7902     } else {\r
7903         nextGameMode = gameMode;\r
7904     }\r
7905 \r
7906     if (appData.noChessProgram) {\r
7907         gameMode = nextGameMode;\r
7908         ModeHighlight();\r
7909         endingGame = 0; /* [HGM] crash */\r
7910         return;\r
7911     }\r
7912 \r
7913     if (first.reuse) {\r
7914         /* Put first chess program into idle state */\r
7915         if (first.pr != NoProc &&\r
7916             (gameMode == MachinePlaysWhite ||\r
7917              gameMode == MachinePlaysBlack ||\r
7918              gameMode == TwoMachinesPlay ||\r
7919              gameMode == IcsPlayingWhite ||\r
7920              gameMode == IcsPlayingBlack ||\r
7921              gameMode == BeginningOfGame)) {\r
7922             SendToProgram("force\n", &first);\r
7923             if (first.usePing) {\r
7924               char buf[MSG_SIZ];\r
7925               sprintf(buf, "ping %d\n", ++first.lastPing);\r
7926               SendToProgram(buf, &first);\r
7927             }\r
7928         }\r
7929     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7930         /* Kill off first chess program */\r
7931         if (first.isr != NULL)\r
7932           RemoveInputSource(first.isr);\r
7933         first.isr = NULL;\r
7934     \r
7935         if (first.pr != NoProc) {\r
7936             ExitAnalyzeMode();\r
7937             DoSleep( appData.delayBeforeQuit );\r
7938             SendToProgram("quit\n", &first);\r
7939             DoSleep( appData.delayAfterQuit );\r
7940             DestroyChildProcess(first.pr, first.useSigterm);\r
7941         }\r
7942         first.pr = NoProc;\r
7943     }\r
7944     if (second.reuse) {\r
7945         /* Put second chess program into idle state */\r
7946         if (second.pr != NoProc &&\r
7947             gameMode == TwoMachinesPlay) {\r
7948             SendToProgram("force\n", &second);\r
7949             if (second.usePing) {\r
7950               char buf[MSG_SIZ];\r
7951               sprintf(buf, "ping %d\n", ++second.lastPing);\r
7952               SendToProgram(buf, &second);\r
7953             }\r
7954         }\r
7955     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7956         /* Kill off second chess program */\r
7957         if (second.isr != NULL)\r
7958           RemoveInputSource(second.isr);\r
7959         second.isr = NULL;\r
7960     \r
7961         if (second.pr != NoProc) {\r
7962             DoSleep( appData.delayBeforeQuit );\r
7963             SendToProgram("quit\n", &second);\r
7964             DoSleep( appData.delayAfterQuit );\r
7965             DestroyChildProcess(second.pr, second.useSigterm);\r
7966         }\r
7967         second.pr = NoProc;\r
7968     }\r
7969 \r
7970     if (matchMode && gameMode == TwoMachinesPlay) {\r
7971         switch (result) {\r
7972         case WhiteWins:\r
7973           if (first.twoMachinesColor[0] == 'w') {\r
7974             first.matchWins++;\r
7975           } else {\r
7976             second.matchWins++;\r
7977           }\r
7978           break;\r
7979         case BlackWins:\r
7980           if (first.twoMachinesColor[0] == 'b') {\r
7981             first.matchWins++;\r
7982           } else {\r
7983             second.matchWins++;\r
7984           }\r
7985           break;\r
7986         default:\r
7987           break;\r
7988         }\r
7989         if (matchGame < appData.matchGames) {\r
7990             char *tmp;\r
7991             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */\r
7992                 tmp = first.twoMachinesColor;\r
7993                 first.twoMachinesColor = second.twoMachinesColor;\r
7994                 second.twoMachinesColor = tmp;\r
7995             }\r
7996             gameMode = nextGameMode;\r
7997             matchGame++;\r
7998             if(appData.matchPause>10000 || appData.matchPause<10)\r
7999                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
8000             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
8001             endingGame = 0; /* [HGM] crash */\r
8002             return;\r
8003         } else {\r
8004             char buf[MSG_SIZ];\r
8005             gameMode = nextGameMode;\r
8006             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),\r
8007                     first.tidy, second.tidy,\r
8008                     first.matchWins, second.matchWins,\r
8009                     appData.matchGames - (first.matchWins + second.matchWins));\r
8010             DisplayFatalError(buf, 0, 0);\r
8011         }\r
8012     }\r
8013     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
8014         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
8015       ExitAnalyzeMode();\r
8016     gameMode = nextGameMode;\r
8017     ModeHighlight();\r
8018     endingGame = 0;  /* [HGM] crash */\r
8019 }\r
8020 \r
8021 /* Assumes program was just initialized (initString sent).\r
8022    Leaves program in force mode. */\r
8023 void\r
8024 FeedMovesToProgram(cps, upto) \r
8025      ChessProgramState *cps;\r
8026      int upto;\r
8027 {\r
8028     int i;\r
8029     \r
8030     if (appData.debugMode)\r
8031       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
8032               startedFromSetupPosition ? "position and " : "",\r
8033               backwardMostMove, upto, cps->which);\r
8034     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
8035         // [HGM] variantswitch: make engine aware of new variant\r
8036         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
8037                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
8038         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
8039         SendToProgram(buf, cps);\r
8040         currentlyInitializedVariant = gameInfo.variant;\r
8041     }\r
8042     SendToProgram("force\n", cps);\r
8043     if (startedFromSetupPosition) {\r
8044         SendBoard(cps, backwardMostMove);\r
8045     if (appData.debugMode) {\r
8046         fprintf(debugFP, "feedMoves\n");\r
8047     }\r
8048     }\r
8049     for (i = backwardMostMove; i < upto; i++) {\r
8050         SendMoveToProgram(i, cps);\r
8051     }\r
8052 }\r
8053 \r
8054 \r
8055 void\r
8056 ResurrectChessProgram()\r
8057 {\r
8058      /* The chess program may have exited.\r
8059         If so, restart it and feed it all the moves made so far. */\r
8060 \r
8061     if (appData.noChessProgram || first.pr != NoProc) return;\r
8062     \r
8063     StartChessProgram(&first);\r
8064     InitChessProgram(&first, FALSE);\r
8065     FeedMovesToProgram(&first, currentMove);\r
8066 \r
8067     if (!first.sendTime) {\r
8068         /* can't tell gnuchess what its clock should read,\r
8069            so we bow to its notion. */\r
8070         ResetClocks();\r
8071         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
8072         timeRemaining[1][currentMove] = blackTimeRemaining;\r
8073     }\r
8074 \r
8075     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||\r
8076                 appData.icsEngineAnalyze) && first.analysisSupport) {\r
8077       SendToProgram("analyze\n", &first);\r
8078       first.analyzing = TRUE;\r
8079     }\r
8080 }\r
8081 \r
8082 /*\r
8083  * Button procedures\r
8084  */\r
8085 void\r
8086 Reset(redraw, init)\r
8087      int redraw, init;\r
8088 {\r
8089     int i;\r
8090 \r
8091     if (appData.debugMode) {\r
8092         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
8093                 redraw, init, gameMode);\r
8094     }\r
8095     pausing = pauseExamInvalid = FALSE;\r
8096     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
8097     firstMove = TRUE;\r
8098     whiteFlag = blackFlag = FALSE;\r
8099     userOfferedDraw = FALSE;\r
8100     hintRequested = bookRequested = FALSE;\r
8101     first.maybeThinking = FALSE;\r
8102     second.maybeThinking = FALSE;\r
8103     first.bookSuspend = FALSE; // [HGM] book\r
8104     second.bookSuspend = FALSE;\r
8105     thinkOutput[0] = NULLCHAR;\r
8106     lastHint[0] = NULLCHAR;\r
8107     ClearGameInfo(&gameInfo);\r
8108     gameInfo.variant = StringToVariant(appData.variant);\r
8109     ics_user_moved = ics_clock_paused = FALSE;\r
8110     ics_getting_history = H_FALSE;\r
8111     ics_gamenum = -1;\r
8112     white_holding[0] = black_holding[0] = NULLCHAR;\r
8113     ClearProgramStats();\r
8114     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode\r
8115     \r
8116     ResetFrontEnd();\r
8117     ClearHighlights();\r
8118     flipView = appData.flipView;\r
8119     ClearPremoveHighlights();\r
8120     gotPremove = FALSE;\r
8121     alarmSounded = FALSE;\r
8122 \r
8123     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8124     if(appData.serverMovesName != NULL) {\r
8125         /* [HGM] prepare to make moves file for broadcasting */\r
8126         clock_t t = clock();\r
8127         if(serverMoves != NULL) fclose(serverMoves);\r
8128         serverMoves = fopen(appData.serverMovesName, "r");\r
8129         if(serverMoves != NULL) {\r
8130             fclose(serverMoves);\r
8131             /* delay 15 sec before overwriting, so all clients can see end */\r
8132             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
8133         }\r
8134         serverMoves = fopen(appData.serverMovesName, "w");\r
8135     }\r
8136 \r
8137     ExitAnalyzeMode();\r
8138     gameMode = BeginningOfGame;\r
8139     ModeHighlight();\r
8140     if(appData.icsActive) gameInfo.variant = VariantNormal;\r
8141     InitPosition(redraw);\r
8142     for (i = 0; i < MAX_MOVES; i++) {\r
8143         if (commentList[i] != NULL) {\r
8144             free(commentList[i]);\r
8145             commentList[i] = NULL;\r
8146         }\r
8147     }\r
8148     ResetClocks();\r
8149     timeRemaining[0][0] = whiteTimeRemaining;\r
8150     timeRemaining[1][0] = blackTimeRemaining;\r
8151     if (first.pr == NULL) {\r
8152         StartChessProgram(&first);\r
8153     }\r
8154     if (init) {\r
8155             InitChessProgram(&first, startedFromSetupPosition);\r
8156     }\r
8157     DisplayTitle("");\r
8158     DisplayMessage("", "");\r
8159     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8160 }\r
8161 \r
8162 void\r
8163 AutoPlayGameLoop()\r
8164 {\r
8165     for (;;) {\r
8166         if (!AutoPlayOneMove())\r
8167           return;\r
8168         if (matchMode || appData.timeDelay == 0)\r
8169           continue;\r
8170         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
8171           return;\r
8172         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
8173         break;\r
8174     }\r
8175 }\r
8176 \r
8177 \r
8178 int\r
8179 AutoPlayOneMove()\r
8180 {\r
8181     int fromX, fromY, toX, toY;\r
8182 \r
8183     if (appData.debugMode) {\r
8184       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
8185     }\r
8186 \r
8187     if (gameMode != PlayFromGameFile)\r
8188       return FALSE;\r
8189 \r
8190     if (currentMove >= forwardMostMove) {\r
8191       gameMode = EditGame;\r
8192       ModeHighlight();\r
8193 \r
8194       /* [AS] Clear current move marker at the end of a game */\r
8195       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
8196 \r
8197       return FALSE;\r
8198     }\r
8199     \r
8200     toX = moveList[currentMove][2] - AAA;\r
8201     toY = moveList[currentMove][3] - ONE;\r
8202 \r
8203     if (moveList[currentMove][1] == '@') {\r
8204         if (appData.highlightLastMove) {\r
8205             SetHighlights(-1, -1, toX, toY);\r
8206         }\r
8207     } else {\r
8208         fromX = moveList[currentMove][0] - AAA;\r
8209         fromY = moveList[currentMove][1] - ONE;\r
8210 \r
8211         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
8212 \r
8213         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
8214 \r
8215         if (appData.highlightLastMove) {\r
8216             SetHighlights(fromX, fromY, toX, toY);\r
8217         }\r
8218     }\r
8219     DisplayMove(currentMove);\r
8220     SendMoveToProgram(currentMove++, &first);\r
8221     DisplayBothClocks();\r
8222     DrawPosition(FALSE, boards[currentMove]);\r
8223     // [HGM] PV info: always display, routine tests if empty\r
8224     DisplayComment(currentMove - 1, commentList[currentMove]);\r
8225     return TRUE;\r
8226 }\r
8227 \r
8228 \r
8229 int\r
8230 LoadGameOneMove(readAhead)\r
8231      ChessMove readAhead;\r
8232 {\r
8233     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
8234     char promoChar = NULLCHAR;\r
8235     ChessMove moveType;\r
8236     char move[MSG_SIZ];\r
8237     char *p, *q;\r
8238     \r
8239     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
8240         gameMode != AnalyzeMode && gameMode != Training) {\r
8241         gameFileFP = NULL;\r
8242         return FALSE;\r
8243     }\r
8244     \r
8245     yyboardindex = forwardMostMove;\r
8246     if (readAhead != (ChessMove)0) {\r
8247       moveType = readAhead;\r
8248     } else {\r
8249       if (gameFileFP == NULL)\r
8250           return FALSE;\r
8251       moveType = (ChessMove) yylex();\r
8252     }\r
8253     \r
8254     done = FALSE;\r
8255     switch (moveType) {\r
8256       case Comment:\r
8257         if (appData.debugMode) \r
8258           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8259         p = yy_text;\r
8260         if (*p == '{' || *p == '[' || *p == '(') {\r
8261             p[strlen(p) - 1] = NULLCHAR;\r
8262             p++;\r
8263         }\r
8264 \r
8265         /* append the comment but don't display it */\r
8266         while (*p == '\n') p++;\r
8267         AppendComment(currentMove, p);\r
8268         return TRUE;\r
8269 \r
8270       case WhiteCapturesEnPassant:\r
8271       case BlackCapturesEnPassant:\r
8272       case WhitePromotionChancellor:\r
8273       case BlackPromotionChancellor:\r
8274       case WhitePromotionArchbishop:\r
8275       case BlackPromotionArchbishop:\r
8276       case WhitePromotionCentaur:\r
8277       case BlackPromotionCentaur:\r
8278       case WhitePromotionQueen:\r
8279       case BlackPromotionQueen:\r
8280       case WhitePromotionRook:\r
8281       case BlackPromotionRook:\r
8282       case WhitePromotionBishop:\r
8283       case BlackPromotionBishop:\r
8284       case WhitePromotionKnight:\r
8285       case BlackPromotionKnight:\r
8286       case WhitePromotionKing:\r
8287       case BlackPromotionKing:\r
8288       case NormalMove:\r
8289       case WhiteKingSideCastle:\r
8290       case WhiteQueenSideCastle:\r
8291       case BlackKingSideCastle:\r
8292       case BlackQueenSideCastle:\r
8293       case WhiteKingSideCastleWild:\r
8294       case WhiteQueenSideCastleWild:\r
8295       case BlackKingSideCastleWild:\r
8296       case BlackQueenSideCastleWild:\r
8297       /* PUSH Fabien */\r
8298       case WhiteHSideCastleFR:\r
8299       case WhiteASideCastleFR:\r
8300       case BlackHSideCastleFR:\r
8301       case BlackASideCastleFR:\r
8302       /* POP Fabien */\r
8303         if (appData.debugMode)\r
8304           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8305         fromX = currentMoveString[0] - AAA;\r
8306         fromY = currentMoveString[1] - ONE;\r
8307         toX = currentMoveString[2] - AAA;\r
8308         toY = currentMoveString[3] - ONE;\r
8309         promoChar = currentMoveString[4];\r
8310         break;\r
8311 \r
8312       case WhiteDrop:\r
8313       case BlackDrop:\r
8314         if (appData.debugMode)\r
8315           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8316         fromX = moveType == WhiteDrop ?\r
8317           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
8318         (int) CharToPiece(ToLower(currentMoveString[0]));\r
8319         fromY = DROP_RANK;\r
8320         toX = currentMoveString[2] - AAA;\r
8321         toY = currentMoveString[3] - ONE;\r
8322         break;\r
8323 \r
8324       case WhiteWins:\r
8325       case BlackWins:\r
8326       case GameIsDrawn:\r
8327       case GameUnfinished:\r
8328         if (appData.debugMode)\r
8329           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
8330         p = strchr(yy_text, '{');\r
8331         if (p == NULL) p = strchr(yy_text, '(');\r
8332         if (p == NULL) {\r
8333             p = yy_text;\r
8334             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
8335         } else {\r
8336             q = strchr(p, *p == '{' ? '}' : ')');\r
8337             if (q != NULL) *q = NULLCHAR;\r
8338             p++;\r
8339         }\r
8340         GameEnds(moveType, p, GE_FILE);\r
8341         done = TRUE;\r
8342         if (cmailMsgLoaded) {\r
8343             ClearHighlights();\r
8344             flipView = WhiteOnMove(currentMove);\r
8345             if (moveType == GameUnfinished) flipView = !flipView;\r
8346             if (appData.debugMode)\r
8347               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
8348         }\r
8349         break;\r
8350 \r
8351       case (ChessMove) 0:       /* end of file */\r
8352         if (appData.debugMode)\r
8353           fprintf(debugFP, "Parser hit end of file\n");\r
8354         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8355                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8356           case MT_NONE:\r
8357           case MT_CHECK:\r
8358             break;\r
8359           case MT_CHECKMATE:\r
8360             if (WhiteOnMove(currentMove)) {\r
8361                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8362             } else {\r
8363                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8364             }\r
8365             break;\r
8366           case MT_STALEMATE:\r
8367             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8368             break;\r
8369         }\r
8370         done = TRUE;\r
8371         break;\r
8372 \r
8373       case MoveNumberOne:\r
8374         if (lastLoadGameStart == GNUChessGame) {\r
8375             /* GNUChessGames have numbers, but they aren't move numbers */\r
8376             if (appData.debugMode)\r
8377               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8378                       yy_text, (int) moveType);\r
8379             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8380         }\r
8381         /* else fall thru */\r
8382 \r
8383       case XBoardGame:\r
8384       case GNUChessGame:\r
8385       case PGNTag:\r
8386         /* Reached start of next game in file */\r
8387         if (appData.debugMode)\r
8388           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
8389         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8390                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8391           case MT_NONE:\r
8392           case MT_CHECK:\r
8393             break;\r
8394           case MT_CHECKMATE:\r
8395             if (WhiteOnMove(currentMove)) {\r
8396                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8397             } else {\r
8398                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8399             }\r
8400             break;\r
8401           case MT_STALEMATE:\r
8402             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8403             break;\r
8404         }\r
8405         done = TRUE;\r
8406         break;\r
8407 \r
8408       case PositionDiagram:     /* should not happen; ignore */\r
8409       case ElapsedTime:         /* ignore */\r
8410       case NAG:                 /* ignore */\r
8411         if (appData.debugMode)\r
8412           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8413                   yy_text, (int) moveType);\r
8414         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8415 \r
8416       case IllegalMove:\r
8417         if (appData.testLegality) {\r
8418             if (appData.debugMode)\r
8419               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
8420             sprintf(move, _("Illegal move: %d.%s%s"),\r
8421                     (forwardMostMove / 2) + 1,\r
8422                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8423             DisplayError(move, 0);\r
8424             done = TRUE;\r
8425         } else {\r
8426             if (appData.debugMode)\r
8427               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
8428                       yy_text, currentMoveString);\r
8429             fromX = currentMoveString[0] - AAA;\r
8430             fromY = currentMoveString[1] - ONE;\r
8431             toX = currentMoveString[2] - AAA;\r
8432             toY = currentMoveString[3] - ONE;\r
8433             promoChar = currentMoveString[4];\r
8434         }\r
8435         break;\r
8436 \r
8437       case AmbiguousMove:\r
8438         if (appData.debugMode)\r
8439           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
8440         sprintf(move, _("Ambiguous move: %d.%s%s"),\r
8441                 (forwardMostMove / 2) + 1,\r
8442                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8443         DisplayError(move, 0);\r
8444         done = TRUE;\r
8445         break;\r
8446 \r
8447       default:\r
8448       case ImpossibleMove:\r
8449         if (appData.debugMode)\r
8450           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);\r
8451         sprintf(move, _("Illegal move: %d.%s%s"),\r
8452                 (forwardMostMove / 2) + 1,\r
8453                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8454         DisplayError(move, 0);\r
8455         done = TRUE;\r
8456         break;\r
8457     }\r
8458 \r
8459     if (done) {\r
8460         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
8461             DrawPosition(FALSE, boards[currentMove]);\r
8462             DisplayBothClocks();\r
8463             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
8464               DisplayComment(currentMove - 1, commentList[currentMove]);\r
8465         }\r
8466         (void) StopLoadGameTimer();\r
8467         gameFileFP = NULL;\r
8468         cmailOldMove = forwardMostMove;\r
8469         return FALSE;\r
8470     } else {\r
8471         /* currentMoveString is set as a side-effect of yylex */\r
8472         strcat(currentMoveString, "\n");\r
8473         strcpy(moveList[forwardMostMove], currentMoveString);\r
8474         \r
8475         thinkOutput[0] = NULLCHAR;\r
8476         MakeMove(fromX, fromY, toX, toY, promoChar);\r
8477         currentMove = forwardMostMove;\r
8478         return TRUE;\r
8479     }\r
8480 }\r
8481 \r
8482 /* Load the nth game from the given file */\r
8483 int\r
8484 LoadGameFromFile(filename, n, title, useList)\r
8485      char *filename;\r
8486      int n;\r
8487      char *title;\r
8488      /*Boolean*/ int useList;\r
8489 {\r
8490     FILE *f;\r
8491     char buf[MSG_SIZ];\r
8492 \r
8493     if (strcmp(filename, "-") == 0) {\r
8494         f = stdin;\r
8495         title = "stdin";\r
8496     } else {\r
8497         f = fopen(filename, "rb");\r
8498         if (f == NULL) {\r
8499             sprintf(buf, _("Can't open \"%s\""), filename);\r
8500             DisplayError(buf, errno);\r
8501             return FALSE;\r
8502         }\r
8503     }\r
8504     if (fseek(f, 0, 0) == -1) {\r
8505         /* f is not seekable; probably a pipe */\r
8506         useList = FALSE;\r
8507     }\r
8508     if (useList && n == 0) {\r
8509         int error = GameListBuild(f);\r
8510         if (error) {\r
8511             DisplayError(_("Cannot build game list"), error);\r
8512         } else if (!ListEmpty(&gameList) &&\r
8513                    ((ListGame *) gameList.tailPred)->number > 1) {\r
8514             GameListPopUp(f, title);\r
8515             return TRUE;\r
8516         }\r
8517         GameListDestroy();\r
8518         n = 1;\r
8519     }\r
8520     if (n == 0) n = 1;\r
8521     return LoadGame(f, n, title, FALSE);\r
8522 }\r
8523 \r
8524 \r
8525 void\r
8526 MakeRegisteredMove()\r
8527 {\r
8528     int fromX, fromY, toX, toY;\r
8529     char promoChar;\r
8530     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8531         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
8532           case CMAIL_MOVE:\r
8533           case CMAIL_DRAW:\r
8534             if (appData.debugMode)\r
8535               fprintf(debugFP, "Restoring %s for game %d\n",\r
8536                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8537     \r
8538             thinkOutput[0] = NULLCHAR;\r
8539             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
8540             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
8541             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
8542             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
8543             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
8544             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
8545             MakeMove(fromX, fromY, toX, toY, promoChar);\r
8546             ShowMove(fromX, fromY, toX, toY);\r
8547               \r
8548             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8549                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8550               case MT_NONE:\r
8551               case MT_CHECK:\r
8552                 break;\r
8553                 \r
8554               case MT_CHECKMATE:\r
8555                 if (WhiteOnMove(currentMove)) {\r
8556                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
8557                 } else {\r
8558                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
8559                 }\r
8560                 break;\r
8561                 \r
8562               case MT_STALEMATE:\r
8563                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
8564                 break;\r
8565             }\r
8566 \r
8567             break;\r
8568             \r
8569           case CMAIL_RESIGN:\r
8570             if (WhiteOnMove(currentMove)) {\r
8571                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
8572             } else {\r
8573                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
8574             }\r
8575             break;\r
8576             \r
8577           case CMAIL_ACCEPT:\r
8578             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
8579             break;\r
8580               \r
8581           default:\r
8582             break;\r
8583         }\r
8584     }\r
8585 \r
8586     return;\r
8587 }\r
8588 \r
8589 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
8590 int\r
8591 CmailLoadGame(f, gameNumber, title, useList)\r
8592      FILE *f;\r
8593      int gameNumber;\r
8594      char *title;\r
8595      int useList;\r
8596 {\r
8597     int retVal;\r
8598 \r
8599     if (gameNumber > nCmailGames) {\r
8600         DisplayError(_("No more games in this message"), 0);\r
8601         return FALSE;\r
8602     }\r
8603     if (f == lastLoadGameFP) {\r
8604         int offset = gameNumber - lastLoadGameNumber;\r
8605         if (offset == 0) {\r
8606             cmailMsg[0] = NULLCHAR;\r
8607             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8608                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8609                 nCmailMovesRegistered--;\r
8610             }\r
8611             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
8612             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
8613                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
8614             }\r
8615         } else {\r
8616             if (! RegisterMove()) return FALSE;\r
8617         }\r
8618     }\r
8619 \r
8620     retVal = LoadGame(f, gameNumber, title, useList);\r
8621 \r
8622     /* Make move registered during previous look at this game, if any */\r
8623     MakeRegisteredMove();\r
8624 \r
8625     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
8626         commentList[currentMove]\r
8627           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
8628         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8629     }\r
8630 \r
8631     return retVal;\r
8632 }\r
8633 \r
8634 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
8635 int\r
8636 ReloadGame(offset)\r
8637      int offset;\r
8638 {\r
8639     int gameNumber = lastLoadGameNumber + offset;\r
8640     if (lastLoadGameFP == NULL) {\r
8641         DisplayError(_("No game has been loaded yet"), 0);\r
8642         return FALSE;\r
8643     }\r
8644     if (gameNumber <= 0) {\r
8645         DisplayError(_("Can't back up any further"), 0);\r
8646         return FALSE;\r
8647     }\r
8648     if (cmailMsgLoaded) {\r
8649         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
8650                              lastLoadGameTitle, lastLoadGameUseList);\r
8651     } else {\r
8652         return LoadGame(lastLoadGameFP, gameNumber,\r
8653                         lastLoadGameTitle, lastLoadGameUseList);\r
8654     }\r
8655 }\r
8656 \r
8657 \r
8658 \r
8659 /* Load the nth game from open file f */\r
8660 int\r
8661 LoadGame(f, gameNumber, title, useList)\r
8662      FILE *f;\r
8663      int gameNumber;\r
8664      char *title;\r
8665      int useList;\r
8666 {\r
8667     ChessMove cm;\r
8668     char buf[MSG_SIZ];\r
8669     int gn = gameNumber;\r
8670     ListGame *lg = NULL;\r
8671     int numPGNTags = 0;\r
8672     int err;\r
8673     GameMode oldGameMode;\r
8674     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
8675 \r
8676     if (appData.debugMode) \r
8677         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
8678 \r
8679     if (gameMode == Training )\r
8680         SetTrainingModeOff();\r
8681 \r
8682     oldGameMode = gameMode;\r
8683     if (gameMode != BeginningOfGame) {\r
8684       Reset(FALSE, TRUE);\r
8685     }\r
8686 \r
8687     gameFileFP = f;\r
8688     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
8689         fclose(lastLoadGameFP);\r
8690     }\r
8691 \r
8692     if (useList) {\r
8693         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
8694         \r
8695         if (lg) {\r
8696             fseek(f, lg->offset, 0);\r
8697             GameListHighlight(gameNumber);\r
8698             gn = 1;\r
8699         }\r
8700         else {\r
8701             DisplayError(_("Game number out of range"), 0);\r
8702             return FALSE;\r
8703         }\r
8704     } else {\r
8705         GameListDestroy();\r
8706         if (fseek(f, 0, 0) == -1) {\r
8707             if (f == lastLoadGameFP ?\r
8708                 gameNumber == lastLoadGameNumber + 1 :\r
8709                 gameNumber == 1) {\r
8710                 gn = 1;\r
8711             } else {\r
8712                 DisplayError(_("Can't seek on game file"), 0);\r
8713                 return FALSE;\r
8714             }\r
8715         }\r
8716     }\r
8717     lastLoadGameFP = f;\r
8718     lastLoadGameNumber = gameNumber;\r
8719     strcpy(lastLoadGameTitle, title);\r
8720     lastLoadGameUseList = useList;\r
8721 \r
8722     yynewfile(f);\r
8723 \r
8724     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
8725         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
8726                 lg->gameInfo.black);\r
8727             DisplayTitle(buf);\r
8728     } else if (*title != NULLCHAR) {\r
8729         if (gameNumber > 1) {\r
8730             sprintf(buf, "%s %d", title, gameNumber);\r
8731             DisplayTitle(buf);\r
8732         } else {\r
8733             DisplayTitle(title);\r
8734         }\r
8735     }\r
8736 \r
8737     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
8738         gameMode = PlayFromGameFile;\r
8739         ModeHighlight();\r
8740     }\r
8741 \r
8742     currentMove = forwardMostMove = backwardMostMove = 0;\r
8743     CopyBoard(boards[0], initialPosition);\r
8744     StopClocks();\r
8745 \r
8746     /*\r
8747      * Skip the first gn-1 games in the file.\r
8748      * Also skip over anything that precedes an identifiable \r
8749      * start of game marker, to avoid being confused by \r
8750      * garbage at the start of the file.  Currently \r
8751      * recognized start of game markers are the move number "1",\r
8752      * the pattern "gnuchess .* game", the pattern\r
8753      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
8754      * A game that starts with one of the latter two patterns\r
8755      * will also have a move number 1, possibly\r
8756      * following a position diagram.\r
8757      * 5-4-02: Let's try being more lenient and allowing a game to\r
8758      * start with an unnumbered move.  Does that break anything?\r
8759      */\r
8760     cm = lastLoadGameStart = (ChessMove) 0;\r
8761     while (gn > 0) {\r
8762         yyboardindex = forwardMostMove;\r
8763         cm = (ChessMove) yylex();\r
8764         switch (cm) {\r
8765           case (ChessMove) 0:\r
8766             if (cmailMsgLoaded) {\r
8767                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
8768             } else {\r
8769                 Reset(TRUE, TRUE);\r
8770                 DisplayError(_("Game not found in file"), 0);\r
8771             }\r
8772             return FALSE;\r
8773 \r
8774           case GNUChessGame:\r
8775           case XBoardGame:\r
8776             gn--;\r
8777             lastLoadGameStart = cm;\r
8778             break;\r
8779             \r
8780           case MoveNumberOne:\r
8781             switch (lastLoadGameStart) {\r
8782               case GNUChessGame:\r
8783               case XBoardGame:\r
8784               case PGNTag:\r
8785                 break;\r
8786               case MoveNumberOne:\r
8787               case (ChessMove) 0:\r
8788                 gn--;           /* count this game */\r
8789                 lastLoadGameStart = cm;\r
8790                 break;\r
8791               default:\r
8792                 /* impossible */\r
8793                 break;\r
8794             }\r
8795             break;\r
8796 \r
8797           case PGNTag:\r
8798             switch (lastLoadGameStart) {\r
8799               case GNUChessGame:\r
8800               case PGNTag:\r
8801               case MoveNumberOne:\r
8802               case (ChessMove) 0:\r
8803                 gn--;           /* count this game */\r
8804                 lastLoadGameStart = cm;\r
8805                 break;\r
8806               case XBoardGame:\r
8807                 lastLoadGameStart = cm; /* game counted already */\r
8808                 break;\r
8809               default:\r
8810                 /* impossible */\r
8811                 break;\r
8812             }\r
8813             if (gn > 0) {\r
8814                 do {\r
8815                     yyboardindex = forwardMostMove;\r
8816                     cm = (ChessMove) yylex();\r
8817                 } while (cm == PGNTag || cm == Comment);\r
8818             }\r
8819             break;\r
8820 \r
8821           case WhiteWins:\r
8822           case BlackWins:\r
8823           case GameIsDrawn:\r
8824             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8825                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8826                     != CMAIL_OLD_RESULT) {\r
8827                     nCmailResults ++ ;\r
8828                     cmailResult[  CMAIL_MAX_GAMES\r
8829                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8830                 }\r
8831             }\r
8832             break;\r
8833 \r
8834           case NormalMove:\r
8835             /* Only a NormalMove can be at the start of a game\r
8836              * without a position diagram. */\r
8837             if (lastLoadGameStart == (ChessMove) 0) {\r
8838               gn--;\r
8839               lastLoadGameStart = MoveNumberOne;\r
8840             }\r
8841             break;\r
8842 \r
8843           default:\r
8844             break;\r
8845         }\r
8846     }\r
8847     \r
8848     if (appData.debugMode)\r
8849       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8850 \r
8851     if (cm == XBoardGame) {\r
8852         /* Skip any header junk before position diagram and/or move 1 */\r
8853         for (;;) {\r
8854             yyboardindex = forwardMostMove;\r
8855             cm = (ChessMove) yylex();\r
8856 \r
8857             if (cm == (ChessMove) 0 ||\r
8858                 cm == GNUChessGame || cm == XBoardGame) {\r
8859                 /* Empty game; pretend end-of-file and handle later */\r
8860                 cm = (ChessMove) 0;\r
8861                 break;\r
8862             }\r
8863 \r
8864             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8865                 cm == PGNTag || cm == Comment)\r
8866               break;\r
8867         }\r
8868     } else if (cm == GNUChessGame) {\r
8869         if (gameInfo.event != NULL) {\r
8870             free(gameInfo.event);\r
8871         }\r
8872         gameInfo.event = StrSave(yy_text);\r
8873     }   \r
8874 \r
8875     startedFromSetupPosition = FALSE;\r
8876     while (cm == PGNTag) {\r
8877         if (appData.debugMode) \r
8878           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8879         err = ParsePGNTag(yy_text, &gameInfo);\r
8880         if (!err) numPGNTags++;\r
8881 \r
8882         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8883         if(gameInfo.variant != oldVariant) {\r
8884             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8885             InitPosition(TRUE);\r
8886             oldVariant = gameInfo.variant;\r
8887             if (appData.debugMode) \r
8888               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8889         }\r
8890 \r
8891 \r
8892         if (gameInfo.fen != NULL) {\r
8893           Board initial_position;\r
8894           startedFromSetupPosition = TRUE;\r
8895           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8896             Reset(TRUE, TRUE);\r
8897             DisplayError(_("Bad FEN position in file"), 0);\r
8898             return FALSE;\r
8899           }\r
8900           CopyBoard(boards[0], initial_position);\r
8901           if (blackPlaysFirst) {\r
8902             currentMove = forwardMostMove = backwardMostMove = 1;\r
8903             CopyBoard(boards[1], initial_position);\r
8904             strcpy(moveList[0], "");\r
8905             strcpy(parseList[0], "");\r
8906             timeRemaining[0][1] = whiteTimeRemaining;\r
8907             timeRemaining[1][1] = blackTimeRemaining;\r
8908             if (commentList[0] != NULL) {\r
8909               commentList[1] = commentList[0];\r
8910               commentList[0] = NULL;\r
8911             }\r
8912           } else {\r
8913             currentMove = forwardMostMove = backwardMostMove = 0;\r
8914           }\r
8915           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */\r
8916           {   int i;\r
8917               initialRulePlies = FENrulePlies;\r
8918               epStatus[forwardMostMove] = FENepStatus;\r
8919               for( i=0; i< nrCastlingRights; i++ )\r
8920                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
8921           }\r
8922           yyboardindex = forwardMostMove;\r
8923           free(gameInfo.fen);\r
8924           gameInfo.fen = NULL;\r
8925         }\r
8926 \r
8927         yyboardindex = forwardMostMove;\r
8928         cm = (ChessMove) yylex();\r
8929 \r
8930         /* Handle comments interspersed among the tags */\r
8931         while (cm == Comment) {\r
8932             char *p;\r
8933             if (appData.debugMode) \r
8934               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8935             p = yy_text;\r
8936             if (*p == '{' || *p == '[' || *p == '(') {\r
8937                 p[strlen(p) - 1] = NULLCHAR;\r
8938                 p++;\r
8939             }\r
8940             while (*p == '\n') p++;\r
8941             AppendComment(currentMove, p);\r
8942             yyboardindex = forwardMostMove;\r
8943             cm = (ChessMove) yylex();\r
8944         }\r
8945     }\r
8946 \r
8947     /* don't rely on existence of Event tag since if game was\r
8948      * pasted from clipboard the Event tag may not exist\r
8949      */\r
8950     if (numPGNTags > 0){\r
8951         char *tags;\r
8952         if (gameInfo.variant == VariantNormal) {\r
8953           gameInfo.variant = StringToVariant(gameInfo.event);\r
8954         }\r
8955         if (!matchMode) {\r
8956           if( appData.autoDisplayTags ) {\r
8957             tags = PGNTags(&gameInfo);\r
8958             TagsPopUp(tags, CmailMsg());\r
8959             free(tags);\r
8960           }\r
8961         }\r
8962     } else {\r
8963         /* Make something up, but don't display it now */\r
8964         SetGameInfo();\r
8965         TagsPopDown();\r
8966     }\r
8967 \r
8968     if (cm == PositionDiagram) {\r
8969         int i, j;\r
8970         char *p;\r
8971         Board initial_position;\r
8972 \r
8973         if (appData.debugMode)\r
8974           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
8975 \r
8976         if (!startedFromSetupPosition) {\r
8977             p = yy_text;\r
8978             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
8979               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
8980                 switch (*p) {\r
8981                   case '[':\r
8982                   case '-':\r
8983                   case ' ':\r
8984                   case '\t':\r
8985                   case '\n':\r
8986                   case '\r':\r
8987                     break;\r
8988                   default:\r
8989                     initial_position[i][j++] = CharToPiece(*p);\r
8990                     break;\r
8991                 }\r
8992             while (*p == ' ' || *p == '\t' ||\r
8993                    *p == '\n' || *p == '\r') p++;\r
8994         \r
8995             if (strncmp(p, "black", strlen("black"))==0)\r
8996               blackPlaysFirst = TRUE;\r
8997             else\r
8998               blackPlaysFirst = FALSE;\r
8999             startedFromSetupPosition = TRUE;\r
9000         \r
9001             CopyBoard(boards[0], initial_position);\r
9002             if (blackPlaysFirst) {\r
9003                 currentMove = forwardMostMove = backwardMostMove = 1;\r
9004                 CopyBoard(boards[1], initial_position);\r
9005                 strcpy(moveList[0], "");\r
9006                 strcpy(parseList[0], "");\r
9007                 timeRemaining[0][1] = whiteTimeRemaining;\r
9008                 timeRemaining[1][1] = blackTimeRemaining;\r
9009                 if (commentList[0] != NULL) {\r
9010                     commentList[1] = commentList[0];\r
9011                     commentList[0] = NULL;\r
9012                 }\r
9013             } else {\r
9014                 currentMove = forwardMostMove = backwardMostMove = 0;\r
9015             }\r
9016         }\r
9017         yyboardindex = forwardMostMove;\r
9018         cm = (ChessMove) yylex();\r
9019     }\r
9020 \r
9021     if (first.pr == NoProc) {\r
9022         StartChessProgram(&first);\r
9023     }\r
9024     InitChessProgram(&first, FALSE);\r
9025     SendToProgram("force\n", &first);\r
9026     if (startedFromSetupPosition) {\r
9027         SendBoard(&first, forwardMostMove);\r
9028     if (appData.debugMode) {\r
9029         fprintf(debugFP, "Load Game\n");\r
9030     }\r
9031         DisplayBothClocks();\r
9032     }      \r
9033 \r
9034     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
9035     loadFlag = appData.suppressLoadMoves;\r
9036 \r
9037     while (cm == Comment) {\r
9038         char *p;\r
9039         if (appData.debugMode) \r
9040           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
9041         p = yy_text;\r
9042         if (*p == '{' || *p == '[' || *p == '(') {\r
9043             p[strlen(p) - 1] = NULLCHAR;\r
9044             p++;\r
9045         }\r
9046         while (*p == '\n') p++;\r
9047         AppendComment(currentMove, p);\r
9048         yyboardindex = forwardMostMove;\r
9049         cm = (ChessMove) yylex();\r
9050     }\r
9051 \r
9052     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
9053         cm == WhiteWins || cm == BlackWins ||\r
9054         cm == GameIsDrawn || cm == GameUnfinished) {\r
9055         DisplayMessage("", _("No moves in game"));\r
9056         if (cmailMsgLoaded) {\r
9057             if (appData.debugMode)\r
9058               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
9059             ClearHighlights();\r
9060             flipView = FALSE;\r
9061         }\r
9062         DrawPosition(FALSE, boards[currentMove]);\r
9063         DisplayBothClocks();\r
9064         gameMode = EditGame;\r
9065         ModeHighlight();\r
9066         gameFileFP = NULL;\r
9067         cmailOldMove = 0;\r
9068         return TRUE;\r
9069     }\r
9070 \r
9071     // [HGM] PV info: routine tests if comment empty\r
9072     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
9073         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9074     }\r
9075     if (!matchMode && appData.timeDelay != 0) \r
9076       DrawPosition(FALSE, boards[currentMove]);\r
9077 \r
9078     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
9079       programStats.ok_to_send = 1;\r
9080     }\r
9081 \r
9082     /* if the first token after the PGN tags is a move\r
9083      * and not move number 1, retrieve it from the parser \r
9084      */\r
9085     if (cm != MoveNumberOne)\r
9086         LoadGameOneMove(cm);\r
9087 \r
9088     /* load the remaining moves from the file */\r
9089     while (LoadGameOneMove((ChessMove)0)) {\r
9090       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9091       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9092     }\r
9093 \r
9094     /* rewind to the start of the game */\r
9095     currentMove = backwardMostMove;\r
9096 \r
9097     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9098 \r
9099     if (oldGameMode == AnalyzeFile ||\r
9100         oldGameMode == AnalyzeMode) {\r
9101       AnalyzeFileEvent();\r
9102     }\r
9103 \r
9104     if (matchMode || appData.timeDelay == 0) {\r
9105       ToEndEvent();\r
9106       gameMode = EditGame;\r
9107       ModeHighlight();\r
9108     } else if (appData.timeDelay > 0) {\r
9109       AutoPlayGameLoop();\r
9110     }\r
9111 \r
9112     if (appData.debugMode) \r
9113         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
9114 \r
9115     loadFlag = 0; /* [HGM] true game starts */\r
9116     return TRUE;\r
9117 }\r
9118 \r
9119 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
9120 int\r
9121 ReloadPosition(offset)\r
9122      int offset;\r
9123 {\r
9124     int positionNumber = lastLoadPositionNumber + offset;\r
9125     if (lastLoadPositionFP == NULL) {\r
9126         DisplayError(_("No position has been loaded yet"), 0);\r
9127         return FALSE;\r
9128     }\r
9129     if (positionNumber <= 0) {\r
9130         DisplayError(_("Can't back up any further"), 0);\r
9131         return FALSE;\r
9132     }\r
9133     return LoadPosition(lastLoadPositionFP, positionNumber,\r
9134                         lastLoadPositionTitle);\r
9135 }\r
9136 \r
9137 /* Load the nth position from the given file */\r
9138 int\r
9139 LoadPositionFromFile(filename, n, title)\r
9140      char *filename;\r
9141      int n;\r
9142      char *title;\r
9143 {\r
9144     FILE *f;\r
9145     char buf[MSG_SIZ];\r
9146 \r
9147     if (strcmp(filename, "-") == 0) {\r
9148         return LoadPosition(stdin, n, "stdin");\r
9149     } else {\r
9150         f = fopen(filename, "rb");\r
9151         if (f == NULL) {\r
9152             sprintf(buf, _("Can't open \"%s\""), filename);\r
9153             DisplayError(buf, errno);\r
9154             return FALSE;\r
9155         } else {\r
9156             return LoadPosition(f, n, title);\r
9157         }\r
9158     }\r
9159 }\r
9160 \r
9161 /* Load the nth position from the given open file, and close it */\r
9162 int\r
9163 LoadPosition(f, positionNumber, title)\r
9164      FILE *f;\r
9165      int positionNumber;\r
9166      char *title;\r
9167 {\r
9168     char *p, line[MSG_SIZ];\r
9169     Board initial_position;\r
9170     int i, j, fenMode, pn;\r
9171     \r
9172     if (gameMode == Training )\r
9173         SetTrainingModeOff();\r
9174 \r
9175     if (gameMode != BeginningOfGame) {\r
9176         Reset(FALSE, TRUE);\r
9177     }\r
9178     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
9179         fclose(lastLoadPositionFP);\r
9180     }\r
9181     if (positionNumber == 0) positionNumber = 1;\r
9182     lastLoadPositionFP = f;\r
9183     lastLoadPositionNumber = positionNumber;\r
9184     strcpy(lastLoadPositionTitle, title);\r
9185     if (first.pr == NoProc) {\r
9186       StartChessProgram(&first);\r
9187       InitChessProgram(&first, FALSE);\r
9188     }    \r
9189     pn = positionNumber;\r
9190     if (positionNumber < 0) {\r
9191         /* Negative position number means to seek to that byte offset */\r
9192         if (fseek(f, -positionNumber, 0) == -1) {\r
9193             DisplayError(_("Can't seek on position file"), 0);\r
9194             return FALSE;\r
9195         };\r
9196         pn = 1;\r
9197     } else {\r
9198         if (fseek(f, 0, 0) == -1) {\r
9199             if (f == lastLoadPositionFP ?\r
9200                 positionNumber == lastLoadPositionNumber + 1 :\r
9201                 positionNumber == 1) {\r
9202                 pn = 1;\r
9203             } else {\r
9204                 DisplayError(_("Can't seek on position file"), 0);\r
9205                 return FALSE;\r
9206             }\r
9207         }\r
9208     }\r
9209     /* See if this file is FEN or old-style xboard */\r
9210     if (fgets(line, MSG_SIZ, f) == NULL) {\r
9211         DisplayError(_("Position not found in file"), 0);\r
9212         return FALSE;\r
9213     }\r
9214 #if 0\r
9215     switch (line[0]) {\r
9216       case '#':  case 'x':\r
9217       default:\r
9218         fenMode = FALSE;\r
9219         break;\r
9220       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
9221       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
9222       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
9223       case '7':  case '8':  case '9':\r
9224       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
9225       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
9226       case 'C':  case 'W':             case 'c':  case 'w': \r
9227         fenMode = TRUE;\r
9228         break;\r
9229     }\r
9230 #else\r
9231     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
9232     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
9233 #endif\r
9234 \r
9235     if (pn >= 2) {\r
9236         if (fenMode || line[0] == '#') pn--;\r
9237         while (pn > 0) {\r
9238             /* skip positions before number pn */\r
9239             if (fgets(line, MSG_SIZ, f) == NULL) {\r
9240                 Reset(TRUE, TRUE);\r
9241                 DisplayError(_("Position not found in file"), 0);\r
9242                 return FALSE;\r
9243             }\r
9244             if (fenMode || line[0] == '#') pn--;\r
9245         }\r
9246     }\r
9247 \r
9248     if (fenMode) {\r
9249         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
9250             DisplayError(_("Bad FEN position in file"), 0);\r
9251             return FALSE;\r
9252         }\r
9253     } else {\r
9254         (void) fgets(line, MSG_SIZ, f);\r
9255         (void) fgets(line, MSG_SIZ, f);\r
9256     \r
9257         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
9258             (void) fgets(line, MSG_SIZ, f);\r
9259             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
9260                 if (*p == ' ')\r
9261                   continue;\r
9262                 initial_position[i][j++] = CharToPiece(*p);\r
9263             }\r
9264         }\r
9265     \r
9266         blackPlaysFirst = FALSE;\r
9267         if (!feof(f)) {\r
9268             (void) fgets(line, MSG_SIZ, f);\r
9269             if (strncmp(line, "black", strlen("black"))==0)\r
9270               blackPlaysFirst = TRUE;\r
9271         }\r
9272     }\r
9273     startedFromSetupPosition = TRUE;\r
9274     \r
9275     SendToProgram("force\n", &first);\r
9276     CopyBoard(boards[0], initial_position);\r
9277     if (blackPlaysFirst) {\r
9278         currentMove = forwardMostMove = backwardMostMove = 1;\r
9279         strcpy(moveList[0], "");\r
9280         strcpy(parseList[0], "");\r
9281         CopyBoard(boards[1], initial_position);\r
9282         DisplayMessage("", _("Black to play"));\r
9283     } else {\r
9284         currentMove = forwardMostMove = backwardMostMove = 0;\r
9285         DisplayMessage("", _("White to play"));\r
9286     }\r
9287           /* [HGM] copy FEN attributes as well */\r
9288           {   int i;\r
9289               initialRulePlies = FENrulePlies;\r
9290               epStatus[forwardMostMove] = FENepStatus;\r
9291               for( i=0; i< nrCastlingRights; i++ )\r
9292                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
9293           }\r
9294     SendBoard(&first, forwardMostMove);\r
9295     if (appData.debugMode) {\r
9296 int i, j;\r
9297   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}\r
9298   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");\r
9299         fprintf(debugFP, "Load Position\n");\r
9300     }\r
9301 \r
9302     if (positionNumber > 1) {\r
9303         sprintf(line, "%s %d", title, positionNumber);\r
9304         DisplayTitle(line);\r
9305     } else {\r
9306         DisplayTitle(title);\r
9307     }\r
9308     gameMode = EditGame;\r
9309     ModeHighlight();\r
9310     ResetClocks();\r
9311     timeRemaining[0][1] = whiteTimeRemaining;\r
9312     timeRemaining[1][1] = blackTimeRemaining;\r
9313     DrawPosition(FALSE, boards[currentMove]);\r
9314    \r
9315     return TRUE;\r
9316 }\r
9317 \r
9318 \r
9319 void\r
9320 CopyPlayerNameIntoFileName(dest, src)\r
9321      char **dest, *src;\r
9322 {\r
9323     while (*src != NULLCHAR && *src != ',') {\r
9324         if (*src == ' ') {\r
9325             *(*dest)++ = '_';\r
9326             src++;\r
9327         } else {\r
9328             *(*dest)++ = *src++;\r
9329         }\r
9330     }\r
9331 }\r
9332 \r
9333 char *DefaultFileName(ext)\r
9334      char *ext;\r
9335 {\r
9336     static char def[MSG_SIZ];\r
9337     char *p;\r
9338 \r
9339     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
9340         p = def;\r
9341         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
9342         *p++ = '-';\r
9343         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
9344         *p++ = '.';\r
9345         strcpy(p, ext);\r
9346     } else {\r
9347         def[0] = NULLCHAR;\r
9348     }\r
9349     return def;\r
9350 }\r
9351 \r
9352 /* Save the current game to the given file */\r
9353 int\r
9354 SaveGameToFile(filename, append)\r
9355      char *filename;\r
9356      int append;\r
9357 {\r
9358     FILE *f;\r
9359     char buf[MSG_SIZ];\r
9360 \r
9361     if (strcmp(filename, "-") == 0) {\r
9362         return SaveGame(stdout, 0, NULL);\r
9363     } else {\r
9364         f = fopen(filename, append ? "a" : "w");\r
9365         if (f == NULL) {\r
9366             sprintf(buf, _("Can't open \"%s\""), filename);\r
9367             DisplayError(buf, errno);\r
9368             return FALSE;\r
9369         } else {\r
9370             return SaveGame(f, 0, NULL);\r
9371         }\r
9372     }\r
9373 }\r
9374 \r
9375 char *\r
9376 SavePart(str)\r
9377      char *str;\r
9378 {\r
9379     static char buf[MSG_SIZ];\r
9380     char *p;\r
9381     \r
9382     p = strchr(str, ' ');\r
9383     if (p == NULL) return str;\r
9384     strncpy(buf, str, p - str);\r
9385     buf[p - str] = NULLCHAR;\r
9386     return buf;\r
9387 }\r
9388 \r
9389 #define PGN_MAX_LINE 75\r
9390 \r
9391 #define PGN_SIDE_WHITE  0\r
9392 #define PGN_SIDE_BLACK  1\r
9393 \r
9394 /* [AS] */\r
9395 static int FindFirstMoveOutOfBook( int side )\r
9396 {\r
9397     int result = -1;\r
9398 \r
9399     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
9400         int index = backwardMostMove;\r
9401         int has_book_hit = 0;\r
9402 \r
9403         if( (index % 2) != side ) {\r
9404             index++;\r
9405         }\r
9406 \r
9407         while( index < forwardMostMove ) {\r
9408             /* Check to see if engine is in book */\r
9409             int depth = pvInfoList[index].depth;\r
9410             int score = pvInfoList[index].score;\r
9411             int in_book = 0;\r
9412 \r
9413             if( depth <= 2 ) {\r
9414                 in_book = 1;\r
9415             }\r
9416             else if( score == 0 && depth == 63 ) {\r
9417                 in_book = 1; /* Zappa */\r
9418             }\r
9419             else if( score == 2 && depth == 99 ) {\r
9420                 in_book = 1; /* Abrok */\r
9421             }\r
9422 \r
9423             has_book_hit += in_book;\r
9424 \r
9425             if( ! in_book ) {\r
9426                 result = index;\r
9427 \r
9428                 break;\r
9429             }\r
9430 \r
9431             index += 2;\r
9432         }\r
9433     }\r
9434 \r
9435     return result;\r
9436 }\r
9437 \r
9438 /* [AS] */\r
9439 void GetOutOfBookInfo( char * buf )\r
9440 {\r
9441     int oob[2];\r
9442     int i;\r
9443     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9444 \r
9445     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
9446     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
9447 \r
9448     *buf = '\0';\r
9449 \r
9450     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
9451         for( i=0; i<2; i++ ) {\r
9452             int idx = oob[i];\r
9453 \r
9454             if( idx >= 0 ) {\r
9455                 if( i > 0 && oob[0] >= 0 ) {\r
9456                     strcat( buf, "   " );\r
9457                 }\r
9458 \r
9459                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
9460                 sprintf( buf+strlen(buf), "%s%.2f", \r
9461                     pvInfoList[idx].score >= 0 ? "+" : "",\r
9462                     pvInfoList[idx].score / 100.0 );\r
9463             }\r
9464         }\r
9465     }\r
9466 }\r
9467 \r
9468 /* Save game in PGN style and close the file */\r
9469 int\r
9470 SaveGamePGN(f)\r
9471      FILE *f;\r
9472 {\r
9473     int i, offset, linelen, newblock;\r
9474     time_t tm;\r
9475 //    char *movetext;\r
9476     char numtext[32];\r
9477     int movelen, numlen, blank;\r
9478     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
9479 \r
9480     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9481     \r
9482     tm = time((time_t *) NULL);\r
9483     \r
9484     PrintPGNTags(f, &gameInfo);\r
9485     \r
9486     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9487         char *fen = PositionToFEN(backwardMostMove, 1);\r
9488         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
9489         fprintf(f, "\n{--------------\n");\r
9490         PrintPosition(f, backwardMostMove);\r
9491         fprintf(f, "--------------}\n");\r
9492         free(fen);\r
9493     }\r
9494     else {\r
9495         /* [AS] Out of book annotation */\r
9496         if( appData.saveOutOfBookInfo ) {\r
9497             char buf[64];\r
9498 \r
9499             GetOutOfBookInfo( buf );\r
9500 \r
9501             if( buf[0] != '\0' ) {\r
9502                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
9503             }\r
9504         }\r
9505 \r
9506         fprintf(f, "\n");\r
9507     }\r
9508 \r
9509     i = backwardMostMove;\r
9510     linelen = 0;\r
9511     newblock = TRUE;\r
9512 \r
9513     while (i < forwardMostMove) {\r
9514         /* Print comments preceding this move */\r
9515         if (commentList[i] != NULL) {\r
9516             if (linelen > 0) fprintf(f, "\n");\r
9517             fprintf(f, "{\n%s}\n", commentList[i]);\r
9518             linelen = 0;\r
9519             newblock = TRUE;\r
9520         }\r
9521 \r
9522         /* Format move number */\r
9523         if ((i % 2) == 0) {\r
9524             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
9525         } else {\r
9526             if (newblock) {\r
9527                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
9528             } else {\r
9529                 numtext[0] = NULLCHAR;\r
9530             }\r
9531         }\r
9532         numlen = strlen(numtext);\r
9533         newblock = FALSE;\r
9534 \r
9535         /* Print move number */\r
9536         blank = linelen > 0 && numlen > 0;\r
9537         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
9538             fprintf(f, "\n");\r
9539             linelen = 0;\r
9540             blank = 0;\r
9541         }\r
9542         if (blank) {\r
9543             fprintf(f, " ");\r
9544             linelen++;\r
9545         }\r
9546         fprintf(f, numtext);\r
9547         linelen += numlen;\r
9548 \r
9549         /* Get move */\r
9550         movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */\r
9551 \r
9552         /* Print move */\r
9553         blank = linelen > 0 && movelen > 0;\r
9554         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9555             fprintf(f, "\n");\r
9556             linelen = 0;\r
9557             blank = 0;\r
9558         }\r
9559         if (blank) {\r
9560             fprintf(f, " ");\r
9561             linelen++;\r
9562         }\r
9563         fprintf(f, parseList[i]);\r
9564         linelen += movelen;\r
9565 \r
9566         /* [AS] Add PV info if present */\r
9567         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9568             /* [HGM] add time */\r
9569             char buf[MSG_SIZ]; int seconds = 0;\r
9570 \r
9571 #if 0\r
9572             if(i >= backwardMostMove) {\r
9573                 if(WhiteOnMove(i))\r
9574                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]\r
9575                                   + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;\r
9576                 else\r
9577                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]\r
9578                                   + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;\r
9579             }\r
9580             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest\r
9581 #else\r
9582             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time\r
9583 #endif\r
9584 \r
9585             if( seconds <= 0) buf[0] = 0; else\r
9586             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {\r
9587                 seconds = (seconds + 4)/10; // round to full seconds\r
9588                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else\r
9589                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
9590             }\r
9591 \r
9592             sprintf( move_buffer, "{%s%.2f/%d%s}", \r
9593                 pvInfoList[i].score >= 0 ? "+" : "",\r
9594                 pvInfoList[i].score / 100.0,\r
9595                 pvInfoList[i].depth,\r
9596                 buf );\r
9597 \r
9598             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */\r
9599 \r
9600             /* Print score/depth */\r
9601             blank = linelen > 0 && movelen > 0;\r
9602             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9603                 fprintf(f, "\n");\r
9604                 linelen = 0;\r
9605                 blank = 0;\r
9606             }\r
9607             if (blank) {\r
9608                 fprintf(f, " ");\r
9609                 linelen++;\r
9610             }\r
9611             fprintf(f, move_buffer);\r
9612             linelen += movelen;\r
9613         }\r
9614 \r
9615         i++;\r
9616     }\r
9617     \r
9618     /* Start a new line */\r
9619     if (linelen > 0) fprintf(f, "\n");\r
9620 \r
9621     /* Print comments after last move */\r
9622     if (commentList[i] != NULL) {\r
9623         fprintf(f, "{\n%s}\n", commentList[i]);\r
9624     }\r
9625 \r
9626     /* Print result */\r
9627     if (gameInfo.resultDetails != NULL &&\r
9628         gameInfo.resultDetails[0] != NULLCHAR) {\r
9629         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
9630                 PGNResult(gameInfo.result));\r
9631     } else {\r
9632         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9633     }\r
9634 \r
9635     fclose(f);\r
9636     return TRUE;\r
9637 }\r
9638 \r
9639 /* Save game in old style and close the file */\r
9640 int\r
9641 SaveGameOldStyle(f)\r
9642      FILE *f;\r
9643 {\r
9644     int i, offset;\r
9645     time_t tm;\r
9646     \r
9647     tm = time((time_t *) NULL);\r
9648     \r
9649     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
9650     PrintOpponents(f);\r
9651     \r
9652     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9653         fprintf(f, "\n[--------------\n");\r
9654         PrintPosition(f, backwardMostMove);\r
9655         fprintf(f, "--------------]\n");\r
9656     } else {\r
9657         fprintf(f, "\n");\r
9658     }\r
9659 \r
9660     i = backwardMostMove;\r
9661     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9662 \r
9663     while (i < forwardMostMove) {\r
9664         if (commentList[i] != NULL) {\r
9665             fprintf(f, "[%s]\n", commentList[i]);\r
9666         }\r
9667 \r
9668         if ((i % 2) == 1) {\r
9669             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
9670             i++;\r
9671         } else {\r
9672             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
9673             i++;\r
9674             if (commentList[i] != NULL) {\r
9675                 fprintf(f, "\n");\r
9676                 continue;\r
9677             }\r
9678             if (i >= forwardMostMove) {\r
9679                 fprintf(f, "\n");\r
9680                 break;\r
9681             }\r
9682             fprintf(f, "%s\n", parseList[i]);\r
9683             i++;\r
9684         }\r
9685     }\r
9686     \r
9687     if (commentList[i] != NULL) {\r
9688         fprintf(f, "[%s]\n", commentList[i]);\r
9689     }\r
9690 \r
9691     /* This isn't really the old style, but it's close enough */\r
9692     if (gameInfo.resultDetails != NULL &&\r
9693         gameInfo.resultDetails[0] != NULLCHAR) {\r
9694         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
9695                 gameInfo.resultDetails);\r
9696     } else {\r
9697         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9698     }\r
9699 \r
9700     fclose(f);\r
9701     return TRUE;\r
9702 }\r
9703 \r
9704 /* Save the current game to open file f and close the file */\r
9705 int\r
9706 SaveGame(f, dummy, dummy2)\r
9707      FILE *f;\r
9708      int dummy;\r
9709      char *dummy2;\r
9710 {\r
9711     if (gameMode == EditPosition) EditPositionDone();\r
9712     if (appData.oldSaveStyle)\r
9713       return SaveGameOldStyle(f);\r
9714     else\r
9715       return SaveGamePGN(f);\r
9716 }\r
9717 \r
9718 /* Save the current position to the given file */\r
9719 int\r
9720 SavePositionToFile(filename)\r
9721      char *filename;\r
9722 {\r
9723     FILE *f;\r
9724     char buf[MSG_SIZ];\r
9725 \r
9726     if (strcmp(filename, "-") == 0) {\r
9727         return SavePosition(stdout, 0, NULL);\r
9728     } else {\r
9729         f = fopen(filename, "a");\r
9730         if (f == NULL) {\r
9731             sprintf(buf, _("Can't open \"%s\""), filename);\r
9732             DisplayError(buf, errno);\r
9733             return FALSE;\r
9734         } else {\r
9735             SavePosition(f, 0, NULL);\r
9736             return TRUE;\r
9737         }\r
9738     }\r
9739 }\r
9740 \r
9741 /* Save the current position to the given open file and close the file */\r
9742 int\r
9743 SavePosition(f, dummy, dummy2)\r
9744      FILE *f;\r
9745      int dummy;\r
9746      char *dummy2;\r
9747 {\r
9748     time_t tm;\r
9749     char *fen;\r
9750     \r
9751     if (appData.oldSaveStyle) {\r
9752         tm = time((time_t *) NULL);\r
9753     \r
9754         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
9755         PrintOpponents(f);\r
9756         fprintf(f, "[--------------\n");\r
9757         PrintPosition(f, currentMove);\r
9758         fprintf(f, "--------------]\n");\r
9759     } else {\r
9760         fen = PositionToFEN(currentMove, 1);\r
9761         fprintf(f, "%s\n", fen);\r
9762         free(fen);\r
9763     }\r
9764     fclose(f);\r
9765     return TRUE;\r
9766 }\r
9767 \r
9768 void\r
9769 ReloadCmailMsgEvent(unregister)\r
9770      int unregister;\r
9771 {\r
9772 #if !WIN32\r
9773     static char *inFilename = NULL;\r
9774     static char *outFilename;\r
9775     int i;\r
9776     struct stat inbuf, outbuf;\r
9777     int status;\r
9778     \r
9779     /* Any registered moves are unregistered if unregister is set, */\r
9780     /* i.e. invoked by the signal handler */\r
9781     if (unregister) {\r
9782         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9783             cmailMoveRegistered[i] = FALSE;\r
9784             if (cmailCommentList[i] != NULL) {\r
9785                 free(cmailCommentList[i]);\r
9786                 cmailCommentList[i] = NULL;\r
9787             }\r
9788         }\r
9789         nCmailMovesRegistered = 0;\r
9790     }\r
9791 \r
9792     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9793         cmailResult[i] = CMAIL_NOT_RESULT;\r
9794     }\r
9795     nCmailResults = 0;\r
9796 \r
9797     if (inFilename == NULL) {\r
9798         /* Because the filenames are static they only get malloced once  */\r
9799         /* and they never get freed                                      */\r
9800         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
9801         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
9802 \r
9803         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
9804         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
9805     }\r
9806     \r
9807     status = stat(outFilename, &outbuf);\r
9808     if (status < 0) {\r
9809         cmailMailedMove = FALSE;\r
9810     } else {\r
9811         status = stat(inFilename, &inbuf);\r
9812         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
9813     }\r
9814     \r
9815     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
9816        counts the games, notes how each one terminated, etc.\r
9817        \r
9818        It would be nice to remove this kludge and instead gather all\r
9819        the information while building the game list.  (And to keep it\r
9820        in the game list nodes instead of having a bunch of fixed-size\r
9821        parallel arrays.)  Note this will require getting each game's\r
9822        termination from the PGN tags, as the game list builder does\r
9823        not process the game moves.  --mann\r
9824        */\r
9825     cmailMsgLoaded = TRUE;\r
9826     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
9827     \r
9828     /* Load first game in the file or popup game menu */\r
9829     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
9830 \r
9831 #endif /* !WIN32 */\r
9832     return;\r
9833 }\r
9834 \r
9835 int\r
9836 RegisterMove()\r
9837 {\r
9838     FILE *f;\r
9839     char string[MSG_SIZ];\r
9840 \r
9841     if (   cmailMailedMove\r
9842         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9843         return TRUE;            /* Allow free viewing  */\r
9844     }\r
9845 \r
9846     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9847     /* with the move registered when the conditions for registering no   */\r
9848     /* longer hold                                                       */\r
9849     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9850         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9851         nCmailMovesRegistered --;\r
9852 \r
9853         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9854           {\r
9855               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9856               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9857           }\r
9858     }\r
9859 \r
9860     if (cmailOldMove == -1) {\r
9861         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);\r
9862         return FALSE;\r
9863     }\r
9864 \r
9865     if (currentMove > cmailOldMove + 1) {\r
9866         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);\r
9867         return FALSE;\r
9868     }\r
9869 \r
9870     if (currentMove < cmailOldMove) {\r
9871         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);\r
9872         return FALSE;\r
9873     }\r
9874 \r
9875     if (forwardMostMove > currentMove) {\r
9876         /* Silently truncate extra moves */\r
9877         TruncateGame();\r
9878     }\r
9879 \r
9880     if (   (currentMove == cmailOldMove + 1)\r
9881         || (   (currentMove == cmailOldMove)\r
9882             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9883                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9884         if (gameInfo.result != GameUnfinished) {\r
9885             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9886         }\r
9887 \r
9888         if (commentList[currentMove] != NULL) {\r
9889             cmailCommentList[lastLoadGameNumber - 1]\r
9890               = StrSave(commentList[currentMove]);\r
9891         }\r
9892         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9893 \r
9894         if (appData.debugMode)\r
9895           fprintf(debugFP, "Saving %s for game %d\n",\r
9896                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9897 \r
9898         sprintf(string,\r
9899                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
9900         \r
9901         f = fopen(string, "w");\r
9902         if (appData.oldSaveStyle) {\r
9903             SaveGameOldStyle(f); /* also closes the file */\r
9904             \r
9905             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
9906             f = fopen(string, "w");\r
9907             SavePosition(f, 0, NULL); /* also closes the file */\r
9908         } else {\r
9909             fprintf(f, "{--------------\n");\r
9910             PrintPosition(f, currentMove);\r
9911             fprintf(f, "--------------}\n\n");\r
9912             \r
9913             SaveGame(f, 0, NULL); /* also closes the file*/\r
9914         }\r
9915         \r
9916         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
9917         nCmailMovesRegistered ++;\r
9918     } else if (nCmailGames == 1) {\r
9919         DisplayError(_("You have not made a move yet"), 0);\r
9920         return FALSE;\r
9921     }\r
9922 \r
9923     return TRUE;\r
9924 }\r
9925 \r
9926 void\r
9927 MailMoveEvent()\r
9928 {\r
9929 #if !WIN32\r
9930     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
9931     FILE *commandOutput;\r
9932     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
9933     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
9934     int nBuffers;\r
9935     int i;\r
9936     int archived;\r
9937     char *arcDir;\r
9938 \r
9939     if (! cmailMsgLoaded) {\r
9940         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);\r
9941         return;\r
9942     }\r
9943 \r
9944     if (nCmailGames == nCmailResults) {\r
9945         DisplayError(_("No unfinished games"), 0);\r
9946         return;\r
9947     }\r
9948 \r
9949 #if CMAIL_PROHIBIT_REMAIL\r
9950     if (cmailMailedMove) {\r
9951         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
9952         DisplayError(msg, 0);\r
9953         return;\r
9954     }\r
9955 #endif\r
9956 \r
9957     if (! (cmailMailedMove || RegisterMove())) return;\r
9958     \r
9959     if (   cmailMailedMove\r
9960         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
9961         sprintf(string, partCommandString,\r
9962                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
9963         commandOutput = popen(string, "r");\r
9964 \r
9965         if (commandOutput == NULL) {\r
9966             DisplayError(_("Failed to invoke cmail"), 0);\r
9967         } else {\r
9968             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
9969                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
9970             }\r
9971             if (nBuffers > 1) {\r
9972                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
9973                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
9974                 nBytes = MSG_SIZ - 1;\r
9975             } else {\r
9976                 (void) memcpy(msg, buffer, nBytes);\r
9977             }\r
9978             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
9979 \r
9980             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
9981                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
9982 \r
9983                 archived = TRUE;\r
9984                 for (i = 0; i < nCmailGames; i ++) {\r
9985                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
9986                         archived = FALSE;\r
9987                     }\r
9988                 }\r
9989                 if (   archived\r
9990                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
9991                         != NULL)) {\r
9992                     sprintf(buffer, "%s/%s.%s.archive",\r
9993                             arcDir,\r
9994                             appData.cmailGameName,\r
9995                             gameInfo.date);\r
9996                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
9997                     cmailMsgLoaded = FALSE;\r
9998                 }\r
9999             }\r
10000 \r
10001             DisplayInformation(msg);\r
10002             pclose(commandOutput);\r
10003         }\r
10004     } else {\r
10005         if ((*cmailMsg) != '\0') {\r
10006             DisplayInformation(cmailMsg);\r
10007         }\r
10008     }\r
10009 \r
10010     return;\r
10011 #endif /* !WIN32 */\r
10012 }\r
10013 \r
10014 char *\r
10015 CmailMsg()\r
10016 {\r
10017 #if WIN32\r
10018     return NULL;\r
10019 #else\r
10020     int  prependComma = 0;\r
10021     char number[5];\r
10022     char string[MSG_SIZ];       /* Space for game-list */\r
10023     int  i;\r
10024     \r
10025     if (!cmailMsgLoaded) return "";\r
10026 \r
10027     if (cmailMailedMove) {\r
10028         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));\r
10029     } else {\r
10030         /* Create a list of games left */\r
10031         sprintf(string, "[");\r
10032         for (i = 0; i < nCmailGames; i ++) {\r
10033             if (! (   cmailMoveRegistered[i]\r
10034                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
10035                 if (prependComma) {\r
10036                     sprintf(number, ",%d", i + 1);\r
10037                 } else {\r
10038                     sprintf(number, "%d", i + 1);\r
10039                     prependComma = 1;\r
10040                 }\r
10041                 \r
10042                 strcat(string, number);\r
10043             }\r
10044         }\r
10045         strcat(string, "]");\r
10046 \r
10047         if (nCmailMovesRegistered + nCmailResults == 0) {\r
10048             switch (nCmailGames) {\r
10049               case 1:\r
10050                 sprintf(cmailMsg,\r
10051                         _("Still need to make move for game\n"));\r
10052                 break;\r
10053                 \r
10054               case 2:\r
10055                 sprintf(cmailMsg,\r
10056                         _("Still need to make moves for both games\n"));\r
10057                 break;\r
10058                 \r
10059               default:\r
10060                 sprintf(cmailMsg,\r
10061                         _("Still need to make moves for all %d games\n"),\r
10062                         nCmailGames);\r
10063                 break;\r
10064             }\r
10065         } else {\r
10066             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
10067               case 1:\r
10068                 sprintf(cmailMsg,\r
10069                         _("Still need to make a move for game %s\n"),\r
10070                         string);\r
10071                 break;\r
10072                 \r
10073               case 0:\r
10074                 if (nCmailResults == nCmailGames) {\r
10075                     sprintf(cmailMsg, _("No unfinished games\n"));\r
10076                 } else {\r
10077                     sprintf(cmailMsg, _("Ready to send mail\n"));\r
10078                 }\r
10079                 break;\r
10080                 \r
10081               default:\r
10082                 sprintf(cmailMsg,\r
10083                         _("Still need to make moves for games %s\n"),\r
10084                         string);\r
10085             }\r
10086         }\r
10087     }\r
10088     return cmailMsg;\r
10089 #endif /* WIN32 */\r
10090 }\r
10091 \r
10092 void\r
10093 ResetGameEvent()\r
10094 {\r
10095     if (gameMode == Training)\r
10096       SetTrainingModeOff();\r
10097 \r
10098     Reset(TRUE, TRUE);\r
10099     cmailMsgLoaded = FALSE;\r
10100     if (appData.icsActive) {\r
10101       SendToICS(ics_prefix);\r
10102       SendToICS("refresh\n");\r
10103     }\r
10104 }\r
10105 \r
10106 void\r
10107 ExitEvent(status)\r
10108      int status;\r
10109 {\r
10110     exiting++;\r
10111     if (exiting > 2) {\r
10112       /* Give up on clean exit */\r
10113       exit(status);\r
10114     }\r
10115     if (exiting > 1) {\r
10116       /* Keep trying for clean exit */\r
10117       return;\r
10118     }\r
10119 \r
10120     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
10121 \r
10122     if (telnetISR != NULL) {\r
10123       RemoveInputSource(telnetISR);\r
10124     }\r
10125     if (icsPR != NoProc) {\r
10126       DestroyChildProcess(icsPR, TRUE);\r
10127     }\r
10128 #if 0\r
10129     /* Save game if resource set and not already saved by GameEnds() */\r
10130     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
10131                              && forwardMostMove > 0) {\r
10132       if (*appData.saveGameFile != NULLCHAR) {\r
10133         SaveGameToFile(appData.saveGameFile, TRUE);\r
10134       } else if (appData.autoSaveGames) {\r
10135         AutoSaveGame();\r
10136       }\r
10137       if (*appData.savePositionFile != NULLCHAR) {\r
10138         SavePositionToFile(appData.savePositionFile);\r
10139       }\r
10140     }\r
10141     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10142 #else\r
10143     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
10144     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);\r
10145 #endif\r
10146     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
10147     /* make sure this other one finishes before killing it!                  */\r
10148     if(endingGame) { int count = 0;\r
10149         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
10150         while(endingGame && count++ < 10) DoSleep(1);\r
10151         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
10152     }\r
10153 \r
10154     /* Kill off chess programs */\r
10155     if (first.pr != NoProc) {\r
10156         ExitAnalyzeMode();\r
10157         \r
10158         DoSleep( appData.delayBeforeQuit );\r
10159         SendToProgram("quit\n", &first);\r
10160         DoSleep( appData.delayAfterQuit );\r
10161         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
10162     }\r
10163     if (second.pr != NoProc) {\r
10164         DoSleep( appData.delayBeforeQuit );\r
10165         SendToProgram("quit\n", &second);\r
10166         DoSleep( appData.delayAfterQuit );\r
10167         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
10168     }\r
10169     if (first.isr != NULL) {\r
10170         RemoveInputSource(first.isr);\r
10171     }\r
10172     if (second.isr != NULL) {\r
10173         RemoveInputSource(second.isr);\r
10174     }\r
10175 \r
10176     ShutDownFrontEnd();\r
10177     exit(status);\r
10178 }\r
10179 \r
10180 void\r
10181 PauseEvent()\r
10182 {\r
10183     if (appData.debugMode)\r
10184         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
10185     if (pausing) {\r
10186         pausing = FALSE;\r
10187         ModeHighlight();\r
10188         if (gameMode == MachinePlaysWhite ||\r
10189             gameMode == MachinePlaysBlack) {\r
10190             StartClocks();\r
10191         } else {\r
10192             DisplayBothClocks();\r
10193         }\r
10194         if (gameMode == PlayFromGameFile) {\r
10195             if (appData.timeDelay >= 0) \r
10196                 AutoPlayGameLoop();\r
10197         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
10198             Reset(FALSE, TRUE);\r
10199             SendToICS(ics_prefix);\r
10200             SendToICS("refresh\n");\r
10201         } else if (currentMove < forwardMostMove) {\r
10202             ForwardInner(forwardMostMove);\r
10203         }\r
10204         pauseExamInvalid = FALSE;\r
10205     } else {\r
10206         switch (gameMode) {\r
10207           default:\r
10208             return;\r
10209           case IcsExamining:\r
10210             pauseExamForwardMostMove = forwardMostMove;\r
10211             pauseExamInvalid = FALSE;\r
10212             /* fall through */\r
10213           case IcsObserving:\r
10214           case IcsPlayingWhite:\r
10215           case IcsPlayingBlack:\r
10216             pausing = TRUE;\r
10217             ModeHighlight();\r
10218             return;\r
10219           case PlayFromGameFile:\r
10220             (void) StopLoadGameTimer();\r
10221             pausing = TRUE;\r
10222             ModeHighlight();\r
10223             break;\r
10224           case BeginningOfGame:\r
10225             if (appData.icsActive) return;\r
10226             /* else fall through */\r
10227           case MachinePlaysWhite:\r
10228           case MachinePlaysBlack:\r
10229           case TwoMachinesPlay:\r
10230             if (forwardMostMove == 0)\r
10231               return;           /* don't pause if no one has moved */\r
10232             if ((gameMode == MachinePlaysWhite &&\r
10233                  !WhiteOnMove(forwardMostMove)) ||\r
10234                 (gameMode == MachinePlaysBlack &&\r
10235                  WhiteOnMove(forwardMostMove))) {\r
10236                 StopClocks();\r
10237             }\r
10238             pausing = TRUE;\r
10239             ModeHighlight();\r
10240             break;\r
10241         }\r
10242     }\r
10243 }\r
10244 \r
10245 void\r
10246 EditCommentEvent()\r
10247 {\r
10248     char title[MSG_SIZ];\r
10249 \r
10250     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
10251         strcpy(title, _("Edit comment"));\r
10252     } else {\r
10253         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,\r
10254                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
10255                 parseList[currentMove - 1]);\r
10256     }\r
10257 \r
10258     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
10259 }\r
10260 \r
10261 \r
10262 void\r
10263 EditTagsEvent()\r
10264 {\r
10265     char *tags = PGNTags(&gameInfo);\r
10266     EditTagsPopUp(tags);\r
10267     free(tags);\r
10268 }\r
10269 \r
10270 void\r
10271 AnalyzeModeEvent()\r
10272 {\r
10273     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
10274       return;\r
10275 \r
10276     if (gameMode != AnalyzeFile) {\r
10277         if (!appData.icsEngineAnalyze) {\r
10278                EditGameEvent();\r
10279                if (gameMode != EditGame) return;\r
10280         }\r
10281         ResurrectChessProgram();\r
10282         SendToProgram("analyze\n", &first);\r
10283         first.analyzing = TRUE;\r
10284         /*first.maybeThinking = TRUE;*/\r
10285         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10286         AnalysisPopUp(_("Analysis"),\r
10287                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10288     }\r
10289     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;\r
10290     pausing = FALSE;\r
10291     ModeHighlight();\r
10292     SetGameInfo();\r
10293 \r
10294     StartAnalysisClock();\r
10295     GetTimeMark(&lastNodeCountTime);\r
10296     lastNodeCount = 0;\r
10297 }\r
10298 \r
10299 void\r
10300 AnalyzeFileEvent()\r
10301 {\r
10302     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
10303       return;\r
10304 \r
10305     if (gameMode != AnalyzeMode) {\r
10306         EditGameEvent();\r
10307         if (gameMode != EditGame) return;\r
10308         ResurrectChessProgram();\r
10309         SendToProgram("analyze\n", &first);\r
10310         first.analyzing = TRUE;\r
10311         /*first.maybeThinking = TRUE;*/\r
10312         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10313         AnalysisPopUp(_("Analysis"),\r
10314                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10315     }\r
10316     gameMode = AnalyzeFile;\r
10317     pausing = FALSE;\r
10318     ModeHighlight();\r
10319     SetGameInfo();\r
10320 \r
10321     StartAnalysisClock();\r
10322     GetTimeMark(&lastNodeCountTime);\r
10323     lastNodeCount = 0;\r
10324 }\r
10325 \r
10326 void\r
10327 MachineWhiteEvent()\r
10328 {\r
10329     char buf[MSG_SIZ];\r
10330     char *bookHit = NULL;\r
10331 \r
10332     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
10333       return;\r
10334 \r
10335 \r
10336     if (gameMode == PlayFromGameFile || \r
10337         gameMode == TwoMachinesPlay  || \r
10338         gameMode == Training         || \r
10339         gameMode == AnalyzeMode      || \r
10340         gameMode == EndOfGame)\r
10341         EditGameEvent();\r
10342 \r
10343     if (gameMode == EditPosition) \r
10344         EditPositionDone();\r
10345 \r
10346     if (!WhiteOnMove(currentMove)) {\r
10347         DisplayError(_("It is not White's turn"), 0);\r
10348         return;\r
10349     }\r
10350   \r
10351     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10352       ExitAnalyzeMode();\r
10353 \r
10354     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10355         gameMode == AnalyzeFile)\r
10356         TruncateGame();\r
10357 \r
10358     ResurrectChessProgram();    /* in case it isn't running */\r
10359     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */\r
10360         gameMode = MachinePlaysWhite;\r
10361         ResetClocks();\r
10362     } else\r
10363     gameMode = MachinePlaysWhite;\r
10364     pausing = FALSE;\r
10365     ModeHighlight();\r
10366     SetGameInfo();\r
10367     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10368     DisplayTitle(buf);\r
10369     if (first.sendName) {\r
10370       sprintf(buf, "name %s\n", gameInfo.black);\r
10371       SendToProgram(buf, &first);\r
10372     }\r
10373     if (first.sendTime) {\r
10374       if (first.useColors) {\r
10375         SendToProgram("black\n", &first); /*gnu kludge*/\r
10376       }\r
10377       SendTimeRemaining(&first, TRUE);\r
10378     }\r
10379     if (first.useColors) {\r
10380       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately\r
10381     }\r
10382     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10383     SetMachineThinkingEnables();\r
10384     first.maybeThinking = TRUE;\r
10385     StartClocks();\r
10386 \r
10387     if (appData.autoFlipView && !flipView) {\r
10388       flipView = !flipView;\r
10389       DrawPosition(FALSE, NULL);\r
10390       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10391     }\r
10392 \r
10393     if(bookHit) { // [HGM] book: simulate book reply\r
10394         static char bookMove[MSG_SIZ]; // a bit generous?\r
10395 \r
10396         programStats.nodes = programStats.depth = programStats.time = \r
10397         programStats.score = programStats.got_only_move = 0;\r
10398         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10399 \r
10400         strcpy(bookMove, "move ");\r
10401         strcat(bookMove, bookHit);\r
10402         HandleMachineMove(bookMove, &first);\r
10403     }\r
10404 }\r
10405 \r
10406 void\r
10407 MachineBlackEvent()\r
10408 {\r
10409     char buf[MSG_SIZ];\r
10410    char *bookHit = NULL;\r
10411 \r
10412     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
10413         return;\r
10414 \r
10415 \r
10416     if (gameMode == PlayFromGameFile || \r
10417         gameMode == TwoMachinesPlay  || \r
10418         gameMode == Training         || \r
10419         gameMode == AnalyzeMode      || \r
10420         gameMode == EndOfGame)\r
10421         EditGameEvent();\r
10422 \r
10423     if (gameMode == EditPosition) \r
10424         EditPositionDone();\r
10425 \r
10426     if (WhiteOnMove(currentMove)) {\r
10427         DisplayError(_("It is not Black's turn"), 0);\r
10428         return;\r
10429     }\r
10430     \r
10431     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10432       ExitAnalyzeMode();\r
10433 \r
10434     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10435         gameMode == AnalyzeFile)\r
10436         TruncateGame();\r
10437 \r
10438     ResurrectChessProgram();    /* in case it isn't running */\r
10439     gameMode = MachinePlaysBlack;\r
10440     pausing = FALSE;\r
10441     ModeHighlight();\r
10442     SetGameInfo();\r
10443     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10444     DisplayTitle(buf);\r
10445     if (first.sendName) {\r
10446       sprintf(buf, "name %s\n", gameInfo.white);\r
10447       SendToProgram(buf, &first);\r
10448     }\r
10449     if (first.sendTime) {\r
10450       if (first.useColors) {\r
10451         SendToProgram("white\n", &first); /*gnu kludge*/\r
10452       }\r
10453       SendTimeRemaining(&first, FALSE);\r
10454     }\r
10455     if (first.useColors) {\r
10456       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately\r
10457     }\r
10458     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10459     SetMachineThinkingEnables();\r
10460     first.maybeThinking = TRUE;\r
10461     StartClocks();\r
10462 \r
10463     if (appData.autoFlipView && flipView) {\r
10464       flipView = !flipView;\r
10465       DrawPosition(FALSE, NULL);\r
10466       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10467     }\r
10468     if(bookHit) { // [HGM] book: simulate book reply\r
10469         static char bookMove[MSG_SIZ]; // a bit generous?\r
10470 \r
10471         programStats.nodes = programStats.depth = programStats.time = \r
10472         programStats.score = programStats.got_only_move = 0;\r
10473         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10474 \r
10475         strcpy(bookMove, "move ");\r
10476         strcat(bookMove, bookHit);\r
10477         HandleMachineMove(bookMove, &first);\r
10478     }\r
10479 }\r
10480 \r
10481 \r
10482 void\r
10483 DisplayTwoMachinesTitle()\r
10484 {\r
10485     char buf[MSG_SIZ];\r
10486     if (appData.matchGames > 0) {\r
10487         if (first.twoMachinesColor[0] == 'w') {\r
10488             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10489                     gameInfo.white, gameInfo.black,\r
10490                     first.matchWins, second.matchWins,\r
10491                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10492         } else {\r
10493             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10494                     gameInfo.white, gameInfo.black,\r
10495                     second.matchWins, first.matchWins,\r
10496                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10497         }\r
10498     } else {\r
10499         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10500     }\r
10501     DisplayTitle(buf);\r
10502 }\r
10503 \r
10504 void\r
10505 TwoMachinesEvent P((void))\r
10506 {\r
10507     int i;\r
10508     char buf[MSG_SIZ];\r
10509     ChessProgramState *onmove;\r
10510     char *bookHit = NULL;\r
10511     \r
10512     if (appData.noChessProgram) return;\r
10513 \r
10514     switch (gameMode) {\r
10515       case TwoMachinesPlay:\r
10516         return;\r
10517       case MachinePlaysWhite:\r
10518       case MachinePlaysBlack:\r
10519         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10520             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
10521             return;\r
10522         }\r
10523         /* fall through */\r
10524       case BeginningOfGame:\r
10525       case PlayFromGameFile:\r
10526       case EndOfGame:\r
10527         EditGameEvent();\r
10528         if (gameMode != EditGame) return;\r
10529         break;\r
10530       case EditPosition:\r
10531         EditPositionDone();\r
10532         break;\r
10533       case AnalyzeMode:\r
10534       case AnalyzeFile:\r
10535         ExitAnalyzeMode();\r
10536         break;\r
10537       case EditGame:\r
10538       default:\r
10539         break;\r
10540     }\r
10541 \r
10542     forwardMostMove = currentMove;\r
10543     ResurrectChessProgram();    /* in case first program isn't running */\r
10544 \r
10545     if (second.pr == NULL) {\r
10546         StartChessProgram(&second);\r
10547         if (second.protocolVersion == 1) {\r
10548           TwoMachinesEventIfReady();\r
10549         } else {\r
10550           /* kludge: allow timeout for initial "feature" command */\r
10551           FreezeUI();\r
10552           DisplayMessage("", _("Starting second chess program"));\r
10553           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
10554         }\r
10555         return;\r
10556     }\r
10557     DisplayMessage("", "");\r
10558     InitChessProgram(&second, FALSE);\r
10559     SendToProgram("force\n", &second);\r
10560     if (startedFromSetupPosition) {\r
10561         SendBoard(&second, backwardMostMove);\r
10562     if (appData.debugMode) {\r
10563         fprintf(debugFP, "Two Machines\n");\r
10564     }\r
10565     }\r
10566     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
10567         SendMoveToProgram(i, &second);\r
10568     }\r
10569 \r
10570     gameMode = TwoMachinesPlay;\r
10571     pausing = FALSE;\r
10572     ModeHighlight();\r
10573     SetGameInfo();\r
10574     DisplayTwoMachinesTitle();\r
10575     firstMove = TRUE;\r
10576     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
10577         onmove = &first;\r
10578     } else {\r
10579         onmove = &second;\r
10580     }\r
10581 \r
10582     SendToProgram(first.computerString, &first);\r
10583     if (first.sendName) {\r
10584       sprintf(buf, "name %s\n", second.tidy);\r
10585       SendToProgram(buf, &first);\r
10586     }\r
10587     SendToProgram(second.computerString, &second);\r
10588     if (second.sendName) {\r
10589       sprintf(buf, "name %s\n", first.tidy);\r
10590       SendToProgram(buf, &second);\r
10591     }\r
10592 \r
10593     ResetClocks();\r
10594     if (!first.sendTime || !second.sendTime) {\r
10595         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10596         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10597     }\r
10598     if (onmove->sendTime) {\r
10599       if (onmove->useColors) {\r
10600         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
10601       }\r
10602       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
10603     }\r
10604     if (onmove->useColors) {\r
10605       SendToProgram(onmove->twoMachinesColor, onmove);\r
10606     }\r
10607     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move\r
10608 //    SendToProgram("go\n", onmove);\r
10609     onmove->maybeThinking = TRUE;\r
10610     SetMachineThinkingEnables();\r
10611 \r
10612     StartClocks();\r
10613 \r
10614     if(bookHit) { // [HGM] book: simulate book reply\r
10615         static char bookMove[MSG_SIZ]; // a bit generous?\r
10616 \r
10617         programStats.nodes = programStats.depth = programStats.time = \r
10618         programStats.score = programStats.got_only_move = 0;\r
10619         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10620 \r
10621         strcpy(bookMove, "move ");\r
10622         strcat(bookMove, bookHit);\r
10623         HandleMachineMove(bookMove, &first);\r
10624     }\r
10625 }\r
10626 \r
10627 void\r
10628 TrainingEvent()\r
10629 {\r
10630     if (gameMode == Training) {\r
10631       SetTrainingModeOff();\r
10632       gameMode = PlayFromGameFile;\r
10633       DisplayMessage("", _("Training mode off"));\r
10634     } else {\r
10635       gameMode = Training;\r
10636       animateTraining = appData.animate;\r
10637 \r
10638       /* make sure we are not already at the end of the game */\r
10639       if (currentMove < forwardMostMove) {\r
10640         SetTrainingModeOn();\r
10641         DisplayMessage("", _("Training mode on"));\r
10642       } else {\r
10643         gameMode = PlayFromGameFile;\r
10644         DisplayError(_("Already at end of game"), 0);\r
10645       }\r
10646     }\r
10647     ModeHighlight();\r
10648 }\r
10649 \r
10650 void\r
10651 IcsClientEvent()\r
10652 {\r
10653     if (!appData.icsActive) return;\r
10654     switch (gameMode) {\r
10655       case IcsPlayingWhite:\r
10656       case IcsPlayingBlack:\r
10657       case IcsObserving:\r
10658       case IcsIdle:\r
10659       case BeginningOfGame:\r
10660       case IcsExamining:\r
10661         return;\r
10662 \r
10663       case EditGame:\r
10664         break;\r
10665 \r
10666       case EditPosition:\r
10667         EditPositionDone();\r
10668         break;\r
10669 \r
10670       case AnalyzeMode:\r
10671       case AnalyzeFile:\r
10672         ExitAnalyzeMode();\r
10673         break;\r
10674         \r
10675       default:\r
10676         EditGameEvent();\r
10677         break;\r
10678     }\r
10679 \r
10680     gameMode = IcsIdle;\r
10681     ModeHighlight();\r
10682     return;\r
10683 }\r
10684 \r
10685 \r
10686 void\r
10687 EditGameEvent()\r
10688 {\r
10689     int i;\r
10690 \r
10691     switch (gameMode) {\r
10692       case Training:\r
10693         SetTrainingModeOff();\r
10694         break;\r
10695       case MachinePlaysWhite:\r
10696       case MachinePlaysBlack:\r
10697       case BeginningOfGame:\r
10698         SendToProgram("force\n", &first);\r
10699         SetUserThinkingEnables();\r
10700         break;\r
10701       case PlayFromGameFile:\r
10702         (void) StopLoadGameTimer();\r
10703         if (gameFileFP != NULL) {\r
10704             gameFileFP = NULL;\r
10705         }\r
10706         break;\r
10707       case EditPosition:\r
10708         EditPositionDone();\r
10709         break;\r
10710       case AnalyzeMode:\r
10711       case AnalyzeFile:\r
10712         ExitAnalyzeMode();\r
10713         SendToProgram("force\n", &first);\r
10714         break;\r
10715       case TwoMachinesPlay:\r
10716         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10717         ResurrectChessProgram();\r
10718         SetUserThinkingEnables();\r
10719         break;\r
10720       case EndOfGame:\r
10721         ResurrectChessProgram();\r
10722         break;\r
10723       case IcsPlayingBlack:\r
10724       case IcsPlayingWhite:\r
10725         DisplayError(_("Warning: You are still playing a game"), 0);\r
10726         break;\r
10727       case IcsObserving:\r
10728         DisplayError(_("Warning: You are still observing a game"), 0);\r
10729         break;\r
10730       case IcsExamining:\r
10731         DisplayError(_("Warning: You are still examining a game"), 0);\r
10732         break;\r
10733       case IcsIdle:\r
10734         break;\r
10735       case EditGame:\r
10736       default:\r
10737         return;\r
10738     }\r
10739     \r
10740     pausing = FALSE;\r
10741     StopClocks();\r
10742     first.offeredDraw = second.offeredDraw = 0;\r
10743 \r
10744     if (gameMode == PlayFromGameFile) {\r
10745         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10746         blackTimeRemaining = timeRemaining[1][currentMove];\r
10747         DisplayTitle("");\r
10748     }\r
10749 \r
10750     if (gameMode == MachinePlaysWhite ||\r
10751         gameMode == MachinePlaysBlack ||\r
10752         gameMode == TwoMachinesPlay ||\r
10753         gameMode == EndOfGame) {\r
10754         i = forwardMostMove;\r
10755         while (i > currentMove) {\r
10756             SendToProgram("undo\n", &first);\r
10757             i--;\r
10758         }\r
10759         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10760         blackTimeRemaining = timeRemaining[1][currentMove];\r
10761         DisplayBothClocks();\r
10762         if (whiteFlag || blackFlag) {\r
10763             whiteFlag = blackFlag = 0;\r
10764         }\r
10765         DisplayTitle("");\r
10766     }           \r
10767     \r
10768     gameMode = EditGame;\r
10769     ModeHighlight();\r
10770     SetGameInfo();\r
10771 }\r
10772 \r
10773 \r
10774 void\r
10775 EditPositionEvent()\r
10776 {\r
10777     if (gameMode == EditPosition) {\r
10778         EditGameEvent();\r
10779         return;\r
10780     }\r
10781     \r
10782     EditGameEvent();\r
10783     if (gameMode != EditGame) return;\r
10784     \r
10785     gameMode = EditPosition;\r
10786     ModeHighlight();\r
10787     SetGameInfo();\r
10788     if (currentMove > 0)\r
10789       CopyBoard(boards[0], boards[currentMove]);\r
10790     \r
10791     blackPlaysFirst = !WhiteOnMove(currentMove);\r
10792     ResetClocks();\r
10793     currentMove = forwardMostMove = backwardMostMove = 0;\r
10794     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10795     DisplayMove(-1);\r
10796 }\r
10797 \r
10798 void\r
10799 ExitAnalyzeMode()\r
10800 {\r
10801     /* [DM] icsEngineAnalyze - possible call from other functions */\r
10802     if (appData.icsEngineAnalyze) {\r
10803         appData.icsEngineAnalyze = FALSE;\r
10804 \r
10805         DisplayMessage("",_("Close ICS engine analyze..."));\r
10806     }\r
10807     if (first.analysisSupport && first.analyzing) {\r
10808       SendToProgram("exit\n", &first);\r
10809       first.analyzing = FALSE;\r
10810     }\r
10811     AnalysisPopDown();\r
10812     thinkOutput[0] = NULLCHAR;\r
10813 }\r
10814 \r
10815 void\r
10816 EditPositionDone()\r
10817 {\r
10818     startedFromSetupPosition = TRUE;\r
10819     InitChessProgram(&first, FALSE);\r
10820     SendToProgram("force\n", &first);\r
10821     if (blackPlaysFirst) {\r
10822         strcpy(moveList[0], "");\r
10823         strcpy(parseList[0], "");\r
10824         currentMove = forwardMostMove = backwardMostMove = 1;\r
10825         CopyBoard(boards[1], boards[0]);\r
10826         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */\r
10827         { int i;\r
10828           epStatus[1] = epStatus[0];\r
10829           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];\r
10830         }\r
10831     } else {\r
10832         currentMove = forwardMostMove = backwardMostMove = 0;\r
10833     }\r
10834     SendBoard(&first, forwardMostMove);\r
10835     if (appData.debugMode) {\r
10836         fprintf(debugFP, "EditPosDone\n");\r
10837     }\r
10838     DisplayTitle("");\r
10839     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10840     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10841     gameMode = EditGame;\r
10842     ModeHighlight();\r
10843     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10844     ClearHighlights(); /* [AS] */\r
10845 }\r
10846 \r
10847 /* Pause for `ms' milliseconds */\r
10848 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10849 void\r
10850 TimeDelay(ms)\r
10851      long ms;\r
10852 {\r
10853     TimeMark m1, m2;\r
10854 \r
10855     GetTimeMark(&m1);\r
10856     do {\r
10857         GetTimeMark(&m2);\r
10858     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
10859 }\r
10860 \r
10861 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10862 void\r
10863 SendMultiLineToICS(buf)\r
10864      char *buf;\r
10865 {\r
10866     char temp[MSG_SIZ+1], *p;\r
10867     int len;\r
10868 \r
10869     len = strlen(buf);\r
10870     if (len > MSG_SIZ)\r
10871       len = MSG_SIZ;\r
10872   \r
10873     strncpy(temp, buf, len);\r
10874     temp[len] = 0;\r
10875 \r
10876     p = temp;\r
10877     while (*p) {\r
10878         if (*p == '\n' || *p == '\r')\r
10879           *p = ' ';\r
10880         ++p;\r
10881     }\r
10882 \r
10883     strcat(temp, "\n");\r
10884     SendToICS(temp);\r
10885     SendToPlayer(temp, strlen(temp));\r
10886 }\r
10887 \r
10888 void\r
10889 SetWhiteToPlayEvent()\r
10890 {\r
10891     if (gameMode == EditPosition) {\r
10892         blackPlaysFirst = FALSE;\r
10893         DisplayBothClocks();    /* works because currentMove is 0 */\r
10894     } else if (gameMode == IcsExamining) {\r
10895         SendToICS(ics_prefix);\r
10896         SendToICS("tomove white\n");\r
10897     }\r
10898 }\r
10899 \r
10900 void\r
10901 SetBlackToPlayEvent()\r
10902 {\r
10903     if (gameMode == EditPosition) {\r
10904         blackPlaysFirst = TRUE;\r
10905         currentMove = 1;        /* kludge */\r
10906         DisplayBothClocks();\r
10907         currentMove = 0;\r
10908     } else if (gameMode == IcsExamining) {\r
10909         SendToICS(ics_prefix);\r
10910         SendToICS("tomove black\n");\r
10911     }\r
10912 }\r
10913 \r
10914 void\r
10915 EditPositionMenuEvent(selection, x, y)\r
10916      ChessSquare selection;\r
10917      int x, y;\r
10918 {\r
10919     char buf[MSG_SIZ];\r
10920     ChessSquare piece = boards[0][y][x];\r
10921 \r
10922     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
10923 \r
10924     switch (selection) {\r
10925       case ClearBoard:\r
10926         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
10927             SendToICS(ics_prefix);\r
10928             SendToICS("bsetup clear\n");\r
10929         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
10930             SendToICS(ics_prefix);\r
10931             SendToICS("clearboard\n");\r
10932         } else {\r
10933             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;\r
10934                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */\r
10935                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
10936                     if (gameMode == IcsExamining) {\r
10937                         if (boards[currentMove][y][x] != EmptySquare) {\r
10938                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
10939                                     AAA + x, ONE + y);\r
10940                             SendToICS(buf);\r
10941                         }\r
10942                     } else {\r
10943                         boards[0][y][x] = p;\r
10944                     }\r
10945                 }\r
10946             }\r
10947         }\r
10948         if (gameMode == EditPosition) {\r
10949             DrawPosition(FALSE, boards[0]);\r
10950         }\r
10951         break;\r
10952 \r
10953       case WhitePlay:\r
10954         SetWhiteToPlayEvent();\r
10955         break;\r
10956 \r
10957       case BlackPlay:\r
10958         SetBlackToPlayEvent();\r
10959         break;\r
10960 \r
10961       case EmptySquare:\r
10962         if (gameMode == IcsExamining) {\r
10963             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
10964             SendToICS(buf);\r
10965         } else {\r
10966             boards[0][y][x] = EmptySquare;\r
10967             DrawPosition(FALSE, boards[0]);\r
10968         }\r
10969         break;\r
10970 \r
10971       case PromotePiece:\r
10972         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
10973            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
10974             selection = (ChessSquare) (PROMOTED piece);\r
10975         } else if(piece == EmptySquare) selection = WhiteSilver;\r
10976         else selection = (ChessSquare)((int)piece - 1);\r
10977         goto defaultlabel;\r
10978 \r
10979       case DemotePiece:\r
10980         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
10981            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
10982             selection = (ChessSquare) (DEMOTED piece);\r
10983         } else if(piece == EmptySquare) selection = BlackSilver;\r
10984         else selection = (ChessSquare)((int)piece + 1);       \r
10985         goto defaultlabel;\r
10986 \r
10987       case WhiteQueen:\r
10988       case BlackQueen:\r
10989         if(gameInfo.variant == VariantShatranj ||\r
10990            gameInfo.variant == VariantXiangqi  ||\r
10991            gameInfo.variant == VariantCourier    )\r
10992             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
10993         goto defaultlabel;\r
10994 \r
10995       case WhiteKing:\r
10996       case BlackKing:\r
10997         if(gameInfo.variant == VariantXiangqi)\r
10998             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
10999         if(gameInfo.variant == VariantKnightmate)\r
11000             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
11001       default:\r
11002         defaultlabel:\r
11003         if (gameMode == IcsExamining) {\r
11004             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
11005                     PieceToChar(selection), AAA + x, ONE + y);\r
11006             SendToICS(buf);\r
11007         } else {\r
11008             boards[0][y][x] = selection;\r
11009             DrawPosition(FALSE, boards[0]);\r
11010         }\r
11011         break;\r
11012     }\r
11013 }\r
11014 \r
11015 \r
11016 void\r
11017 DropMenuEvent(selection, x, y)\r
11018      ChessSquare selection;\r
11019      int x, y;\r
11020 {\r
11021     ChessMove moveType;\r
11022 \r
11023     switch (gameMode) {\r
11024       case IcsPlayingWhite:\r
11025       case MachinePlaysBlack:\r
11026         if (!WhiteOnMove(currentMove)) {\r
11027             DisplayMoveError(_("It is Black's turn"));\r
11028             return;\r
11029         }\r
11030         moveType = WhiteDrop;\r
11031         break;\r
11032       case IcsPlayingBlack:\r
11033       case MachinePlaysWhite:\r
11034         if (WhiteOnMove(currentMove)) {\r
11035             DisplayMoveError(_("It is White's turn"));\r
11036             return;\r
11037         }\r
11038         moveType = BlackDrop;\r
11039         break;\r
11040       case EditGame:\r
11041         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
11042         break;\r
11043       default:\r
11044         return;\r
11045     }\r
11046 \r
11047     if (moveType == BlackDrop && selection < BlackPawn) {\r
11048       selection = (ChessSquare) ((int) selection\r
11049                                  + (int) BlackPawn - (int) WhitePawn);\r
11050     }\r
11051     if (boards[currentMove][y][x] != EmptySquare) {\r
11052         DisplayMoveError(_("That square is occupied"));\r
11053         return;\r
11054     }\r
11055 \r
11056     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
11057 }\r
11058 \r
11059 void\r
11060 AcceptEvent()\r
11061 {\r
11062     /* Accept a pending offer of any kind from opponent */\r
11063     \r
11064     if (appData.icsActive) {\r
11065         SendToICS(ics_prefix);\r
11066         SendToICS("accept\n");\r
11067     } else if (cmailMsgLoaded) {\r
11068         if (currentMove == cmailOldMove &&\r
11069             commentList[cmailOldMove] != NULL &&\r
11070             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11071                    "Black offers a draw" : "White offers a draw")) {\r
11072             TruncateGame();\r
11073             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11074             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11075         } else {\r
11076             DisplayError(_("There is no pending offer on this move"), 0);\r
11077             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11078         }\r
11079     } else {\r
11080         /* Not used for offers from chess program */\r
11081     }\r
11082 }\r
11083 \r
11084 void\r
11085 DeclineEvent()\r
11086 {\r
11087     /* Decline a pending offer of any kind from opponent */\r
11088     \r
11089     if (appData.icsActive) {\r
11090         SendToICS(ics_prefix);\r
11091         SendToICS("decline\n");\r
11092     } else if (cmailMsgLoaded) {\r
11093         if (currentMove == cmailOldMove &&\r
11094             commentList[cmailOldMove] != NULL &&\r
11095             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11096                    "Black offers a draw" : "White offers a draw")) {\r
11097 #ifdef NOTDEF\r
11098             AppendComment(cmailOldMove, "Draw declined");\r
11099             DisplayComment(cmailOldMove - 1, "Draw declined");\r
11100 #endif /*NOTDEF*/\r
11101         } else {\r
11102             DisplayError(_("There is no pending offer on this move"), 0);\r
11103         }\r
11104     } else {\r
11105         /* Not used for offers from chess program */\r
11106     }\r
11107 }\r
11108 \r
11109 void\r
11110 RematchEvent()\r
11111 {\r
11112     /* Issue ICS rematch command */\r
11113     if (appData.icsActive) {\r
11114         SendToICS(ics_prefix);\r
11115         SendToICS("rematch\n");\r
11116     }\r
11117 }\r
11118 \r
11119 void\r
11120 CallFlagEvent()\r
11121 {\r
11122     /* Call your opponent's flag (claim a win on time) */\r
11123     if (appData.icsActive) {\r
11124         SendToICS(ics_prefix);\r
11125         SendToICS("flag\n");\r
11126     } else {\r
11127         switch (gameMode) {\r
11128           default:\r
11129             return;\r
11130           case MachinePlaysWhite:\r
11131             if (whiteFlag) {\r
11132                 if (blackFlag)\r
11133                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11134                            GE_PLAYER);\r
11135                 else\r
11136                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
11137             } else {\r
11138                 DisplayError(_("Your opponent is not out of time"), 0);\r
11139             }\r
11140             break;\r
11141           case MachinePlaysBlack:\r
11142             if (blackFlag) {\r
11143                 if (whiteFlag)\r
11144                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11145                            GE_PLAYER);\r
11146                 else\r
11147                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
11148             } else {\r
11149                 DisplayError(_("Your opponent is not out of time"), 0);\r
11150             }\r
11151             break;\r
11152         }\r
11153     }\r
11154 }\r
11155 \r
11156 void\r
11157 DrawEvent()\r
11158 {\r
11159     /* Offer draw or accept pending draw offer from opponent */\r
11160     \r
11161     if (appData.icsActive) {\r
11162         /* Note: tournament rules require draw offers to be\r
11163            made after you make your move but before you punch\r
11164            your clock.  Currently ICS doesn't let you do that;\r
11165            instead, you immediately punch your clock after making\r
11166            a move, but you can offer a draw at any time. */\r
11167         \r
11168         SendToICS(ics_prefix);\r
11169         SendToICS("draw\n");\r
11170     } else if (cmailMsgLoaded) {\r
11171         if (currentMove == cmailOldMove &&\r
11172             commentList[cmailOldMove] != NULL &&\r
11173             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11174                    "Black offers a draw" : "White offers a draw")) {\r
11175             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11176             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11177         } else if (currentMove == cmailOldMove + 1) {\r
11178             char *offer = WhiteOnMove(cmailOldMove) ?\r
11179               "White offers a draw" : "Black offers a draw";\r
11180             AppendComment(currentMove, offer);\r
11181             DisplayComment(currentMove - 1, offer);\r
11182             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
11183         } else {\r
11184             DisplayError(_("You must make your move before offering a draw"), 0);\r
11185             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11186         }\r
11187     } else if (first.offeredDraw) {\r
11188         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
11189     } else {\r
11190         if (first.sendDrawOffers) {\r
11191             SendToProgram("draw\n", &first);\r
11192             userOfferedDraw = TRUE;\r
11193         }\r
11194     }\r
11195 }\r
11196 \r
11197 void\r
11198 AdjournEvent()\r
11199 {\r
11200     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
11201     \r
11202     if (appData.icsActive) {\r
11203         SendToICS(ics_prefix);\r
11204         SendToICS("adjourn\n");\r
11205     } else {\r
11206         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
11207     }\r
11208 }\r
11209 \r
11210 \r
11211 void\r
11212 AbortEvent()\r
11213 {\r
11214     /* Offer Abort or accept pending Abort offer from opponent */\r
11215     \r
11216     if (appData.icsActive) {\r
11217         SendToICS(ics_prefix);\r
11218         SendToICS("abort\n");\r
11219     } else {\r
11220         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
11221     }\r
11222 }\r
11223 \r
11224 void\r
11225 ResignEvent()\r
11226 {\r
11227     /* Resign.  You can do this even if it's not your turn. */\r
11228     \r
11229     if (appData.icsActive) {\r
11230         SendToICS(ics_prefix);\r
11231         SendToICS("resign\n");\r
11232     } else {\r
11233         switch (gameMode) {\r
11234           case MachinePlaysWhite:\r
11235             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11236             break;\r
11237           case MachinePlaysBlack:\r
11238             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11239             break;\r
11240           case EditGame:\r
11241             if (cmailMsgLoaded) {\r
11242                 TruncateGame();\r
11243                 if (WhiteOnMove(cmailOldMove)) {\r
11244                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11245                 } else {\r
11246                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11247                 }\r
11248                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
11249             }\r
11250             break;\r
11251           default:\r
11252             break;\r
11253         }\r
11254     }\r
11255 }\r
11256 \r
11257 \r
11258 void\r
11259 StopObservingEvent()\r
11260 {\r
11261     /* Stop observing current games */\r
11262     SendToICS(ics_prefix);\r
11263     SendToICS("unobserve\n");\r
11264 }\r
11265 \r
11266 void\r
11267 StopExaminingEvent()\r
11268 {\r
11269     /* Stop observing current game */\r
11270     SendToICS(ics_prefix);\r
11271     SendToICS("unexamine\n");\r
11272 }\r
11273 \r
11274 void\r
11275 ForwardInner(target)\r
11276      int target;\r
11277 {\r
11278     int limit;\r
11279 \r
11280     if (appData.debugMode)\r
11281         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
11282                 target, currentMove, forwardMostMove);\r
11283 \r
11284     if (gameMode == EditPosition)\r
11285       return;\r
11286 \r
11287     if (gameMode == PlayFromGameFile && !pausing)\r
11288       PauseEvent();\r
11289     \r
11290     if (gameMode == IcsExamining && pausing)\r
11291       limit = pauseExamForwardMostMove;\r
11292     else\r
11293       limit = forwardMostMove;\r
11294     \r
11295     if (target > limit) target = limit;\r
11296 \r
11297     if (target > 0 && moveList[target - 1][0]) {\r
11298         int fromX, fromY, toX, toY;\r
11299         toX = moveList[target - 1][2] - AAA;\r
11300         toY = moveList[target - 1][3] - ONE;\r
11301         if (moveList[target - 1][1] == '@') {\r
11302             if (appData.highlightLastMove) {\r
11303                 SetHighlights(-1, -1, toX, toY);\r
11304             }\r
11305         } else {\r
11306             fromX = moveList[target - 1][0] - AAA;\r
11307             fromY = moveList[target - 1][1] - ONE;\r
11308             if (target == currentMove + 1) {\r
11309                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
11310             }\r
11311             if (appData.highlightLastMove) {\r
11312                 SetHighlights(fromX, fromY, toX, toY);\r
11313             }\r
11314         }\r
11315     }\r
11316     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
11317         gameMode == Training || gameMode == PlayFromGameFile || \r
11318         gameMode == AnalyzeFile) {\r
11319         while (currentMove < target) {\r
11320             SendMoveToProgram(currentMove++, &first);\r
11321         }\r
11322     } else {\r
11323         currentMove = target;\r
11324     }\r
11325     \r
11326     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11327         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11328         blackTimeRemaining = timeRemaining[1][currentMove];\r
11329     }\r
11330     DisplayBothClocks();\r
11331     DisplayMove(currentMove - 1);\r
11332     DrawPosition(FALSE, boards[currentMove]);\r
11333     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11334     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
11335         DisplayComment(currentMove - 1, commentList[currentMove]);\r
11336     }\r
11337 }\r
11338 \r
11339 \r
11340 void\r
11341 ForwardEvent()\r
11342 {\r
11343     if (gameMode == IcsExamining && !pausing) {\r
11344         SendToICS(ics_prefix);\r
11345         SendToICS("forward\n");\r
11346     } else {\r
11347         ForwardInner(currentMove + 1);\r
11348     }\r
11349 }\r
11350 \r
11351 void\r
11352 ToEndEvent()\r
11353 {\r
11354     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11355         /* to optimze, we temporarily turn off analysis mode while we feed\r
11356          * the remaining moves to the engine. Otherwise we get analysis output\r
11357          * after each move.\r
11358          */ \r
11359         if (first.analysisSupport) {\r
11360           SendToProgram("exit\nforce\n", &first);\r
11361           first.analyzing = FALSE;\r
11362         }\r
11363     }\r
11364         \r
11365     if (gameMode == IcsExamining && !pausing) {\r
11366         SendToICS(ics_prefix);\r
11367         SendToICS("forward 999999\n");\r
11368     } else {\r
11369         ForwardInner(forwardMostMove);\r
11370     }\r
11371 \r
11372     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11373         /* we have fed all the moves, so reactivate analysis mode */\r
11374         SendToProgram("analyze\n", &first);\r
11375         first.analyzing = TRUE;\r
11376         /*first.maybeThinking = TRUE;*/\r
11377         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11378     }\r
11379 }\r
11380 \r
11381 void\r
11382 BackwardInner(target)\r
11383      int target;\r
11384 {\r
11385     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
11386 \r
11387     if (appData.debugMode)\r
11388         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
11389                 target, currentMove, forwardMostMove);\r
11390 \r
11391     if (gameMode == EditPosition) return;\r
11392     if (currentMove <= backwardMostMove) {\r
11393         ClearHighlights();\r
11394         DrawPosition(full_redraw, boards[currentMove]);\r
11395         return;\r
11396     }\r
11397     if (gameMode == PlayFromGameFile && !pausing)\r
11398       PauseEvent();\r
11399     \r
11400     if (moveList[target][0]) {\r
11401         int fromX, fromY, toX, toY;\r
11402         toX = moveList[target][2] - AAA;\r
11403         toY = moveList[target][3] - ONE;\r
11404         if (moveList[target][1] == '@') {\r
11405             if (appData.highlightLastMove) {\r
11406                 SetHighlights(-1, -1, toX, toY);\r
11407             }\r
11408         } else {\r
11409             fromX = moveList[target][0] - AAA;\r
11410             fromY = moveList[target][1] - ONE;\r
11411             if (target == currentMove - 1) {\r
11412                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
11413             }\r
11414             if (appData.highlightLastMove) {\r
11415                 SetHighlights(fromX, fromY, toX, toY);\r
11416             }\r
11417         }\r
11418     }\r
11419     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
11420         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
11421         while (currentMove > target) {\r
11422             SendToProgram("undo\n", &first);\r
11423             currentMove--;\r
11424         }\r
11425     } else {\r
11426         currentMove = target;\r
11427     }\r
11428     \r
11429     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11430         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11431         blackTimeRemaining = timeRemaining[1][currentMove];\r
11432     }\r
11433     DisplayBothClocks();\r
11434     DisplayMove(currentMove - 1);\r
11435     DrawPosition(full_redraw, boards[currentMove]);\r
11436     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11437     // [HGM] PV info: routine tests if comment empty\r
11438     DisplayComment(currentMove - 1, commentList[currentMove]);\r
11439 }\r
11440 \r
11441 void\r
11442 BackwardEvent()\r
11443 {\r
11444     if (gameMode == IcsExamining && !pausing) {\r
11445         SendToICS(ics_prefix);\r
11446         SendToICS("backward\n");\r
11447     } else {\r
11448         BackwardInner(currentMove - 1);\r
11449     }\r
11450 }\r
11451 \r
11452 void\r
11453 ToStartEvent()\r
11454 {\r
11455     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11456         /* to optimze, we temporarily turn off analysis mode while we undo\r
11457          * all the moves. Otherwise we get analysis output after each undo.\r
11458          */ \r
11459         if (first.analysisSupport) {\r
11460           SendToProgram("exit\nforce\n", &first);\r
11461           first.analyzing = FALSE;\r
11462         }\r
11463     }\r
11464 \r
11465     if (gameMode == IcsExamining && !pausing) {\r
11466         SendToICS(ics_prefix);\r
11467         SendToICS("backward 999999\n");\r
11468     } else {\r
11469         BackwardInner(backwardMostMove);\r
11470     }\r
11471 \r
11472     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11473         /* we have fed all the moves, so reactivate analysis mode */\r
11474         SendToProgram("analyze\n", &first);\r
11475         first.analyzing = TRUE;\r
11476         /*first.maybeThinking = TRUE;*/\r
11477         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11478     }\r
11479 }\r
11480 \r
11481 void\r
11482 ToNrEvent(int to)\r
11483 {\r
11484   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
11485   if (to >= forwardMostMove) to = forwardMostMove;\r
11486   if (to <= backwardMostMove) to = backwardMostMove;\r
11487   if (to < currentMove) {\r
11488     BackwardInner(to);\r
11489   } else {\r
11490     ForwardInner(to);\r
11491   }\r
11492 }\r
11493 \r
11494 void\r
11495 RevertEvent()\r
11496 {\r
11497     if (gameMode != IcsExamining) {\r
11498         DisplayError(_("You are not examining a game"), 0);\r
11499         return;\r
11500     }\r
11501     if (pausing) {\r
11502         DisplayError(_("You can't revert while pausing"), 0);\r
11503         return;\r
11504     }\r
11505     SendToICS(ics_prefix);\r
11506     SendToICS("revert\n");\r
11507 }\r
11508 \r
11509 void\r
11510 RetractMoveEvent()\r
11511 {\r
11512     switch (gameMode) {\r
11513       case MachinePlaysWhite:\r
11514       case MachinePlaysBlack:\r
11515         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
11516             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
11517             return;\r
11518         }\r
11519         if (forwardMostMove < 2) return;\r
11520         currentMove = forwardMostMove = forwardMostMove - 2;\r
11521         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11522         blackTimeRemaining = timeRemaining[1][currentMove];\r
11523         DisplayBothClocks();\r
11524         DisplayMove(currentMove - 1);\r
11525         ClearHighlights();/*!! could figure this out*/\r
11526         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
11527         SendToProgram("remove\n", &first);\r
11528         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
11529         break;\r
11530 \r
11531       case BeginningOfGame:\r
11532       default:\r
11533         break;\r
11534 \r
11535       case IcsPlayingWhite:\r
11536       case IcsPlayingBlack:\r
11537         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
11538             SendToICS(ics_prefix);\r
11539             SendToICS("takeback 2\n");\r
11540         } else {\r
11541             SendToICS(ics_prefix);\r
11542             SendToICS("takeback 1\n");\r
11543         }\r
11544         break;\r
11545     }\r
11546 }\r
11547 \r
11548 void\r
11549 MoveNowEvent()\r
11550 {\r
11551     ChessProgramState *cps;\r
11552 \r
11553     switch (gameMode) {\r
11554       case MachinePlaysWhite:\r
11555         if (!WhiteOnMove(forwardMostMove)) {\r
11556             DisplayError(_("It is your turn"), 0);\r
11557             return;\r
11558         }\r
11559         cps = &first;\r
11560         break;\r
11561       case MachinePlaysBlack:\r
11562         if (WhiteOnMove(forwardMostMove)) {\r
11563             DisplayError(_("It is your turn"), 0);\r
11564             return;\r
11565         }\r
11566         cps = &first;\r
11567         break;\r
11568       case TwoMachinesPlay:\r
11569         if (WhiteOnMove(forwardMostMove) ==\r
11570             (first.twoMachinesColor[0] == 'w')) {\r
11571             cps = &first;\r
11572         } else {\r
11573             cps = &second;\r
11574         }\r
11575         break;\r
11576       case BeginningOfGame:\r
11577       default:\r
11578         return;\r
11579     }\r
11580     SendToProgram("?\n", cps);\r
11581 }\r
11582 \r
11583 void\r
11584 TruncateGameEvent()\r
11585 {\r
11586     EditGameEvent();\r
11587     if (gameMode != EditGame) return;\r
11588     TruncateGame();\r
11589 }\r
11590 \r
11591 void\r
11592 TruncateGame()\r
11593 {\r
11594     if (forwardMostMove > currentMove) {\r
11595         if (gameInfo.resultDetails != NULL) {\r
11596             free(gameInfo.resultDetails);\r
11597             gameInfo.resultDetails = NULL;\r
11598             gameInfo.result = GameUnfinished;\r
11599         }\r
11600         forwardMostMove = currentMove;\r
11601         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
11602                    currentMove-1);\r
11603     }\r
11604 }\r
11605 \r
11606 void\r
11607 HintEvent()\r
11608 {\r
11609     if (appData.noChessProgram) return;\r
11610     switch (gameMode) {\r
11611       case MachinePlaysWhite:\r
11612         if (WhiteOnMove(forwardMostMove)) {\r
11613             DisplayError(_("Wait until your turn"), 0);\r
11614             return;\r
11615         }\r
11616         break;\r
11617       case BeginningOfGame:\r
11618       case MachinePlaysBlack:\r
11619         if (!WhiteOnMove(forwardMostMove)) {\r
11620             DisplayError(_("Wait until your turn"), 0);\r
11621             return;\r
11622         }\r
11623         break;\r
11624       default:\r
11625         DisplayError(_("No hint available"), 0);\r
11626         return;\r
11627     }\r
11628     SendToProgram("hint\n", &first);\r
11629     hintRequested = TRUE;\r
11630 }\r
11631 \r
11632 void\r
11633 BookEvent()\r
11634 {\r
11635     if (appData.noChessProgram) return;\r
11636     switch (gameMode) {\r
11637       case MachinePlaysWhite:\r
11638         if (WhiteOnMove(forwardMostMove)) {\r
11639             DisplayError(_("Wait until your turn"), 0);\r
11640             return;\r
11641         }\r
11642         break;\r
11643       case BeginningOfGame:\r
11644       case MachinePlaysBlack:\r
11645         if (!WhiteOnMove(forwardMostMove)) {\r
11646             DisplayError(_("Wait until your turn"), 0);\r
11647             return;\r
11648         }\r
11649         break;\r
11650       case EditPosition:\r
11651         EditPositionDone();\r
11652         break;\r
11653       case TwoMachinesPlay:\r
11654         return;\r
11655       default:\r
11656         break;\r
11657     }\r
11658     SendToProgram("bk\n", &first);\r
11659     bookOutput[0] = NULLCHAR;\r
11660     bookRequested = TRUE;\r
11661 }\r
11662 \r
11663 void\r
11664 AboutGameEvent()\r
11665 {\r
11666     char *tags = PGNTags(&gameInfo);\r
11667     TagsPopUp(tags, CmailMsg());\r
11668     free(tags);\r
11669 }\r
11670 \r
11671 /* end button procedures */\r
11672 \r
11673 void\r
11674 PrintPosition(fp, move)\r
11675      FILE *fp;\r
11676      int move;\r
11677 {\r
11678     int i, j;\r
11679     \r
11680     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11681         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11682             char c = PieceToChar(boards[move][i][j]);\r
11683             fputc(c == 'x' ? '.' : c, fp);\r
11684             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
11685         }\r
11686     }\r
11687     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
11688       fprintf(fp, "white to play\n");\r
11689     else\r
11690       fprintf(fp, "black to play\n");\r
11691 }\r
11692 \r
11693 void\r
11694 PrintOpponents(fp)\r
11695      FILE *fp;\r
11696 {\r
11697     if (gameInfo.white != NULL) {\r
11698         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
11699     } else {\r
11700         fprintf(fp, "\n");\r
11701     }\r
11702 }\r
11703 \r
11704 /* Find last component of program's own name, using some heuristics */\r
11705 void\r
11706 TidyProgramName(prog, host, buf)\r
11707      char *prog, *host, buf[MSG_SIZ];\r
11708 {\r
11709     char *p, *q;\r
11710     int local = (strcmp(host, "localhost") == 0);\r
11711     while (!local && (p = strchr(prog, ';')) != NULL) {\r
11712         p++;\r
11713         while (*p == ' ') p++;\r
11714         prog = p;\r
11715     }\r
11716     if (*prog == '"' || *prog == '\'') {\r
11717         q = strchr(prog + 1, *prog);\r
11718     } else {\r
11719         q = strchr(prog, ' ');\r
11720     }\r
11721     if (q == NULL) q = prog + strlen(prog);\r
11722     p = q;\r
11723     while (p >= prog && *p != '/' && *p != '\\') p--;\r
11724     p++;\r
11725     if(p == prog && *p == '"') p++;\r
11726     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
11727     memcpy(buf, p, q - p);\r
11728     buf[q - p] = NULLCHAR;\r
11729     if (!local) {\r
11730         strcat(buf, "@");\r
11731         strcat(buf, host);\r
11732     }\r
11733 }\r
11734 \r
11735 char *\r
11736 TimeControlTagValue()\r
11737 {\r
11738     char buf[MSG_SIZ];\r
11739     if (!appData.clockMode) {\r
11740         strcpy(buf, "-");\r
11741     } else if (movesPerSession > 0) {\r
11742         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
11743     } else if (timeIncrement == 0) {\r
11744         sprintf(buf, "%ld", timeControl/1000);\r
11745     } else {\r
11746         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
11747     }\r
11748     return StrSave(buf);\r
11749 }\r
11750 \r
11751 void\r
11752 SetGameInfo()\r
11753 {\r
11754     /* This routine is used only for certain modes */\r
11755     VariantClass v = gameInfo.variant;\r
11756     ClearGameInfo(&gameInfo);\r
11757     gameInfo.variant = v;\r
11758 \r
11759     switch (gameMode) {\r
11760       case MachinePlaysWhite:\r
11761         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11762         gameInfo.site = StrSave(HostName());\r
11763         gameInfo.date = PGNDate();\r
11764         gameInfo.round = StrSave("-");\r
11765         gameInfo.white = StrSave(first.tidy);\r
11766         gameInfo.black = StrSave(UserName());\r
11767         gameInfo.timeControl = TimeControlTagValue();\r
11768         break;\r
11769 \r
11770       case MachinePlaysBlack:\r
11771         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11772         gameInfo.site = StrSave(HostName());\r
11773         gameInfo.date = PGNDate();\r
11774         gameInfo.round = StrSave("-");\r
11775         gameInfo.white = StrSave(UserName());\r
11776         gameInfo.black = StrSave(first.tidy);\r
11777         gameInfo.timeControl = TimeControlTagValue();\r
11778         break;\r
11779 \r
11780       case TwoMachinesPlay:\r
11781         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11782         gameInfo.site = StrSave(HostName());\r
11783         gameInfo.date = PGNDate();\r
11784         if (matchGame > 0) {\r
11785             char buf[MSG_SIZ];\r
11786             sprintf(buf, "%d", matchGame);\r
11787             gameInfo.round = StrSave(buf);\r
11788         } else {\r
11789             gameInfo.round = StrSave("-");\r
11790         }\r
11791         if (first.twoMachinesColor[0] == 'w') {\r
11792             gameInfo.white = StrSave(first.tidy);\r
11793             gameInfo.black = StrSave(second.tidy);\r
11794         } else {\r
11795             gameInfo.white = StrSave(second.tidy);\r
11796             gameInfo.black = StrSave(first.tidy);\r
11797         }\r
11798         gameInfo.timeControl = TimeControlTagValue();\r
11799         break;\r
11800 \r
11801       case EditGame:\r
11802         gameInfo.event = StrSave("Edited game");\r
11803         gameInfo.site = StrSave(HostName());\r
11804         gameInfo.date = PGNDate();\r
11805         gameInfo.round = StrSave("-");\r
11806         gameInfo.white = StrSave("-");\r
11807         gameInfo.black = StrSave("-");\r
11808         break;\r
11809 \r
11810       case EditPosition:\r
11811         gameInfo.event = StrSave("Edited position");\r
11812         gameInfo.site = StrSave(HostName());\r
11813         gameInfo.date = PGNDate();\r
11814         gameInfo.round = StrSave("-");\r
11815         gameInfo.white = StrSave("-");\r
11816         gameInfo.black = StrSave("-");\r
11817         break;\r
11818 \r
11819       case IcsPlayingWhite:\r
11820       case IcsPlayingBlack:\r
11821       case IcsObserving:\r
11822       case IcsExamining:\r
11823         break;\r
11824 \r
11825       case PlayFromGameFile:\r
11826         gameInfo.event = StrSave("Game from non-PGN file");\r
11827         gameInfo.site = StrSave(HostName());\r
11828         gameInfo.date = PGNDate();\r
11829         gameInfo.round = StrSave("-");\r
11830         gameInfo.white = StrSave("?");\r
11831         gameInfo.black = StrSave("?");\r
11832         break;\r
11833 \r
11834       default:\r
11835         break;\r
11836     }\r
11837 }\r
11838 \r
11839 void\r
11840 ReplaceComment(index, text)\r
11841      int index;\r
11842      char *text;\r
11843 {\r
11844     int len;\r
11845 \r
11846     while (*text == '\n') text++;\r
11847     len = strlen(text);\r
11848     while (len > 0 && text[len - 1] == '\n') len--;\r
11849 \r
11850     if (commentList[index] != NULL)\r
11851       free(commentList[index]);\r
11852 \r
11853     if (len == 0) {\r
11854         commentList[index] = NULL;\r
11855         return;\r
11856     }\r
11857     commentList[index] = (char *) malloc(len + 2);\r
11858     strncpy(commentList[index], text, len);\r
11859     commentList[index][len] = '\n';\r
11860     commentList[index][len + 1] = NULLCHAR;\r
11861 }\r
11862 \r
11863 void\r
11864 CrushCRs(text)\r
11865      char *text;\r
11866 {\r
11867   char *p = text;\r
11868   char *q = text;\r
11869   char ch;\r
11870 \r
11871   do {\r
11872     ch = *p++;\r
11873     if (ch == '\r') continue;\r
11874     *q++ = ch;\r
11875   } while (ch != '\0');\r
11876 }\r
11877 \r
11878 void\r
11879 AppendComment(index, text)\r
11880      int index;\r
11881      char *text;\r
11882 {\r
11883     int oldlen, len;\r
11884     char *old;\r
11885 \r
11886     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11887 \r
11888     CrushCRs(text);\r
11889     while (*text == '\n') text++;\r
11890     len = strlen(text);\r
11891     while (len > 0 && text[len - 1] == '\n') len--;\r
11892 \r
11893     if (len == 0) return;\r
11894 \r
11895     if (commentList[index] != NULL) {\r
11896         old = commentList[index];\r
11897         oldlen = strlen(old);\r
11898         commentList[index] = (char *) malloc(oldlen + len + 2);\r
11899         strcpy(commentList[index], old);\r
11900         free(old);\r
11901         strncpy(&commentList[index][oldlen], text, len);\r
11902         commentList[index][oldlen + len] = '\n';\r
11903         commentList[index][oldlen + len + 1] = NULLCHAR;\r
11904     } else {\r
11905         commentList[index] = (char *) malloc(len + 2);\r
11906         strncpy(commentList[index], text, len);\r
11907         commentList[index][len] = '\n';\r
11908         commentList[index][len + 1] = NULLCHAR;\r
11909     }\r
11910 }\r
11911 \r
11912 static char * FindStr( char * text, char * sub_text )\r
11913 {\r
11914     char * result = strstr( text, sub_text );\r
11915 \r
11916     if( result != NULL ) {\r
11917         result += strlen( sub_text );\r
11918     }\r
11919 \r
11920     return result;\r
11921 }\r
11922 \r
11923 /* [AS] Try to extract PV info from PGN comment */\r
11924 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
11925 char *GetInfoFromComment( int index, char * text )\r
11926 {\r
11927     char * sep = text;\r
11928 \r
11929     if( text != NULL && index > 0 ) {\r
11930         int score = 0;\r
11931         int depth = 0;\r
11932         int time = -1, sec = 0, deci;\r
11933         char * s_eval = FindStr( text, "[%eval " );\r
11934         char * s_emt = FindStr( text, "[%emt " );\r
11935 \r
11936         if( s_eval != NULL || s_emt != NULL ) {\r
11937             /* New style */\r
11938             char delim;\r
11939 \r
11940             if( s_eval != NULL ) {\r
11941                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
11942                     return text;\r
11943                 }\r
11944 \r
11945                 if( delim != ']' ) {\r
11946                     return text;\r
11947                 }\r
11948             }\r
11949 \r
11950             if( s_emt != NULL ) {\r
11951             }\r
11952         }\r
11953         else {\r
11954             /* We expect something like: [+|-]nnn.nn/dd */\r
11955             int score_lo = 0;\r
11956 \r
11957             sep = strchr( text, '/' );\r
11958             if( sep == NULL || sep < (text+4) ) {\r
11959                 return text;\r
11960             }\r
11961 \r
11962             time = -1; sec = -1; deci = -1;\r
11963             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
11964                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&\r
11965                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
11966                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
11967                 return text;\r
11968             }\r
11969 \r
11970             if( score_lo < 0 || score_lo >= 100 ) {\r
11971                 return text;\r
11972             }\r
11973 \r
11974             if(sec >= 0) time = 600*time + 10*sec; else\r
11975             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec\r
11976 \r
11977             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
11978 \r
11979             /* [HGM] PV time: now locate end of PV info */\r
11980             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
11981             if(time >= 0)\r
11982             while( *++sep >= '0' && *sep <= '9'); // strip time\r
11983             if(sec >= 0)\r
11984             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
11985             if(deci >= 0)\r
11986             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds\r
11987             while(*sep == ' ') sep++;\r
11988         }\r
11989 \r
11990         if( depth <= 0 ) {\r
11991             return text;\r
11992         }\r
11993 \r
11994         if( time < 0 ) {\r
11995             time = -1;\r
11996         }\r
11997 \r
11998         pvInfoList[index-1].depth = depth;\r
11999         pvInfoList[index-1].score = score;\r
12000         pvInfoList[index-1].time  = 10*time; // centi-sec\r
12001     }\r
12002     return sep;\r
12003 }\r
12004 \r
12005 void\r
12006 SendToProgram(message, cps)\r
12007      char *message;\r
12008      ChessProgramState *cps;\r
12009 {\r
12010     int count, outCount, error;\r
12011     char buf[MSG_SIZ];\r
12012 \r
12013     if (cps->pr == NULL) return;\r
12014     Attention(cps);\r
12015     \r
12016     if (appData.debugMode) {\r
12017         TimeMark now;\r
12018         GetTimeMark(&now);\r
12019         fprintf(debugFP, "%ld >%-6s: %s", \r
12020                 SubtractTimeMarks(&now, &programStartTime),\r
12021                 cps->which, message);\r
12022     }\r
12023     \r
12024     count = strlen(message);\r
12025     outCount = OutputToProcess(cps->pr, message, count, &error);\r
12026     if (outCount < count && !exiting \r
12027                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
12028         sprintf(buf, _("Error writing to %s chess program"), cps->which);\r
12029         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12030             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12031                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12032                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
12033             } else {\r
12034                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12035             }\r
12036             gameInfo.resultDetails = buf;\r
12037         }\r
12038         DisplayFatalError(buf, error, 1);\r
12039     }\r
12040 }\r
12041 \r
12042 void\r
12043 ReceiveFromProgram(isr, closure, message, count, error)\r
12044      InputSourceRef isr;\r
12045      VOIDSTAR closure;\r
12046      char *message;\r
12047      int count;\r
12048      int error;\r
12049 {\r
12050     char *end_str;\r
12051     char buf[MSG_SIZ];\r
12052     ChessProgramState *cps = (ChessProgramState *)closure;\r
12053 \r
12054     if (isr != cps->isr) return; /* Killed intentionally */\r
12055     if (count <= 0) {\r
12056         if (count == 0) {\r
12057             sprintf(buf,\r
12058                     _("Error: %s chess program (%s) exited unexpectedly"),\r
12059                     cps->which, cps->program);\r
12060         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12061                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12062                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12063                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);\r
12064                 } else {\r
12065                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12066                 }\r
12067                 gameInfo.resultDetails = buf;\r
12068             }\r
12069             RemoveInputSource(cps->isr);\r
12070             DisplayFatalError(buf, 0, 1);\r
12071         } else {\r
12072             sprintf(buf,\r
12073                     _("Error reading from %s chess program (%s)"),\r
12074                     cps->which, cps->program);\r
12075             RemoveInputSource(cps->isr);\r
12076 \r
12077             /* [AS] Program is misbehaving badly... kill it */\r
12078             if( count == -2 ) {\r
12079                 DestroyChildProcess( cps->pr, 9 );\r
12080                 cps->pr = NoProc;\r
12081             }\r
12082 \r
12083             DisplayFatalError(buf, error, 1);\r
12084         }\r
12085         return;\r
12086     }\r
12087     \r
12088     if ((end_str = strchr(message, '\r')) != NULL)\r
12089       *end_str = NULLCHAR;\r
12090     if ((end_str = strchr(message, '\n')) != NULL)\r
12091       *end_str = NULLCHAR;\r
12092     \r
12093     if (appData.debugMode) {\r
12094         TimeMark now; int print = 1;\r
12095         char *quote = ""; char c; int i;\r
12096 \r
12097         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
12098                 char start = message[0];\r
12099                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
12100                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
12101                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&\r
12102                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
12103                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
12104                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&\r
12105                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
12106                         { quote = "# "; print = (appData.engineComments == 2); }\r
12107                 message[0] = start; // restore original message\r
12108         }\r
12109         if(print) {\r
12110                 GetTimeMark(&now);\r
12111                 fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
12112                         SubtractTimeMarks(&now, &programStartTime), cps->which, \r
12113                         quote,\r
12114                         message);\r
12115         }\r
12116     }\r
12117 \r
12118     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */\r
12119     if (appData.icsEngineAnalyze) {\r
12120         if (strstr(message, "whisper") != NULL ||\r
12121              strstr(message, "kibitz") != NULL || \r
12122             strstr(message, "tellics") != NULL) return;\r
12123     }\r
12124 \r
12125     HandleMachineMove(message, cps);\r
12126 }\r
12127 \r
12128 \r
12129 void\r
12130 SendTimeControl(cps, mps, tc, inc, sd, st)\r
12131      ChessProgramState *cps;\r
12132      int mps, inc, sd, st;\r
12133      long tc;\r
12134 {\r
12135     char buf[MSG_SIZ];\r
12136     int seconds;\r
12137 \r
12138     if( timeControl_2 > 0 ) {\r
12139         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
12140             tc = timeControl_2;\r
12141         }\r
12142     }\r
12143     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
12144     inc /= cps->timeOdds;\r
12145     st  /= cps->timeOdds;\r
12146 \r
12147     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
12148 \r
12149     if (st > 0) {\r
12150       /* Set exact time per move, normally using st command */\r
12151       if (cps->stKludge) {\r
12152         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
12153         seconds = st % 60;\r
12154         if (seconds == 0) {\r
12155           sprintf(buf, "level 1 %d\n", st/60);\r
12156         } else {\r
12157           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
12158         }\r
12159       } else {\r
12160         sprintf(buf, "st %d\n", st);\r
12161       }\r
12162     } else {\r
12163       /* Set conventional or incremental time control, using level command */\r
12164       if (seconds == 0) {\r
12165         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
12166            Fixed in later versions, but still avoid :seconds\r
12167            when seconds is 0. */\r
12168         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
12169       } else {\r
12170         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
12171                 seconds, inc/1000);\r
12172       }\r
12173     }\r
12174     SendToProgram(buf, cps);\r
12175 \r
12176     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
12177     /* Orthogonally, limit search to given depth */\r
12178     if (sd > 0) {\r
12179       if (cps->sdKludge) {\r
12180         sprintf(buf, "depth\n%d\n", sd);\r
12181       } else {\r
12182         sprintf(buf, "sd %d\n", sd);\r
12183       }\r
12184       SendToProgram(buf, cps);\r
12185     }\r
12186 \r
12187     if(cps->nps > 0) { /* [HGM] nps */\r
12188         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
12189         else {\r
12190                 sprintf(buf, "nps %d\n", cps->nps);\r
12191               SendToProgram(buf, cps);\r
12192         }\r
12193     }\r
12194 }\r
12195 \r
12196 ChessProgramState *WhitePlayer()\r
12197 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
12198 {\r
12199     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || \r
12200        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)\r
12201         return &second;\r
12202     return &first;\r
12203 }\r
12204 \r
12205 void\r
12206 SendTimeRemaining(cps, machineWhite)\r
12207      ChessProgramState *cps;\r
12208      int /*boolean*/ machineWhite;\r
12209 {\r
12210     char message[MSG_SIZ];\r
12211     long time, otime;\r
12212 \r
12213     /* Note: this routine must be called when the clocks are stopped\r
12214        or when they have *just* been set or switched; otherwise\r
12215        it will be off by the time since the current tick started.\r
12216     */\r
12217     if (machineWhite) {\r
12218         time = whiteTimeRemaining / 10;\r
12219         otime = blackTimeRemaining / 10;\r
12220     } else {\r
12221         time = blackTimeRemaining / 10;\r
12222         otime = whiteTimeRemaining / 10;\r
12223     }\r
12224     /* [HGM] translate opponent's time by time-odds factor */\r
12225     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
12226     if (appData.debugMode) {\r
12227         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
12228     }\r
12229 \r
12230     if (time <= 0) time = 1;\r
12231     if (otime <= 0) otime = 1;\r
12232     \r
12233     sprintf(message, "time %ld\n", time);\r
12234     SendToProgram(message, cps);\r
12235 \r
12236     sprintf(message, "otim %ld\n", otime);\r
12237     SendToProgram(message, cps);\r
12238 }\r
12239 \r
12240 int\r
12241 BoolFeature(p, name, loc, cps)\r
12242      char **p;\r
12243      char *name;\r
12244      int *loc;\r
12245      ChessProgramState *cps;\r
12246 {\r
12247   char buf[MSG_SIZ];\r
12248   int len = strlen(name);\r
12249   int val;\r
12250   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12251     (*p) += len + 1;\r
12252     sscanf(*p, "%d", &val);\r
12253     *loc = (val != 0);\r
12254     while (**p && **p != ' ') (*p)++;\r
12255     sprintf(buf, "accepted %s\n", name);\r
12256     SendToProgram(buf, cps);\r
12257     return TRUE;\r
12258   }\r
12259   return FALSE;\r
12260 }\r
12261 \r
12262 int\r
12263 IntFeature(p, name, loc, cps)\r
12264      char **p;\r
12265      char *name;\r
12266      int *loc;\r
12267      ChessProgramState *cps;\r
12268 {\r
12269   char buf[MSG_SIZ];\r
12270   int len = strlen(name);\r
12271   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12272     (*p) += len + 1;\r
12273     sscanf(*p, "%d", loc);\r
12274     while (**p && **p != ' ') (*p)++;\r
12275     sprintf(buf, "accepted %s\n", name);\r
12276     SendToProgram(buf, cps);\r
12277     return TRUE;\r
12278   }\r
12279   return FALSE;\r
12280 }\r
12281 \r
12282 int\r
12283 StringFeature(p, name, loc, cps)\r
12284      char **p;\r
12285      char *name;\r
12286      char loc[];\r
12287      ChessProgramState *cps;\r
12288 {\r
12289   char buf[MSG_SIZ];\r
12290   int len = strlen(name);\r
12291   if (strncmp((*p), name, len) == 0\r
12292       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
12293     (*p) += len + 2;\r
12294     sscanf(*p, "%[^\"]", loc);\r
12295     while (**p && **p != '\"') (*p)++;\r
12296     if (**p == '\"') (*p)++;\r
12297     sprintf(buf, "accepted %s\n", name);\r
12298     SendToProgram(buf, cps);\r
12299     return TRUE;\r
12300   }\r
12301   return FALSE;\r
12302 }\r
12303 \r
12304 int \r
12305 ParseOption(Option *opt, ChessProgramState *cps)\r
12306 // [HGM] options: process the string that defines an engine option, and determine\r
12307 // name, type, default value, and allowed value range\r
12308 {\r
12309         char *p, *q, buf[MSG_SIZ];\r
12310         int n, min = (-1)<<31, max = 1<<31, def;\r
12311 \r
12312         if(p = strstr(opt->name, " -spin ")) {\r
12313             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;\r
12314             if(max < min) max = min; // enforce consistency\r
12315             if(def < min) def = min;\r
12316             if(def > max) def = max;\r
12317             opt->value = def;\r
12318             opt->min = min;\r
12319             opt->max = max;\r
12320             opt->type = Spin;\r
12321         } else if(p = strstr(opt->name, " -string ")) {\r
12322             opt->textValue = p+9;\r
12323             opt->type = TextBox;\r
12324         } else if(p = strstr(opt->name, " -check ")) {\r
12325             if(sscanf(p, " -check %d", &def) < 1) return FALSE;\r
12326             opt->value = (def != 0);\r
12327             opt->type = CheckBox;\r
12328         } else if(p = strstr(opt->name, " -combo ")) {\r
12329             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type\r
12330             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices\r
12331             opt->value = n = 0;\r
12332             while(q = StrStr(q, " /// ")) {\r
12333                 n++; *q = 0;    // count choices, and null-terminate each of them\r
12334                 q += 5;\r
12335                 if(*q == '*') { // remember default, which is marked with * prefix\r
12336                     q++;\r
12337                     opt->value = n;\r
12338                 }\r
12339                 cps->comboList[cps->comboCnt++] = q;\r
12340             }\r
12341             cps->comboList[cps->comboCnt++] = NULL;\r
12342             opt->max = n + 1;\r
12343             opt->type = ComboBox;\r
12344         } else if(p = strstr(opt->name, " -button")) {\r
12345             opt->type = Button;\r
12346         } else if(p = strstr(opt->name, " -save")) {\r
12347             opt->type = SaveButton;\r
12348         } else return FALSE;\r
12349         *p = 0; // terminate option name\r
12350         // now look if the command-line options define a setting for this engine option.\r
12351         if(cps->optionSettings && cps->optionSettings[0])\r
12352             p = strstr(cps->optionSettings, opt->name); else p = NULL;\r
12353         if(p && (p == cps->optionSettings || p[-1] == ',')) {\r
12354                 sprintf(buf, "option %s", p);\r
12355                 if(p = strstr(buf, ",")) *p = 0;\r
12356                 strcat(buf, "\n");\r
12357                 SendToProgram(buf, cps);\r
12358         }\r
12359         return TRUE;\r
12360 }\r
12361 \r
12362 void\r
12363 FeatureDone(cps, val)\r
12364      ChessProgramState* cps;\r
12365      int val;\r
12366 {\r
12367   DelayedEventCallback cb = GetDelayedEvent();\r
12368   if ((cb == InitBackEnd3 && cps == &first) ||\r
12369       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
12370     CancelDelayedEvent();\r
12371     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
12372   }\r
12373   cps->initDone = val;\r
12374 }\r
12375 \r
12376 /* Parse feature command from engine */\r
12377 void\r
12378 ParseFeatures(args, cps)\r
12379      char* args;\r
12380      ChessProgramState *cps;  \r
12381 {\r
12382   char *p = args;\r
12383   char *q;\r
12384   int val;\r
12385   char buf[MSG_SIZ];\r
12386 \r
12387   for (;;) {\r
12388     while (*p == ' ') p++;\r
12389     if (*p == NULLCHAR) return;\r
12390 \r
12391     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
12392     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
12393     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
12394     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
12395     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
12396     if (BoolFeature(&p, "reuse", &val, cps)) {\r
12397       /* Engine can disable reuse, but can't enable it if user said no */\r
12398       if (!val) cps->reuse = FALSE;\r
12399       continue;\r
12400     }\r
12401     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
12402     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
12403       if (gameMode == TwoMachinesPlay) {\r
12404         DisplayTwoMachinesTitle();\r
12405       } else {\r
12406         DisplayTitle("");\r
12407       }\r
12408       continue;\r
12409     }\r
12410     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
12411     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
12412     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
12413     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
12414     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
12415     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
12416     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
12417     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
12418     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
12419     if (IntFeature(&p, "done", &val, cps)) {\r
12420       FeatureDone(cps, val);\r
12421       continue;\r
12422     }\r
12423     /* Added by Tord: */\r
12424     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
12425     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
12426     /* End of additions by Tord */\r
12427 \r
12428     /* [HGM] added features: */\r
12429     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
12430     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
12431     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
12432     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
12433     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12434     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
12435     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {\r
12436         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature\r
12437         if(cps->nrOptions >= MAX_OPTIONS) {\r
12438             cps->nrOptions--;\r
12439             sprintf(buf, "%s engine has too many options\n", cps->which);\r
12440             DisplayError(buf, 0);\r
12441         }\r
12442         continue;\r
12443     }\r
12444     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12445     /* End of additions by HGM */\r
12446 \r
12447     /* unknown feature: complain and skip */\r
12448     q = p;\r
12449     while (*q && *q != '=') q++;\r
12450     sprintf(buf, "rejected %.*s\n", q-p, p);\r
12451     SendToProgram(buf, cps);\r
12452     p = q;\r
12453     if (*p == '=') {\r
12454       p++;\r
12455       if (*p == '\"') {\r
12456         p++;\r
12457         while (*p && *p != '\"') p++;\r
12458         if (*p == '\"') p++;\r
12459       } else {\r
12460         while (*p && *p != ' ') p++;\r
12461       }\r
12462     }\r
12463   }\r
12464 \r
12465 }\r
12466 \r
12467 void\r
12468 PeriodicUpdatesEvent(newState)\r
12469      int newState;\r
12470 {\r
12471     if (newState == appData.periodicUpdates)\r
12472       return;\r
12473 \r
12474     appData.periodicUpdates=newState;\r
12475 \r
12476     /* Display type changes, so update it now */\r
12477     DisplayAnalysis();\r
12478 \r
12479     /* Get the ball rolling again... */\r
12480     if (newState) {\r
12481         AnalysisPeriodicEvent(1);\r
12482         StartAnalysisClock();\r
12483     }\r
12484 }\r
12485 \r
12486 void\r
12487 PonderNextMoveEvent(newState)\r
12488      int newState;\r
12489 {\r
12490     if (newState == appData.ponderNextMove) return;\r
12491     if (gameMode == EditPosition) EditPositionDone();\r
12492     if (newState) {\r
12493         SendToProgram("hard\n", &first);\r
12494         if (gameMode == TwoMachinesPlay) {\r
12495             SendToProgram("hard\n", &second);\r
12496         }\r
12497     } else {\r
12498         SendToProgram("easy\n", &first);\r
12499         thinkOutput[0] = NULLCHAR;\r
12500         if (gameMode == TwoMachinesPlay) {\r
12501             SendToProgram("easy\n", &second);\r
12502         }\r
12503     }\r
12504     appData.ponderNextMove = newState;\r
12505 }\r
12506 \r
12507 void\r
12508 NewSettingEvent(option, command, value)\r
12509      char *command;\r
12510      int option, value;\r
12511 {\r
12512     char buf[MSG_SIZ];\r
12513 \r
12514     if (gameMode == EditPosition) EditPositionDone();\r
12515     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);\r
12516     SendToProgram(buf, &first);\r
12517     if (gameMode == TwoMachinesPlay) {\r
12518         SendToProgram(buf, &second);\r
12519     }\r
12520 }\r
12521 \r
12522 void\r
12523 ShowThinkingEvent()\r
12524 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup\r
12525 {\r
12526     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated\r
12527     int newState = appData.showThinking\r
12528         // [HGM] thinking: other features now need thinking output as well\r
12529         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();\r
12530     \r
12531     if (oldState == newState) return;\r
12532     oldState = newState;\r
12533     if (gameMode == EditPosition) EditPositionDone();\r
12534     if (oldState) {\r
12535         SendToProgram("post\n", &first);\r
12536         if (gameMode == TwoMachinesPlay) {\r
12537             SendToProgram("post\n", &second);\r
12538         }\r
12539     } else {\r
12540         SendToProgram("nopost\n", &first);\r
12541         thinkOutput[0] = NULLCHAR;\r
12542         if (gameMode == TwoMachinesPlay) {\r
12543             SendToProgram("nopost\n", &second);\r
12544         }\r
12545     }\r
12546 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!\r
12547 }\r
12548 \r
12549 void\r
12550 AskQuestionEvent(title, question, replyPrefix, which)\r
12551      char *title; char *question; char *replyPrefix; char *which;\r
12552 {\r
12553   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
12554   if (pr == NoProc) return;\r
12555   AskQuestion(title, question, replyPrefix, pr);\r
12556 }\r
12557 \r
12558 void\r
12559 DisplayMove(moveNumber)\r
12560      int moveNumber;\r
12561 {\r
12562     char message[MSG_SIZ];\r
12563     char res[MSG_SIZ];\r
12564     char cpThinkOutput[MSG_SIZ];\r
12565 \r
12566     if(appData.noGUI) return; // [HGM] fast: suppress display of moves\r
12567     \r
12568     if (moveNumber == forwardMostMove - 1 || \r
12569         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12570 \r
12571         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
12572 \r
12573         if (strchr(cpThinkOutput, '\n')) {\r
12574             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
12575         }\r
12576     } else {\r
12577         *cpThinkOutput = NULLCHAR;\r
12578     }\r
12579 \r
12580     /* [AS] Hide thinking from human user */\r
12581     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
12582         *cpThinkOutput = NULLCHAR;\r
12583         if( thinkOutput[0] != NULLCHAR ) {\r
12584             int i;\r
12585 \r
12586             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
12587                 cpThinkOutput[i] = '.';\r
12588             }\r
12589             cpThinkOutput[i] = NULLCHAR;\r
12590             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
12591         }\r
12592     }\r
12593 \r
12594     if (moveNumber == forwardMostMove - 1 &&\r
12595         gameInfo.resultDetails != NULL) {\r
12596         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
12597             sprintf(res, " %s", PGNResult(gameInfo.result));\r
12598         } else {\r
12599             sprintf(res, " {%s} %s",\r
12600                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
12601         }\r
12602     } else {\r
12603         res[0] = NULLCHAR;\r
12604     }\r
12605 \r
12606     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12607         DisplayMessage(res, cpThinkOutput);\r
12608     } else {\r
12609         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
12610                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
12611                 parseList[moveNumber], res);\r
12612         DisplayMessage(message, cpThinkOutput);\r
12613     }\r
12614 }\r
12615 \r
12616 void\r
12617 DisplayAnalysisText(text)\r
12618      char *text;\r
12619 {\r
12620     char buf[MSG_SIZ];\r
12621 \r
12622     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile \r
12623                || appData.icsEngineAnalyze) {\r
12624         sprintf(buf, "Analysis (%s)", first.tidy);\r
12625         AnalysisPopUp(buf, text);\r
12626     }\r
12627 }\r
12628 \r
12629 static int\r
12630 only_one_move(str)\r
12631      char *str;\r
12632 {\r
12633     while (*str && isspace(*str)) ++str;\r
12634     while (*str && !isspace(*str)) ++str;\r
12635     if (!*str) return 1;\r
12636     while (*str && isspace(*str)) ++str;\r
12637     if (!*str) return 1;\r
12638     return 0;\r
12639 }\r
12640 \r
12641 void\r
12642 DisplayAnalysis()\r
12643 {\r
12644     char buf[MSG_SIZ];\r
12645     char lst[MSG_SIZ / 2];\r
12646     double nps;\r
12647     static char *xtra[] = { "", " (--)", " (++)" };\r
12648     int h, m, s, cs;\r
12649   \r
12650     if (programStats.time == 0) {\r
12651         programStats.time = 1;\r
12652     }\r
12653   \r
12654     if (programStats.got_only_move) {\r
12655         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
12656     } else {\r
12657         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
12658 \r
12659         nps = (u64ToDouble(programStats.nodes) /\r
12660              ((double)programStats.time /100.0));\r
12661 \r
12662         cs = programStats.time % 100;\r
12663         s = programStats.time / 100;\r
12664         h = (s / (60*60));\r
12665         s = s - h*60*60;\r
12666         m = (s/60);\r
12667         s = s - m*60;\r
12668 \r
12669         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
12670           if (programStats.move_name[0] != NULLCHAR) {\r
12671             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12672                     programStats.depth,\r
12673                     programStats.nr_moves-programStats.moves_left,\r
12674                     programStats.nr_moves, programStats.move_name,\r
12675                     ((float)programStats.score)/100.0, lst,\r
12676                     only_one_move(lst)?\r
12677                     xtra[programStats.got_fail] : "",\r
12678                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12679           } else {\r
12680             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12681                     programStats.depth,\r
12682                     programStats.nr_moves-programStats.moves_left,\r
12683                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
12684                     lst,\r
12685                     only_one_move(lst)?\r
12686                     xtra[programStats.got_fail] : "",\r
12687                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12688           }\r
12689         } else {\r
12690             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12691                     programStats.depth,\r
12692                     ((float)programStats.score)/100.0,\r
12693                     lst,\r
12694                     only_one_move(lst)?\r
12695                     xtra[programStats.got_fail] : "",\r
12696                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12697         }\r
12698     }\r
12699     DisplayAnalysisText(buf);\r
12700 }\r
12701 \r
12702 void\r
12703 DisplayComment(moveNumber, text)\r
12704      int moveNumber;\r
12705      char *text;\r
12706 {\r
12707     char title[MSG_SIZ];\r
12708     char buf[8000]; // comment can be long!\r
12709     int score, depth;\r
12710 \r
12711     if( appData.autoDisplayComment ) {\r
12712         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12713             strcpy(title, "Comment");\r
12714         } else {\r
12715             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
12716                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
12717                     parseList[moveNumber]);\r
12718         }\r
12719     } else title[0] = 0;\r
12720 \r
12721     // [HGM] PV info: display PV info together with (or as) comment\r
12722     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
12723         if(text == NULL) text = "";                                           \r
12724         score = pvInfoList[moveNumber].score;\r
12725         sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
12726                               depth, (pvInfoList[moveNumber].time+50)/100, text);\r
12727         CommentPopUp(title, buf);\r
12728     } else\r
12729     if (text != NULL)\r
12730         CommentPopUp(title, text);\r
12731 }\r
12732 \r
12733 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
12734  * might be busy thinking or pondering.  It can be omitted if your\r
12735  * gnuchess is configured to stop thinking immediately on any user\r
12736  * input.  However, that gnuchess feature depends on the FIONREAD\r
12737  * ioctl, which does not work properly on some flavors of Unix.\r
12738  */\r
12739 void\r
12740 Attention(cps)\r
12741      ChessProgramState *cps;\r
12742 {\r
12743 #if ATTENTION\r
12744     if (!cps->useSigint) return;\r
12745     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
12746     switch (gameMode) {\r
12747       case MachinePlaysWhite:\r
12748       case MachinePlaysBlack:\r
12749       case TwoMachinesPlay:\r
12750       case IcsPlayingWhite:\r
12751       case IcsPlayingBlack:\r
12752       case AnalyzeMode:\r
12753       case AnalyzeFile:\r
12754         /* Skip if we know it isn't thinking */\r
12755         if (!cps->maybeThinking) return;\r
12756         if (appData.debugMode)\r
12757           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
12758         InterruptChildProcess(cps->pr);\r
12759         cps->maybeThinking = FALSE;\r
12760         break;\r
12761       default:\r
12762         break;\r
12763     }\r
12764 #endif /*ATTENTION*/\r
12765 }\r
12766 \r
12767 int\r
12768 CheckFlags()\r
12769 {\r
12770     if (whiteTimeRemaining <= 0) {\r
12771         if (!whiteFlag) {\r
12772             whiteFlag = TRUE;\r
12773             if (appData.icsActive) {\r
12774                 if (appData.autoCallFlag &&\r
12775                     gameMode == IcsPlayingBlack && !blackFlag) {\r
12776                   SendToICS(ics_prefix);\r
12777                   SendToICS("flag\n");\r
12778                 }\r
12779             } else {\r
12780                 if (blackFlag) {\r
12781                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12782                 } else {\r
12783                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));\r
12784                     if (appData.autoCallFlag) {\r
12785                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
12786                         return TRUE;\r
12787                     }\r
12788                 }\r
12789             }\r
12790         }\r
12791     }\r
12792     if (blackTimeRemaining <= 0) {\r
12793         if (!blackFlag) {\r
12794             blackFlag = TRUE;\r
12795             if (appData.icsActive) {\r
12796                 if (appData.autoCallFlag &&\r
12797                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
12798                   SendToICS(ics_prefix);\r
12799                   SendToICS("flag\n");\r
12800                 }\r
12801             } else {\r
12802                 if (whiteFlag) {\r
12803                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12804                 } else {\r
12805                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));\r
12806                     if (appData.autoCallFlag) {\r
12807                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
12808                         return TRUE;\r
12809                     }\r
12810                 }\r
12811             }\r
12812         }\r
12813     }\r
12814     return FALSE;\r
12815 }\r
12816 \r
12817 void\r
12818 CheckTimeControl()\r
12819 {\r
12820     if (!appData.clockMode || appData.icsActive ||\r
12821         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
12822 \r
12823     /*\r
12824      * add time to clocks when time control is achieved ([HGM] now also used for increment)\r
12825      */\r
12826     if ( !WhiteOnMove(forwardMostMove) )\r
12827         /* White made time control */\r
12828         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12829         /* [HGM] time odds: correct new time quota for time odds! */\r
12830                                             / WhitePlayer()->timeOdds;\r
12831       else\r
12832         /* Black made time control */\r
12833         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12834                                             / WhitePlayer()->other->timeOdds;\r
12835 }\r
12836 \r
12837 void\r
12838 DisplayBothClocks()\r
12839 {\r
12840     int wom = gameMode == EditPosition ?\r
12841       !blackPlaysFirst : WhiteOnMove(currentMove);\r
12842     DisplayWhiteClock(whiteTimeRemaining, wom);\r
12843     DisplayBlackClock(blackTimeRemaining, !wom);\r
12844 }\r
12845 \r
12846 \r
12847 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
12848    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
12849    to use other calls if you don't.  Clocks will be less accurate if\r
12850    you have neither ftime nor gettimeofday.\r
12851 */\r
12852 \r
12853 /* VS 2008 requires the #include outside of the function */\r
12854 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME\r
12855 #include <sys/timeb.h>\r
12856 #endif\r
12857 \r
12858 /* Get the current time as a TimeMark */\r
12859 void\r
12860 GetTimeMark(tm)\r
12861      TimeMark *tm;\r
12862 {\r
12863 #if HAVE_GETTIMEOFDAY\r
12864 \r
12865     struct timeval timeVal;\r
12866     struct timezone timeZone;\r
12867 \r
12868     gettimeofday(&timeVal, &timeZone);\r
12869     tm->sec = (long) timeVal.tv_sec; \r
12870     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
12871 \r
12872 #else /*!HAVE_GETTIMEOFDAY*/\r
12873 #if HAVE_FTIME\r
12874 \r
12875 // include <sys/timeb.h> / moved to just above start of function\r
12876     struct timeb timeB;\r
12877 \r
12878     ftime(&timeB);\r
12879     tm->sec = (long) timeB.time;\r
12880     tm->ms = (int) timeB.millitm;\r
12881 \r
12882 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
12883     tm->sec = (long) time(NULL);\r
12884     tm->ms = 0;\r
12885 #endif\r
12886 #endif\r
12887 }\r
12888 \r
12889 /* Return the difference in milliseconds between two\r
12890    time marks.  We assume the difference will fit in a long!\r
12891 */\r
12892 long\r
12893 SubtractTimeMarks(tm2, tm1)\r
12894      TimeMark *tm2, *tm1;\r
12895 {\r
12896     return 1000L*(tm2->sec - tm1->sec) +\r
12897            (long) (tm2->ms - tm1->ms);\r
12898 }\r
12899 \r
12900 \r
12901 /*\r
12902  * Code to manage the game clocks.\r
12903  *\r
12904  * In tournament play, black starts the clock and then white makes a move.\r
12905  * We give the human user a slight advantage if he is playing white---the\r
12906  * clocks don't run until he makes his first move, so it takes zero time.\r
12907  * Also, we don't account for network lag, so we could get out of sync\r
12908  * with GNU Chess's clock -- but then, referees are always right.  \r
12909  */\r
12910 \r
12911 static TimeMark tickStartTM;\r
12912 static long intendedTickLength;\r
12913 \r
12914 long\r
12915 NextTickLength(timeRemaining)\r
12916      long timeRemaining;\r
12917 {\r
12918     long nominalTickLength, nextTickLength;\r
12919 \r
12920     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
12921       nominalTickLength = 100L;\r
12922     else\r
12923       nominalTickLength = 1000L;\r
12924     nextTickLength = timeRemaining % nominalTickLength;\r
12925     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
12926 \r
12927     return nextTickLength;\r
12928 }\r
12929 \r
12930 /* Adjust clock one minute up or down */\r
12931 void\r
12932 AdjustClock(Boolean which, int dir)\r
12933 {\r
12934     if(which) blackTimeRemaining += 60000*dir;\r
12935     else      whiteTimeRemaining += 60000*dir;\r
12936     DisplayBothClocks();\r
12937 }\r
12938 \r
12939 /* Stop clocks and reset to a fresh time control */\r
12940 void\r
12941 ResetClocks() \r
12942 {\r
12943     (void) StopClockTimer();\r
12944     if (appData.icsActive) {\r
12945         whiteTimeRemaining = blackTimeRemaining = 0;\r
12946     } else { /* [HGM] correct new time quote for time odds */\r
12947         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
12948         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
12949     }\r
12950     if (whiteFlag || blackFlag) {\r
12951         DisplayTitle("");\r
12952         whiteFlag = blackFlag = FALSE;\r
12953     }\r
12954     DisplayBothClocks();\r
12955 }\r
12956 \r
12957 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
12958 \r
12959 /* Decrement running clock by amount of time that has passed */\r
12960 void\r
12961 DecrementClocks()\r
12962 {\r
12963     long timeRemaining;\r
12964     long lastTickLength, fudge;\r
12965     TimeMark now;\r
12966 \r
12967     if (!appData.clockMode) return;\r
12968     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
12969         \r
12970     GetTimeMark(&now);\r
12971 \r
12972     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
12973 \r
12974     /* Fudge if we woke up a little too soon */\r
12975     fudge = intendedTickLength - lastTickLength;\r
12976     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
12977 \r
12978     if (WhiteOnMove(forwardMostMove)) {\r
12979         if(whiteNPS >= 0) lastTickLength = 0;\r
12980         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
12981         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
12982                           WhiteOnMove(currentMove));\r
12983     } else {\r
12984         if(blackNPS >= 0) lastTickLength = 0;\r
12985         timeRemaining = blackTimeRemaining -= lastTickLength;\r
12986         DisplayBlackClock(blackTimeRemaining - fudge,\r
12987                           !WhiteOnMove(currentMove));\r
12988     }\r
12989 \r
12990     if (CheckFlags()) return;\r
12991         \r
12992     tickStartTM = now;\r
12993     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
12994     StartClockTimer(intendedTickLength);\r
12995 \r
12996     /* if the time remaining has fallen below the alarm threshold, sound the\r
12997      * alarm. if the alarm has sounded and (due to a takeback or time control\r
12998      * with increment) the time remaining has increased to a level above the\r
12999      * threshold, reset the alarm so it can sound again. \r
13000      */\r
13001     \r
13002     if (appData.icsActive && appData.icsAlarm) {\r
13003 \r
13004         /* make sure we are dealing with the user's clock */\r
13005         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
13006                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
13007            )) return;\r
13008 \r
13009         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
13010             alarmSounded = FALSE;\r
13011         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
13012             PlayAlarmSound();\r
13013             alarmSounded = TRUE;\r
13014         }\r
13015     }\r
13016 }\r
13017 \r
13018 \r
13019 /* A player has just moved, so stop the previously running\r
13020    clock and (if in clock mode) start the other one.\r
13021    We redisplay both clocks in case we're in ICS mode, because\r
13022    ICS gives us an update to both clocks after every move.\r
13023    Note that this routine is called *after* forwardMostMove\r
13024    is updated, so the last fractional tick must be subtracted\r
13025    from the color that is *not* on move now.\r
13026 */\r
13027 void\r
13028 SwitchClocks()\r
13029 {\r
13030     long lastTickLength;\r
13031     TimeMark now;\r
13032     int flagged = FALSE;\r
13033 \r
13034     GetTimeMark(&now);\r
13035 \r
13036     if (StopClockTimer() && appData.clockMode) {\r
13037         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13038         if (WhiteOnMove(forwardMostMove)) {\r
13039             if(blackNPS >= 0) lastTickLength = 0;\r
13040             blackTimeRemaining -= lastTickLength;\r
13041            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13042 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13043                  pvInfoList[forwardMostMove-1].time =               // use GUI time\r
13044                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
13045         } else {\r
13046            if(whiteNPS >= 0) lastTickLength = 0;\r
13047            whiteTimeRemaining -= lastTickLength;\r
13048            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13049 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13050                  pvInfoList[forwardMostMove-1].time = \r
13051                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
13052         }\r
13053         flagged = CheckFlags();\r
13054     }\r
13055     CheckTimeControl();\r
13056 \r
13057     if (flagged || !appData.clockMode) return;\r
13058 \r
13059     switch (gameMode) {\r
13060       case MachinePlaysBlack:\r
13061       case MachinePlaysWhite:\r
13062       case BeginningOfGame:\r
13063         if (pausing) return;\r
13064         break;\r
13065 \r
13066       case EditGame:\r
13067       case PlayFromGameFile:\r
13068       case IcsExamining:\r
13069         return;\r
13070 \r
13071       default:\r
13072         break;\r
13073     }\r
13074 \r
13075     tickStartTM = now;\r
13076     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13077       whiteTimeRemaining : blackTimeRemaining);\r
13078     StartClockTimer(intendedTickLength);\r
13079 }\r
13080         \r
13081 \r
13082 /* Stop both clocks */\r
13083 void\r
13084 StopClocks()\r
13085 {       \r
13086     long lastTickLength;\r
13087     TimeMark now;\r
13088 \r
13089     if (!StopClockTimer()) return;\r
13090     if (!appData.clockMode) return;\r
13091 \r
13092     GetTimeMark(&now);\r
13093 \r
13094     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13095     if (WhiteOnMove(forwardMostMove)) {\r
13096         if(whiteNPS >= 0) lastTickLength = 0;\r
13097         whiteTimeRemaining -= lastTickLength;\r
13098         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
13099     } else {\r
13100         if(blackNPS >= 0) lastTickLength = 0;\r
13101         blackTimeRemaining -= lastTickLength;\r
13102         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
13103     }\r
13104     CheckFlags();\r
13105 }\r
13106         \r
13107 /* Start clock of player on move.  Time may have been reset, so\r
13108    if clock is already running, stop and restart it. */\r
13109 void\r
13110 StartClocks()\r
13111 {\r
13112     (void) StopClockTimer(); /* in case it was running already */\r
13113     DisplayBothClocks();\r
13114     if (CheckFlags()) return;\r
13115 \r
13116     if (!appData.clockMode) return;\r
13117     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
13118 \r
13119     GetTimeMark(&tickStartTM);\r
13120     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13121       whiteTimeRemaining : blackTimeRemaining);\r
13122 \r
13123    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
13124     whiteNPS = blackNPS = -1; \r
13125     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
13126        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
13127         whiteNPS = first.nps;\r
13128     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
13129        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
13130         blackNPS = first.nps;\r
13131     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
13132         whiteNPS = second.nps;\r
13133     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
13134         blackNPS = second.nps;\r
13135     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
13136 \r
13137     StartClockTimer(intendedTickLength);\r
13138 }\r
13139 \r
13140 char *\r
13141 TimeString(ms)\r
13142      long ms;\r
13143 {\r
13144     long second, minute, hour, day;\r
13145     char *sign = "";\r
13146     static char buf[32];\r
13147     \r
13148     if (ms > 0 && ms <= 9900) {\r
13149       /* convert milliseconds to tenths, rounding up */\r
13150       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
13151 \r
13152       sprintf(buf, " %03.1f ", tenths/10.0);\r
13153       return buf;\r
13154     }\r
13155 \r
13156     /* convert milliseconds to seconds, rounding up */\r
13157     /* use floating point to avoid strangeness of integer division\r
13158        with negative dividends on many machines */\r
13159     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
13160 \r
13161     if (second < 0) {\r
13162         sign = "-";\r
13163         second = -second;\r
13164     }\r
13165     \r
13166     day = second / (60 * 60 * 24);\r
13167     second = second % (60 * 60 * 24);\r
13168     hour = second / (60 * 60);\r
13169     second = second % (60 * 60);\r
13170     minute = second / 60;\r
13171     second = second % 60;\r
13172     \r
13173     if (day > 0)\r
13174       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
13175               sign, day, hour, minute, second);\r
13176     else if (hour > 0)\r
13177       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
13178     else\r
13179       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
13180     \r
13181     return buf;\r
13182 }\r
13183 \r
13184 \r
13185 /*\r
13186  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
13187  */\r
13188 char *\r
13189 StrStr(string, match)\r
13190      char *string, *match;\r
13191 {\r
13192     int i, length;\r
13193     \r
13194     length = strlen(match);\r
13195     \r
13196     for (i = strlen(string) - length; i >= 0; i--, string++)\r
13197       if (!strncmp(match, string, length))\r
13198         return string;\r
13199     \r
13200     return NULL;\r
13201 }\r
13202 \r
13203 char *\r
13204 StrCaseStr(string, match)\r
13205      char *string, *match;\r
13206 {\r
13207     int i, j, length;\r
13208     \r
13209     length = strlen(match);\r
13210     \r
13211     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
13212         for (j = 0; j < length; j++) {\r
13213             if (ToLower(match[j]) != ToLower(string[j]))\r
13214               break;\r
13215         }\r
13216         if (j == length) return string;\r
13217     }\r
13218 \r
13219     return NULL;\r
13220 }\r
13221 \r
13222 #ifndef _amigados\r
13223 int\r
13224 StrCaseCmp(s1, s2)\r
13225      char *s1, *s2;\r
13226 {\r
13227     char c1, c2;\r
13228     \r
13229     for (;;) {\r
13230         c1 = ToLower(*s1++);\r
13231         c2 = ToLower(*s2++);\r
13232         if (c1 > c2) return 1;\r
13233         if (c1 < c2) return -1;\r
13234         if (c1 == NULLCHAR) return 0;\r
13235     }\r
13236 }\r
13237 \r
13238 \r
13239 int\r
13240 ToLower(c)\r
13241      int c;\r
13242 {\r
13243     return isupper(c) ? tolower(c) : c;\r
13244 }\r
13245 \r
13246 \r
13247 int\r
13248 ToUpper(c)\r
13249      int c;\r
13250 {\r
13251     return islower(c) ? toupper(c) : c;\r
13252 }\r
13253 #endif /* !_amigados    */\r
13254 \r
13255 char *\r
13256 StrSave(s)\r
13257      char *s;\r
13258 {\r
13259     char *ret;\r
13260 \r
13261     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
13262         strcpy(ret, s);\r
13263     }\r
13264     return ret;\r
13265 }\r
13266 \r
13267 char *\r
13268 StrSavePtr(s, savePtr)\r
13269      char *s, **savePtr;\r
13270 {\r
13271     if (*savePtr) {\r
13272         free(*savePtr);\r
13273     }\r
13274     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
13275         strcpy(*savePtr, s);\r
13276     }\r
13277     return(*savePtr);\r
13278 }\r
13279 \r
13280 char *\r
13281 PGNDate()\r
13282 {\r
13283     time_t clock;\r
13284     struct tm *tm;\r
13285     char buf[MSG_SIZ];\r
13286 \r
13287     clock = time((time_t *)NULL);\r
13288     tm = localtime(&clock);\r
13289     sprintf(buf, "%04d.%02d.%02d",\r
13290             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
13291     return StrSave(buf);\r
13292 }\r
13293 \r
13294 \r
13295 char *\r
13296 PositionToFEN(move, useFEN960)\r
13297      int move;\r
13298      int useFEN960;\r
13299 {\r
13300     int i, j, fromX, fromY, toX, toY;\r
13301     int whiteToPlay;\r
13302     char buf[128];\r
13303     char *p, *q;\r
13304     int emptycount;\r
13305     ChessSquare piece;\r
13306 \r
13307     whiteToPlay = (gameMode == EditPosition) ?\r
13308       !blackPlaysFirst : (move % 2 == 0);\r
13309     p = buf;\r
13310 \r
13311     /* Piece placement data */\r
13312     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13313         emptycount = 0;\r
13314         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
13315             if (boards[move][i][j] == EmptySquare) {\r
13316                 emptycount++;\r
13317             } else { ChessSquare piece = boards[move][i][j];\r
13318                 if (emptycount > 0) {\r
13319                     if(emptycount<10) /* [HGM] can be >= 10 */\r
13320                         *p++ = '0' + emptycount;\r
13321                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13322                     emptycount = 0;\r
13323                 }\r
13324                 if(PieceToChar(piece) == '+') {\r
13325                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
13326                     *p++ = '+';\r
13327                     piece = (ChessSquare)(DEMOTED piece);\r
13328                 } \r
13329                 *p++ = PieceToChar(piece);\r
13330                 if(p[-1] == '~') {\r
13331                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
13332                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
13333                     *p++ = '~';\r
13334                 }\r
13335             }\r
13336         }\r
13337         if (emptycount > 0) {\r
13338             if(emptycount<10) /* [HGM] can be >= 10 */\r
13339                 *p++ = '0' + emptycount;\r
13340             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13341             emptycount = 0;\r
13342         }\r
13343         *p++ = '/';\r
13344     }\r
13345     *(p - 1) = ' ';\r
13346 \r
13347     /* [HGM] print Crazyhouse or Shogi holdings */\r
13348     if( gameInfo.holdingsWidth ) {\r
13349         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
13350         q = p;\r
13351         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
13352             piece = boards[move][i][BOARD_WIDTH-1];\r
13353             if( piece != EmptySquare )\r
13354               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
13355                   *p++ = PieceToChar(piece);\r
13356         }\r
13357         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
13358             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
13359             if( piece != EmptySquare )\r
13360               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
13361                   *p++ = PieceToChar(piece);\r
13362         }\r
13363 \r
13364         if( q == p ) *p++ = '-';\r
13365         *p++ = ']';\r
13366         *p++ = ' ';\r
13367     }\r
13368 \r
13369     /* Active color */\r
13370     *p++ = whiteToPlay ? 'w' : 'b';\r
13371     *p++ = ' ';\r
13372 \r
13373   if(nrCastlingRights) {\r
13374      q = p;\r
13375      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
13376        /* [HGM] write directly from rights */\r
13377            if(castlingRights[move][2] >= 0 &&\r
13378               castlingRights[move][0] >= 0   )\r
13379                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
13380            if(castlingRights[move][2] >= 0 &&\r
13381               castlingRights[move][1] >= 0   )\r
13382                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
13383            if(castlingRights[move][5] >= 0 &&\r
13384               castlingRights[move][3] >= 0   )\r
13385                 *p++ = castlingRights[move][3] + AAA;\r
13386            if(castlingRights[move][5] >= 0 &&\r
13387               castlingRights[move][4] >= 0   )\r
13388                 *p++ = castlingRights[move][4] + AAA;\r
13389      } else {\r
13390 \r
13391         /* [HGM] write true castling rights */\r
13392         if( nrCastlingRights == 6 ) {\r
13393             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
13394                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
13395             if(castlingRights[move][1] == BOARD_LEFT &&\r
13396                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
13397             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
13398                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
13399             if(castlingRights[move][4] == BOARD_LEFT &&\r
13400                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
13401         }\r
13402      }\r
13403      if (q == p) *p++ = '-'; /* No castling rights */\r
13404      *p++ = ' ';\r
13405   }\r
13406 \r
13407   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13408      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13409     /* En passant target square */\r
13410     if (move > backwardMostMove) {\r
13411         fromX = moveList[move - 1][0] - AAA;\r
13412         fromY = moveList[move - 1][1] - ONE;\r
13413         toX = moveList[move - 1][2] - AAA;\r
13414         toY = moveList[move - 1][3] - ONE;\r
13415         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
13416             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
13417             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
13418             fromX == toX) {\r
13419             /* 2-square pawn move just happened */\r
13420             *p++ = toX + AAA;\r
13421             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
13422         } else {\r
13423             *p++ = '-';\r
13424         }\r
13425     } else {\r
13426         *p++ = '-';\r
13427     }\r
13428     *p++ = ' ';\r
13429   }\r
13430 \r
13431     /* [HGM] find reversible plies */\r
13432     {   int i = 0, j=move;\r
13433 \r
13434         if (appData.debugMode) { int k;\r
13435             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
13436             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
13437                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
13438 \r
13439         }\r
13440 \r
13441         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
13442         if( j == backwardMostMove ) i += initialRulePlies;\r
13443         sprintf(p, "%d ", i);\r
13444         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
13445     }\r
13446     /* Fullmove number */\r
13447     sprintf(p, "%d", (move / 2) + 1);\r
13448     \r
13449     return StrSave(buf);\r
13450 }\r
13451 \r
13452 Boolean\r
13453 ParseFEN(board, blackPlaysFirst, fen)\r
13454     Board board;\r
13455      int *blackPlaysFirst;\r
13456      char *fen;\r
13457 {\r
13458     int i, j;\r
13459     char *p;\r
13460     int emptycount;\r
13461     ChessSquare piece;\r
13462 \r
13463     p = fen;\r
13464 \r
13465     /* [HGM] by default clear Crazyhouse holdings, if present */\r
13466     if(gameInfo.holdingsWidth) {\r
13467        for(i=0; i<BOARD_HEIGHT; i++) {\r
13468            board[i][0]             = EmptySquare; /* black holdings */\r
13469            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
13470            board[i][1]             = (ChessSquare) 0; /* black counts */\r
13471            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
13472        }\r
13473     }\r
13474 \r
13475     /* Piece placement data */\r
13476     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13477         j = 0;\r
13478         for (;;) {\r
13479             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
13480                 if (*p == '/') p++;\r
13481                 emptycount = gameInfo.boardWidth - j;\r
13482                 while (emptycount--)\r
13483                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13484                 break;\r
13485 #if(BOARD_SIZE >= 10)\r
13486             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
13487                 p++; emptycount=10;\r
13488                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13489                 while (emptycount--)\r
13490                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13491 #endif\r
13492             } else if (isdigit(*p)) {\r
13493                 emptycount = *p++ - '0';\r
13494                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
13495                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13496                 while (emptycount--)\r
13497                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13498             } else if (*p == '+' || isalpha(*p)) {\r
13499                 if (j >= gameInfo.boardWidth) return FALSE;\r
13500                 if(*p=='+') {\r
13501                     piece = CharToPiece(*++p);\r
13502                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
13503                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
13504                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
13505                 } else piece = CharToPiece(*p++);\r
13506 \r
13507                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
13508                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
13509                     piece = (ChessSquare) (PROMOTED piece);\r
13510                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
13511                     p++;\r
13512                 }\r
13513                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
13514             } else {\r
13515                 return FALSE;\r
13516             }\r
13517         }\r
13518     }\r
13519     while (*p == '/' || *p == ' ') p++;\r
13520 \r
13521     /* [HGM] look for Crazyhouse holdings here */\r
13522     while(*p==' ') p++;\r
13523     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
13524         if(*p == '[') p++;\r
13525         if(*p == '-' ) *p++; /* empty holdings */ else {\r
13526             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
13527             /* if we would allow FEN reading to set board size, we would   */\r
13528             /* have to add holdings and shift the board read so far here   */\r
13529             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
13530                 *p++;\r
13531                 if((int) piece >= (int) BlackPawn ) {\r
13532                     i = (int)piece - (int)BlackPawn;\r
13533                     i = PieceToNumber((ChessSquare)i);\r
13534                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13535                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
13536                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
13537                 } else {\r
13538                     i = (int)piece - (int)WhitePawn;\r
13539                     i = PieceToNumber((ChessSquare)i);\r
13540                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13541                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
13542                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
13543                 }\r
13544             }\r
13545         }\r
13546         if(*p == ']') *p++;\r
13547     }\r
13548 \r
13549     while(*p == ' ') p++;\r
13550 \r
13551     /* Active color */\r
13552     switch (*p++) {\r
13553       case 'w':\r
13554         *blackPlaysFirst = FALSE;\r
13555         break;\r
13556       case 'b': \r
13557         *blackPlaysFirst = TRUE;\r
13558         break;\r
13559       default:\r
13560         return FALSE;\r
13561     }\r
13562 \r
13563     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
13564     /* return the extra info in global variiables             */\r
13565 \r
13566     /* set defaults in case FEN is incomplete */\r
13567     FENepStatus = EP_UNKNOWN;\r
13568     for(i=0; i<nrCastlingRights; i++ ) {\r
13569         FENcastlingRights[i] =\r
13570             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
13571     }   /* assume possible unless obviously impossible */\r
13572     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
13573     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
13574     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
13575     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
13576     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
13577     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
13578     FENrulePlies = 0;\r
13579 \r
13580     while(*p==' ') p++;\r
13581     if(nrCastlingRights) {\r
13582       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
13583           /* castling indicator present, so default becomes no castlings */\r
13584           for(i=0; i<nrCastlingRights; i++ ) {\r
13585                  FENcastlingRights[i] = -1;\r
13586           }\r
13587       }\r
13588       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
13589              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
13590              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
13591              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
13592         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
13593 \r
13594         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
13595             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
13596             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
13597         }\r
13598         switch(c) {\r
13599           case'K':\r
13600               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
13601               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
13602               FENcastlingRights[2] = whiteKingFile;\r
13603               break;\r
13604           case'Q':\r
13605               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
13606               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
13607               FENcastlingRights[2] = whiteKingFile;\r
13608               break;\r
13609           case'k':\r
13610               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
13611               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
13612               FENcastlingRights[5] = blackKingFile;\r
13613               break;\r
13614           case'q':\r
13615               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
13616               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
13617               FENcastlingRights[5] = blackKingFile;\r
13618           case '-':\r
13619               break;\r
13620           default: /* FRC castlings */\r
13621               if(c >= 'a') { /* black rights */\r
13622                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13623                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
13624                   if(i == BOARD_RGHT) break;\r
13625                   FENcastlingRights[5] = i;\r
13626                   c -= AAA;\r
13627                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
13628                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
13629                   if(c > i)\r
13630                       FENcastlingRights[3] = c;\r
13631                   else\r
13632                       FENcastlingRights[4] = c;\r
13633               } else { /* white rights */\r
13634                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13635                     if(board[0][i] == WhiteKing) break;\r
13636                   if(i == BOARD_RGHT) break;\r
13637                   FENcastlingRights[2] = i;\r
13638                   c -= AAA - 'a' + 'A';\r
13639                   if(board[0][c] >= WhiteKing) break;\r
13640                   if(c > i)\r
13641                       FENcastlingRights[0] = c;\r
13642                   else\r
13643                       FENcastlingRights[1] = c;\r
13644               }\r
13645         }\r
13646       }\r
13647     if (appData.debugMode) {\r
13648         fprintf(debugFP, "FEN castling rights:");\r
13649         for(i=0; i<nrCastlingRights; i++)\r
13650         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
13651         fprintf(debugFP, "\n");\r
13652     }\r
13653 \r
13654       while(*p==' ') p++;\r
13655     }\r
13656 \r
13657     /* read e.p. field in games that know e.p. capture */\r
13658     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13659        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13660       if(*p=='-') {\r
13661         p++; FENepStatus = EP_NONE;\r
13662       } else {\r
13663          char c = *p++ - AAA;\r
13664 \r
13665          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
13666          if(*p >= '0' && *p <='9') *p++;\r
13667          FENepStatus = c;\r
13668       }\r
13669     }\r
13670 \r
13671 \r
13672     if(sscanf(p, "%d", &i) == 1) {\r
13673         FENrulePlies = i; /* 50-move ply counter */\r
13674         /* (The move number is still ignored)    */\r
13675     }\r
13676 \r
13677     return TRUE;\r
13678 }\r
13679       \r
13680 void\r
13681 EditPositionPasteFEN(char *fen)\r
13682 {\r
13683   if (fen != NULL) {\r
13684     Board initial_position;\r
13685 \r
13686     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
13687       DisplayError(_("Bad FEN position in clipboard"), 0);\r
13688       return ;\r
13689     } else {\r
13690       int savedBlackPlaysFirst = blackPlaysFirst;\r
13691       EditPositionEvent();\r
13692       blackPlaysFirst = savedBlackPlaysFirst;\r
13693       CopyBoard(boards[0], initial_position);\r
13694           /* [HGM] copy FEN attributes as well */\r
13695           {   int i;\r
13696               initialRulePlies = FENrulePlies;\r
13697               epStatus[0] = FENepStatus;\r
13698               for( i=0; i<nrCastlingRights; i++ )\r
13699                   castlingRights[0][i] = FENcastlingRights[i];\r
13700           }\r
13701       EditPositionDone();\r
13702       DisplayBothClocks();\r
13703       DrawPosition(FALSE, boards[currentMove]);\r
13704     }\r
13705   }\r
13706 }\r