fixed long-algebraic form of drops
[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, char *castle, char *ep));\r
161 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));\r
162 void ShowMove P((int fromX, int fromY, int toX, int toY));\r
163 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,\r
164                    /*char*/int promoChar));\r
165 void BackwardInner P((int target));\r
166 void ForwardInner P((int target));\r
167 void GameEnds P((ChessMove result, char *resultDetails, int whosays));\r
168 void EditPositionDone P((void));\r
169 void PrintOpponents P((FILE *fp));\r
170 void PrintPosition P((FILE *fp, int move));\r
171 void StartChessProgram P((ChessProgramState *cps));\r
172 void SendToProgram P((char *message, ChessProgramState *cps));\r
173 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));\r
174 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,\r
175                            char *buf, int count, int error));\r
176 void SendTimeControl P((ChessProgramState *cps,\r
177                         int mps, long tc, int inc, int sd, int st));\r
178 char *TimeControlTagValue P((void));\r
179 void Attention P((ChessProgramState *cps));\r
180 void FeedMovesToProgram P((ChessProgramState *cps, int upto));\r
181 void ResurrectChessProgram P((void));\r
182 void DisplayComment P((int moveNumber, char *text));\r
183 void DisplayMove P((int moveNumber));\r
184 void DisplayAnalysis P((void));\r
185 \r
186 void ParseGameHistory P((char *game));\r
187 void ParseBoard12 P((char *string));\r
188 void StartClocks P((void));\r
189 void SwitchClocks P((void));\r
190 void StopClocks P((void));\r
191 void ResetClocks P((void));\r
192 char *PGNDate P((void));\r
193 void SetGameInfo P((void));\r
194 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));\r
195 int RegisterMove P((void));\r
196 void MakeRegisteredMove P((void));\r
197 void TruncateGame P((void));\r
198 int looking_at P((char *, int *, char *));\r
199 void CopyPlayerNameIntoFileName P((char **, char *));\r
200 char *SavePart P((char *));\r
201 int SaveGameOldStyle P((FILE *));\r
202 int SaveGamePGN P((FILE *));\r
203 void GetTimeMark P((TimeMark *));\r
204 long SubtractTimeMarks P((TimeMark *, TimeMark *));\r
205 int CheckFlags P((void));\r
206 long NextTickLength P((long));\r
207 void CheckTimeControl P((void));\r
208 void show_bytes P((FILE *, char *, int));\r
209 int string_to_rating P((char *str));\r
210 void ParseFeatures P((char* args, ChessProgramState *cps));\r
211 void InitBackEnd3 P((void));\r
212 void FeatureDone P((ChessProgramState* cps, int val));\r
213 void InitChessProgram P((ChessProgramState *cps, int setup));\r
214 void OutputKibitz(int window, char *text);\r
215 int PerpetualChase(int first, int last);\r
216 int EngineOutputIsUp();\r
217 void InitDrawingSizes(int x, int y);\r
218 \r
219 #ifdef WIN32\r
220        extern void ConsoleCreate();\r
221 #endif\r
222 \r
223 ChessProgramState *WhitePlayer();\r
224 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c\r
225 int VerifyDisplayMode P(());\r
226 \r
227 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment\r
228 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c\r
229 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move\r
230 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book\r
231 extern char installDir[MSG_SIZ];\r
232 \r
233 extern int tinyLayout, smallLayout;\r
234 ChessProgramStats programStats;\r
235 static int exiting = 0; /* [HGM] moved to top */\r
236 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;\r
237 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */\r
238 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */\r
239 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */\r
240 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */\r
241 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */\r
242 int opponentKibitzes;\r
243 \r
244 /* States for ics_getting_history */\r
245 #define H_FALSE 0\r
246 #define H_REQUESTED 1\r
247 #define H_GOT_REQ_HEADER 2\r
248 #define H_GOT_UNREQ_HEADER 3\r
249 #define H_GETTING_MOVES 4\r
250 #define H_GOT_UNWANTED_HEADER 5\r
251 \r
252 /* whosays values for GameEnds */\r
253 #define GE_ICS 0\r
254 #define GE_ENGINE 1\r
255 #define GE_PLAYER 2\r
256 #define GE_FILE 3\r
257 #define GE_XBOARD 4\r
258 #define GE_ENGINE1 5\r
259 #define GE_ENGINE2 6\r
260 \r
261 /* Maximum number of games in a cmail message */\r
262 #define CMAIL_MAX_GAMES 20\r
263 \r
264 /* Different types of move when calling RegisterMove */\r
265 #define CMAIL_MOVE   0\r
266 #define CMAIL_RESIGN 1\r
267 #define CMAIL_DRAW   2\r
268 #define CMAIL_ACCEPT 3\r
269 \r
270 /* Different types of result to remember for each game */\r
271 #define CMAIL_NOT_RESULT 0\r
272 #define CMAIL_OLD_RESULT 1\r
273 #define CMAIL_NEW_RESULT 2\r
274 \r
275 /* Telnet protocol constants */\r
276 #define TN_WILL 0373\r
277 #define TN_WONT 0374\r
278 #define TN_DO   0375\r
279 #define TN_DONT 0376\r
280 #define TN_IAC  0377\r
281 #define TN_ECHO 0001\r
282 #define TN_SGA  0003\r
283 #define TN_PORT 23\r
284 \r
285 /* [AS] */\r
286 static char * safeStrCpy( char * dst, const char * src, size_t count )\r
287 {\r
288     assert( dst != NULL );\r
289     assert( src != NULL );\r
290     assert( count > 0 );\r
291 \r
292     strncpy( dst, src, count );\r
293     dst[ count-1 ] = '\0';\r
294     return dst;\r
295 }\r
296 \r
297 #if 0\r
298 //[HGM] for future use? Conditioned out for now to suppress warning.\r
299 static char * safeStrCat( char * dst, const char * src, size_t count )\r
300 {\r
301     size_t  dst_len;\r
302 \r
303     assert( dst != NULL );\r
304     assert( src != NULL );\r
305     assert( count > 0 );\r
306 \r
307     dst_len = strlen(dst);\r
308 \r
309     assert( count > dst_len ); /* Buffer size must be greater than current length */\r
310 \r
311     safeStrCpy( dst + dst_len, src, count - dst_len );\r
312 \r
313     return dst;\r
314 }\r
315 #endif\r
316 \r
317 /* Some compiler can't cast u64 to double\r
318  * This function do the job for us:\r
319 \r
320  * We use the highest bit for cast, this only\r
321  * works if the highest bit is not\r
322  * in use (This should not happen)\r
323  *\r
324  * We used this for all compiler\r
325  */\r
326 double\r
327 u64ToDouble(u64 value)\r
328 {\r
329   double r;\r
330   u64 tmp = value & u64Const(0x7fffffffffffffff);\r
331   r = (double)(s64)tmp;\r
332   if (value & u64Const(0x8000000000000000))\r
333        r +=  9.2233720368547758080e18; /* 2^63 */\r
334  return r;\r
335 }\r
336 \r
337 /* Fake up flags for now, as we aren't keeping track of castling\r
338    availability yet. [HGM] Change of logic: the flag now only\r
339    indicates the type of castlings allowed by the rule of the game.\r
340    The actual rights themselves are maintained in the array\r
341    castlingRights, as part of the game history, and are not probed\r
342    by this function.\r
343  */\r
344 int\r
345 PosFlags(index)\r
346 {\r
347   int flags = F_ALL_CASTLE_OK;\r
348   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;\r
349   switch (gameInfo.variant) {\r
350   case VariantSuicide:\r
351     flags &= ~F_ALL_CASTLE_OK;\r
352   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!\r
353     flags |= F_IGNORE_CHECK;\r
354   case VariantLosers:\r
355     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist\r
356     break;\r
357   case VariantAtomic:\r
358     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;\r
359     break;\r
360   case VariantKriegspiel:\r
361     flags |= F_KRIEGSPIEL_CAPTURE;\r
362     break;\r
363   case VariantCapaRandom: \r
364   case VariantFischeRandom:\r
365     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */\r
366   case VariantNoCastle:\r
367   case VariantShatranj:\r
368   case VariantCourier:\r
369     flags &= ~F_ALL_CASTLE_OK;\r
370     break;\r
371   default:\r
372     break;\r
373   }\r
374   return flags;\r
375 }\r
376 \r
377 FILE *gameFileFP, *debugFP;\r
378 \r
379 /* \r
380     [AS] Note: sometimes, the sscanf() function is used to parse the input\r
381     into a fixed-size buffer. Because of this, we must be prepared to\r
382     receive strings as long as the size of the input buffer, which is currently\r
383     set to 4K for Windows and 8K for the rest.\r
384     So, we must either allocate sufficiently large buffers here, or\r
385     reduce the size of the input buffer in the input reading part.\r
386 */\r
387 \r
388 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];\r
389 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];\r
390 char thinkOutput1[MSG_SIZ*10];\r
391 \r
392 ChessProgramState first, second;\r
393 \r
394 /* premove variables */\r
395 int premoveToX = 0;\r
396 int premoveToY = 0;\r
397 int premoveFromX = 0;\r
398 int premoveFromY = 0;\r
399 int premovePromoChar = 0;\r
400 int gotPremove = 0;\r
401 Boolean alarmSounded;\r
402 /* end premove variables */\r
403 \r
404 char *ics_prefix = "$";\r
405 int ics_type = ICS_GENERIC;\r
406 \r
407 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;\r
408 int pauseExamForwardMostMove = 0;\r
409 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;\r
410 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];\r
411 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;\r
412 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;\r
413 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;\r
414 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;\r
415 int whiteFlag = FALSE, blackFlag = FALSE;\r
416 int userOfferedDraw = FALSE;\r
417 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;\r
418 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;\r
419 int cmailMoveType[CMAIL_MAX_GAMES];\r
420 long ics_clock_paused = 0;\r
421 ProcRef icsPR = NoProc, cmailPR = NoProc;\r
422 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;\r
423 GameMode gameMode = BeginningOfGame;\r
424 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];\r
425 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];\r
426 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */\r
427 int hiddenThinkOutputState = 0; /* [AS] */\r
428 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */\r
429 int adjudicateLossPlies = 6;\r
430 char white_holding[64], black_holding[64];\r
431 TimeMark lastNodeCountTime;\r
432 long lastNodeCount=0;\r
433 int have_sent_ICS_logon = 0;\r
434 int movesPerSession;\r
435 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;\r
436 long timeControl_2; /* [AS] Allow separate time controls */\r
437 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */\r
438 long timeRemaining[2][MAX_MOVES];\r
439 int matchGame = 0;\r
440 TimeMark programStartTime;\r
441 char ics_handle[MSG_SIZ];\r
442 int have_set_title = 0;\r
443 \r
444 /* animateTraining preserves the state of appData.animate\r
445  * when Training mode is activated. This allows the\r
446  * response to be animated when appData.animate == TRUE and\r
447  * appData.animateDragging == TRUE.\r
448  */\r
449 Boolean animateTraining;\r
450 \r
451 GameInfo gameInfo;\r
452 \r
453 AppData appData;\r
454 \r
455 Board boards[MAX_MOVES];\r
456 /* [HGM] Following 7 needed for accurate legality tests: */\r
457 char  epStatus[MAX_MOVES];\r
458 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1\r
459 char  castlingRank[BOARD_SIZE]; // and corresponding ranks\r
460 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];\r
461 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status\r
462 int   initialRulePlies, FENrulePlies;\r
463 char  FENepStatus;\r
464 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)\r
465 int loadFlag = 0; \r
466 int shuffleOpenings;\r
467 \r
468 ChessSquare  FIDEArray[2][BOARD_SIZE] = {\r
469     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
470         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
471     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
472         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
473 };\r
474 \r
475 ChessSquare twoKingsArray[2][BOARD_SIZE] = {\r
476     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
477         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },\r
478     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
479         BlackKing, BlackKing, BlackKnight, BlackRook }\r
480 };\r
481 \r
482 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {\r
483     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,\r
484         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },\r
485     { BlackRook, BlackMan, BlackBishop, BlackQueen,\r
486         BlackUnicorn, BlackBishop, BlackMan, BlackRook }\r
487 };\r
488 \r
489 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */\r
490     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,\r
491         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
492     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,\r
493         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
494 };\r
495 \r
496 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */\r
497     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,\r
498         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
499     { BlackRook, BlackKnight, BlackAlfil, BlackKing,\r
500         BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
501 };\r
502 \r
503 \r
504 #if (BOARD_SIZE>=10)\r
505 ChessSquare ShogiArray[2][BOARD_SIZE] = {\r
506     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,\r
507         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },\r
508     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,\r
509         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }\r
510 };\r
511 \r
512 ChessSquare XiangqiArray[2][BOARD_SIZE] = {\r
513     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,\r
514         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
515     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,\r
516         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
517 };\r
518 \r
519 ChessSquare CapablancaArray[2][BOARD_SIZE] = {\r
520     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, \r
521         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },\r
522     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, \r
523         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
524 };\r
525 \r
526 ChessSquare GreatArray[2][BOARD_SIZE] = {\r
527     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, \r
528         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },\r
529     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, \r
530         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },\r
531 };\r
532 \r
533 ChessSquare JanusArray[2][BOARD_SIZE] = {\r
534     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, \r
535         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },\r
536     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, \r
537         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }\r
538 };\r
539 \r
540 #ifdef GOTHIC\r
541 ChessSquare GothicArray[2][BOARD_SIZE] = {\r
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, \r
543         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },\r
544     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, \r
545         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }\r
546 };\r
547 #else // !GOTHIC\r
548 #define GothicArray CapablancaArray\r
549 #endif // !GOTHIC\r
550 \r
551 #ifdef FALCON\r
552 ChessSquare FalconArray[2][BOARD_SIZE] = {\r
553     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, \r
554         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },\r
555     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, \r
556         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }\r
557 };\r
558 #else // !FALCON\r
559 #define FalconArray CapablancaArray\r
560 #endif // !FALCON\r
561 \r
562 #else // !(BOARD_SIZE>=10)\r
563 #define XiangqiPosition FIDEArray\r
564 #define CapablancaArray FIDEArray\r
565 #define GothicArray FIDEArray\r
566 #define GreatArray FIDEArray\r
567 #endif // !(BOARD_SIZE>=10)\r
568 \r
569 #if (BOARD_SIZE>=12)\r
570 ChessSquare CourierArray[2][BOARD_SIZE] = {\r
571     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,\r
572         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },\r
573     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,\r
574         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }\r
575 };\r
576 #else // !(BOARD_SIZE>=12)\r
577 #define CourierArray CapablancaArray\r
578 #endif // !(BOARD_SIZE>=12)\r
579 \r
580 \r
581 Board initialPosition;\r
582 \r
583 \r
584 /* Convert str to a rating. Checks for special cases of "----",\r
585 \r
586    "++++", etc. Also strips ()'s */\r
587 int\r
588 string_to_rating(str)\r
589   char *str;\r
590 {\r
591   while(*str && !isdigit(*str)) ++str;\r
592   if (!*str)\r
593     return 0;   /* One of the special "no rating" cases */\r
594   else\r
595     return atoi(str);\r
596 }\r
597 \r
598 void\r
599 ClearProgramStats()\r
600 {\r
601     /* Init programStats */\r
602     programStats.movelist[0] = 0;\r
603     programStats.depth = 0;\r
604     programStats.nr_moves = 0;\r
605     programStats.moves_left = 0;\r
606     programStats.nodes = 0;\r
607     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output\r
608     programStats.score = 0;\r
609     programStats.got_only_move = 0;\r
610     programStats.got_fail = 0;\r
611     programStats.line_is_book = 0;\r
612 }\r
613 \r
614 void\r
615 InitBackEnd1()\r
616 {\r
617     int matched, min, sec;\r
618 \r
619     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options\r
620 \r
621     GetTimeMark(&programStartTime);\r
622     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level\r
623 \r
624     ClearProgramStats();\r
625     programStats.ok_to_send = 1;\r
626     programStats.seen_stat = 0;\r
627 \r
628     /*\r
629      * Initialize game list\r
630      */\r
631     ListNew(&gameList);\r
632 \r
633 \r
634     /*\r
635      * Internet chess server status\r
636      */\r
637     if (appData.icsActive) {\r
638         appData.matchMode = FALSE;\r
639         appData.matchGames = 0;\r
640 #if ZIPPY       \r
641         appData.noChessProgram = !appData.zippyPlay;\r
642 #else\r
643         appData.zippyPlay = FALSE;\r
644         appData.zippyTalk = FALSE;\r
645         appData.noChessProgram = TRUE;\r
646 #endif\r
647         if (*appData.icsHelper != NULLCHAR) {\r
648             appData.useTelnet = TRUE;\r
649             appData.telnetProgram = appData.icsHelper;\r
650         }\r
651     } else {\r
652         appData.zippyTalk = appData.zippyPlay = FALSE;\r
653     }\r
654 \r
655     /* [AS] Initialize pv info list [HGM] and game state */\r
656     {\r
657         int i, j;\r
658 \r
659         for( i=0; i<MAX_MOVES; i++ ) {\r
660             pvInfoList[i].depth = -1;\r
661             epStatus[i]=EP_NONE;\r
662             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
663         }\r
664     }\r
665 \r
666     /*\r
667      * Parse timeControl resource\r
668      */\r
669     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,\r
670                           appData.movesPerSession)) {\r
671         char buf[MSG_SIZ];\r
672         sprintf(buf, _("bad timeControl option %s"), appData.timeControl);\r
673         DisplayFatalError(buf, 0, 2);\r
674     }\r
675 \r
676     /*\r
677      * Parse searchTime resource\r
678      */\r
679     if (*appData.searchTime != NULLCHAR) {\r
680         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);\r
681         if (matched == 1) {\r
682             searchTime = min * 60;\r
683         } else if (matched == 2) {\r
684             searchTime = min * 60 + sec;\r
685         } else {\r
686             char buf[MSG_SIZ];\r
687             sprintf(buf, _("bad searchTime option %s"), appData.searchTime);\r
688             DisplayFatalError(buf, 0, 2);\r
689         }\r
690     }\r
691 \r
692     /* [AS] Adjudication threshold */\r
693     adjudicateLossThreshold = appData.adjudicateLossThreshold;\r
694     \r
695     first.which = "first";\r
696     second.which = "second";\r
697     first.maybeThinking = second.maybeThinking = FALSE;\r
698     first.pr = second.pr = NoProc;\r
699     first.isr = second.isr = NULL;\r
700     first.sendTime = second.sendTime = 2;\r
701     first.sendDrawOffers = 1;\r
702     if (appData.firstPlaysBlack) {\r
703         first.twoMachinesColor = "black\n";\r
704         second.twoMachinesColor = "white\n";\r
705     } else {\r
706         first.twoMachinesColor = "white\n";\r
707         second.twoMachinesColor = "black\n";\r
708     }\r
709     first.program = appData.firstChessProgram;\r
710     second.program = appData.secondChessProgram;\r
711     first.host = appData.firstHost;\r
712     second.host = appData.secondHost;\r
713     first.dir = appData.firstDirectory;\r
714     second.dir = appData.secondDirectory;\r
715     first.other = &second;\r
716     second.other = &first;\r
717     first.initString = appData.initString;\r
718     second.initString = appData.secondInitString;\r
719     first.computerString = appData.firstComputerString;\r
720     second.computerString = appData.secondComputerString;\r
721     first.useSigint = second.useSigint = TRUE;\r
722     first.useSigterm = second.useSigterm = TRUE;\r
723     first.reuse = appData.reuseFirst;\r
724     second.reuse = appData.reuseSecond;\r
725     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second\r
726     second.nps = appData.secondNPS;\r
727     first.useSetboard = second.useSetboard = FALSE;\r
728     first.useSAN = second.useSAN = FALSE;\r
729     first.usePing = second.usePing = FALSE;\r
730     first.lastPing = second.lastPing = 0;\r
731     first.lastPong = second.lastPong = 0;\r
732     first.usePlayother = second.usePlayother = FALSE;\r
733     first.useColors = second.useColors = TRUE;\r
734     first.useUsermove = second.useUsermove = FALSE;\r
735     first.sendICS = second.sendICS = FALSE;\r
736     first.sendName = second.sendName = appData.icsActive;\r
737     first.sdKludge = second.sdKludge = FALSE;\r
738     first.stKludge = second.stKludge = FALSE;\r
739     TidyProgramName(first.program, first.host, first.tidy);\r
740     TidyProgramName(second.program, second.host, second.tidy);\r
741     first.matchWins = second.matchWins = 0;\r
742     strcpy(first.variants, appData.variant);\r
743     strcpy(second.variants, appData.variant);\r
744     first.analysisSupport = second.analysisSupport = 2; /* detect */\r
745     first.analyzing = second.analyzing = FALSE;\r
746     first.initDone = second.initDone = FALSE;\r
747 \r
748     /* New features added by Tord: */\r
749     first.useFEN960 = FALSE; second.useFEN960 = FALSE;\r
750     first.useOOCastle = TRUE; second.useOOCastle = TRUE;\r
751     /* End of new features added by Tord. */\r
752     first.fenOverride  = appData.fenOverride1;\r
753     second.fenOverride = appData.fenOverride2;\r
754 \r
755     /* [HGM] time odds: set factor for each machine */\r
756     first.timeOdds  = appData.firstTimeOdds;\r
757     second.timeOdds = appData.secondTimeOdds;\r
758     { int norm = 1;\r
759         if(appData.timeOddsMode) {\r
760             norm = first.timeOdds;\r
761             if(norm > second.timeOdds) norm = second.timeOdds;\r
762         }\r
763         first.timeOdds /= norm;\r
764         second.timeOdds /= norm;\r
765     }\r
766 \r
767     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/\r
768     first.accumulateTC = appData.firstAccumulateTC;\r
769     second.accumulateTC = appData.secondAccumulateTC;\r
770     first.maxNrOfSessions = second.maxNrOfSessions = 1;\r
771 \r
772     /* [HGM] debug */\r
773     first.debug = second.debug = FALSE;\r
774     first.supportsNPS = second.supportsNPS = UNKNOWN;\r
775 \r
776     /* [HGM] options */\r
777     first.optionSettings  = appData.firstOptions;\r
778     second.optionSettings = appData.secondOptions;\r
779 \r
780     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
781     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
782     first.isUCI = appData.firstIsUCI; /* [AS] */\r
783     second.isUCI = appData.secondIsUCI; /* [AS] */\r
784     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
785     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
786 \r
787     if (appData.firstProtocolVersion > PROTOVER ||\r
788         appData.firstProtocolVersion < 1) {\r
789       char buf[MSG_SIZ];\r
790       sprintf(buf, _("protocol version %d not supported"),\r
791               appData.firstProtocolVersion);\r
792       DisplayFatalError(buf, 0, 2);\r
793     } else {\r
794       first.protocolVersion = appData.firstProtocolVersion;\r
795     }\r
796 \r
797     if (appData.secondProtocolVersion > PROTOVER ||\r
798         appData.secondProtocolVersion < 1) {\r
799       char buf[MSG_SIZ];\r
800       sprintf(buf, _("protocol version %d not supported"),\r
801               appData.secondProtocolVersion);\r
802       DisplayFatalError(buf, 0, 2);\r
803     } else {\r
804       second.protocolVersion = appData.secondProtocolVersion;\r
805     }\r
806 \r
807     if (appData.icsActive) {\r
808         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */\r
809     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
810         appData.clockMode = FALSE;\r
811         first.sendTime = second.sendTime = 0;\r
812     }\r
813     \r
814 #if ZIPPY\r
815     /* Override some settings from environment variables, for backward\r
816        compatibility.  Unfortunately it's not feasible to have the env\r
817        vars just set defaults, at least in xboard.  Ugh.\r
818     */\r
819     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
820       ZippyInit();\r
821     }\r
822 #endif\r
823     \r
824     if (appData.noChessProgram) {\r
825         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
826                                         + strlen(PATCHLEVEL));\r
827         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
828     } else {\r
829 #if 0\r
830         char *p, *q;\r
831         q = first.program;\r
832         while (*q != ' ' && *q != NULLCHAR) q++;\r
833         p = q;\r
834         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */\r
835         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
836                                         + strlen(PATCHLEVEL) + (q - p));\r
837         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
838         strncat(programVersion, p, q - p);\r
839 #else\r
840         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */\r
841         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
842                                         + strlen(PATCHLEVEL) + strlen(first.tidy));\r
843         sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);\r
844 #endif\r
845     }\r
846 \r
847     if (!appData.icsActive) {\r
848       char buf[MSG_SIZ];\r
849       /* Check for variants that are supported only in ICS mode,\r
850          or not at all.  Some that are accepted here nevertheless\r
851          have bugs; see comments below.\r
852       */\r
853       VariantClass variant = StringToVariant(appData.variant);\r
854       switch (variant) {\r
855       case VariantBughouse:     /* need four players and two boards */\r
856       case VariantKriegspiel:   /* need to hide pieces and move details */\r
857       /* case VariantFischeRandom: (Fabien: moved below) */\r
858         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);\r
859         DisplayFatalError(buf, 0, 2);\r
860         return;\r
861 \r
862       case VariantUnknown:\r
863       case VariantLoadable:\r
864       case Variant29:\r
865       case Variant30:\r
866       case Variant31:\r
867       case Variant32:\r
868       case Variant33:\r
869       case Variant34:\r
870       case Variant35:\r
871       case Variant36:\r
872       default:\r
873         sprintf(buf, _("Unknown variant name %s"), appData.variant);\r
874         DisplayFatalError(buf, 0, 2);\r
875         return;\r
876 \r
877       case VariantXiangqi:    /* [HGM] repetition rules not implemented */\r
878       case VariantFairy:      /* [HGM] TestLegality definitely off! */\r
879       case VariantGothic:     /* [HGM] should work */\r
880       case VariantCapablanca: /* [HGM] should work */\r
881       case VariantCourier:    /* [HGM] initial forced moves not implemented */\r
882       case VariantShogi:      /* [HGM] drops not tested for legality */\r
883       case VariantKnightmate: /* [HGM] should work */\r
884       case VariantCylinder:   /* [HGM] untested */\r
885       case VariantFalcon:     /* [HGM] untested */\r
886       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)\r
887                                  offboard interposition not understood */\r
888       case VariantNormal:     /* definitely works! */\r
889       case VariantWildCastle: /* pieces not automatically shuffled */\r
890       case VariantNoCastle:   /* pieces not automatically shuffled */\r
891       case VariantFischeRandom: /* [HGM] works and shuffles pieces */\r
892       case VariantLosers:     /* should work except for win condition,\r
893                                  and doesn't know captures are mandatory */\r
894       case VariantSuicide:    /* should work except for win condition,\r
895                                  and doesn't know captures are mandatory */\r
896       case VariantGiveaway:   /* should work except for win condition,\r
897                                  and doesn't know captures are mandatory */\r
898       case VariantTwoKings:   /* should work */\r
899       case VariantAtomic:     /* should work except for win condition */\r
900       case Variant3Check:     /* should work except for win condition */\r
901       case VariantShatranj:   /* should work except for all win conditions */\r
902       case VariantBerolina:   /* might work if TestLegality is off */\r
903       case VariantCapaRandom: /* should work */\r
904       case VariantJanus:      /* should work */\r
905       case VariantSuper:      /* experimental */\r
906       case VariantGreat:      /* experimental, requires legality testing to be off */\r
907         break;\r
908       }\r
909     }\r
910 \r
911     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard\r
912     InitEngineUCI( installDir, &second );\r
913 }\r
914 \r
915 int NextIntegerFromString( char ** str, long * value )\r
916 {\r
917     int result = -1;\r
918     char * s = *str;\r
919 \r
920     while( *s == ' ' || *s == '\t' ) {\r
921         s++;\r
922     }\r
923 \r
924     *value = 0;\r
925 \r
926     if( *s >= '0' && *s <= '9' ) {\r
927         while( *s >= '0' && *s <= '9' ) {\r
928             *value = *value * 10 + (*s - '0');\r
929             s++;\r
930         }\r
931 \r
932         result = 0;\r
933     }\r
934 \r
935     *str = s;\r
936 \r
937     return result;\r
938 }\r
939 \r
940 int NextTimeControlFromString( char ** str, long * value )\r
941 {\r
942     long temp;\r
943     int result = NextIntegerFromString( str, &temp );\r
944 \r
945     if( result == 0 ) {\r
946         *value = temp * 60; /* Minutes */\r
947         if( **str == ':' ) {\r
948             (*str)++;\r
949             result = NextIntegerFromString( str, &temp );\r
950             *value += temp; /* Seconds */\r
951         }\r
952     }\r
953 \r
954     return result;\r
955 }\r
956 \r
957 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)\r
958 {   /* [HGM] routine added to read '+moves/time' for secondary time control */\r
959     int result = -1; long temp, temp2;\r
960 \r
961     if(**str != '+') return -1; // old params remain in force!\r
962     (*str)++;\r
963     if( NextTimeControlFromString( str, &temp ) ) return -1;\r
964 \r
965     if(**str != '/') {\r
966         /* time only: incremental or sudden-death time control */\r
967         if(**str == '+') { /* increment follows; read it */\r
968             (*str)++;\r
969             if(result = NextIntegerFromString( str, &temp2)) return -1;\r
970             *inc = temp2 * 1000;\r
971         } else *inc = 0;\r
972         *moves = 0; *tc = temp * 1000; \r
973         return 0;\r
974     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */\r
975 \r
976     (*str)++; /* classical time control */\r
977     result = NextTimeControlFromString( str, &temp2);\r
978     if(result == 0) {\r
979         *moves = temp/60;\r
980         *tc    = temp2 * 1000;\r
981         *inc   = 0;\r
982     }\r
983     return result;\r
984 }\r
985 \r
986 int GetTimeQuota(int movenr)\r
987 {   /* [HGM] get time to add from the multi-session time-control string */\r
988     int moves=1; /* kludge to force reading of first session */\r
989     long time, increment;\r
990     char *s = fullTimeControlString;\r
991 \r
992     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);\r
993     do {\r
994         if(moves) NextSessionFromString(&s, &moves, &time, &increment);\r
995         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);\r
996         if(movenr == -1) return time;    /* last move before new session     */\r
997         if(!moves) return increment;     /* current session is incremental   */\r
998         if(movenr >= 0) movenr -= moves; /* we already finished this session */\r
999     } while(movenr >= -1);               /* try again for next session       */\r
1000 \r
1001     return 0; // no new time quota on this move\r
1002 }\r
1003 \r
1004 int\r
1005 ParseTimeControl(tc, ti, mps)\r
1006      char *tc;\r
1007      int ti;\r
1008      int mps;\r
1009 {\r
1010 #if 0\r
1011     int matched, min, sec;\r
1012 \r
1013     matched = sscanf(tc, "%d:%d", &min, &sec);\r
1014     if (matched == 1) {\r
1015         timeControl = min * 60 * 1000;\r
1016     } else if (matched == 2) {\r
1017         timeControl = (min * 60 + sec) * 1000;\r
1018     } else {\r
1019         return FALSE;\r
1020     }\r
1021 #else\r
1022     long tc1;\r
1023     long tc2;\r
1024     char buf[MSG_SIZ];\r
1025 \r
1026     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;\r
1027     if(ti > 0) {\r
1028         if(mps)\r
1029              sprintf(buf, "+%d/%s+%d", mps, tc, ti);\r
1030         else sprintf(buf, "+%s+%d", tc, ti);\r
1031     } else {\r
1032         if(mps)\r
1033              sprintf(buf, "+%d/%s", mps, tc);\r
1034         else sprintf(buf, "+%s", tc);\r
1035     }\r
1036     fullTimeControlString = StrSave(buf);\r
1037 \r
1038     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
1039         return FALSE;\r
1040     }\r
1041 \r
1042     if( *tc == '/' ) {\r
1043         /* Parse second time control */\r
1044         tc++;\r
1045 \r
1046         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
1047             return FALSE;\r
1048         }\r
1049 \r
1050         if( tc2 == 0 ) {\r
1051             return FALSE;\r
1052         }\r
1053 \r
1054         timeControl_2 = tc2 * 1000;\r
1055     }\r
1056     else {\r
1057         timeControl_2 = 0;\r
1058     }\r
1059 \r
1060     if( tc1 == 0 ) {\r
1061         return FALSE;\r
1062     }\r
1063 \r
1064     timeControl = tc1 * 1000;\r
1065 #endif\r
1066 \r
1067     if (ti >= 0) {\r
1068         timeIncrement = ti * 1000;  /* convert to ms */\r
1069         movesPerSession = 0;\r
1070     } else {\r
1071         timeIncrement = 0;\r
1072         movesPerSession = mps;\r
1073     }\r
1074     return TRUE;\r
1075 }\r
1076 \r
1077 void\r
1078 InitBackEnd2()\r
1079 {\r
1080     if (appData.debugMode) {\r
1081         fprintf(debugFP, "%s\n", programVersion);\r
1082     }\r
1083 \r
1084     if (appData.matchGames > 0) {\r
1085         appData.matchMode = TRUE;\r
1086     } else if (appData.matchMode) {\r
1087         appData.matchGames = 1;\r
1088     }\r
1089     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */\r
1090         appData.matchGames = appData.sameColorGames;\r
1091     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */\r
1092         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;\r
1093         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;\r
1094     }\r
1095     Reset(TRUE, FALSE);\r
1096     if (appData.noChessProgram || first.protocolVersion == 1) {\r
1097       InitBackEnd3();\r
1098     } else {\r
1099       /* kludge: allow timeout for initial "feature" commands */\r
1100       FreezeUI();\r
1101       DisplayMessage("", _("Starting chess program"));\r
1102       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
1103     }\r
1104 }\r
1105 \r
1106 void\r
1107 InitBackEnd3 P((void))\r
1108 {\r
1109     GameMode initialMode;\r
1110     char buf[MSG_SIZ];\r
1111     int err;\r
1112 \r
1113     InitChessProgram(&first, startedFromSetupPosition);\r
1114 \r
1115 \r
1116     if (appData.icsActive) {\r
1117 #ifdef WIN32\r
1118         /* [DM] Make a console window if needed [HGM] merged ifs */\r
1119         ConsoleCreate(); \r
1120 #endif\r
1121         err = establish();\r
1122         if (err != 0) {\r
1123             if (*appData.icsCommPort != NULLCHAR) {\r
1124                 sprintf(buf, _("Could not open comm port %s"),  \r
1125                         appData.icsCommPort);\r
1126             } else {\r
1127                 sprintf(buf, _("Could not connect to host %s, port %s"),  \r
1128                         appData.icsHost, appData.icsPort);\r
1129             }\r
1130             DisplayFatalError(buf, err, 1);\r
1131             return;\r
1132         }\r
1133         SetICSMode();\r
1134         telnetISR =\r
1135           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
1136         fromUserISR =\r
1137           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
1138     } else if (appData.noChessProgram) {\r
1139         SetNCPMode();\r
1140     } else {\r
1141         SetGNUMode();\r
1142     }\r
1143 \r
1144     if (*appData.cmailGameName != NULLCHAR) {\r
1145         SetCmailMode();\r
1146         OpenLoopback(&cmailPR);\r
1147         cmailISR =\r
1148           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
1149     }\r
1150     \r
1151     ThawUI();\r
1152     DisplayMessage("", "");\r
1153     if (StrCaseCmp(appData.initialMode, "") == 0) {\r
1154       initialMode = BeginningOfGame;\r
1155     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
1156       initialMode = TwoMachinesPlay;\r
1157     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
1158       initialMode = AnalyzeFile; \r
1159     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
1160       initialMode = AnalyzeMode;\r
1161     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
1162       initialMode = MachinePlaysWhite;\r
1163     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
1164       initialMode = MachinePlaysBlack;\r
1165     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
1166       initialMode = EditGame;\r
1167     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
1168       initialMode = EditPosition;\r
1169     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
1170       initialMode = Training;\r
1171     } else {\r
1172       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);\r
1173       DisplayFatalError(buf, 0, 2);\r
1174       return;\r
1175     }\r
1176 \r
1177     if (appData.matchMode) {\r
1178         /* Set up machine vs. machine match */\r
1179         if (appData.noChessProgram) {\r
1180             DisplayFatalError(_("Can't have a match with no chess programs"),\r
1181                               0, 2);\r
1182             return;\r
1183         }\r
1184         matchMode = TRUE;\r
1185         matchGame = 1;\r
1186         if (*appData.loadGameFile != NULLCHAR) {\r
1187             int index = appData.loadGameIndex; // [HGM] autoinc\r
1188             if(index<0) lastIndex = index = 1;\r
1189             if (!LoadGameFromFile(appData.loadGameFile,\r
1190                                   index,\r
1191                                   appData.loadGameFile, FALSE)) {\r
1192                 DisplayFatalError(_("Bad game file"), 0, 1);\r
1193                 return;\r
1194             }\r
1195         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1196             int index = appData.loadPositionIndex; // [HGM] autoinc\r
1197             if(index<0) lastIndex = index = 1;\r
1198             if (!LoadPositionFromFile(appData.loadPositionFile,\r
1199                                       index,\r
1200                                       appData.loadPositionFile)) {\r
1201                 DisplayFatalError(_("Bad position file"), 0, 1);\r
1202                 return;\r
1203             }\r
1204         }\r
1205         TwoMachinesEvent();\r
1206     } else if (*appData.cmailGameName != NULLCHAR) {\r
1207         /* Set up cmail mode */\r
1208         ReloadCmailMsgEvent(TRUE);\r
1209     } else {\r
1210         /* Set up other modes */\r
1211         if (initialMode == AnalyzeFile) {\r
1212           if (*appData.loadGameFile == NULLCHAR) {\r
1213             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);\r
1214             return;\r
1215           }\r
1216         }\r
1217         if (*appData.loadGameFile != NULLCHAR) {\r
1218             (void) LoadGameFromFile(appData.loadGameFile,\r
1219                                     appData.loadGameIndex,\r
1220                                     appData.loadGameFile, TRUE);\r
1221         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1222             (void) LoadPositionFromFile(appData.loadPositionFile,\r
1223                                         appData.loadPositionIndex,\r
1224                                         appData.loadPositionFile);\r
1225             /* [HGM] try to make self-starting even after FEN load */\r
1226             /* to allow automatic setup of fairy variants with wtm */\r
1227             if(initialMode == BeginningOfGame && !blackPlaysFirst) {\r
1228                 gameMode = BeginningOfGame;\r
1229                 setboardSpoiledMachineBlack = 1;\r
1230             }\r
1231             /* [HGM] loadPos: make that every new game uses the setup */\r
1232             /* from file as long as we do not switch variant          */\r
1233             if(!blackPlaysFirst) { int i;\r
1234                 startedFromPositionFile = TRUE;\r
1235                 CopyBoard(filePosition, boards[0]);\r
1236                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];\r
1237             }\r
1238         }\r
1239         if (initialMode == AnalyzeMode) {\r
1240           if (appData.noChessProgram) {\r
1241             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);\r
1242             return;\r
1243           }\r
1244           if (appData.icsActive) {\r
1245             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);\r
1246             return;\r
1247           }\r
1248           AnalyzeModeEvent();\r
1249         } else if (initialMode == AnalyzeFile) {\r
1250           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent\r
1251           ShowThinkingEvent();\r
1252           AnalyzeFileEvent();\r
1253           AnalysisPeriodicEvent(1);\r
1254         } else if (initialMode == MachinePlaysWhite) {\r
1255           if (appData.noChessProgram) {\r
1256             DisplayFatalError(_("MachineWhite mode requires a chess engine"),\r
1257                               0, 2);\r
1258             return;\r
1259           }\r
1260           if (appData.icsActive) {\r
1261             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),\r
1262                               0, 2);\r
1263             return;\r
1264           }\r
1265           MachineWhiteEvent();\r
1266         } else if (initialMode == MachinePlaysBlack) {\r
1267           if (appData.noChessProgram) {\r
1268             DisplayFatalError(_("MachineBlack mode requires a chess engine"),\r
1269                               0, 2);\r
1270             return;\r
1271           }\r
1272           if (appData.icsActive) {\r
1273             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),\r
1274                               0, 2);\r
1275             return;\r
1276           }\r
1277           MachineBlackEvent();\r
1278         } else if (initialMode == TwoMachinesPlay) {\r
1279           if (appData.noChessProgram) {\r
1280             DisplayFatalError(_("TwoMachines mode requires a chess engine"),\r
1281                               0, 2);\r
1282             return;\r
1283           }\r
1284           if (appData.icsActive) {\r
1285             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),\r
1286                               0, 2);\r
1287             return;\r
1288           }\r
1289           TwoMachinesEvent();\r
1290         } else if (initialMode == EditGame) {\r
1291           EditGameEvent();\r
1292         } else if (initialMode == EditPosition) {\r
1293           EditPositionEvent();\r
1294         } else if (initialMode == Training) {\r
1295           if (*appData.loadGameFile == NULLCHAR) {\r
1296             DisplayFatalError(_("Training mode requires a game file"), 0, 2);\r
1297             return;\r
1298           }\r
1299           TrainingEvent();\r
1300         }\r
1301     }\r
1302 }\r
1303 \r
1304 /*\r
1305  * Establish will establish a contact to a remote host.port.\r
1306  * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
1307  *  used to talk to the host.\r
1308  * Returns 0 if okay, error code if not.\r
1309  */\r
1310 int\r
1311 establish()\r
1312 {\r
1313     char buf[MSG_SIZ];\r
1314 \r
1315     if (*appData.icsCommPort != NULLCHAR) {\r
1316         /* Talk to the host through a serial comm port */\r
1317         return OpenCommPort(appData.icsCommPort, &icsPR);\r
1318 \r
1319     } else if (*appData.gateway != NULLCHAR) {\r
1320         if (*appData.remoteShell == NULLCHAR) {\r
1321             /* Use the rcmd protocol to run telnet program on a gateway host */\r
1322             sprintf(buf, "%s %s %s",\r
1323                     appData.telnetProgram, appData.icsHost, appData.icsPort);\r
1324             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
1325 \r
1326         } else {\r
1327             /* Use the rsh program to run telnet program on a gateway host */\r
1328             if (*appData.remoteUser == NULLCHAR) {\r
1329                 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,\r
1330                         appData.gateway, appData.telnetProgram,\r
1331                         appData.icsHost, appData.icsPort);\r
1332             } else {\r
1333                 sprintf(buf, "%s %s -l %s %s %s %s",\r
1334                         appData.remoteShell, appData.gateway, \r
1335                         appData.remoteUser, appData.telnetProgram,\r
1336                         appData.icsHost, appData.icsPort);\r
1337             }\r
1338             return StartChildProcess(buf, "", &icsPR);\r
1339 \r
1340         }\r
1341     } else if (appData.useTelnet) {\r
1342         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
1343 \r
1344     } else {\r
1345         /* TCP socket interface differs somewhat between\r
1346            Unix and NT; handle details in the front end.\r
1347            */\r
1348         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
1349     }\r
1350 }\r
1351 \r
1352 void\r
1353 show_bytes(fp, buf, count)\r
1354      FILE *fp;\r
1355      char *buf;\r
1356      int count;\r
1357 {\r
1358     while (count--) {\r
1359         if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
1360             fprintf(fp, "\\%03o", *buf & 0xff);\r
1361         } else {\r
1362             putc(*buf, fp);\r
1363         }\r
1364         buf++;\r
1365     }\r
1366     fflush(fp);\r
1367 }\r
1368 \r
1369 /* Returns an errno value */\r
1370 int\r
1371 OutputMaybeTelnet(pr, message, count, outError)\r
1372      ProcRef pr;\r
1373      char *message;\r
1374      int count;\r
1375      int *outError;\r
1376 {\r
1377     char buf[8192], *p, *q, *buflim;\r
1378     int left, newcount, outcount;\r
1379 \r
1380     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
1381         *appData.gateway != NULLCHAR) {\r
1382         if (appData.debugMode) {\r
1383             fprintf(debugFP, ">ICS: ");\r
1384             show_bytes(debugFP, message, count);\r
1385             fprintf(debugFP, "\n");\r
1386         }\r
1387         return OutputToProcess(pr, message, count, outError);\r
1388     }\r
1389 \r
1390     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
1391     p = message;\r
1392     q = buf;\r
1393     left = count;\r
1394     newcount = 0;\r
1395     while (left) {\r
1396         if (q >= buflim) {\r
1397             if (appData.debugMode) {\r
1398                 fprintf(debugFP, ">ICS: ");\r
1399                 show_bytes(debugFP, buf, newcount);\r
1400                 fprintf(debugFP, "\n");\r
1401             }\r
1402             outcount = OutputToProcess(pr, buf, newcount, outError);\r
1403             if (outcount < newcount) return -1; /* to be sure */\r
1404             q = buf;\r
1405             newcount = 0;\r
1406         }\r
1407         if (*p == '\n') {\r
1408             *q++ = '\r';\r
1409             newcount++;\r
1410         } else if (((unsigned char) *p) == TN_IAC) {\r
1411             *q++ = (char) TN_IAC;\r
1412             newcount ++;\r
1413         }\r
1414         *q++ = *p++;\r
1415         newcount++;\r
1416         left--;\r
1417     }\r
1418     if (appData.debugMode) {\r
1419         fprintf(debugFP, ">ICS: ");\r
1420         show_bytes(debugFP, buf, newcount);\r
1421         fprintf(debugFP, "\n");\r
1422     }\r
1423     outcount = OutputToProcess(pr, buf, newcount, outError);\r
1424     if (outcount < newcount) return -1; /* to be sure */\r
1425     return count;\r
1426 }\r
1427 \r
1428 void\r
1429 read_from_player(isr, closure, message, count, error)\r
1430      InputSourceRef isr;\r
1431      VOIDSTAR closure;\r
1432      char *message;\r
1433      int count;\r
1434      int error;\r
1435 {\r
1436     int outError, outCount;\r
1437     static int gotEof = 0;\r
1438 \r
1439     /* Pass data read from player on to ICS */\r
1440     if (count > 0) {\r
1441         gotEof = 0;\r
1442         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
1443         if (outCount < count) {\r
1444             DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1445         }\r
1446     } else if (count < 0) {\r
1447         RemoveInputSource(isr);\r
1448         DisplayFatalError(_("Error reading from keyboard"), error, 1);\r
1449     } else if (gotEof++ > 0) {\r
1450         RemoveInputSource(isr);\r
1451         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);\r
1452     }\r
1453 }\r
1454 \r
1455 void\r
1456 SendToICS(s)\r
1457      char *s;\r
1458 {\r
1459     int count, outCount, outError;\r
1460 \r
1461     if (icsPR == NULL) return;\r
1462 \r
1463     count = strlen(s);\r
1464     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
1465     if (outCount < count) {\r
1466         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1467     }\r
1468 }\r
1469 \r
1470 /* This is used for sending logon scripts to the ICS. Sending\r
1471    without a delay causes problems when using timestamp on ICC\r
1472    (at least on my machine). */\r
1473 void\r
1474 SendToICSDelayed(s,msdelay)\r
1475      char *s;\r
1476      long msdelay;\r
1477 {\r
1478     int count, outCount, outError;\r
1479 \r
1480     if (icsPR == NULL) return;\r
1481 \r
1482     count = strlen(s);\r
1483     if (appData.debugMode) {\r
1484         fprintf(debugFP, ">ICS: ");\r
1485         show_bytes(debugFP, s, count);\r
1486         fprintf(debugFP, "\n");\r
1487     }\r
1488     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
1489                                       msdelay);\r
1490     if (outCount < count) {\r
1491         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1492     }\r
1493 }\r
1494 \r
1495 \r
1496 /* Remove all highlighting escape sequences in s\r
1497    Also deletes any suffix starting with '(' \r
1498    */\r
1499 char *\r
1500 StripHighlightAndTitle(s)\r
1501      char *s;\r
1502 {\r
1503     static char retbuf[MSG_SIZ];\r
1504     char *p = retbuf;\r
1505 \r
1506     while (*s != NULLCHAR) {\r
1507         while (*s == '\033') {\r
1508             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1509             if (*s != NULLCHAR) s++;\r
1510         }\r
1511         while (*s != NULLCHAR && *s != '\033') {\r
1512             if (*s == '(' || *s == '[') {\r
1513                 *p = NULLCHAR;\r
1514                 return retbuf;\r
1515             }\r
1516             *p++ = *s++;\r
1517         }\r
1518     }\r
1519     *p = NULLCHAR;\r
1520     return retbuf;\r
1521 }\r
1522 \r
1523 /* Remove all highlighting escape sequences in s */\r
1524 char *\r
1525 StripHighlight(s)\r
1526      char *s;\r
1527 {\r
1528     static char retbuf[MSG_SIZ];\r
1529     char *p = retbuf;\r
1530 \r
1531     while (*s != NULLCHAR) {\r
1532         while (*s == '\033') {\r
1533             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1534             if (*s != NULLCHAR) s++;\r
1535         }\r
1536         while (*s != NULLCHAR && *s != '\033') {\r
1537             *p++ = *s++;\r
1538         }\r
1539     }\r
1540     *p = NULLCHAR;\r
1541     return retbuf;\r
1542 }\r
1543 \r
1544 char *variantNames[] = VARIANT_NAMES;\r
1545 char *\r
1546 VariantName(v)\r
1547      VariantClass v;\r
1548 {\r
1549     return variantNames[v];\r
1550 }\r
1551 \r
1552 \r
1553 /* Identify a variant from the strings the chess servers use or the\r
1554    PGN Variant tag names we use. */\r
1555 VariantClass\r
1556 StringToVariant(e)\r
1557      char *e;\r
1558 {\r
1559     char *p;\r
1560     int wnum = -1;\r
1561     VariantClass v = VariantNormal;\r
1562     int i, found = FALSE;\r
1563     char buf[MSG_SIZ];\r
1564 \r
1565     if (!e) return v;\r
1566 \r
1567     /* [HGM] skip over optional board-size prefixes */\r
1568     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||\r
1569         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
1570         while( *e++ != '_');\r
1571     }\r
1572 \r
1573     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1574       if (StrCaseStr(e, variantNames[i])) {\r
1575         v = (VariantClass) i;\r
1576         found = TRUE;\r
1577         break;\r
1578       }\r
1579     }\r
1580 \r
1581     if (!found) {\r
1582       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1583           || StrCaseStr(e, "wild/fr") \r
1584           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {\r
1585         v = VariantFischeRandom;\r
1586       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1587                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1588         p += i;\r
1589         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1590         if (isdigit(*p)) {\r
1591           wnum = atoi(p);\r
1592         } else {\r
1593           wnum = -1;\r
1594         }\r
1595         switch (wnum) {\r
1596         case 0: /* FICS only, actually */\r
1597         case 1:\r
1598           /* Castling legal even if K starts on d-file */\r
1599           v = VariantWildCastle;\r
1600           break;\r
1601         case 2:\r
1602         case 3:\r
1603         case 4:\r
1604           /* Castling illegal even if K & R happen to start in\r
1605              normal positions. */\r
1606           v = VariantNoCastle;\r
1607           break;\r
1608         case 5:\r
1609         case 7:\r
1610         case 8:\r
1611         case 10:\r
1612         case 11:\r
1613         case 12:\r
1614         case 13:\r
1615         case 14:\r
1616         case 15:\r
1617         case 18:\r
1618         case 19:\r
1619           /* Castling legal iff K & R start in normal positions */\r
1620           v = VariantNormal;\r
1621           break;\r
1622         case 6:\r
1623         case 20:\r
1624         case 21:\r
1625           /* Special wilds for position setup; unclear what to do here */\r
1626           v = VariantLoadable;\r
1627           break;\r
1628         case 9:\r
1629           /* Bizarre ICC game */\r
1630           v = VariantTwoKings;\r
1631           break;\r
1632         case 16:\r
1633           v = VariantKriegspiel;\r
1634           break;\r
1635         case 17:\r
1636           v = VariantLosers;\r
1637           break;\r
1638         case 22:\r
1639           v = VariantFischeRandom;\r
1640           break;\r
1641         case 23:\r
1642           v = VariantCrazyhouse;\r
1643           break;\r
1644         case 24:\r
1645           v = VariantBughouse;\r
1646           break;\r
1647         case 25:\r
1648           v = Variant3Check;\r
1649           break;\r
1650         case 26:\r
1651           /* Not quite the same as FICS suicide! */\r
1652           v = VariantGiveaway;\r
1653           break;\r
1654         case 27:\r
1655           v = VariantAtomic;\r
1656           break;\r
1657         case 28:\r
1658           v = VariantShatranj;\r
1659           break;\r
1660 \r
1661         /* Temporary names for future ICC types.  The name *will* change in \r
1662            the next xboard/WinBoard release after ICC defines it. */\r
1663         case 29:\r
1664           v = Variant29;\r
1665           break;\r
1666         case 30:\r
1667           v = Variant30;\r
1668           break;\r
1669         case 31:\r
1670           v = Variant31;\r
1671           break;\r
1672         case 32:\r
1673           v = Variant32;\r
1674           break;\r
1675         case 33:\r
1676           v = Variant33;\r
1677           break;\r
1678         case 34:\r
1679           v = Variant34;\r
1680           break;\r
1681         case 35:\r
1682           v = Variant35;\r
1683           break;\r
1684         case 36:\r
1685           v = Variant36;\r
1686           break;\r
1687         case 37:\r
1688           v = VariantShogi;\r
1689           break;\r
1690         case 38:\r
1691           v = VariantXiangqi;\r
1692           break;\r
1693         case 39:\r
1694           v = VariantCourier;\r
1695           break;\r
1696         case 40:\r
1697           v = VariantGothic;\r
1698           break;\r
1699         case 41:\r
1700           v = VariantCapablanca;\r
1701           break;\r
1702         case 42:\r
1703           v = VariantKnightmate;\r
1704           break;\r
1705         case 43:\r
1706           v = VariantFairy;\r
1707           break;\r
1708         case 44:\r
1709           v = VariantCylinder;\r
1710           break;\r
1711         case 45:\r
1712           v = VariantFalcon;\r
1713           break;\r
1714         case 46:\r
1715           v = VariantCapaRandom;\r
1716           break;\r
1717         case 47:\r
1718           v = VariantBerolina;\r
1719           break;\r
1720         case 48:\r
1721           v = VariantJanus;\r
1722           break;\r
1723         case 49:\r
1724           v = VariantSuper;\r
1725           break;\r
1726         case 50:\r
1727           v = VariantGreat;\r
1728           break;\r
1729         case -1:\r
1730           /* Found "wild" or "w" in the string but no number;\r
1731              must assume it's normal chess. */\r
1732           v = VariantNormal;\r
1733           break;\r
1734         default:\r
1735           sprintf(buf, _("Unknown wild type %d"), wnum);\r
1736           DisplayError(buf, 0);\r
1737           v = VariantUnknown;\r
1738           break;\r
1739         }\r
1740       }\r
1741     }\r
1742     if (appData.debugMode) {\r
1743       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),\r
1744               e, wnum, VariantName(v));\r
1745     }\r
1746     return v;\r
1747 }\r
1748 \r
1749 static int leftover_start = 0, leftover_len = 0;\r
1750 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1751 \r
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1753    advance *index beyond it, and set leftover_start to the new value of\r
1754    *index; else return FALSE.  If pattern contains the character '*', it\r
1755    matches any sequence of characters not containing '\r', '\n', or the\r
1756    character following the '*' (if any), and the matched sequence(s) are\r
1757    copied into star_match.\r
1758    */\r
1759 int\r
1760 looking_at(buf, index, pattern)\r
1761      char *buf;\r
1762      int *index;\r
1763      char *pattern;\r
1764 {\r
1765     char *bufp = &buf[*index], *patternp = pattern;\r
1766     int star_count = 0;\r
1767     char *matchp = star_match[0];\r
1768     \r
1769     for (;;) {\r
1770         if (*patternp == NULLCHAR) {\r
1771             *index = leftover_start = bufp - buf;\r
1772             *matchp = NULLCHAR;\r
1773             return TRUE;\r
1774         }\r
1775         if (*bufp == NULLCHAR) return FALSE;\r
1776         if (*patternp == '*') {\r
1777             if (*bufp == *(patternp + 1)) {\r
1778                 *matchp = NULLCHAR;\r
1779                 matchp = star_match[++star_count];\r
1780                 patternp += 2;\r
1781                 bufp++;\r
1782                 continue;\r
1783             } else if (*bufp == '\n' || *bufp == '\r') {\r
1784                 patternp++;\r
1785                 if (*patternp == NULLCHAR)\r
1786                   continue;\r
1787                 else\r
1788                   return FALSE;\r
1789             } else {\r
1790                 *matchp++ = *bufp++;\r
1791                 continue;\r
1792             }\r
1793         }\r
1794         if (*patternp != *bufp) return FALSE;\r
1795         patternp++;\r
1796         bufp++;\r
1797     }\r
1798 }\r
1799 \r
1800 void\r
1801 SendToPlayer(data, length)\r
1802      char *data;\r
1803      int length;\r
1804 {\r
1805     int error, outCount;\r
1806     outCount = OutputToProcess(NoProc, data, length, &error);\r
1807     if (outCount < length) {\r
1808         DisplayFatalError(_("Error writing to display"), error, 1);\r
1809     }\r
1810 }\r
1811 \r
1812 void\r
1813 PackHolding(packed, holding)\r
1814      char packed[];\r
1815      char *holding;\r
1816 {\r
1817     char *p = holding;\r
1818     char *q = packed;\r
1819     int runlength = 0;\r
1820     int curr = 9999;\r
1821     do {\r
1822         if (*p == curr) {\r
1823             runlength++;\r
1824         } else {\r
1825             switch (runlength) {\r
1826               case 0:\r
1827                 break;\r
1828               case 1:\r
1829                 *q++ = curr;\r
1830                 break;\r
1831               case 2:\r
1832                 *q++ = curr;\r
1833                 *q++ = curr;\r
1834                 break;\r
1835               default:\r
1836                 sprintf(q, "%d", runlength);\r
1837                 while (*q) q++;\r
1838                 *q++ = curr;\r
1839                 break;\r
1840             }\r
1841             runlength = 1;\r
1842             curr = *p;\r
1843         }\r
1844     } while (*p++);\r
1845     *q = NULLCHAR;\r
1846 }\r
1847 \r
1848 /* Telnet protocol requests from the front end */\r
1849 void\r
1850 TelnetRequest(ddww, option)\r
1851      unsigned char ddww, option;\r
1852 {\r
1853     unsigned char msg[3];\r
1854     int outCount, outError;\r
1855 \r
1856     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1857 \r
1858     if (appData.debugMode) {\r
1859         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1860         switch (ddww) {\r
1861           case TN_DO:\r
1862             ddwwStr = "DO";\r
1863             break;\r
1864           case TN_DONT:\r
1865             ddwwStr = "DONT";\r
1866             break;\r
1867           case TN_WILL:\r
1868             ddwwStr = "WILL";\r
1869             break;\r
1870           case TN_WONT:\r
1871             ddwwStr = "WONT";\r
1872             break;\r
1873           default:\r
1874             ddwwStr = buf1;\r
1875             sprintf(buf1, "%d", ddww);\r
1876             break;\r
1877         }\r
1878         switch (option) {\r
1879           case TN_ECHO:\r
1880             optionStr = "ECHO";\r
1881             break;\r
1882           default:\r
1883             optionStr = buf2;\r
1884             sprintf(buf2, "%d", option);\r
1885             break;\r
1886         }\r
1887         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1888     }\r
1889     msg[0] = TN_IAC;\r
1890     msg[1] = ddww;\r
1891     msg[2] = option;\r
1892     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1893     if (outCount < 3) {\r
1894         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1895     }\r
1896 }\r
1897 \r
1898 void\r
1899 DoEcho()\r
1900 {\r
1901     if (!appData.icsActive) return;\r
1902     TelnetRequest(TN_DO, TN_ECHO);\r
1903 }\r
1904 \r
1905 void\r
1906 DontEcho()\r
1907 {\r
1908     if (!appData.icsActive) return;\r
1909     TelnetRequest(TN_DONT, TN_ECHO);\r
1910 }\r
1911 \r
1912 void\r
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
1914 {\r
1915     /* put the holdings sent to us by the server on the board holdings area */\r
1916     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
1917     char p;\r
1918     ChessSquare piece;\r
1919 \r
1920     if(gameInfo.holdingsWidth < 2)  return;\r
1921 \r
1922     if( (int)lowestPiece >= BlackPawn ) {\r
1923         holdingsColumn = 0;\r
1924         countsColumn = 1;\r
1925         holdingsStartRow = BOARD_HEIGHT-1;\r
1926         direction = -1;\r
1927     } else {\r
1928         holdingsColumn = BOARD_WIDTH-1;\r
1929         countsColumn = BOARD_WIDTH-2;\r
1930         holdingsStartRow = 0;\r
1931         direction = 1;\r
1932     }\r
1933 \r
1934     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
1935         board[i][holdingsColumn] = EmptySquare;\r
1936         board[i][countsColumn]   = (ChessSquare) 0;\r
1937     }\r
1938     while( (p=*holdings++) != NULLCHAR ) {\r
1939         piece = CharToPiece( ToUpper(p) );\r
1940         if(piece == EmptySquare) continue;\r
1941         /*j = (int) piece - (int) WhitePawn;*/\r
1942         j = PieceToNumber(piece);\r
1943         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1944         if(j < 0) continue;               /* should not happen */\r
1945         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );\r
1946         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
1947         board[holdingsStartRow+j*direction][countsColumn]++;\r
1948     }\r
1949 \r
1950 }\r
1951 \r
1952 \r
1953 void\r
1954 VariantSwitch(Board board, VariantClass newVariant)\r
1955 {\r
1956    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;\r
1957    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;\r
1958 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;\r
1959 \r
1960    startedFromPositionFile = FALSE;\r
1961    if(gameInfo.variant == newVariant) return;\r
1962 \r
1963    /* [HGM] This routine is called each time an assignment is made to\r
1964     * gameInfo.variant during a game, to make sure the board sizes\r
1965     * are set to match the new variant. If that means adding or deleting\r
1966     * holdings, we shift the playing board accordingly\r
1967     * This kludge is needed because in ICS observe mode, we get boards\r
1968     * of an ongoing game without knowing the variant, and learn about the\r
1969     * latter only later. This can be because of the move list we requested,\r
1970     * in which case the game history is refilled from the beginning anyway,\r
1971     * but also when receiving holdings of a crazyhouse game. In the latter\r
1972     * case we want to add those holdings to the already received position.\r
1973     */\r
1974 \r
1975 \r
1976   if (appData.debugMode) {\r
1977     fprintf(debugFP, "Switch board from %s to %s\n",\r
1978                VariantName(gameInfo.variant), VariantName(newVariant));\r
1979     setbuf(debugFP, NULL);\r
1980   }\r
1981     shuffleOpenings = 0;       /* [HGM] shuffle */\r
1982     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
1983     switch(newVariant) {\r
1984             case VariantShogi:\r
1985               newWidth = 9;  newHeight = 9;\r
1986               gameInfo.holdingsSize = 7;\r
1987             case VariantBughouse:\r
1988             case VariantCrazyhouse:\r
1989               newHoldingsWidth = 2; break;\r
1990             default:\r
1991               newHoldingsWidth = gameInfo.holdingsSize = 0;\r
1992     }\r
1993 \r
1994     if(newWidth  != gameInfo.boardWidth  ||\r
1995        newHeight != gameInfo.boardHeight ||\r
1996        newHoldingsWidth != gameInfo.holdingsWidth ) {\r
1997 \r
1998         /* shift position to new playing area, if needed */\r
1999         if(newHoldingsWidth > gameInfo.holdingsWidth) {\r
2000            for(i=0; i<BOARD_HEIGHT; i++) \r
2001                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)\r
2002                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
2003                                                      board[i][j];\r
2004            for(i=0; i<newHeight; i++) {\r
2005                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;\r
2006                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;\r
2007            }\r
2008         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {\r
2009            for(i=0; i<BOARD_HEIGHT; i++)\r
2010                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
2011                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
2012                                                  board[i][j];\r
2013         }\r
2014 \r
2015         gameInfo.boardWidth  = newWidth;\r
2016         gameInfo.boardHeight = newHeight;\r
2017         gameInfo.holdingsWidth = newHoldingsWidth;\r
2018         gameInfo.variant = newVariant;\r
2019         InitDrawingSizes(-2, 0);\r
2020 \r
2021         /* [HGM] The following should definitely be solved in a better way */\r
2022 #if 0\r
2023         CopyBoard(board, tempBoard); /* save position in case it is board[0] */\r
2024         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];\r
2025         saveEP = epStatus[0];\r
2026 #endif\r
2027         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */\r
2028 #if 0\r
2029         epStatus[0] = saveEP;\r
2030         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];\r
2031         CopyBoard(tempBoard, board); /* restore position received from ICS   */\r
2032 #endif\r
2033     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }\r
2034 \r
2035     forwardMostMove = oldForwardMostMove;\r
2036     backwardMostMove = oldBackwardMostMove;\r
2037     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */\r
2038 }\r
2039 \r
2040 static int loggedOn = FALSE;\r
2041 \r
2042 /*-- Game start info cache: --*/\r
2043 int gs_gamenum;\r
2044 char gs_kind[MSG_SIZ];\r
2045 static char player1Name[128] = "";\r
2046 static char player2Name[128] = "";\r
2047 static int player1Rating = -1;\r
2048 static int player2Rating = -1;\r
2049 /*----------------------------*/\r
2050 \r
2051 ColorClass curColor = ColorNormal;\r
2052 int suppressKibitz = 0;\r
2053 \r
2054 void\r
2055 read_from_ics(isr, closure, data, count, error)\r
2056      InputSourceRef isr;\r
2057      VOIDSTAR closure;\r
2058      char *data;\r
2059      int count;\r
2060      int error;\r
2061 {\r
2062 #define BUF_SIZE 8192\r
2063 #define STARTED_NONE 0\r
2064 #define STARTED_MOVES 1\r
2065 #define STARTED_BOARD 2\r
2066 #define STARTED_OBSERVE 3\r
2067 #define STARTED_HOLDINGS 4\r
2068 #define STARTED_CHATTER 5\r
2069 #define STARTED_COMMENT 6\r
2070 #define STARTED_MOVES_NOHIDE 7\r
2071     \r
2072     static int started = STARTED_NONE;\r
2073     static char parse[20000];\r
2074     static int parse_pos = 0;\r
2075     static char buf[BUF_SIZE + 1];\r
2076     static int firstTime = TRUE, intfSet = FALSE;\r
2077     static ColorClass prevColor = ColorNormal;\r
2078     static int savingComment = FALSE;\r
2079     char str[500];\r
2080     int i, oldi;\r
2081     int buf_len;\r
2082     int next_out;\r
2083     int tkind;\r
2084     int backup;    /* [DM] For zippy color lines */\r
2085     char *p;\r
2086 \r
2087     if (appData.debugMode) {\r
2088       if (!error) {\r
2089         fprintf(debugFP, "<ICS: ");\r
2090         show_bytes(debugFP, data, count);\r
2091         fprintf(debugFP, "\n");\r
2092       }\r
2093     }\r
2094 \r
2095     if (appData.debugMode) { int f = forwardMostMove;\r
2096         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,\r
2097                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
2098     }\r
2099     if (count > 0) {\r
2100         /* If last read ended with a partial line that we couldn't parse,\r
2101            prepend it to the new read and try again. */\r
2102         if (leftover_len > 0) {\r
2103             for (i=0; i<leftover_len; i++)\r
2104               buf[i] = buf[leftover_start + i];\r
2105         }\r
2106 \r
2107         /* Copy in new characters, removing nulls and \r's */\r
2108         buf_len = leftover_len;\r
2109         for (i = 0; i < count; i++) {\r
2110             if (data[i] != NULLCHAR && data[i] != '\r')\r
2111               buf[buf_len++] = data[i];\r
2112             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && \r
2113                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') \r
2114                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous\r
2115         }\r
2116 \r
2117         buf[buf_len] = NULLCHAR;\r
2118         next_out = leftover_len;\r
2119         leftover_start = 0;\r
2120         \r
2121         i = 0;\r
2122         while (i < buf_len) {\r
2123             /* Deal with part of the TELNET option negotiation\r
2124                protocol.  We refuse to do anything beyond the\r
2125                defaults, except that we allow the WILL ECHO option,\r
2126                which ICS uses to turn off password echoing when we are\r
2127                directly connected to it.  We reject this option\r
2128                if localLineEditing mode is on (always on in xboard)\r
2129                and we are talking to port 23, which might be a real\r
2130                telnet server that will try to keep WILL ECHO on permanently.\r
2131              */\r
2132             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
2133                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
2134                 unsigned char option;\r
2135                 oldi = i;\r
2136                 switch ((unsigned char) buf[++i]) {\r
2137                   case TN_WILL:\r
2138                     if (appData.debugMode)\r
2139                       fprintf(debugFP, "\n<WILL ");\r
2140                     switch (option = (unsigned char) buf[++i]) {\r
2141                       case TN_ECHO:\r
2142                         if (appData.debugMode)\r
2143                           fprintf(debugFP, "ECHO ");\r
2144                         /* Reply only if this is a change, according\r
2145                            to the protocol rules. */\r
2146                         if (remoteEchoOption) break;\r
2147                         if (appData.localLineEditing &&\r
2148                             atoi(appData.icsPort) == TN_PORT) {\r
2149                             TelnetRequest(TN_DONT, TN_ECHO);\r
2150                         } else {\r
2151                             EchoOff();\r
2152                             TelnetRequest(TN_DO, TN_ECHO);\r
2153                             remoteEchoOption = TRUE;\r
2154                         }\r
2155                         break;\r
2156                       default:\r
2157                         if (appData.debugMode)\r
2158                           fprintf(debugFP, "%d ", option);\r
2159                         /* Whatever this is, we don't want it. */\r
2160                         TelnetRequest(TN_DONT, option);\r
2161                         break;\r
2162                     }\r
2163                     break;\r
2164                   case TN_WONT:\r
2165                     if (appData.debugMode)\r
2166                       fprintf(debugFP, "\n<WONT ");\r
2167                     switch (option = (unsigned char) buf[++i]) {\r
2168                       case TN_ECHO:\r
2169                         if (appData.debugMode)\r
2170                           fprintf(debugFP, "ECHO ");\r
2171                         /* Reply only if this is a change, according\r
2172                            to the protocol rules. */\r
2173                         if (!remoteEchoOption) break;\r
2174                         EchoOn();\r
2175                         TelnetRequest(TN_DONT, TN_ECHO);\r
2176                         remoteEchoOption = FALSE;\r
2177                         break;\r
2178                       default:\r
2179                         if (appData.debugMode)\r
2180                           fprintf(debugFP, "%d ", (unsigned char) option);\r
2181                         /* Whatever this is, it must already be turned\r
2182                            off, because we never agree to turn on\r
2183                            anything non-default, so according to the\r
2184                            protocol rules, we don't reply. */\r
2185                         break;\r
2186                     }\r
2187                     break;\r
2188                   case TN_DO:\r
2189                     if (appData.debugMode)\r
2190                       fprintf(debugFP, "\n<DO ");\r
2191                     switch (option = (unsigned char) buf[++i]) {\r
2192                       default:\r
2193                         /* Whatever this is, we refuse to do it. */\r
2194                         if (appData.debugMode)\r
2195                           fprintf(debugFP, "%d ", option);\r
2196                         TelnetRequest(TN_WONT, option);\r
2197                         break;\r
2198                     }\r
2199                     break;\r
2200                   case TN_DONT:\r
2201                     if (appData.debugMode)\r
2202                       fprintf(debugFP, "\n<DONT ");\r
2203                     switch (option = (unsigned char) buf[++i]) {\r
2204                       default:\r
2205                         if (appData.debugMode)\r
2206                           fprintf(debugFP, "%d ", option);\r
2207                         /* Whatever this is, we are already not doing\r
2208                            it, because we never agree to do anything\r
2209                            non-default, so according to the protocol\r
2210                            rules, we don't reply. */\r
2211                         break;\r
2212                     }\r
2213                     break;\r
2214                   case TN_IAC:\r
2215                     if (appData.debugMode)\r
2216                       fprintf(debugFP, "\n<IAC ");\r
2217                     /* Doubled IAC; pass it through */\r
2218                     i--;\r
2219                     break;\r
2220                   default:\r
2221                     if (appData.debugMode)\r
2222                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
2223                     /* Drop all other telnet commands on the floor */\r
2224                     break;\r
2225                 }\r
2226                 if (oldi > next_out)\r
2227                   SendToPlayer(&buf[next_out], oldi - next_out);\r
2228                 if (++i > next_out)\r
2229                   next_out = i;\r
2230                 continue;\r
2231             }\r
2232                 \r
2233             /* OK, this at least will *usually* work */\r
2234             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
2235                 loggedOn = TRUE;\r
2236             }\r
2237             \r
2238             if (loggedOn && !intfSet) {\r
2239                 if (ics_type == ICS_ICC) {\r
2240                   sprintf(str,\r
2241                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
2242                           programVersion);\r
2243 \r
2244                 } else if (ics_type == ICS_CHESSNET) {\r
2245                   sprintf(str, "/style 12\n");\r
2246                 } else {\r
2247                   strcpy(str, "alias $ @\n$set interface ");\r
2248                   strcat(str, programVersion);\r
2249                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
2250 #ifdef WIN32\r
2251                   strcat(str, "$iset nohighlight 1\n");\r
2252 #endif\r
2253                   strcat(str, "$iset lock 1\n$style 12\n");\r
2254                 }\r
2255                 SendToICS(str);\r
2256                 intfSet = TRUE;\r
2257             }\r
2258 \r
2259             if (started == STARTED_COMMENT) {\r
2260                 /* Accumulate characters in comment */\r
2261                 parse[parse_pos++] = buf[i];\r
2262                 if (buf[i] == '\n') {\r
2263                     parse[parse_pos] = NULLCHAR;\r
2264                     if(!suppressKibitz) // [HGM] kibitz\r
2265                         AppendComment(forwardMostMove, StripHighlight(parse));\r
2266                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window\r
2267                         int nrDigit = 0, nrAlph = 0, i;\r
2268                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input\r
2269                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }\r
2270                         parse[parse_pos] = NULLCHAR;\r
2271                         // try to be smart: if it does not look like search info, it should go to\r
2272                         // ICS interaction window after all, not to engine-output window.\r
2273                         for(i=0; i<parse_pos; i++) { // count letters and digits\r
2274                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');\r
2275                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');\r
2276                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');\r
2277                         }\r
2278                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info\r
2279                             int depth=0; float score;\r
2280                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {\r
2281                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph\r
2282                                 pvInfoList[forwardMostMove-1].depth = depth;\r
2283                                 pvInfoList[forwardMostMove-1].score = 100*score;\r
2284                             }\r
2285                             OutputKibitz(suppressKibitz, parse);\r
2286                         } else {\r
2287                             char tmp[MSG_SIZ];\r
2288                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);\r
2289                             SendToPlayer(tmp, strlen(tmp));\r
2290                         }\r
2291                     }\r
2292                     started = STARTED_NONE;\r
2293                 } else {\r
2294                     /* Don't match patterns against characters in chatter */\r
2295                     i++;\r
2296                     continue;\r
2297                 }\r
2298             }\r
2299             if (started == STARTED_CHATTER) {\r
2300                 if (buf[i] != '\n') {\r
2301                     /* Don't match patterns against characters in chatter */\r
2302                     i++;\r
2303                     continue;\r
2304                 }\r
2305                 started = STARTED_NONE;\r
2306             }\r
2307 \r
2308             /* Kludge to deal with rcmd protocol */\r
2309             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2310                 DisplayFatalError(&buf[1], 0, 1);\r
2311                 continue;\r
2312             } else {\r
2313                 firstTime = FALSE;\r
2314             }\r
2315 \r
2316             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2317                 ics_type = ICS_ICC;\r
2318                 ics_prefix = "/";\r
2319                 if (appData.debugMode)\r
2320                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2321                 continue;\r
2322             }\r
2323             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2324                 ics_type = ICS_FICS;\r
2325                 ics_prefix = "$";\r
2326                 if (appData.debugMode)\r
2327                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2328                 continue;\r
2329             }\r
2330             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2331                 ics_type = ICS_CHESSNET;\r
2332                 ics_prefix = "/";\r
2333                 if (appData.debugMode)\r
2334                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2335                 continue;\r
2336             }\r
2337 \r
2338             if (!loggedOn &&\r
2339                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2340                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2341                  looking_at(buf, &i, "will be \"*\""))) {\r
2342               strcpy(ics_handle, star_match[0]);\r
2343               continue;\r
2344             }\r
2345 \r
2346             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2347               char buf[MSG_SIZ];\r
2348               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2349               DisplayIcsInteractionTitle(buf);\r
2350               have_set_title = TRUE;\r
2351             }\r
2352 \r
2353             /* skip finger notes */\r
2354             if (started == STARTED_NONE &&\r
2355                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2356                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2357                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2358               started = STARTED_CHATTER;\r
2359               i += 3;\r
2360               continue;\r
2361             }\r
2362 \r
2363             /* skip formula vars */\r
2364             if (started == STARTED_NONE &&\r
2365                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2366               started = STARTED_CHATTER;\r
2367               i += 3;\r
2368               continue;\r
2369             }\r
2370 \r
2371             oldi = i;\r
2372             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window\r
2373             if (appData.autoKibitz && started == STARTED_NONE && \r
2374                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze\r
2375                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {\r
2376                 if(looking_at(buf, &i, "* kibitzes: ") &&\r
2377                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || \r
2378                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent\r
2379                         suppressKibitz = TRUE;\r
2380                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]\r
2381                                 && (gameMode == IcsPlayingWhite)) ||\r
2382                            (StrStr(star_match[0], gameInfo.black) == star_match[0]\r
2383                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz\r
2384                             started = STARTED_CHATTER; // own kibitz we simply discard\r
2385                         else {\r
2386                             started = STARTED_COMMENT; // make sure it will be collected in parse[]\r
2387                             parse_pos = 0; parse[0] = NULLCHAR;\r
2388                             savingComment = TRUE;\r
2389                             suppressKibitz = gameMode != IcsObserving ? 2 :\r
2390                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;\r
2391                         } \r
2392                         continue;\r
2393                 } else\r
2394                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz\r
2395                     started = STARTED_CHATTER;\r
2396                     suppressKibitz = TRUE;\r
2397                 }\r
2398             } // [HGM] kibitz: end of patch\r
2399 \r
2400             if (appData.zippyTalk || appData.zippyPlay) {\r
2401                 /* [DM] Backup address for color zippy lines */\r
2402                 backup = i;\r
2403 #if ZIPPY\r
2404        #ifdef WIN32\r
2405                if (loggedOn == TRUE)\r
2406                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||\r
2407                           (appData.zippyPlay && ZippyMatch(buf, &backup)));\r
2408        #else\r
2409                 if (ZippyControl(buf, &i) ||\r
2410                     ZippyConverse(buf, &i) ||\r
2411                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2412                       loggedOn = TRUE;\r
2413                       if (!appData.colorize) continue;\r
2414                 }\r
2415        #endif\r
2416 #endif\r
2417             } // [DM] 'else { ' deleted\r
2418                 if (/* Don't color "message" or "messages" output */\r
2419                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2420                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2421                     looking_at(buf, &i, "--* (*:*): ") ||\r
2422                     /* Regular tells and says */\r
2423                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2424                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2425                     looking_at(buf, &i, "* says: ") ||\r
2426                     /* Message notifications (same color as tells) */\r
2427                     looking_at(buf, &i, "* has left a message ") ||\r
2428                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2429                     /* Whispers and kibitzes */\r
2430                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2431                     looking_at(buf, &i, "* kibitzes: ") ||\r
2432                     /* Channel tells */\r
2433                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2434 \r
2435                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2436                       /* Avoid "tells you:" spoofs in channels */\r
2437                      tkind = 3;\r
2438                   }\r
2439                   if (star_match[0][0] == NULLCHAR ||\r
2440                       strchr(star_match[0], ' ') ||\r
2441                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2442                     /* Reject bogus matches */\r
2443                     i = oldi;\r
2444                   } else {\r
2445                     if (appData.colorize) {\r
2446                       if (oldi > next_out) {\r
2447                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2448                         next_out = oldi;\r
2449                       }\r
2450                       switch (tkind) {\r
2451                       case 1:\r
2452                         Colorize(ColorTell, FALSE);\r
2453                         curColor = ColorTell;\r
2454                         break;\r
2455                       case 2:\r
2456                         Colorize(ColorKibitz, FALSE);\r
2457                         curColor = ColorKibitz;\r
2458                         break;\r
2459                       case 3:\r
2460                         p = strrchr(star_match[1], '(');\r
2461                         if (p == NULL) {\r
2462                           p = star_match[1];\r
2463                         } else {\r
2464                           p++;\r
2465                         }\r
2466                         if (atoi(p) == 1) {\r
2467                           Colorize(ColorChannel1, FALSE);\r
2468                           curColor = ColorChannel1;\r
2469                         } else {\r
2470                           Colorize(ColorChannel, FALSE);\r
2471                           curColor = ColorChannel;\r
2472                         }\r
2473                         break;\r
2474                       case 5:\r
2475                         curColor = ColorNormal;\r
2476                         break;\r
2477                       }\r
2478                     }\r
2479                     if (started == STARTED_NONE && appData.autoComment &&\r
2480                         (gameMode == IcsObserving ||\r
2481                          gameMode == IcsPlayingWhite ||\r
2482                          gameMode == IcsPlayingBlack)) {\r
2483                       parse_pos = i - oldi;\r
2484                       memcpy(parse, &buf[oldi], parse_pos);\r
2485                       parse[parse_pos] = NULLCHAR;\r
2486                       started = STARTED_COMMENT;\r
2487                       savingComment = TRUE;\r
2488                     } else {\r
2489                       started = STARTED_CHATTER;\r
2490                       savingComment = FALSE;\r
2491                     }\r
2492                     loggedOn = TRUE;\r
2493                     continue;\r
2494                   }\r
2495                 }\r
2496 \r
2497                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2498                     looking_at(buf, &i, "* c-shouts: ")) {\r
2499                     if (appData.colorize) {\r
2500                         if (oldi > next_out) {\r
2501                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2502                             next_out = oldi;\r
2503                         }\r
2504                         Colorize(ColorSShout, FALSE);\r
2505                         curColor = ColorSShout;\r
2506                     }\r
2507                     loggedOn = TRUE;\r
2508                     started = STARTED_CHATTER;\r
2509                     continue;\r
2510                 }\r
2511 \r
2512                 if (looking_at(buf, &i, "--->")) {\r
2513                     loggedOn = TRUE;\r
2514                     continue;\r
2515                 }\r
2516 \r
2517                 if (looking_at(buf, &i, "* shouts: ") ||\r
2518                     looking_at(buf, &i, "--> ")) {\r
2519                     if (appData.colorize) {\r
2520                         if (oldi > next_out) {\r
2521                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2522                             next_out = oldi;\r
2523                         }\r
2524                         Colorize(ColorShout, FALSE);\r
2525                         curColor = ColorShout;\r
2526                     }\r
2527                     loggedOn = TRUE;\r
2528                     started = STARTED_CHATTER;\r
2529                     continue;\r
2530                 }\r
2531 \r
2532                 if (looking_at( buf, &i, "Challenge:")) {\r
2533                     if (appData.colorize) {\r
2534                         if (oldi > next_out) {\r
2535                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2536                             next_out = oldi;\r
2537                         }\r
2538                         Colorize(ColorChallenge, FALSE);\r
2539                         curColor = ColorChallenge;\r
2540                     }\r
2541                     loggedOn = TRUE;\r
2542                     continue;\r
2543                 }\r
2544 \r
2545                 if (looking_at(buf, &i, "* offers you") ||\r
2546                     looking_at(buf, &i, "* offers to be") ||\r
2547                     looking_at(buf, &i, "* would like to") ||\r
2548                     looking_at(buf, &i, "* requests to") ||\r
2549                     looking_at(buf, &i, "Your opponent offers") ||\r
2550                     looking_at(buf, &i, "Your opponent requests")) {\r
2551 \r
2552                     if (appData.colorize) {\r
2553                         if (oldi > next_out) {\r
2554                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2555                             next_out = oldi;\r
2556                         }\r
2557                         Colorize(ColorRequest, FALSE);\r
2558                         curColor = ColorRequest;\r
2559                     }\r
2560                     continue;\r
2561                 }\r
2562 \r
2563                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2564                     if (appData.colorize) {\r
2565                         if (oldi > next_out) {\r
2566                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2567                             next_out = oldi;\r
2568                         }\r
2569                         Colorize(ColorSeek, FALSE);\r
2570                         curColor = ColorSeek;\r
2571                     }\r
2572                     continue;\r
2573             }\r
2574 \r
2575             if (looking_at(buf, &i, "\\   ")) {\r
2576                 if (prevColor != ColorNormal) {\r
2577                     if (oldi > next_out) {\r
2578                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2579                         next_out = oldi;\r
2580                     }\r
2581                     Colorize(prevColor, TRUE);\r
2582                     curColor = prevColor;\r
2583                 }\r
2584                 if (savingComment) {\r
2585                     parse_pos = i - oldi;\r
2586                     memcpy(parse, &buf[oldi], parse_pos);\r
2587                     parse[parse_pos] = NULLCHAR;\r
2588                     started = STARTED_COMMENT;\r
2589                 } else {\r
2590                     started = STARTED_CHATTER;\r
2591                 }\r
2592                 continue;\r
2593             }\r
2594 \r
2595             if (looking_at(buf, &i, "Black Strength :") ||\r
2596                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2597                 looking_at(buf, &i, "<10>") ||\r
2598                 looking_at(buf, &i, "#@#")) {\r
2599                 /* Wrong board style */\r
2600                 loggedOn = TRUE;\r
2601                 SendToICS(ics_prefix);\r
2602                 SendToICS("set style 12\n");\r
2603                 SendToICS(ics_prefix);\r
2604                 SendToICS("refresh\n");\r
2605                 continue;\r
2606             }\r
2607             \r
2608             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2609                 ICSInitScript();\r
2610                 have_sent_ICS_logon = 1;\r
2611                 continue;\r
2612             }\r
2613               \r
2614             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2615                 (looking_at(buf, &i, "\n<12> ") ||\r
2616                  looking_at(buf, &i, "<12> "))) {\r
2617                 loggedOn = TRUE;\r
2618                 if (oldi > next_out) {\r
2619                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2620                 }\r
2621                 next_out = i;\r
2622                 started = STARTED_BOARD;\r
2623                 parse_pos = 0;\r
2624                 continue;\r
2625             }\r
2626 \r
2627             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2628                 looking_at(buf, &i, "<b1> ")) {\r
2629                 if (oldi > next_out) {\r
2630                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2631                 }\r
2632                 next_out = i;\r
2633                 started = STARTED_HOLDINGS;\r
2634                 parse_pos = 0;\r
2635                 continue;\r
2636             }\r
2637 \r
2638             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2639                 loggedOn = TRUE;\r
2640                 /* Header for a move list -- first line */\r
2641 \r
2642                 switch (ics_getting_history) {\r
2643                   case H_FALSE:\r
2644                     switch (gameMode) {\r
2645                       case IcsIdle:\r
2646                       case BeginningOfGame:\r
2647                         /* User typed "moves" or "oldmoves" while we\r
2648                            were idle.  Pretend we asked for these\r
2649                            moves and soak them up so user can step\r
2650                            through them and/or save them.\r
2651                            */\r
2652                         Reset(FALSE, TRUE);\r
2653                         gameMode = IcsObserving;\r
2654                         ModeHighlight();\r
2655                         ics_gamenum = -1;\r
2656                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2657                         break;\r
2658                       case EditGame: /*?*/\r
2659                       case EditPosition: /*?*/\r
2660                         /* Should above feature work in these modes too? */\r
2661                         /* For now it doesn't */\r
2662                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2663                         break;\r
2664                       default:\r
2665                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2666                         break;\r
2667                     }\r
2668                     break;\r
2669                   case H_REQUESTED:\r
2670                     /* Is this the right one? */\r
2671                     if (gameInfo.white && gameInfo.black &&\r
2672                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2673                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2674                         /* All is well */\r
2675                         ics_getting_history = H_GOT_REQ_HEADER;\r
2676                     }\r
2677                     break;\r
2678                   case H_GOT_REQ_HEADER:\r
2679                   case H_GOT_UNREQ_HEADER:\r
2680                   case H_GOT_UNWANTED_HEADER:\r
2681                   case H_GETTING_MOVES:\r
2682                     /* Should not happen */\r
2683                     DisplayError(_("Error gathering move list: two headers"), 0);\r
2684                     ics_getting_history = H_FALSE;\r
2685                     break;\r
2686                 }\r
2687 \r
2688                 /* Save player ratings into gameInfo if needed */\r
2689                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2690                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2691                     (gameInfo.whiteRating == -1 ||\r
2692                      gameInfo.blackRating == -1)) {\r
2693 \r
2694                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2695                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2696                     if (appData.debugMode)\r
2697                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), \r
2698                               gameInfo.whiteRating, gameInfo.blackRating);\r
2699                 }\r
2700                 continue;\r
2701             }\r
2702 \r
2703             if (looking_at(buf, &i,\r
2704               "* * match, initial time: * minute*, increment: * second")) {\r
2705                 /* Header for a move list -- second line */\r
2706                 /* Initial board will follow if this is a wild game */\r
2707                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2708                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2709                 gameInfo.event = StrSave(str);\r
2710                 /* [HGM] we switched variant. Translate boards if needed. */\r
2711                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2712                 continue;\r
2713             }\r
2714 \r
2715             if (looking_at(buf, &i, "Move  ")) {\r
2716                 /* Beginning of a move list */\r
2717                 switch (ics_getting_history) {\r
2718                   case H_FALSE:\r
2719                     /* Normally should not happen */\r
2720                     /* Maybe user hit reset while we were parsing */\r
2721                     break;\r
2722                   case H_REQUESTED:\r
2723                     /* Happens if we are ignoring a move list that is not\r
2724                      * the one we just requested.  Common if the user\r
2725                      * tries to observe two games without turning off\r
2726                      * getMoveList */\r
2727                     break;\r
2728                   case H_GETTING_MOVES:\r
2729                     /* Should not happen */\r
2730                     DisplayError(_("Error gathering move list: nested"), 0);\r
2731                     ics_getting_history = H_FALSE;\r
2732                     break;\r
2733                   case H_GOT_REQ_HEADER:\r
2734                     ics_getting_history = H_GETTING_MOVES;\r
2735                     started = STARTED_MOVES;\r
2736                     parse_pos = 0;\r
2737                     if (oldi > next_out) {\r
2738                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2739                     }\r
2740                     break;\r
2741                   case H_GOT_UNREQ_HEADER:\r
2742                     ics_getting_history = H_GETTING_MOVES;\r
2743                     started = STARTED_MOVES_NOHIDE;\r
2744                     parse_pos = 0;\r
2745                     break;\r
2746                   case H_GOT_UNWANTED_HEADER:\r
2747                     ics_getting_history = H_FALSE;\r
2748                     break;\r
2749                 }\r
2750                 continue;\r
2751             }                           \r
2752             \r
2753             if (looking_at(buf, &i, "% ") ||\r
2754                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2755                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book\r
2756                 savingComment = FALSE;\r
2757                 switch (started) {\r
2758                   case STARTED_MOVES:\r
2759                   case STARTED_MOVES_NOHIDE:\r
2760                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2761                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2762                     ParseGameHistory(parse);\r
2763 #if ZIPPY\r
2764                     if (appData.zippyPlay && first.initDone) {\r
2765                         FeedMovesToProgram(&first, forwardMostMove);\r
2766                         if (gameMode == IcsPlayingWhite) {\r
2767                             if (WhiteOnMove(forwardMostMove)) {\r
2768                                 if (first.sendTime) {\r
2769                                   if (first.useColors) {\r
2770                                     SendToProgram("black\n", &first); \r
2771                                   }\r
2772                                   SendTimeRemaining(&first, TRUE);\r
2773                                 }\r
2774 #if 0\r
2775                                 if (first.useColors) {\r
2776                                   SendToProgram("white\ngo\n", &first);\r
2777                                 } else {\r
2778                                   SendToProgram("go\n", &first);\r
2779                                 }\r
2780 #else\r
2781                                 if (first.useColors) {\r
2782                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent\r
2783                                 }\r
2784                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos\r
2785 #endif\r
2786                                 first.maybeThinking = TRUE;\r
2787                             } else {\r
2788                                 if (first.usePlayother) {\r
2789                                   if (first.sendTime) {\r
2790                                     SendTimeRemaining(&first, TRUE);\r
2791                                   }\r
2792                                   SendToProgram("playother\n", &first);\r
2793                                   firstMove = FALSE;\r
2794                                 } else {\r
2795                                   firstMove = TRUE;\r
2796                                 }\r
2797                             }\r
2798                         } else if (gameMode == IcsPlayingBlack) {\r
2799                             if (!WhiteOnMove(forwardMostMove)) {\r
2800                                 if (first.sendTime) {\r
2801                                   if (first.useColors) {\r
2802                                     SendToProgram("white\n", &first);\r
2803                                   }\r
2804                                   SendTimeRemaining(&first, FALSE);\r
2805                                 }\r
2806 #if 0\r
2807                                 if (first.useColors) {\r
2808                                   SendToProgram("black\ngo\n", &first);\r
2809                                 } else {\r
2810                                   SendToProgram("go\n", &first);\r
2811                                 }\r
2812 #else\r
2813                                 if (first.useColors) {\r
2814                                   SendToProgram("black\n", &first);\r
2815                                 }\r
2816                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);\r
2817 #endif\r
2818                                 first.maybeThinking = TRUE;\r
2819                             } else {\r
2820                                 if (first.usePlayother) {\r
2821                                   if (first.sendTime) {\r
2822                                     SendTimeRemaining(&first, FALSE);\r
2823                                   }\r
2824                                   SendToProgram("playother\n", &first);\r
2825                                   firstMove = FALSE;\r
2826                                 } else {\r
2827                                   firstMove = TRUE;\r
2828                                 }\r
2829                             }\r
2830                         }                       \r
2831                     }\r
2832 #endif\r
2833                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2834                         /* Moves came from oldmoves or moves command\r
2835                            while we weren't doing anything else.\r
2836                            */\r
2837                         currentMove = forwardMostMove;\r
2838                         ClearHighlights();/*!!could figure this out*/\r
2839                         flipView = appData.flipView;\r
2840                         DrawPosition(FALSE, boards[currentMove]);\r
2841                         DisplayBothClocks();\r
2842                         sprintf(str, "%s vs. %s",\r
2843                                 gameInfo.white, gameInfo.black);\r
2844                         DisplayTitle(str);\r
2845                         gameMode = IcsIdle;\r
2846                     } else {\r
2847                         /* Moves were history of an active game */\r
2848                         if (gameInfo.resultDetails != NULL) {\r
2849                             free(gameInfo.resultDetails);\r
2850                             gameInfo.resultDetails = NULL;\r
2851                         }\r
2852                     }\r
2853                     HistorySet(parseList, backwardMostMove,\r
2854                                forwardMostMove, currentMove-1);\r
2855                     DisplayMove(currentMove - 1);\r
2856                     if (started == STARTED_MOVES) next_out = i;\r
2857                     started = STARTED_NONE;\r
2858                     ics_getting_history = H_FALSE;\r
2859                     break;\r
2860 \r
2861                   case STARTED_OBSERVE:\r
2862                     started = STARTED_NONE;\r
2863                     SendToICS(ics_prefix);\r
2864                     SendToICS("refresh\n");\r
2865                     break;\r
2866 \r
2867                   default:\r
2868                     break;\r
2869                 }\r
2870                 if(bookHit) { // [HGM] book: simulate book reply\r
2871                     static char bookMove[MSG_SIZ]; // a bit generous?\r
2872 \r
2873                     programStats.nodes = programStats.depth = programStats.time = \r
2874                     programStats.score = programStats.got_only_move = 0;\r
2875                     sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
2876 \r
2877                     strcpy(bookMove, "move ");\r
2878                     strcat(bookMove, bookHit);\r
2879                     HandleMachineMove(bookMove, &first);\r
2880                 }\r
2881                 continue;\r
2882             }\r
2883             \r
2884             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2885                  started == STARTED_HOLDINGS ||\r
2886                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2887                 /* Accumulate characters in move list or board */\r
2888                 parse[parse_pos++] = buf[i];\r
2889             }\r
2890             \r
2891             /* Start of game messages.  Mostly we detect start of game\r
2892                when the first board image arrives.  On some versions\r
2893                of the ICS, though, we need to do a "refresh" after starting\r
2894                to observe in order to get the current board right away. */\r
2895             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2896                 started = STARTED_OBSERVE;\r
2897                 continue;\r
2898             }\r
2899 \r
2900             /* Handle auto-observe */\r
2901             if (appData.autoObserve &&\r
2902                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2903                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2904                 char *player;\r
2905                 /* Choose the player that was highlighted, if any. */\r
2906                 if (star_match[0][0] == '\033' ||\r
2907                     star_match[1][0] != '\033') {\r
2908                     player = star_match[0];\r
2909                 } else {\r
2910                     player = star_match[2];\r
2911                 }\r
2912                 sprintf(str, "%sobserve %s\n",\r
2913                         ics_prefix, StripHighlightAndTitle(player));\r
2914                 SendToICS(str);\r
2915 \r
2916                 /* Save ratings from notify string */\r
2917                 strcpy(player1Name, star_match[0]);\r
2918                 player1Rating = string_to_rating(star_match[1]);\r
2919                 strcpy(player2Name, star_match[2]);\r
2920                 player2Rating = string_to_rating(star_match[3]);\r
2921 \r
2922                 if (appData.debugMode)\r
2923                   fprintf(debugFP, \r
2924                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2925                           player1Name, player1Rating,\r
2926                           player2Name, player2Rating);\r
2927 \r
2928                 continue;\r
2929             }\r
2930 \r
2931             /* Deal with automatic examine mode after a game,\r
2932                and with IcsObserving -> IcsExamining transition */\r
2933             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2934                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2935 \r
2936                 int gamenum = atoi(star_match[0]);\r
2937                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2938                     gamenum == ics_gamenum) {\r
2939                     /* We were already playing or observing this game;\r
2940                        no need to refetch history */\r
2941                     gameMode = IcsExamining;\r
2942                     if (pausing) {\r
2943                         pauseExamForwardMostMove = forwardMostMove;\r
2944                     } else if (currentMove < forwardMostMove) {\r
2945                         ForwardInner(forwardMostMove);\r
2946                     }\r
2947                 } else {\r
2948                     /* I don't think this case really can happen */\r
2949                     SendToICS(ics_prefix);\r
2950                     SendToICS("refresh\n");\r
2951                 }\r
2952                 continue;\r
2953             }    \r
2954             \r
2955             /* Error messages */\r
2956             if (ics_user_moved) {\r
2957                 if (looking_at(buf, &i, "Illegal move") ||\r
2958                     looking_at(buf, &i, "Not a legal move") ||\r
2959                     looking_at(buf, &i, "Your king is in check") ||\r
2960                     looking_at(buf, &i, "It isn't your turn") ||\r
2961                     looking_at(buf, &i, "It is not your move")) {\r
2962                     /* Illegal move */\r
2963                     ics_user_moved = 0;\r
2964                     if (forwardMostMove > backwardMostMove) {\r
2965                         currentMove = --forwardMostMove;\r
2966                         DisplayMove(currentMove - 1); /* before DMError */\r
2967                         DisplayMoveError(_("Illegal move (rejected by ICS)"));\r
2968                         DrawPosition(FALSE, boards[currentMove]);\r
2969                         SwitchClocks();\r
2970                         DisplayBothClocks();\r
2971                     }\r
2972                     continue;\r
2973                 }\r
2974             }\r
2975 \r
2976             if (looking_at(buf, &i, "still have time") ||\r
2977                 looking_at(buf, &i, "not out of time") ||\r
2978                 looking_at(buf, &i, "either player is out of time") ||\r
2979                 looking_at(buf, &i, "has timeseal; checking")) {\r
2980                 /* We must have called his flag a little too soon */\r
2981                 whiteFlag = blackFlag = FALSE;\r
2982                 continue;\r
2983             }\r
2984 \r
2985             if (looking_at(buf, &i, "added * seconds to") ||\r
2986                 looking_at(buf, &i, "seconds were added to")) {\r
2987                 /* Update the clocks */\r
2988                 SendToICS(ics_prefix);\r
2989                 SendToICS("refresh\n");\r
2990                 continue;\r
2991             }\r
2992 \r
2993             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2994                 ics_clock_paused = TRUE;\r
2995                 StopClocks();\r
2996                 continue;\r
2997             }\r
2998 \r
2999             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
3000                 ics_clock_paused = FALSE;\r
3001                 StartClocks();\r
3002                 continue;\r
3003             }\r
3004 \r
3005             /* Grab player ratings from the Creating: message.\r
3006                Note we have to check for the special case when\r
3007                the ICS inserts things like [white] or [black]. */\r
3008             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
3009                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
3010                 /* star_matches:\r
3011                    0    player 1 name (not necessarily white)\r
3012                    1    player 1 rating\r
3013                    2    empty, white, or black (IGNORED)\r
3014                    3    player 2 name (not necessarily black)\r
3015                    4    player 2 rating\r
3016                    \r
3017                    The names/ratings are sorted out when the game\r
3018                    actually starts (below).\r
3019                 */\r
3020                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
3021                 player1Rating = string_to_rating(star_match[1]);\r
3022                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
3023                 player2Rating = string_to_rating(star_match[4]);\r
3024 \r
3025                 if (appData.debugMode)\r
3026                   fprintf(debugFP, \r
3027                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
3028                           player1Name, player1Rating,\r
3029                           player2Name, player2Rating);\r
3030 \r
3031                 continue;\r
3032             }\r
3033             \r
3034             /* Improved generic start/end-of-game messages */\r
3035             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
3036                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
3037                 /* If tkind == 0: */\r
3038                 /* star_match[0] is the game number */\r
3039                 /*           [1] is the white player's name */\r
3040                 /*           [2] is the black player's name */\r
3041                 /* For end-of-game: */\r
3042                 /*           [3] is the reason for the game end */\r
3043                 /*           [4] is a PGN end game-token, preceded by " " */\r
3044                 /* For start-of-game: */\r
3045                 /*           [3] begins with "Creating" or "Continuing" */\r
3046                 /*           [4] is " *" or empty (don't care). */\r
3047                 int gamenum = atoi(star_match[0]);\r
3048                 char *whitename, *blackname, *why, *endtoken;\r
3049                 ChessMove endtype = (ChessMove) 0;\r
3050 \r
3051                 if (tkind == 0) {\r
3052                   whitename = star_match[1];\r
3053                   blackname = star_match[2];\r
3054                   why = star_match[3];\r
3055                   endtoken = star_match[4];\r
3056                 } else {\r
3057                   whitename = star_match[1];\r
3058                   blackname = star_match[3];\r
3059                   why = star_match[5];\r
3060                   endtoken = star_match[6];\r
3061                 }\r
3062 \r
3063                 /* Game start messages */\r
3064                 if (strncmp(why, "Creating ", 9) == 0 ||\r
3065                     strncmp(why, "Continuing ", 11) == 0) {\r
3066                     gs_gamenum = gamenum;\r
3067                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
3068 #if ZIPPY\r
3069                     if (appData.zippyPlay) {\r
3070                         ZippyGameStart(whitename, blackname);\r
3071                     }\r
3072 #endif /*ZIPPY*/\r
3073                     continue;\r
3074                 }\r
3075 \r
3076                 /* Game end messages */\r
3077                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
3078                     ics_gamenum != gamenum) {\r
3079                     continue;\r
3080                 }\r
3081                 while (endtoken[0] == ' ') endtoken++;\r
3082                 switch (endtoken[0]) {\r
3083                   case '*':\r
3084                   default:\r
3085                     endtype = GameUnfinished;\r
3086                     break;\r
3087                   case '0':\r
3088                     endtype = BlackWins;\r
3089                     break;\r
3090                   case '1':\r
3091                     if (endtoken[1] == '/')\r
3092                       endtype = GameIsDrawn;\r
3093                     else\r
3094                       endtype = WhiteWins;\r
3095                     break;\r
3096                 }\r
3097                 GameEnds(endtype, why, GE_ICS);\r
3098 #if ZIPPY\r
3099                 if (appData.zippyPlay && first.initDone) {\r
3100                     ZippyGameEnd(endtype, why);\r
3101                     if (first.pr == NULL) {\r
3102                       /* Start the next process early so that we'll\r
3103                          be ready for the next challenge */\r
3104                       StartChessProgram(&first);\r
3105                     }\r
3106                     /* Send "new" early, in case this command takes\r
3107                        a long time to finish, so that we'll be ready\r
3108                        for the next challenge. */\r
3109                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'\r
3110                     Reset(TRUE, TRUE);\r
3111                 }\r
3112 #endif /*ZIPPY*/\r
3113                 continue;\r
3114             }\r
3115 \r
3116             if (looking_at(buf, &i, "Removing game * from observation") ||\r
3117                 looking_at(buf, &i, "no longer observing game *") ||\r
3118                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
3119                 if (gameMode == IcsObserving &&\r
3120                     atoi(star_match[0]) == ics_gamenum)\r
3121                   {\r
3122                       /* icsEngineAnalyze */\r
3123                       if (appData.icsEngineAnalyze) {\r
3124                             ExitAnalyzeMode();\r
3125                             ModeHighlight();\r
3126                       }\r
3127                       StopClocks();\r
3128                       gameMode = IcsIdle;\r
3129                       ics_gamenum = -1;\r
3130                       ics_user_moved = FALSE;\r
3131                   }\r
3132                 continue;\r
3133             }\r
3134 \r
3135             if (looking_at(buf, &i, "no longer examining game *")) {\r
3136                 if (gameMode == IcsExamining &&\r
3137                     atoi(star_match[0]) == ics_gamenum)\r
3138                   {\r
3139                       gameMode = IcsIdle;\r
3140                       ics_gamenum = -1;\r
3141                       ics_user_moved = FALSE;\r
3142                   }\r
3143                 continue;\r
3144             }\r
3145 \r
3146             /* Advance leftover_start past any newlines we find,\r
3147                so only partial lines can get reparsed */\r
3148             if (looking_at(buf, &i, "\n")) {\r
3149                 prevColor = curColor;\r
3150                 if (curColor != ColorNormal) {\r
3151                     if (oldi > next_out) {\r
3152                         SendToPlayer(&buf[next_out], oldi - next_out);\r
3153                         next_out = oldi;\r
3154                     }\r
3155                     Colorize(ColorNormal, FALSE);\r
3156                     curColor = ColorNormal;\r
3157                 }\r
3158                 if (started == STARTED_BOARD) {\r
3159                     started = STARTED_NONE;\r
3160                     parse[parse_pos] = NULLCHAR;\r
3161                     ParseBoard12(parse);\r
3162                     ics_user_moved = 0;\r
3163 \r
3164                     /* Send premove here */\r
3165                     if (appData.premove) {\r
3166                       char str[MSG_SIZ];\r
3167                       if (currentMove == 0 &&\r
3168                           gameMode == IcsPlayingWhite &&\r
3169                           appData.premoveWhite) {\r
3170                         sprintf(str, "%s%s\n", ics_prefix,\r
3171                                 appData.premoveWhiteText);\r
3172                         if (appData.debugMode)\r
3173                           fprintf(debugFP, "Sending premove:\n");\r
3174                         SendToICS(str);\r
3175                       } else if (currentMove == 1 &&\r
3176                                  gameMode == IcsPlayingBlack &&\r
3177                                  appData.premoveBlack) {\r
3178                         sprintf(str, "%s%s\n", ics_prefix,\r
3179                                 appData.premoveBlackText);\r
3180                         if (appData.debugMode)\r
3181                           fprintf(debugFP, "Sending premove:\n");\r
3182                         SendToICS(str);\r
3183                       } else if (gotPremove) {\r
3184                         gotPremove = 0;\r
3185                         ClearPremoveHighlights();\r
3186                         if (appData.debugMode)\r
3187                           fprintf(debugFP, "Sending premove:\n");\r
3188                           UserMoveEvent(premoveFromX, premoveFromY, \r
3189                                         premoveToX, premoveToY, \r
3190                                         premovePromoChar);\r
3191                       }\r
3192                     }\r
3193 \r
3194                     /* Usually suppress following prompt */\r
3195                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
3196                         if (looking_at(buf, &i, "*% ")) {\r
3197                             savingComment = FALSE;\r
3198                         }\r
3199                     }\r
3200                     next_out = i;\r
3201                 } else if (started == STARTED_HOLDINGS) {\r
3202                     int gamenum;\r
3203                     char new_piece[MSG_SIZ];\r
3204                     started = STARTED_NONE;\r
3205                     parse[parse_pos] = NULLCHAR;\r
3206                     if (appData.debugMode)\r
3207                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
3208                                                         parse, currentMove);\r
3209                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
3210                         gamenum == ics_gamenum) {\r
3211                         if (gameInfo.variant == VariantNormal) {\r
3212                           /* [HGM] We seem to switch variant during a game!\r
3213                            * Presumably no holdings were displayed, so we have\r
3214                            * to move the position two files to the right to\r
3215                            * create room for them!\r
3216                            */\r
3217                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
3218                           /* Get a move list just to see the header, which\r
3219                              will tell us whether this is really bug or zh */\r
3220                           if (ics_getting_history == H_FALSE) {\r
3221                             ics_getting_history = H_REQUESTED;\r
3222                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3223                             SendToICS(str);\r
3224                           }\r
3225                         }\r
3226                         new_piece[0] = NULLCHAR;\r
3227                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
3228                                &gamenum, white_holding, black_holding,\r
3229                                new_piece);\r
3230                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
3231                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
3232                         /* [HGM] copy holdings to board holdings area */\r
3233                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
3234                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
3235 #if ZIPPY\r
3236                         if (appData.zippyPlay && first.initDone) {\r
3237                             ZippyHoldings(white_holding, black_holding,\r
3238                                           new_piece);\r
3239                         }\r
3240 #endif /*ZIPPY*/\r
3241                         if (tinyLayout || smallLayout) {\r
3242                             char wh[16], bh[16];\r
3243                             PackHolding(wh, white_holding);\r
3244                             PackHolding(bh, black_holding);\r
3245                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
3246                                     gameInfo.white, gameInfo.black);\r
3247                         } else {\r
3248                             sprintf(str, "%s [%s] vs. %s [%s]",\r
3249                                     gameInfo.white, white_holding,\r
3250                                     gameInfo.black, black_holding);\r
3251                         }\r
3252 \r
3253                         DrawPosition(FALSE, boards[currentMove]);\r
3254                         DisplayTitle(str);\r
3255                     }\r
3256                     /* Suppress following prompt */\r
3257                     if (looking_at(buf, &i, "*% ")) {\r
3258                         savingComment = FALSE;\r
3259                     }\r
3260                     next_out = i;\r
3261                 }\r
3262                 continue;\r
3263             }\r
3264 \r
3265             i++;                /* skip unparsed character and loop back */\r
3266         }\r
3267         \r
3268         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window\r
3269             started != STARTED_HOLDINGS && i > next_out) {\r
3270             SendToPlayer(&buf[next_out], i - next_out);\r
3271             next_out = i;\r
3272         }\r
3273         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above\r
3274         \r
3275         leftover_len = buf_len - leftover_start;\r
3276         /* if buffer ends with something we couldn't parse,\r
3277            reparse it after appending the next read */\r
3278         \r
3279     } else if (count == 0) {\r
3280         RemoveInputSource(isr);\r
3281         DisplayFatalError(_("Connection closed by ICS"), 0, 0);\r
3282     } else {\r
3283         DisplayFatalError(_("Error reading from ICS"), error, 1);\r
3284     }\r
3285 }\r
3286 \r
3287 \r
3288 /* Board style 12 looks like this:\r
3289    \r
3290    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0\r
3291    \r
3292  * The "<12> " is stripped before it gets to this routine.  The two\r
3293  * trailing 0's (flip state and clock ticking) are later addition, and\r
3294  * some chess servers may not have them, or may have only the first.\r
3295  * Additional trailing fields may be added in the future.  \r
3296  */\r
3297 \r
3298 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"\r
3299 \r
3300 #define RELATION_OBSERVING_PLAYED    0\r
3301 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
3302 #define RELATION_PLAYING_MYMOVE      1\r
3303 #define RELATION_PLAYING_NOTMYMOVE  -1\r
3304 #define RELATION_EXAMINING           2\r
3305 #define RELATION_ISOLATED_BOARD     -3\r
3306 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
3307 \r
3308 void\r
3309 ParseBoard12(string)\r
3310      char *string;\r
3311\r
3312     GameMode newGameMode;\r
3313     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;\r
3314     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;\r
3315     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
3316     char to_play, board_chars[200];\r
3317     char move_str[500], str[500], elapsed_time[500];\r
3318     char black[32], white[32];\r
3319     Board board;\r
3320     int prevMove = currentMove;\r
3321     int ticking = 2;\r
3322     ChessMove moveType;\r
3323     int fromX, fromY, toX, toY;\r
3324     char promoChar;\r
3325     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */\r
3326     char *bookHit = NULL; // [HGM] book\r
3327 \r
3328     fromX = fromY = toX = toY = -1;\r
3329     \r
3330     newGame = FALSE;\r
3331 \r
3332     if (appData.debugMode)\r
3333       fprintf(debugFP, _("Parsing board: %s\n"), string);\r
3334 \r
3335     move_str[0] = NULLCHAR;\r
3336     elapsed_time[0] = NULLCHAR;\r
3337     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */\r
3338         int  i = 0, j;\r
3339         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {\r
3340             if(string[i] == ' ') { ranks++; files = 0; }\r
3341             else files++;\r
3342             i++;\r
3343         }\r
3344         for(j = 0; j <i; j++) board_chars[j] = string[j];\r
3345         board_chars[i] = '\0';\r
3346         string += i + 1;\r
3347     }\r
3348     n = sscanf(string, PATTERN, &to_play, &double_push,\r
3349                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
3350                &gamenum, white, black, &relation, &basetime, &increment,\r
3351                &white_stren, &black_stren, &white_time, &black_time,\r
3352                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
3353                &ticking);\r
3354 \r
3355     if (n < 21) {\r
3356         sprintf(str, _("Failed to parse board string:\n\"%s\""), string);\r
3357         DisplayError(str, 0);\r
3358         return;\r
3359     }\r
3360 \r
3361     /* Convert the move number to internal form */\r
3362     moveNum = (moveNum - 1) * 2;\r
3363     if (to_play == 'B') moveNum++;\r
3364     if (moveNum >= MAX_MOVES) {\r
3365       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
3366                         0, 1);\r
3367       return;\r
3368     }\r
3369     \r
3370     switch (relation) {\r
3371       case RELATION_OBSERVING_PLAYED:\r
3372       case RELATION_OBSERVING_STATIC:\r
3373         if (gamenum == -1) {\r
3374             /* Old ICC buglet */\r
3375             relation = RELATION_OBSERVING_STATIC;\r
3376         }\r
3377         newGameMode = IcsObserving;\r
3378         break;\r
3379       case RELATION_PLAYING_MYMOVE:\r
3380       case RELATION_PLAYING_NOTMYMOVE:\r
3381         newGameMode =\r
3382           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3383             IcsPlayingWhite : IcsPlayingBlack;\r
3384         break;\r
3385       case RELATION_EXAMINING:\r
3386         newGameMode = IcsExamining;\r
3387         break;\r
3388       case RELATION_ISOLATED_BOARD:\r
3389       default:\r
3390         /* Just display this board.  If user was doing something else,\r
3391            we will forget about it until the next board comes. */ \r
3392         newGameMode = IcsIdle;\r
3393         break;\r
3394       case RELATION_STARTING_POSITION:\r
3395         newGameMode = gameMode;\r
3396         break;\r
3397     }\r
3398     \r
3399     /* Modify behavior for initial board display on move listing\r
3400        of wild games.\r
3401        */\r
3402     switch (ics_getting_history) {\r
3403       case H_FALSE:\r
3404       case H_REQUESTED:\r
3405         break;\r
3406       case H_GOT_REQ_HEADER:\r
3407       case H_GOT_UNREQ_HEADER:\r
3408         /* This is the initial position of the current game */\r
3409         gamenum = ics_gamenum;\r
3410         moveNum = 0;            /* old ICS bug workaround */\r
3411         if (to_play == 'B') {\r
3412           startedFromSetupPosition = TRUE;\r
3413           blackPlaysFirst = TRUE;\r
3414           moveNum = 1;\r
3415           if (forwardMostMove == 0) forwardMostMove = 1;\r
3416           if (backwardMostMove == 0) backwardMostMove = 1;\r
3417           if (currentMove == 0) currentMove = 1;\r
3418         }\r
3419         newGameMode = gameMode;\r
3420         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3421         break;\r
3422       case H_GOT_UNWANTED_HEADER:\r
3423         /* This is an initial board that we don't want */\r
3424         return;\r
3425       case H_GETTING_MOVES:\r
3426         /* Should not happen */\r
3427         DisplayError(_("Error gathering move list: extra board"), 0);\r
3428         ics_getting_history = H_FALSE;\r
3429         return;\r
3430     }\r
3431     \r
3432     /* Take action if this is the first board of a new game, or of a\r
3433        different game than is currently being displayed.  */\r
3434     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3435         relation == RELATION_ISOLATED_BOARD) {\r
3436         \r
3437         /* Forget the old game and get the history (if any) of the new one */\r
3438         if (gameMode != BeginningOfGame) {\r
3439           Reset(FALSE, TRUE);\r
3440         }\r
3441         newGame = TRUE;\r
3442         if (appData.autoRaiseBoard) BoardToTop();\r
3443         prevMove = -3;\r
3444         if (gamenum == -1) {\r
3445             newGameMode = IcsIdle;\r
3446         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3447                    appData.getMoveList) {\r
3448             /* Need to get game history */\r
3449             ics_getting_history = H_REQUESTED;\r
3450             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3451             SendToICS(str);\r
3452         }\r
3453         \r
3454         /* Initially flip the board to have black on the bottom if playing\r
3455            black or if the ICS flip flag is set, but let the user change\r
3456            it with the Flip View button. */\r
3457         flipView = appData.autoFlipView ? \r
3458           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3459           appData.flipView;\r
3460         \r
3461         /* Done with values from previous mode; copy in new ones */\r
3462         gameMode = newGameMode;\r
3463         ModeHighlight();\r
3464         ics_gamenum = gamenum;\r
3465         if (gamenum == gs_gamenum) {\r
3466             int klen = strlen(gs_kind);\r
3467             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3468             sprintf(str, "ICS %s", gs_kind);\r
3469             gameInfo.event = StrSave(str);\r
3470         } else {\r
3471             gameInfo.event = StrSave("ICS game");\r
3472         }\r
3473         gameInfo.site = StrSave(appData.icsHost);\r
3474         gameInfo.date = PGNDate();\r
3475         gameInfo.round = StrSave("-");\r
3476         gameInfo.white = StrSave(white);\r
3477         gameInfo.black = StrSave(black);\r
3478         timeControl = basetime * 60 * 1000;\r
3479         timeControl_2 = 0;\r
3480         timeIncrement = increment * 1000;\r
3481         movesPerSession = 0;\r
3482         gameInfo.timeControl = TimeControlTagValue();\r
3483         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3484   if (appData.debugMode) {\r
3485     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3486     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3487     setbuf(debugFP, NULL);\r
3488   }\r
3489 \r
3490         gameInfo.outOfBook = NULL;\r
3491         \r
3492         /* Do we have the ratings? */\r
3493         if (strcmp(player1Name, white) == 0 &&\r
3494             strcmp(player2Name, black) == 0) {\r
3495             if (appData.debugMode)\r
3496               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3497                       player1Rating, player2Rating);\r
3498             gameInfo.whiteRating = player1Rating;\r
3499             gameInfo.blackRating = player2Rating;\r
3500         } else if (strcmp(player2Name, white) == 0 &&\r
3501                    strcmp(player1Name, black) == 0) {\r
3502             if (appData.debugMode)\r
3503               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3504                       player2Rating, player1Rating);\r
3505             gameInfo.whiteRating = player2Rating;\r
3506             gameInfo.blackRating = player1Rating;\r
3507         }\r
3508         player1Name[0] = player2Name[0] = NULLCHAR;\r
3509 \r
3510         /* Silence shouts if requested */\r
3511         if (appData.quietPlay &&\r
3512             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3513             SendToICS(ics_prefix);\r
3514             SendToICS("set shout 0\n");\r
3515         }\r
3516     }\r
3517     \r
3518     /* Deal with midgame name changes */\r
3519     if (!newGame) {\r
3520         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3521             if (gameInfo.white) free(gameInfo.white);\r
3522             gameInfo.white = StrSave(white);\r
3523         }\r
3524         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3525             if (gameInfo.black) free(gameInfo.black);\r
3526             gameInfo.black = StrSave(black);\r
3527         }\r
3528     }\r
3529     \r
3530     /* Throw away game result if anything actually changes in examine mode */\r
3531     if (gameMode == IcsExamining && !newGame) {\r
3532         gameInfo.result = GameUnfinished;\r
3533         if (gameInfo.resultDetails != NULL) {\r
3534             free(gameInfo.resultDetails);\r
3535             gameInfo.resultDetails = NULL;\r
3536         }\r
3537     }\r
3538     \r
3539     /* In pausing && IcsExamining mode, we ignore boards coming\r
3540        in if they are in a different variation than we are. */\r
3541     if (pauseExamInvalid) return;\r
3542     if (pausing && gameMode == IcsExamining) {\r
3543         if (moveNum <= pauseExamForwardMostMove) {\r
3544             pauseExamInvalid = TRUE;\r
3545             forwardMostMove = pauseExamForwardMostMove;\r
3546             return;\r
3547         }\r
3548     }\r
3549     \r
3550   if (appData.debugMode) {\r
3551     fprintf(debugFP, "load %dx%d board\n", files, ranks);\r
3552   }\r
3553     /* Parse the board */\r
3554     for (k = 0; k < ranks; k++) {\r
3555       for (j = 0; j < files; j++)\r
3556         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);\r
3557       if(gameInfo.holdingsWidth > 1) {\r
3558            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3559            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3560       }\r
3561     }\r
3562     CopyBoard(boards[moveNum], board);\r
3563     if (moveNum == 0) {\r
3564         startedFromSetupPosition =\r
3565           !CompareBoards(board, initialPosition);\r
3566         if(startedFromSetupPosition)\r
3567             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */\r
3568     }\r
3569 \r
3570     /* [HGM] Set castling rights. Take the outermost Rooks,\r
3571        to make it also work for FRC opening positions. Note that board12\r
3572        is really defective for later FRC positions, as it has no way to\r
3573        indicate which Rook can castle if they are on the same side of King.\r
3574        For the initial position we grant rights to the outermost Rooks,\r
3575        and remember thos rights, and we then copy them on positions\r
3576        later in an FRC game. This means WB might not recognize castlings with\r
3577        Rooks that have moved back to their original position as illegal,\r
3578        but in ICS mode that is not its job anyway.\r
3579     */\r
3580     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)\r
3581     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;\r
3582 \r
3583         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3584             if(board[0][i] == WhiteRook) j = i;\r
3585         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3586         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3587             if(board[0][i] == WhiteRook) j = i;\r
3588         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3589         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3590             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3591         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3592         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3593             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3594         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3595 \r
3596         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }\r
3597         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3598             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;\r
3599         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3600             if(board[BOARD_HEIGHT-1][k] == bKing)\r
3601                 initialRights[5] = castlingRights[moveNum][5] = k;\r
3602     } else { int r;\r
3603         r = castlingRights[moveNum][0] = initialRights[0];\r
3604         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;\r
3605         r = castlingRights[moveNum][1] = initialRights[1];\r
3606         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;\r
3607         r = castlingRights[moveNum][3] = initialRights[3];\r
3608         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;\r
3609         r = castlingRights[moveNum][4] = initialRights[4];\r
3610         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;\r
3611         /* wildcastle kludge: always assume King has rights */\r
3612         r = castlingRights[moveNum][2] = initialRights[2];\r
3613         r = castlingRights[moveNum][5] = initialRights[5];\r
3614     }\r
3615     /* [HGM] e.p. rights. Assume that ICS sends file number here? */\r
3616     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;\r
3617 \r
3618     \r
3619     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3620         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3621         /* This was an initial position from a move list, not\r
3622            the current position */\r
3623         return;\r
3624     }\r
3625     \r
3626     /* Update currentMove and known move number limits */\r
3627     newMove = newGame || moveNum > forwardMostMove;\r
3628 \r
3629     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */\r
3630     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {\r
3631         takeback = forwardMostMove - moveNum;\r
3632         for (i = 0; i < takeback; i++) {\r
3633              if (appData.debugMode) fprintf(debugFP, "take back move\n");\r
3634              SendToProgram("undo\n", &first);\r
3635         }\r
3636     }\r
3637 \r
3638     if (newGame) {\r
3639         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3640         if (gameMode == IcsExamining && moveNum == 0) {\r
3641           /* Workaround for ICS limitation: we are not told the wild\r
3642              type when starting to examine a game.  But if we ask for\r
3643              the move list, the move list header will tell us */\r
3644             ics_getting_history = H_REQUESTED;\r
3645             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3646             SendToICS(str);\r
3647         }\r
3648     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3649                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3650         forwardMostMove = moveNum;\r
3651         if (!pausing || currentMove > forwardMostMove)\r
3652           currentMove = forwardMostMove;\r
3653     } else {\r
3654         /* New part of history that is not contiguous with old part */ \r
3655         if (pausing && gameMode == IcsExamining) {\r
3656             pauseExamInvalid = TRUE;\r
3657             forwardMostMove = pauseExamForwardMostMove;\r
3658             return;\r
3659         }\r
3660         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3661         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3662             ics_getting_history = H_REQUESTED;\r
3663             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3664             SendToICS(str);\r
3665         }\r
3666     }\r
3667     \r
3668     /* Update the clocks */\r
3669     if (strchr(elapsed_time, '.')) {\r
3670       /* Time is in ms */\r
3671       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3672       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3673     } else {\r
3674       /* Time is in seconds */\r
3675       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3676       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3677     }\r
3678       \r
3679 \r
3680 #if ZIPPY\r
3681     if (appData.zippyPlay && newGame &&\r
3682         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3683         gameMode != IcsExamining)\r
3684       ZippyFirstBoard(moveNum, basetime, increment);\r
3685 #endif\r
3686     \r
3687     /* Put the move on the move list, first converting\r
3688        to canonical algebraic form. */\r
3689     if (moveNum > 0) {\r
3690   if (appData.debugMode) {\r
3691     if (appData.debugMode) { int f = forwardMostMove;\r
3692         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,\r
3693                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
3694     }\r
3695     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3696     fprintf(debugFP, "moveNum = %d\n", moveNum);\r
3697     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3698     setbuf(debugFP, NULL);\r
3699   }\r
3700         if (moveNum <= backwardMostMove) {\r
3701             /* We don't know what the board looked like before\r
3702                this move.  Punt. */\r
3703             strcpy(parseList[moveNum - 1], move_str);\r
3704             strcat(parseList[moveNum - 1], " ");\r
3705             strcat(parseList[moveNum - 1], elapsed_time);\r
3706             moveList[moveNum - 1][0] = NULLCHAR;\r
3707         } else if (strcmp(move_str, "none") == 0) {\r
3708             // [HGM] long SAN: swapped order; test for 'none' before parsing move\r
3709             /* Again, we don't know what the board looked like;\r
3710                this is really the start of the game. */\r
3711             parseList[moveNum - 1][0] = NULLCHAR;\r
3712             moveList[moveNum - 1][0] = NULLCHAR;\r
3713             backwardMostMove = moveNum;\r
3714             startedFromSetupPosition = TRUE;\r
3715             fromX = fromY = toX = toY = -1;\r
3716         } else {\r
3717           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. \r
3718           //                 So we parse the long-algebraic move string in stead of the SAN move\r
3719           int valid; char buf[MSG_SIZ], *prom;\r
3720 \r
3721           // str looks something like "Q/a1-a2"; kill the slash\r
3722           if(str[1] == '/') \r
3723                 sprintf(buf, "%c%s", str[0], str+2);\r
3724           else  strcpy(buf, str); // might be castling\r
3725           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) \r
3726                 strcat(buf, prom); // long move lacks promo specification!\r
3727           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)\r
3728                 if(appData.debugMode) \r
3729                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);\r
3730                 strcpy(move_str, buf);\r
3731           }\r
3732           valid = ParseOneMove(move_str, moveNum - 1, &moveType,\r
3733                                 &fromX, &fromY, &toX, &toY, &promoChar)\r
3734                || ParseOneMove(buf, moveNum - 1, &moveType,\r
3735                                 &fromX, &fromY, &toX, &toY, &promoChar);\r
3736           // end of long SAN patch\r
3737           if (valid) {\r
3738             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3739                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3740                                      fromY, fromX, toY, toX, promoChar,\r
3741                                      parseList[moveNum-1]);\r
3742             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3743                              castlingRights[moveNum]) ) {\r
3744               case MT_NONE:\r
3745               case MT_STALEMATE:\r
3746               default:\r
3747                 break;\r
3748               case MT_CHECK:\r
3749                 if(gameInfo.variant != VariantShogi)\r
3750                     strcat(parseList[moveNum - 1], "+");\r
3751                 break;\r
3752               case MT_CHECKMATE:\r
3753               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate\r
3754                 strcat(parseList[moveNum - 1], "#");\r
3755                 break;\r
3756             }\r
3757             strcat(parseList[moveNum - 1], " ");\r
3758             strcat(parseList[moveNum - 1], elapsed_time);\r
3759             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3760             strcpy(moveList[moveNum - 1], currentMoveString);\r
3761             strcat(moveList[moveNum - 1], "\n");\r
3762           } else {\r
3763             /* Move from ICS was illegal!?  Punt. */\r
3764   if (appData.debugMode) {\r
3765     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3766     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3767   }\r
3768 #if 0\r
3769             if (appData.testLegality && appData.debugMode) {\r
3770                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3771                 DisplayError(str, 0);\r
3772             }\r
3773 #endif\r
3774             strcpy(parseList[moveNum - 1], move_str);\r
3775             strcat(parseList[moveNum - 1], " ");\r
3776             strcat(parseList[moveNum - 1], elapsed_time);\r
3777             moveList[moveNum - 1][0] = NULLCHAR;\r
3778             fromX = fromY = toX = toY = -1;\r
3779           }\r
3780         }\r
3781   if (appData.debugMode) {\r
3782     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3783     setbuf(debugFP, NULL);\r
3784   }\r
3785 \r
3786 #if ZIPPY\r
3787         /* Send move to chess program (BEFORE animating it). */\r
3788         if (appData.zippyPlay && !newGame && newMove && \r
3789            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3790 \r
3791             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3792                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3793                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3794                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),\r
3795                             move_str);\r
3796                     DisplayError(str, 0);\r
3797                 } else {\r
3798                     if (first.sendTime) {\r
3799                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3800                     }\r
3801                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book\r
3802                     if (firstMove && !bookHit) {\r
3803                         firstMove = FALSE;\r
3804                         if (first.useColors) {\r
3805                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3806                                         "white\ngo\n" :\r
3807                                         "black\ngo\n", &first);\r
3808                         } else {\r
3809                           SendToProgram("go\n", &first);\r
3810                         }\r
3811                         first.maybeThinking = TRUE;\r
3812                     }\r
3813                 }\r
3814             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3815               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3816                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);\r
3817                 DisplayError(str, 0);\r
3818               } else {\r
3819                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!\r
3820                 SendMoveToProgram(moveNum - 1, &first);\r
3821               }\r
3822             }\r
3823         }\r
3824 #endif\r
3825     }\r
3826 \r
3827     if (moveNum > 0 && !gotPremove) {\r
3828         /* If move comes from a remote source, animate it.  If it\r
3829            isn't remote, it will have already been animated. */\r
3830         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3831             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3832         }\r
3833         if (!pausing && appData.highlightLastMove) {\r
3834             SetHighlights(fromX, fromY, toX, toY);\r
3835         }\r
3836     }\r
3837     \r
3838     /* Start the clocks */\r
3839     whiteFlag = blackFlag = FALSE;\r
3840     appData.clockMode = !(basetime == 0 && increment == 0);\r
3841     if (ticking == 0) {\r
3842       ics_clock_paused = TRUE;\r
3843       StopClocks();\r
3844     } else if (ticking == 1) {\r
3845       ics_clock_paused = FALSE;\r
3846     }\r
3847     if (gameMode == IcsIdle ||\r
3848         relation == RELATION_OBSERVING_STATIC ||\r
3849         relation == RELATION_EXAMINING ||\r
3850         ics_clock_paused)\r
3851       DisplayBothClocks();\r
3852     else\r
3853       StartClocks();\r
3854     \r
3855     /* Display opponents and material strengths */\r
3856     if (gameInfo.variant != VariantBughouse &&\r
3857         gameInfo.variant != VariantCrazyhouse) {\r
3858         if (tinyLayout || smallLayout) {\r
3859             if(gameInfo.variant == VariantNormal)\r
3860                 sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3861                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3862                     basetime, increment);\r
3863             else\r
3864                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", \r
3865                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3866                     basetime, increment, (int) gameInfo.variant);\r
3867         } else {\r
3868             if(gameInfo.variant == VariantNormal)\r
3869                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3870                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3871                     basetime, increment);\r
3872             else\r
3873                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", \r
3874                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3875                     basetime, increment, VariantName(gameInfo.variant));\r
3876         }\r
3877         DisplayTitle(str);\r
3878   if (appData.debugMode) {\r
3879     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);\r
3880   }\r
3881     }\r
3882 \r
3883    \r
3884     /* Display the board */\r
3885     if (!pausing) {\r
3886       \r
3887       if (appData.premove)\r
3888           if (!gotPremove || \r
3889              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3890              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3891               ClearPremoveHighlights();\r
3892 \r
3893       DrawPosition(FALSE, boards[currentMove]);\r
3894       DisplayMove(moveNum - 1);\r
3895       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3896         RingBell();\r
3897     }\r
3898 \r
3899     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3900 #if ZIPPY\r
3901     if(bookHit) { // [HGM] book: simulate book reply\r
3902         static char bookMove[MSG_SIZ]; // a bit generous?\r
3903 \r
3904         programStats.nodes = programStats.depth = programStats.time = \r
3905         programStats.score = programStats.got_only_move = 0;\r
3906         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
3907 \r
3908         strcpy(bookMove, "move ");\r
3909         strcat(bookMove, bookHit);\r
3910         HandleMachineMove(bookMove, &first);\r
3911     }\r
3912 #endif\r
3913 }\r
3914 \r
3915 void\r
3916 GetMoveListEvent()\r
3917 {\r
3918     char buf[MSG_SIZ];\r
3919     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3920         ics_getting_history = H_REQUESTED;\r
3921         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3922         SendToICS(buf);\r
3923     }\r
3924 }\r
3925 \r
3926 void\r
3927 AnalysisPeriodicEvent(force)\r
3928      int force;\r
3929 {\r
3930     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3931          && !force) || !appData.periodicUpdates)\r
3932       return;\r
3933 \r
3934     /* Send . command to Crafty to collect stats */\r
3935     SendToProgram(".\n", &first);\r
3936 \r
3937     /* Don't send another until we get a response (this makes\r
3938        us stop sending to old Crafty's which don't understand\r
3939        the "." command (sending illegal cmds resets node count & time,\r
3940        which looks bad)) */\r
3941     programStats.ok_to_send = 0;\r
3942 }\r
3943 \r
3944 void\r
3945 SendMoveToProgram(moveNum, cps)\r
3946      int moveNum;\r
3947      ChessProgramState *cps;\r
3948 {\r
3949     char buf[MSG_SIZ];\r
3950 \r
3951     if (cps->useUsermove) {\r
3952       SendToProgram("usermove ", cps);\r
3953     }\r
3954     if (cps->useSAN) {\r
3955       char *space;\r
3956       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3957         int len = space - parseList[moveNum];\r
3958         memcpy(buf, parseList[moveNum], len);\r
3959         buf[len++] = '\n';\r
3960         buf[len] = NULLCHAR;\r
3961       } else {\r
3962         sprintf(buf, "%s\n", parseList[moveNum]);\r
3963       }\r
3964       SendToProgram(buf, cps);\r
3965     } else {\r
3966       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */\r
3967         AlphaRank(moveList[moveNum], 4);\r
3968         SendToProgram(moveList[moveNum], cps);\r
3969         AlphaRank(moveList[moveNum], 4); // and back\r
3970       } else\r
3971       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3972        * the engine. It would be nice to have a better way to identify castle \r
3973        * moves here. */\r
3974       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)\r
3975                                                                          && cps->useOOCastle) {\r
3976         int fromX = moveList[moveNum][0] - AAA; \r
3977         int fromY = moveList[moveNum][1] - ONE;\r
3978         int toX = moveList[moveNum][2] - AAA; \r
3979         int toY = moveList[moveNum][3] - ONE;\r
3980         if((boards[moveNum][fromY][fromX] == WhiteKing \r
3981             && boards[moveNum][toY][toX] == WhiteRook)\r
3982            || (boards[moveNum][fromY][fromX] == BlackKing \r
3983                && boards[moveNum][toY][toX] == BlackRook)) {\r
3984           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3985           else SendToProgram("O-O-O\n", cps);\r
3986         }\r
3987         else SendToProgram(moveList[moveNum], cps);\r
3988       }\r
3989       else SendToProgram(moveList[moveNum], cps);\r
3990       /* End of additions by Tord */\r
3991     }\r
3992 \r
3993     /* [HGM] setting up the opening has brought engine in force mode! */\r
3994     /*       Send 'go' if we are in a mode where machine should play. */\r
3995     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
3996         (gameMode == TwoMachinesPlay   ||\r
3997 #ifdef ZIPPY\r
3998          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||\r
3999 #endif\r
4000          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
4001         SendToProgram("go\n", cps);\r
4002   if (appData.debugMode) {\r
4003     fprintf(debugFP, "(extra)\n");\r
4004   }\r
4005     }\r
4006     setboardSpoiledMachineBlack = 0;\r
4007 }\r
4008 \r
4009 void\r
4010 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
4011      ChessMove moveType;\r
4012      int fromX, fromY, toX, toY;\r
4013 {\r
4014     char user_move[MSG_SIZ];\r
4015 \r
4016     switch (moveType) {\r
4017       default:\r
4018         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),\r
4019                 (int)moveType, fromX, fromY, toX, toY);\r
4020         DisplayError(user_move + strlen("say "), 0);\r
4021         break;\r
4022       case WhiteKingSideCastle:\r
4023       case BlackKingSideCastle:\r
4024       case WhiteQueenSideCastleWild:\r
4025       case BlackQueenSideCastleWild:\r
4026       /* PUSH Fabien */\r
4027       case WhiteHSideCastleFR:\r
4028       case BlackHSideCastleFR:\r
4029       /* POP Fabien */\r
4030         sprintf(user_move, "o-o\n");\r
4031         break;\r
4032       case WhiteQueenSideCastle:\r
4033       case BlackQueenSideCastle:\r
4034       case WhiteKingSideCastleWild:\r
4035       case BlackKingSideCastleWild:\r
4036       /* PUSH Fabien */\r
4037       case WhiteASideCastleFR:\r
4038       case BlackASideCastleFR:\r
4039       /* POP Fabien */\r
4040         sprintf(user_move, "o-o-o\n");\r
4041         break;\r
4042       case WhitePromotionQueen:\r
4043       case BlackPromotionQueen:\r
4044       case WhitePromotionRook:\r
4045       case BlackPromotionRook:\r
4046       case WhitePromotionBishop:\r
4047       case BlackPromotionBishop:\r
4048       case WhitePromotionKnight:\r
4049       case BlackPromotionKnight:\r
4050       case WhitePromotionKing:\r
4051       case BlackPromotionKing:\r
4052       case WhitePromotionChancellor:\r
4053       case BlackPromotionChancellor:\r
4054       case WhitePromotionArchbishop:\r
4055       case BlackPromotionArchbishop:\r
4056         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)\r
4057             sprintf(user_move, "%c%c%c%c=%c\n",\r
4058                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4059                 PieceToChar(WhiteFerz));\r
4060         else if(gameInfo.variant == VariantGreat)\r
4061             sprintf(user_move, "%c%c%c%c=%c\n",\r
4062                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4063                 PieceToChar(WhiteMan));\r
4064         else\r
4065             sprintf(user_move, "%c%c%c%c=%c\n",\r
4066                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4067                 PieceToChar(PromoPiece(moveType)));\r
4068         break;\r
4069       case WhiteDrop:\r
4070       case BlackDrop:\r
4071         sprintf(user_move, "%c@%c%c\n",\r
4072                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
4073                 AAA + toX, ONE + toY);\r
4074         break;\r
4075       case NormalMove:\r
4076       case WhiteCapturesEnPassant:\r
4077       case BlackCapturesEnPassant:\r
4078       case IllegalMove:  /* could be a variant we don't quite understand */\r
4079         sprintf(user_move, "%c%c%c%c\n",\r
4080                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
4081         break;\r
4082     }\r
4083     SendToICS(user_move);\r
4084 }\r
4085 \r
4086 void\r
4087 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
4088      int rf, ff, rt, ft;\r
4089      char promoChar;\r
4090      char move[7];\r
4091 {\r
4092     if (rf == DROP_RANK) {\r
4093         sprintf(move, "%c@%c%c\n",\r
4094                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
4095     } else {\r
4096         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
4097             sprintf(move, "%c%c%c%c\n",\r
4098                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
4099         } else {\r
4100             sprintf(move, "%c%c%c%c%c\n",\r
4101                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
4102         }\r
4103     }\r
4104 }\r
4105 \r
4106 void\r
4107 ProcessICSInitScript(f)\r
4108      FILE *f;\r
4109 {\r
4110     char buf[MSG_SIZ];\r
4111 \r
4112     while (fgets(buf, MSG_SIZ, f)) {\r
4113         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
4114     }\r
4115 \r
4116     fclose(f);\r
4117 }\r
4118 \r
4119 \r
4120 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
4121 void\r
4122 AlphaRank(char *move, int n)\r
4123 {\r
4124 //    char *p = move, c; int x, y;\r
4125 \r
4126     if (appData.debugMode) {\r
4127         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
4128     }\r
4129 \r
4130     if(move[1]=='*' && \r
4131        move[2]>='0' && move[2]<='9' &&\r
4132        move[3]>='a' && move[3]<='x'    ) {\r
4133         move[1] = '@';\r
4134         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4135         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4136     } else\r
4137     if(move[0]>='0' && move[0]<='9' &&\r
4138        move[1]>='a' && move[1]<='x' &&\r
4139        move[2]>='0' && move[2]<='9' &&\r
4140        move[3]>='a' && move[3]<='x'    ) {\r
4141         /* input move, Shogi -> normal */\r
4142         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;\r
4143         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\r
4144         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4145         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4146     } else\r
4147     if(move[1]=='@' &&\r
4148        move[3]>='0' && move[3]<='9' &&\r
4149        move[2]>='a' && move[2]<='x'    ) {\r
4150         move[1] = '*';\r
4151         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4152         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4153     } else\r
4154     if(\r
4155        move[0]>='a' && move[0]<='x' &&\r
4156        move[3]>='0' && move[3]<='9' &&\r
4157        move[2]>='a' && move[2]<='x'    ) {\r
4158          /* output move, normal -> Shogi */\r
4159         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
4160         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
4161         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4162         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4163         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
4164     }\r
4165     if (appData.debugMode) {\r
4166         fprintf(debugFP, "   out = '%s'\n", move);\r
4167     }\r
4168 }\r
4169 \r
4170 /* Parser for moves from gnuchess, ICS, or user typein box */\r
4171 Boolean\r
4172 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
4173      char *move;\r
4174      int moveNum;\r
4175      ChessMove *moveType;\r
4176      int *fromX, *fromY, *toX, *toY;\r
4177      char *promoChar;\r
4178 {       \r
4179     if (appData.debugMode) {\r
4180         fprintf(debugFP, "move to parse: %s\n", move);\r
4181     }\r
4182     *moveType = yylexstr(moveNum, move);\r
4183 \r
4184     switch (*moveType) {\r
4185       case WhitePromotionChancellor:\r
4186       case BlackPromotionChancellor:\r
4187       case WhitePromotionArchbishop:\r
4188       case BlackPromotionArchbishop:\r
4189       case WhitePromotionQueen:\r
4190       case BlackPromotionQueen:\r
4191       case WhitePromotionRook:\r
4192       case BlackPromotionRook:\r
4193       case WhitePromotionBishop:\r
4194       case BlackPromotionBishop:\r
4195       case WhitePromotionKnight:\r
4196       case BlackPromotionKnight:\r
4197       case WhitePromotionKing:\r
4198       case BlackPromotionKing:\r
4199       case NormalMove:\r
4200       case WhiteCapturesEnPassant:\r
4201       case BlackCapturesEnPassant:\r
4202       case WhiteKingSideCastle:\r
4203       case WhiteQueenSideCastle:\r
4204       case BlackKingSideCastle:\r
4205       case BlackQueenSideCastle:\r
4206       case WhiteKingSideCastleWild:\r
4207       case WhiteQueenSideCastleWild:\r
4208       case BlackKingSideCastleWild:\r
4209       case BlackQueenSideCastleWild:\r
4210       /* Code added by Tord: */\r
4211       case WhiteHSideCastleFR:\r
4212       case WhiteASideCastleFR:\r
4213       case BlackHSideCastleFR:\r
4214       case BlackASideCastleFR:\r
4215       /* End of code added by Tord */\r
4216       case IllegalMove:         /* bug or odd chess variant */\r
4217         *fromX = currentMoveString[0] - AAA;\r
4218         *fromY = currentMoveString[1] - ONE;\r
4219         *toX = currentMoveString[2] - AAA;\r
4220         *toY = currentMoveString[3] - ONE;\r
4221         *promoChar = currentMoveString[4];\r
4222         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
4223             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
4224     if (appData.debugMode) {\r
4225         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
4226     }\r
4227             *fromX = *fromY = *toX = *toY = 0;\r
4228             return FALSE;\r
4229         }\r
4230         if (appData.testLegality) {\r
4231           return (*moveType != IllegalMove);\r
4232         } else {\r
4233           return !(fromX == fromY && toX == toY);\r
4234         }\r
4235 \r
4236       case WhiteDrop:\r
4237       case BlackDrop:\r
4238         *fromX = *moveType == WhiteDrop ?\r
4239           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
4240           (int) CharToPiece(ToLower(currentMoveString[0]));\r
4241         *fromY = DROP_RANK;\r
4242         *toX = currentMoveString[2] - AAA;\r
4243         *toY = currentMoveString[3] - ONE;\r
4244         *promoChar = NULLCHAR;\r
4245         return TRUE;\r
4246 \r
4247       case AmbiguousMove:\r
4248       case ImpossibleMove:\r
4249       case (ChessMove) 0:       /* end of file */\r
4250       case ElapsedTime:\r
4251       case Comment:\r
4252       case PGNTag:\r
4253       case NAG:\r
4254       case WhiteWins:\r
4255       case BlackWins:\r
4256       case GameIsDrawn:\r
4257       default:\r
4258     if (appData.debugMode) {\r
4259         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
4260     }\r
4261         /* bug? */\r
4262         *fromX = *fromY = *toX = *toY = 0;\r
4263         *promoChar = NULLCHAR;\r
4264         return FALSE;\r
4265     }\r
4266 }\r
4267 \r
4268 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.\r
4269 // All positions will have equal probability, but the current method will not provide a unique\r
4270 // numbering scheme for arrays that contain 3 or more pieces of the same kind.\r
4271 #define DARK 1\r
4272 #define LITE 2\r
4273 #define ANY 3\r
4274 \r
4275 int squaresLeft[4];\r
4276 int piecesLeft[(int)BlackPawn];\r
4277 int seed, nrOfShuffles;\r
4278 \r
4279 void GetPositionNumber()\r
4280 {       // sets global variable seed\r
4281         int i;\r
4282 \r
4283         seed = appData.defaultFrcPosition;\r
4284         if(seed < 0) { // randomize based on time for negative FRC position numbers\r
4285                 for(i=0; i<50; i++) seed += random();\r
4286                 seed = random() ^ random() >> 8 ^ random() << 8;\r
4287                 if(seed<0) seed = -seed;\r
4288         }\r
4289 }\r
4290 \r
4291 int put(Board board, int pieceType, int rank, int n, int shade)\r
4292 // put the piece on the (n-1)-th empty squares of the given shade\r
4293 {\r
4294         int i;\r
4295 \r
4296         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
4297                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {\r
4298                         board[rank][i] = (ChessSquare) pieceType;\r
4299                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;\r
4300                         squaresLeft[ANY]--;\r
4301                         piecesLeft[pieceType]--; \r
4302                         return i;\r
4303                 }\r
4304         }\r
4305         return -1;\r
4306 }\r
4307 \r
4308 \r
4309 void AddOnePiece(Board board, int pieceType, int rank, int shade)\r
4310 // calculate where the next piece goes, (any empty square), and put it there\r
4311 {\r
4312         int i;\r
4313 \r
4314         i = seed % squaresLeft[shade];\r
4315         nrOfShuffles *= squaresLeft[shade];\r
4316         seed /= squaresLeft[shade];\r
4317         put(board, pieceType, rank, i, shade);\r
4318 }\r
4319 \r
4320 void AddTwoPieces(Board board, int pieceType, int rank)\r
4321 // calculate where the next 2 identical pieces go, (any empty square), and put it there\r
4322 {\r
4323         int i, n=squaresLeft[ANY], j=n-1, k;\r
4324 \r
4325         k = n*(n-1)/2; // nr of possibilities, not counting permutations\r
4326         i = seed % k;  // pick one\r
4327         nrOfShuffles *= k;\r
4328         seed /= k;\r
4329         while(i >= j) i -= j--;\r
4330         j = n - 1 - j; i += j;\r
4331         put(board, pieceType, rank, j, ANY);\r
4332         put(board, pieceType, rank, i, ANY);\r
4333 }\r
4334 \r
4335 void SetUpShuffle(Board board, int number)\r
4336 {\r
4337         int i, p, first=1;\r
4338 \r
4339         GetPositionNumber(); nrOfShuffles = 1;\r
4340 \r
4341         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;\r
4342         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;\r
4343         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];\r
4344 \r
4345         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;\r
4346 \r
4347         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board\r
4348             p = (int) board[0][i];\r
4349             if(p < (int) BlackPawn) piecesLeft[p] ++;\r
4350             board[0][i] = EmptySquare;\r
4351         }\r
4352 \r
4353         if(PosFlags(0) & F_ALL_CASTLE_OK) {\r
4354             // shuffles restricted to allow normal castling put KRR first\r
4355             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle\r
4356                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4357             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles\r
4358                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4359             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling\r
4360                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);\r
4361             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling\r
4362                 put(board, WhiteRook, 0, 0, ANY);\r
4363             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle\r
4364         }\r
4365 \r
4366         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)\r
4367             // only for even boards make effort to put pairs of colorbound pieces on opposite colors\r
4368             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {\r
4369                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;\r
4370                 while(piecesLeft[p] >= 2) {\r
4371                     AddOnePiece(board, p, 0, LITE);\r
4372                     AddOnePiece(board, p, 0, DARK);\r
4373                 }\r
4374                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)\r
4375             }\r
4376 \r
4377         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {\r
4378             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere\r
4379             // but we leave King and Rooks for last, to possibly obey FRC restriction\r
4380             if(p == (int)WhiteRook) continue;\r
4381             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations\r
4382             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece\r
4383         }\r
4384 \r
4385         // now everything is placed, except perhaps King (Unicorn) and Rooks\r
4386 \r
4387         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {\r
4388             // Last King gets castling rights\r
4389             while(piecesLeft[(int)WhiteUnicorn]) {\r
4390                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4391                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4392             }\r
4393 \r
4394             while(piecesLeft[(int)WhiteKing]) {\r
4395                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4396                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4397             }\r
4398 \r
4399 \r
4400         } else {\r
4401             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);\r
4402             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);\r
4403         }\r
4404 \r
4405         // Only Rooks can be left; simply place them all\r
4406         while(piecesLeft[(int)WhiteRook]) {\r
4407                 i = put(board, WhiteRook, 0, 0, ANY);\r
4408                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights\r
4409                         if(first) {\r
4410                                 first=0;\r
4411                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;\r
4412                         }\r
4413                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;\r
4414                 }\r
4415         }\r
4416         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white\r
4417             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;\r
4418         }\r
4419 \r
4420         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize\r
4421 }\r
4422 \r
4423 int SetCharTable( char *table, const char * map )\r
4424 /* [HGM] moved here from winboard.c because of its general usefulness */\r
4425 /*       Basically a safe strcpy that uses the last character as King */\r
4426 {\r
4427     int result = FALSE; int NrPieces;\r
4428 \r
4429     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
4430                     && NrPieces >= 12 && !(NrPieces&1)) {\r
4431         int i; /* [HGM] Accept even length from 12 to 34 */\r
4432 \r
4433         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
4434         for( i=0; i<NrPieces/2-1; i++ ) {\r
4435             table[i] = map[i];\r
4436             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
4437         }\r
4438         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
4439         table[(int) BlackKing]  = map[NrPieces-1];\r
4440 \r
4441         result = TRUE;\r
4442     }\r
4443 \r
4444     return result;\r
4445 }\r
4446 \r
4447 void Prelude(Board board)\r
4448 {       // [HGM] superchess: random selection of exo-pieces\r
4449         int i, j, k; ChessSquare p; \r
4450         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };\r
4451 \r
4452         GetPositionNumber(); // use FRC position number\r
4453 \r
4454         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table\r
4455             SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4456             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) \r
4457                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;\r
4458         }\r
4459 \r
4460         j = seed%4;                 seed /= 4; \r
4461         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4462         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4463         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4464         j = seed%3 + (seed%3 >= j); seed /= 3; \r
4465         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4466         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4467         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4468         j = seed%3;                 seed /= 3; \r
4469         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4470         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4471         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4472         j = seed%2 + (seed%2 >= j); seed /= 2; \r
4473         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4474         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4475         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4476         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);\r
4477         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);\r
4478         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);\r
4479         put(board, exoPieces[0],    0, 0, ANY);\r
4480         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];\r
4481 }\r
4482 \r
4483 void\r
4484 InitPosition(redraw)\r
4485      int redraw;\r
4486 {\r
4487     ChessSquare (* pieces)[BOARD_SIZE];\r
4488     int i, j, pawnRow, overrule,\r
4489     oldx = gameInfo.boardWidth,\r
4490     oldy = gameInfo.boardHeight,\r
4491     oldh = gameInfo.holdingsWidth,\r
4492     oldv = gameInfo.variant;\r
4493 \r
4494     currentMove = forwardMostMove = backwardMostMove = 0;\r
4495     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request\r
4496 \r
4497     /* [AS] Initialize pv info list [HGM] and game status */\r
4498     {\r
4499         for( i=0; i<MAX_MOVES; i++ ) {\r
4500             pvInfoList[i].depth = 0;\r
4501             epStatus[i]=EP_NONE;\r
4502             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
4503         }\r
4504 \r
4505         initialRulePlies = 0; /* 50-move counter start */\r
4506 \r
4507         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4508         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4509     }\r
4510 \r
4511     \r
4512     /* [HGM] logic here is completely changed. In stead of full positions */\r
4513     /* the initialized data only consist of the two backranks. The switch */\r
4514     /* selects which one we will use, which is than copied to the Board   */\r
4515     /* initialPosition, which for the rest is initialized by Pawns and    */\r
4516     /* empty squares. This initial position is then copied to boards[0],  */\r
4517     /* possibly after shuffling, so that it remains available.            */\r
4518 \r
4519     gameInfo.holdingsWidth = 0; /* default board sizes */\r
4520     gameInfo.boardWidth    = 8;\r
4521     gameInfo.boardHeight   = 8;\r
4522     gameInfo.holdingsSize  = 0;\r
4523     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
4524     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
4525     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
4526 \r
4527     switch (gameInfo.variant) {\r
4528     case VariantFischeRandom:\r
4529       shuffleOpenings = TRUE;\r
4530     default:\r
4531       pieces = FIDEArray;\r
4532       break;\r
4533     case VariantShatranj:\r
4534       pieces = ShatranjArray;\r
4535       nrCastlingRights = 0;\r
4536       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
4537       break;\r
4538     case VariantTwoKings:\r
4539       pieces = twoKingsArray;\r
4540       break;\r
4541     case VariantCapaRandom:\r
4542       shuffleOpenings = TRUE;\r
4543     case VariantCapablanca:\r
4544       pieces = CapablancaArray;\r
4545       gameInfo.boardWidth = 10;\r
4546       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4547       break;\r
4548     case VariantGothic:\r
4549       pieces = GothicArray;\r
4550       gameInfo.boardWidth = 10;\r
4551       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4552       break;\r
4553     case VariantJanus:\r
4554       pieces = JanusArray;\r
4555       gameInfo.boardWidth = 10;\r
4556       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
4557       nrCastlingRights = 6;\r
4558         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4559         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4560         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;\r
4561         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4562         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4563         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;\r
4564       break;\r
4565     case VariantFalcon:\r
4566       pieces = FalconArray;\r
4567       gameInfo.boardWidth = 10;\r
4568       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4569       break;\r
4570     case VariantXiangqi:\r
4571       pieces = XiangqiArray;\r
4572       gameInfo.boardWidth  = 9;\r
4573       gameInfo.boardHeight = 10;\r
4574       nrCastlingRights = 0;\r
4575       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4576       break;\r
4577     case VariantShogi:\r
4578       pieces = ShogiArray;\r
4579       gameInfo.boardWidth  = 9;\r
4580       gameInfo.boardHeight = 9;\r
4581       gameInfo.holdingsSize = 7;\r
4582       nrCastlingRights = 0;\r
4583       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4584       break;\r
4585     case VariantCourier:\r
4586       pieces = CourierArray;\r
4587       gameInfo.boardWidth  = 12;\r
4588       nrCastlingRights = 0;\r
4589       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4590       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4591       break;\r
4592     case VariantKnightmate:\r
4593       pieces = KnightmateArray;\r
4594       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4595       break;\r
4596     case VariantFairy:\r
4597       pieces = fairyArray;\r
4598       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4599       break;\r
4600     case VariantGreat:\r
4601       pieces = GreatArray;\r
4602       gameInfo.boardWidth = 10;\r
4603       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");\r
4604       gameInfo.holdingsSize = 8;\r
4605       break;\r
4606     case VariantSuper:\r
4607       pieces = FIDEArray;\r
4608       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
4609       gameInfo.holdingsSize = 8;\r
4610       startedFromSetupPosition = TRUE;\r
4611       break;\r
4612     case VariantCrazyhouse:\r
4613     case VariantBughouse:\r
4614       pieces = FIDEArray;\r
4615       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4616       gameInfo.holdingsSize = 5;\r
4617       break;\r
4618     case VariantWildCastle:\r
4619       pieces = FIDEArray;\r
4620       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4621       shuffleOpenings = 1;\r
4622       break;\r
4623     case VariantNoCastle:\r
4624       pieces = FIDEArray;\r
4625       nrCastlingRights = 0;\r
4626       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4627       /* !!?unconstrained back-rank shuffle */\r
4628       shuffleOpenings = 1;\r
4629       break;\r
4630     }\r
4631 \r
4632     overrule = 0;\r
4633     if(appData.NrFiles >= 0) {\r
4634         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4635         gameInfo.boardWidth = appData.NrFiles;\r
4636     }\r
4637     if(appData.NrRanks >= 0) {\r
4638         gameInfo.boardHeight = appData.NrRanks;\r
4639     }\r
4640     if(appData.holdingsSize >= 0) {\r
4641         i = appData.holdingsSize;\r
4642         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4643         gameInfo.holdingsSize = i;\r
4644     }\r
4645     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4646     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4647         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);\r
4648 \r
4649     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4650     if(pawnRow < 1) pawnRow = 1;\r
4651 \r
4652     /* User pieceToChar list overrules defaults */\r
4653     if(appData.pieceToCharTable != NULL)\r
4654         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4655 \r
4656     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4657 \r
4658         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4659             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4660         for( i=0; i<BOARD_HEIGHT; i++ )\r
4661             initialPosition[i][j] = s;\r
4662 \r
4663         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4664         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4665         initialPosition[pawnRow][j] = WhitePawn;\r
4666         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4667         if(gameInfo.variant == VariantXiangqi) {\r
4668             if(j&1) {\r
4669                 initialPosition[pawnRow][j] = \r
4670                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4671                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4672                    initialPosition[2][j] = WhiteCannon;\r
4673                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4674                 }\r
4675             }\r
4676         }\r
4677         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4678     }\r
4679     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4680 \r
4681             j=BOARD_LEFT+1;\r
4682             initialPosition[1][j] = WhiteBishop;\r
4683             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4684             j=BOARD_RGHT-2;\r
4685             initialPosition[1][j] = WhiteRook;\r
4686             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4687     }\r
4688 \r
4689     if( nrCastlingRights == -1) {\r
4690         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4691         /*       This sets default castling rights from none to normal corners   */\r
4692         /* Variants with other castling rights must set them themselves above    */\r
4693         nrCastlingRights = 6;\r
4694        \r
4695         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4696         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4697         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4698         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4699         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4700         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4701      }\r
4702 \r
4703      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
4704      if(gameInfo.variant == VariantGreat) { // promotion commoners\r
4705         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;\r
4706         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;\r
4707         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;\r
4708         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;\r
4709      }\r
4710 #if 0\r
4711     if(gameInfo.variant == VariantFischeRandom) {\r
4712       if( appData.defaultFrcPosition < 0 ) {\r
4713         ShuffleFRC( initialPosition );\r
4714       }\r
4715       else {\r
4716         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4717       }\r
4718       startedFromSetupPosition = TRUE;\r
4719     } else \r
4720 #else\r
4721   if (appData.debugMode) {\r
4722     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
4723   }\r
4724     if(shuffleOpenings) {\r
4725         SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
4726         startedFromSetupPosition = TRUE;\r
4727     }\r
4728 #endif\r
4729     if(startedFromPositionFile) {\r
4730       /* [HGM] loadPos: use PositionFile for every new game */\r
4731       CopyBoard(initialPosition, filePosition);\r
4732       for(i=0; i<nrCastlingRights; i++)\r
4733           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4734       startedFromSetupPosition = TRUE;\r
4735     }\r
4736 \r
4737     CopyBoard(boards[0], initialPosition);\r
4738 \r
4739     if(oldx != gameInfo.boardWidth ||\r
4740        oldy != gameInfo.boardHeight ||\r
4741        oldh != gameInfo.holdingsWidth\r
4742 #ifdef GOTHIC\r
4743        || oldv == VariantGothic ||        // For licensing popups\r
4744        gameInfo.variant == VariantGothic\r
4745 #endif\r
4746 #ifdef FALCON\r
4747        || oldv == VariantFalcon ||\r
4748        gameInfo.variant == VariantFalcon\r
4749 #endif\r
4750                                          )\r
4751             InitDrawingSizes(-2 ,0);\r
4752 \r
4753     if (redraw)\r
4754       DrawPosition(TRUE, boards[currentMove]);\r
4755 }\r
4756 \r
4757 void\r
4758 SendBoard(cps, moveNum)\r
4759      ChessProgramState *cps;\r
4760      int moveNum;\r
4761 {\r
4762     char message[MSG_SIZ];\r
4763     \r
4764     if (cps->useSetboard) {\r
4765       char* fen = PositionToFEN(moveNum, cps->fenOverride);\r
4766       sprintf(message, "setboard %s\n", fen);\r
4767       SendToProgram(message, cps);\r
4768       free(fen);\r
4769 \r
4770     } else {\r
4771       ChessSquare *bp;\r
4772       int i, j;\r
4773       /* Kludge to set black to move, avoiding the troublesome and now\r
4774        * deprecated "black" command.\r
4775        */\r
4776       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4777 \r
4778       SendToProgram("edit\n", cps);\r
4779       SendToProgram("#\n", cps);\r
4780       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4781         bp = &boards[moveNum][i][BOARD_LEFT];\r
4782         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4783           if ((int) *bp < (int) BlackPawn) {\r
4784             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4785                     AAA + j, ONE + i);\r
4786             if(message[0] == '+' || message[0] == '~') {\r
4787                 sprintf(message, "%c%c%c+\n",\r
4788                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4789                         AAA + j, ONE + i);\r
4790             }\r
4791             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4792                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4793                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4794             }\r
4795             SendToProgram(message, cps);\r
4796           }\r
4797         }\r
4798       }\r
4799     \r
4800       SendToProgram("c\n", cps);\r
4801       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4802         bp = &boards[moveNum][i][BOARD_LEFT];\r
4803         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4804           if (((int) *bp != (int) EmptySquare)\r
4805               && ((int) *bp >= (int) BlackPawn)) {\r
4806             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4807                     AAA + j, ONE + i);\r
4808             if(message[0] == '+' || message[0] == '~') {\r
4809                 sprintf(message, "%c%c%c+\n",\r
4810                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4811                         AAA + j, ONE + i);\r
4812             }\r
4813             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4814                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4815                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4816             }\r
4817             SendToProgram(message, cps);\r
4818           }\r
4819         }\r
4820       }\r
4821     \r
4822       SendToProgram(".\n", cps);\r
4823     }\r
4824     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4825 }\r
4826 \r
4827 int\r
4828 IsPromotion(fromX, fromY, toX, toY)\r
4829      int fromX, fromY, toX, toY;\r
4830 {\r
4831     /* [HGM] add Shogi promotions */\r
4832     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4833     ChessSquare piece;\r
4834 \r
4835     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4836       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4837    /* [HGM] Note to self: line above also weeds out drops */\r
4838     piece = boards[currentMove][fromY][fromX];\r
4839     if(gameInfo.variant == VariantShogi) {\r
4840         promotionZoneSize = 3;\r
4841         highestPromotingPiece = (int)WhiteKing;\r
4842         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4843            and if in normal chess we then allow promotion to King, why not\r
4844            allow promotion of other piece in Shogi?                         */\r
4845     }\r
4846     if((int)piece >= BlackPawn) {\r
4847         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4848              return FALSE;\r
4849         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4850     } else {\r
4851         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4852            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4853     }\r
4854     return ( (int)piece <= highestPromotingPiece );\r
4855 }\r
4856 \r
4857 int\r
4858 InPalace(row, column)\r
4859      int row, column;\r
4860 {   /* [HGM] for Xiangqi */\r
4861     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4862          column < (BOARD_WIDTH + 4)/2 &&\r
4863          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4864     return FALSE;\r
4865 }\r
4866 \r
4867 int\r
4868 PieceForSquare (x, y)\r
4869      int x;\r
4870      int y;\r
4871 {\r
4872   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
4873      return -1;\r
4874   else\r
4875      return boards[currentMove][y][x];\r
4876 }\r
4877 \r
4878 int\r
4879 OKToStartUserMove(x, y)\r
4880      int x, y;\r
4881 {\r
4882     ChessSquare from_piece;\r
4883     int white_piece;\r
4884 \r
4885     if (matchMode) return FALSE;\r
4886     if (gameMode == EditPosition) return TRUE;\r
4887 \r
4888     if (x >= 0 && y >= 0)\r
4889       from_piece = boards[currentMove][y][x];\r
4890     else\r
4891       from_piece = EmptySquare;\r
4892 \r
4893     if (from_piece == EmptySquare) return FALSE;\r
4894 \r
4895     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4896       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4897 \r
4898     switch (gameMode) {\r
4899       case PlayFromGameFile:\r
4900       case AnalyzeFile:\r
4901       case TwoMachinesPlay:\r
4902       case EndOfGame:\r
4903         return FALSE;\r
4904 \r
4905       case IcsObserving:\r
4906       case IcsIdle:\r
4907         return FALSE;\r
4908 \r
4909       case MachinePlaysWhite:\r
4910       case IcsPlayingBlack:\r
4911         if (appData.zippyPlay) return FALSE;\r
4912         if (white_piece) {\r
4913             DisplayMoveError(_("You are playing Black"));\r
4914             return FALSE;\r
4915         }\r
4916         break;\r
4917 \r
4918       case MachinePlaysBlack:\r
4919       case IcsPlayingWhite:\r
4920         if (appData.zippyPlay) return FALSE;\r
4921         if (!white_piece) {\r
4922             DisplayMoveError(_("You are playing White"));\r
4923             return FALSE;\r
4924         }\r
4925         break;\r
4926 \r
4927       case EditGame:\r
4928         if (!white_piece && WhiteOnMove(currentMove)) {\r
4929             DisplayMoveError(_("It is White's turn"));\r
4930             return FALSE;\r
4931         }           \r
4932         if (white_piece && !WhiteOnMove(currentMove)) {\r
4933             DisplayMoveError(_("It is Black's turn"));\r
4934             return FALSE;\r
4935         }           \r
4936         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
4937             /* Editing correspondence game history */\r
4938             /* Could disallow this or prompt for confirmation */\r
4939             cmailOldMove = -1;\r
4940         }\r
4941         if (currentMove < forwardMostMove) {\r
4942             /* Discarding moves */\r
4943             /* Could prompt for confirmation here,\r
4944                but I don't think that's such a good idea */\r
4945             forwardMostMove = currentMove;\r
4946         }\r
4947         break;\r
4948 \r
4949       case BeginningOfGame:\r
4950         if (appData.icsActive) return FALSE;\r
4951         if (!appData.noChessProgram) {\r
4952             if (!white_piece) {\r
4953                 DisplayMoveError(_("You are playing White"));\r
4954                 return FALSE;\r
4955             }\r
4956         }\r
4957         break;\r
4958         \r
4959       case Training:\r
4960         if (!white_piece && WhiteOnMove(currentMove)) {\r
4961             DisplayMoveError(_("It is White's turn"));\r
4962             return FALSE;\r
4963         }           \r
4964         if (white_piece && !WhiteOnMove(currentMove)) {\r
4965             DisplayMoveError(_("It is Black's turn"));\r
4966             return FALSE;\r
4967         }           \r
4968         break;\r
4969 \r
4970       default:\r
4971       case IcsExamining:\r
4972         break;\r
4973     }\r
4974     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
4975         && gameMode != AnalyzeFile && gameMode != Training) {\r
4976         DisplayMoveError(_("Displayed position is not current"));\r
4977         return FALSE;\r
4978     }\r
4979     return TRUE;\r
4980 }\r
4981 \r
4982 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
4983 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
4984 int lastLoadGameUseList = FALSE;\r
4985 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
4986 ChessMove lastLoadGameStart = (ChessMove) 0;\r
4987 \r
4988 \r
4989 ChessMove\r
4990 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
4991      int fromX, fromY, toX, toY;\r
4992      int promoChar;\r
4993 {\r
4994     ChessMove moveType;\r
4995     ChessSquare pdown, pup;\r
4996 \r
4997     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
4998     if ((fromX == toX) && (fromY == toY)) {\r
4999         return ImpossibleMove;\r
5000     }\r
5001 \r
5002     /* [HGM] suppress all moves into holdings area and guard band */\r
5003     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
5004             return ImpossibleMove;\r
5005 \r
5006     /* [HGM] <sameColor> moved to here from winboard.c */\r
5007     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
5008     pdown = boards[currentMove][fromY][fromX];\r
5009     pup = boards[currentMove][toY][toX];\r
5010     if (    gameMode != EditPosition &&\r
5011             (WhitePawn <= pdown && pdown < BlackPawn &&\r
5012              WhitePawn <= pup && pup < BlackPawn  ||\r
5013              BlackPawn <= pdown && pdown < EmptySquare &&\r
5014              BlackPawn <= pup && pup < EmptySquare \r
5015             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
5016                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
5017                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
5018         )           )\r
5019          return ImpossibleMove;\r
5020 \r
5021     /* Check if the user is playing in turn.  This is complicated because we\r
5022        let the user "pick up" a piece before it is his turn.  So the piece he\r
5023        tried to pick up may have been captured by the time he puts it down!\r
5024        Therefore we use the color the user is supposed to be playing in this\r
5025        test, not the color of the piece that is currently on the starting\r
5026        square---except in EditGame mode, where the user is playing both\r
5027        sides; fortunately there the capture race can't happen.  (It can\r
5028        now happen in IcsExamining mode, but that's just too bad.  The user\r
5029        will get a somewhat confusing message in that case.)\r
5030        */\r
5031 \r
5032     switch (gameMode) {\r
5033       case PlayFromGameFile:\r
5034       case AnalyzeFile:\r
5035       case TwoMachinesPlay:\r
5036       case EndOfGame:\r
5037       case IcsObserving:\r
5038       case IcsIdle:\r
5039         /* We switched into a game mode where moves are not accepted,\r
5040            perhaps while the mouse button was down. */\r
5041         return ImpossibleMove;\r
5042 \r
5043       case MachinePlaysWhite:\r
5044         /* User is moving for Black */\r
5045         if (WhiteOnMove(currentMove)) {\r
5046             DisplayMoveError(_("It is White's turn"));\r
5047             return ImpossibleMove;\r
5048         }\r
5049         break;\r
5050 \r
5051       case MachinePlaysBlack:\r
5052         /* User is moving for White */\r
5053         if (!WhiteOnMove(currentMove)) {\r
5054             DisplayMoveError(_("It is Black's turn"));\r
5055             return ImpossibleMove;\r
5056         }\r
5057         break;\r
5058 \r
5059       case EditGame:\r
5060       case IcsExamining:\r
5061       case BeginningOfGame:\r
5062       case AnalyzeMode:\r
5063       case Training:\r
5064         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
5065             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
5066             /* User is moving for Black */\r
5067             if (WhiteOnMove(currentMove)) {\r
5068                 DisplayMoveError(_("It is White's turn"));\r
5069                 return ImpossibleMove;\r
5070             }\r
5071         } else {\r
5072             /* User is moving for White */\r
5073             if (!WhiteOnMove(currentMove)) {\r
5074                 DisplayMoveError(_("It is Black's turn"));\r
5075                 return ImpossibleMove;\r
5076             }\r
5077         }\r
5078         break;\r
5079 \r
5080       case IcsPlayingBlack:\r
5081         /* User is moving for Black */\r
5082         if (WhiteOnMove(currentMove)) {\r
5083             if (!appData.premove) {\r
5084                 DisplayMoveError(_("It is White's turn"));\r
5085             } else if (toX >= 0 && toY >= 0) {\r
5086                 premoveToX = toX;\r
5087                 premoveToY = toY;\r
5088                 premoveFromX = fromX;\r
5089                 premoveFromY = fromY;\r
5090                 premovePromoChar = promoChar;\r
5091                 gotPremove = 1;\r
5092                 if (appData.debugMode) \r
5093                     fprintf(debugFP, "Got premove: fromX %d,"\r
5094                             "fromY %d, toX %d, toY %d\n",\r
5095                             fromX, fromY, toX, toY);\r
5096             }\r
5097             return ImpossibleMove;\r
5098         }\r
5099         break;\r
5100 \r
5101       case IcsPlayingWhite:\r
5102         /* User is moving for White */\r
5103         if (!WhiteOnMove(currentMove)) {\r
5104             if (!appData.premove) {\r
5105                 DisplayMoveError(_("It is Black's turn"));\r
5106             } else if (toX >= 0 && toY >= 0) {\r
5107                 premoveToX = toX;\r
5108                 premoveToY = toY;\r
5109                 premoveFromX = fromX;\r
5110                 premoveFromY = fromY;\r
5111                 premovePromoChar = promoChar;\r
5112                 gotPremove = 1;\r
5113                 if (appData.debugMode) \r
5114                     fprintf(debugFP, "Got premove: fromX %d,"\r
5115                             "fromY %d, toX %d, toY %d\n",\r
5116                             fromX, fromY, toX, toY);\r
5117             }\r
5118             return ImpossibleMove;\r
5119         }\r
5120         break;\r
5121 \r
5122       default:\r
5123         break;\r
5124 \r
5125       case EditPosition:\r
5126         /* EditPosition, empty square, or different color piece;\r
5127            click-click move is possible */\r
5128         if (toX == -2 || toY == -2) {\r
5129             boards[0][fromY][fromX] = EmptySquare;\r
5130             return AmbiguousMove;\r
5131         } else if (toX >= 0 && toY >= 0) {\r
5132             boards[0][toY][toX] = boards[0][fromY][fromX];\r
5133             boards[0][fromY][fromX] = EmptySquare;\r
5134             return AmbiguousMove;\r
5135         }\r
5136         return ImpossibleMove;\r
5137     }\r
5138 \r
5139     /* [HGM] If move started in holdings, it means a drop */\r
5140     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
5141          if( pup != EmptySquare ) return ImpossibleMove;\r
5142          if(appData.testLegality) {\r
5143              /* it would be more logical if LegalityTest() also figured out\r
5144               * which drops are legal. For now we forbid pawns on back rank.\r
5145               * Shogi is on its own here...\r
5146               */\r
5147              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
5148                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
5149                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
5150          }\r
5151          return WhiteDrop; /* Not needed to specify white or black yet */\r
5152     }\r
5153 \r
5154     userOfferedDraw = FALSE;\r
5155         \r
5156     /* [HGM] always test for legality, to get promotion info */\r
5157     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
5158                           epStatus[currentMove], castlingRights[currentMove],\r
5159                                          fromY, fromX, toY, toX, promoChar);\r
5160 \r
5161     /* [HGM] but possibly ignore an IllegalMove result */\r
5162     if (appData.testLegality) {\r
5163         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
5164             DisplayMoveError(_("Illegal move"));\r
5165             return ImpossibleMove;\r
5166         }\r
5167     }\r
5168 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);\r
5169     return moveType;\r
5170     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
5171        function is made into one that returns an OK move type if FinishMove\r
5172        should be called. This to give the calling driver routine the\r
5173        opportunity to finish the userMove input with a promotion popup,\r
5174        without bothering the user with this for invalid or illegal moves */\r
5175 \r
5176 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
5177 }\r
5178 \r
5179 /* Common tail of UserMoveEvent and DropMenuEvent */\r
5180 int\r
5181 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
5182      ChessMove moveType;\r
5183      int fromX, fromY, toX, toY;\r
5184      /*char*/int promoChar;\r
5185 {\r
5186     char *bookHit = 0;\r
5187 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);\r
5188     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { \r
5189         // [HGM] superchess: suppress promotions to non-available piece\r
5190         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5191         if(WhiteOnMove(currentMove)) {\r
5192             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;\r
5193         } else {\r
5194             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;\r
5195         }\r
5196     }\r
5197 \r
5198     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion\r
5199        move type in caller when we know the move is a legal promotion */\r
5200     if(moveType == NormalMove && promoChar)\r
5201         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
5202 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);\r
5203     /* [HGM] convert drag-and-drop piece drops to standard form */\r
5204     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
5205          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
5206          fromX = boards[currentMove][fromY][fromX];\r
5207          fromY = DROP_RANK;\r
5208     }\r
5209 \r
5210     /* [HGM] <popupFix> The following if has been moved here from\r
5211        UserMoveEvent(). Because it seemed to belon here (why not allow\r
5212        piece drops in training games?), and because it can only be\r
5213        performed after it is known to what we promote. */\r
5214     if (gameMode == Training) {\r
5215       /* compare the move played on the board to the next move in the\r
5216        * game. If they match, display the move and the opponent's response. \r
5217        * If they don't match, display an error message.\r
5218        */\r
5219       int saveAnimate;\r
5220       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;\r
5221       CopyBoard(testBoard, boards[currentMove]);\r
5222       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);\r
5223 \r
5224       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
5225         ForwardInner(currentMove+1);\r
5226 \r
5227         /* Autoplay the opponent's response.\r
5228          * if appData.animate was TRUE when Training mode was entered,\r
5229          * the response will be animated.\r
5230          */\r
5231         saveAnimate = appData.animate;\r
5232         appData.animate = animateTraining;\r
5233         ForwardInner(currentMove+1);\r
5234         appData.animate = saveAnimate;\r
5235 \r
5236         /* check for the end of the game */\r
5237         if (currentMove >= forwardMostMove) {\r
5238           gameMode = PlayFromGameFile;\r
5239           ModeHighlight();\r
5240           SetTrainingModeOff();\r
5241           DisplayInformation(_("End of game"));\r
5242         }\r
5243       } else {\r
5244         DisplayError(_("Incorrect move"), 0);\r
5245       }\r
5246       return 1;\r
5247     }\r
5248 \r
5249   /* Ok, now we know that the move is good, so we can kill\r
5250      the previous line in Analysis Mode */\r
5251   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
5252     forwardMostMove = currentMove;\r
5253   }\r
5254 \r
5255   /* If we need the chess program but it's dead, restart it */\r
5256   ResurrectChessProgram();\r
5257 \r
5258   /* A user move restarts a paused game*/\r
5259   if (pausing)\r
5260     PauseEvent();\r
5261 \r
5262   thinkOutput[0] = NULLCHAR;\r
5263 \r
5264   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
5265 \r
5266   if (gameMode == BeginningOfGame) {\r
5267     if (appData.noChessProgram) {\r
5268       gameMode = EditGame;\r
5269       SetGameInfo();\r
5270     } else {\r
5271       char buf[MSG_SIZ];\r
5272       gameMode = MachinePlaysBlack;\r
5273       StartClocks();\r
5274       SetGameInfo();\r
5275       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
5276       DisplayTitle(buf);\r
5277       if (first.sendName) {\r
5278         sprintf(buf, "name %s\n", gameInfo.white);\r
5279         SendToProgram(buf, &first);\r
5280       }\r
5281       StartClocks();\r
5282     }\r
5283     ModeHighlight();\r
5284   }\r
5285 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);\r
5286   /* Relay move to ICS or chess engine */\r
5287   if (appData.icsActive) {\r
5288     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
5289         gameMode == IcsExamining) {\r
5290       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5291       ics_user_moved = 1;\r
5292     }\r
5293   } else {\r
5294     if (first.sendTime && (gameMode == BeginningOfGame ||\r
5295                            gameMode == MachinePlaysWhite ||\r
5296                            gameMode == MachinePlaysBlack)) {\r
5297       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
5298     }\r
5299     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
5300          // [HGM] book: if program might be playing, let it use book\r
5301         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);\r
5302         first.maybeThinking = TRUE;\r
5303     } else SendMoveToProgram(forwardMostMove-1, &first);\r
5304     if (currentMove == cmailOldMove + 1) {\r
5305       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
5306     }\r
5307   }\r
5308 \r
5309   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5310 \r
5311   switch (gameMode) {\r
5312   case EditGame:\r
5313     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
5314                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
5315     case MT_NONE:\r
5316     case MT_CHECK:\r
5317       break;\r
5318     case MT_CHECKMATE:\r
5319     case MT_STAINMATE:\r
5320       if (WhiteOnMove(currentMove)) {\r
5321         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
5322       } else {\r
5323         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
5324       }\r
5325       break;\r
5326     case MT_STALEMATE:\r
5327       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
5328       break;\r
5329     }\r
5330     break;\r
5331     \r
5332   case MachinePlaysBlack:\r
5333   case MachinePlaysWhite:\r
5334     /* disable certain menu options while machine is thinking */\r
5335     SetMachineThinkingEnables();\r
5336     break;\r
5337 \r
5338   default:\r
5339     break;\r
5340   }\r
5341 \r
5342   if(bookHit) { // [HGM] book: simulate book reply\r
5343         static char bookMove[MSG_SIZ]; // a bit generous?\r
5344 \r
5345         programStats.nodes = programStats.depth = programStats.time = \r
5346         programStats.score = programStats.got_only_move = 0;\r
5347         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
5348 \r
5349         strcpy(bookMove, "move ");\r
5350         strcat(bookMove, bookHit);\r
5351         HandleMachineMove(bookMove, &first);\r
5352   }\r
5353   return 1;\r
5354 }\r
5355 \r
5356 void\r
5357 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
5358      int fromX, fromY, toX, toY;\r
5359      int promoChar;\r
5360 {\r
5361     /* [HGM] This routine was added to allow calling of its two logical\r
5362        parts from other modules in the old way. Before, UserMoveEvent()\r
5363        automatically called FinishMove() if the move was OK, and returned\r
5364        otherwise. I separated the two, in order to make it possible to\r
5365        slip a promotion popup in between. But that it always needs two\r
5366        calls, to the first part, (now called UserMoveTest() ), and to\r
5367        FinishMove if the first part succeeded. Calls that do not need\r
5368        to do anything in between, can call this routine the old way. \r
5369     */\r
5370     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
5371 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);\r
5372     if(moveType != ImpossibleMove)\r
5373         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
5374 }\r
5375 \r
5376 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
5377 {\r
5378 //    char * hint = lastHint;\r
5379     FrontEndProgramStats stats;\r
5380 \r
5381     stats.which = cps == &first ? 0 : 1;\r
5382     stats.depth = cpstats->depth;\r
5383     stats.nodes = cpstats->nodes;\r
5384     stats.score = cpstats->score;\r
5385     stats.time = cpstats->time;\r
5386     stats.pv = cpstats->movelist;\r
5387     stats.hint = lastHint;\r
5388     stats.an_move_index = 0;\r
5389     stats.an_move_count = 0;\r
5390 \r
5391     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
5392         stats.hint = cpstats->move_name;\r
5393         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
5394         stats.an_move_count = cpstats->nr_moves;\r
5395     }\r
5396 \r
5397     SetProgramStats( &stats );\r
5398 }\r
5399 \r
5400 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)\r
5401 {   // [HGM] book: this routine intercepts moves to simulate book replies\r
5402     char *bookHit = NULL;\r
5403 \r
5404     //first determine if the incoming move brings opponent into his book\r
5405     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
5406         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
5407     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");\r
5408     if(bookHit != NULL && !cps->bookSuspend) {\r
5409         // make sure opponent is not going to reply after receiving move to book position\r
5410         SendToProgram("force\n", cps);\r
5411         cps->bookSuspend = TRUE; // flag indicating it has to be restarted\r
5412     }\r
5413     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move\r
5414     // now arrange restart after book miss\r
5415     if(bookHit) {\r
5416         // after a book hit we never send 'go', and the code after the call to this routine\r
5417         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').\r
5418         char buf[MSG_SIZ];\r
5419         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(\r
5420         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it\r
5421         SendToProgram(buf, cps);\r
5422         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'\r
5423     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine\r
5424         SendToProgram("go\n", cps);\r
5425         cps->bookSuspend = FALSE; // after a 'go' we are never suspended\r
5426     } else { // 'go' might be sent based on 'firstMove' after this routine returns\r
5427         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return\r
5428             SendToProgram("go\n", cps); \r
5429         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss\r
5430     }\r
5431     return bookHit; // notify caller of hit, so it can take action to send move to opponent\r
5432 }\r
5433 \r
5434 char *savedMessage;\r
5435 ChessProgramState *savedState;\r
5436 void DeferredBookMove(void)\r
5437 {\r
5438         if(savedState->lastPing != savedState->lastPong)\r
5439                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
5440         else\r
5441         HandleMachineMove(savedMessage, savedState);\r
5442 }\r
5443 \r
5444 void\r
5445 HandleMachineMove(message, cps)\r
5446      char *message;\r
5447      ChessProgramState *cps;\r
5448 {\r
5449     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
5450     char realname[MSG_SIZ];\r
5451     int fromX, fromY, toX, toY;\r
5452     ChessMove moveType;\r
5453     char promoChar;\r
5454     char *p;\r
5455     int machineWhite;\r
5456     char *bookHit;\r
5457 \r
5458 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit\r
5459     /*\r
5460      * Kludge to ignore BEL characters\r
5461      */\r
5462     while (*message == '\007') message++;\r
5463 \r
5464     /*\r
5465      * [HGM] engine debug message: ignore lines starting with '#' character\r
5466      */\r
5467     if(cps->debug && *message == '#') return;\r
5468 \r
5469     /*\r
5470      * Look for book output\r
5471      */\r
5472     if (cps == &first && bookRequested) {\r
5473         if (message[0] == '\t' || message[0] == ' ') {\r
5474             /* Part of the book output is here; append it */\r
5475             strcat(bookOutput, message);\r
5476             strcat(bookOutput, "  \n");\r
5477             return;\r
5478         } else if (bookOutput[0] != NULLCHAR) {\r
5479             /* All of book output has arrived; display it */\r
5480             char *p = bookOutput;\r
5481             while (*p != NULLCHAR) {\r
5482                 if (*p == '\t') *p = ' ';\r
5483                 p++;\r
5484             }\r
5485             DisplayInformation(bookOutput);\r
5486             bookRequested = FALSE;\r
5487             /* Fall through to parse the current output */\r
5488         }\r
5489     }\r
5490 \r
5491     /*\r
5492      * Look for machine move.\r
5493      */\r
5494     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
5495         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
5496     {\r
5497         /* This method is only useful on engines that support ping */\r
5498         if (cps->lastPing != cps->lastPong) {\r
5499           if (gameMode == BeginningOfGame) {\r
5500             /* Extra move from before last new; ignore */\r
5501             if (appData.debugMode) {\r
5502                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5503             }\r
5504           } else {\r
5505             if (appData.debugMode) {\r
5506                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5507                         cps->which, gameMode);\r
5508             }\r
5509 \r
5510             SendToProgram("undo\n", cps);\r
5511           }\r
5512           return;\r
5513         }\r
5514 \r
5515         switch (gameMode) {\r
5516           case BeginningOfGame:\r
5517             /* Extra move from before last reset; ignore */\r
5518             if (appData.debugMode) {\r
5519                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5520             }\r
5521             return;\r
5522 \r
5523           case EndOfGame:\r
5524           case IcsIdle:\r
5525           default:\r
5526             /* Extra move after we tried to stop.  The mode test is\r
5527                not a reliable way of detecting this problem, but it's\r
5528                the best we can do on engines that don't support ping.\r
5529             */\r
5530             if (appData.debugMode) {\r
5531                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5532                         cps->which, gameMode);\r
5533             }\r
5534             SendToProgram("undo\n", cps);\r
5535             return;\r
5536 \r
5537           case MachinePlaysWhite:\r
5538           case IcsPlayingWhite:\r
5539             machineWhite = TRUE;\r
5540             break;\r
5541 \r
5542           case MachinePlaysBlack:\r
5543           case IcsPlayingBlack:\r
5544             machineWhite = FALSE;\r
5545             break;\r
5546 \r
5547           case TwoMachinesPlay:\r
5548             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5549             break;\r
5550         }\r
5551         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5552             if (appData.debugMode) {\r
5553                 fprintf(debugFP,\r
5554                         "Ignoring move out of turn by %s, gameMode %d"\r
5555                         ", forwardMost %d\n",\r
5556                         cps->which, gameMode, forwardMostMove);\r
5557             }\r
5558             return;\r
5559         }\r
5560 \r
5561     if (appData.debugMode) { int f = forwardMostMove;\r
5562         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5563                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5564     }\r
5565         if(cps->alphaRank) AlphaRank(machineMove, 4);\r
5566         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5567                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5568             /* Machine move could not be parsed; ignore it. */\r
5569             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),\r
5570                     machineMove, cps->which);\r
5571             DisplayError(buf1, 0);\r
5572             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",\r
5573                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);\r
5574             if (gameMode == TwoMachinesPlay) {\r
5575               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5576                        buf1, GE_XBOARD);\r
5577             }\r
5578             return;\r
5579         }\r
5580 \r
5581         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5582         /* So we have to redo legality test with true e.p. status here,  */\r
5583         /* to make sure an illegal e.p. capture does not slip through,   */\r
5584         /* to cause a forfeit on a justified illegal-move complaint      */\r
5585         /* of the opponent.                                              */\r
5586         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5587             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5588                                                               ) {\r
5589            ChessMove moveType;\r
5590            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5591                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5592                              fromY, fromX, toY, toX, promoChar);\r
5593             if (appData.debugMode) {\r
5594                 int i;\r
5595                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5596                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5597                 fprintf(debugFP, "castling rights\n");\r
5598             }\r
5599             if(moveType == IllegalMove) {\r
5600                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5601                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5602                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5603                            buf1, GE_XBOARD);\r
5604                 return;\r
5605            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
5606            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5607               when they shouldn't (like TSCP-Gothic) */\r
5608            switch(moveType) {\r
5609              case WhiteASideCastleFR:\r
5610              case BlackASideCastleFR:\r
5611                toX+=2;\r
5612                currentMoveString[2]++;\r
5613                break;\r
5614              case WhiteHSideCastleFR:\r
5615              case BlackHSideCastleFR:\r
5616                toX--;\r
5617                currentMoveString[2]--;\r
5618                break;\r
5619              default: ; // nothing to do, but suppresses warning of pedantic compilers\r
5620            }\r
5621         }\r
5622         hintRequested = FALSE;\r
5623         lastHint[0] = NULLCHAR;\r
5624         bookRequested = FALSE;\r
5625         /* Program may be pondering now */\r
5626         cps->maybeThinking = TRUE;\r
5627         if (cps->sendTime == 2) cps->sendTime = 1;\r
5628         if (cps->offeredDraw) cps->offeredDraw--;\r
5629 \r
5630 #if ZIPPY\r
5631         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5632             first.initDone) {\r
5633           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5634           ics_user_moved = 1;\r
5635           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
5636                 char buf[3*MSG_SIZ];\r
5637 \r
5638                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",\r
5639                         programStats.score / 100.,\r
5640                         programStats.depth,\r
5641                         programStats.time / 100.,\r
5642                         u64ToDouble(programStats.nodes),\r
5643                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),\r
5644                         programStats.movelist);\r
5645                 SendToICS(buf);\r
5646           }\r
5647         }\r
5648 #endif\r
5649         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5650         strcpy(machineMove, currentMoveString);\r
5651         strcat(machineMove, "\n");\r
5652         strcpy(moveList[forwardMostMove], machineMove);\r
5653 \r
5654         /* [AS] Save move info and clear stats for next move */\r
5655         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5656         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5657         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5658         ClearProgramStats();\r
5659         thinkOutput[0] = NULLCHAR;\r
5660         hiddenThinkOutputState = 0;\r
5661 \r
5662         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5663 \r
5664         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5665         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5666             int count = 0;\r
5667 \r
5668             while( count < adjudicateLossPlies ) {\r
5669                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5670 \r
5671                 if( count & 1 ) {\r
5672                     score = -score; /* Flip score for winning side */\r
5673                 }\r
5674 \r
5675                 if( score > adjudicateLossThreshold ) {\r
5676                     break;\r
5677                 }\r
5678 \r
5679                 count++;\r
5680             }\r
5681 \r
5682             if( count >= adjudicateLossPlies ) {\r
5683                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5684 \r
5685                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5686                     "Xboard adjudication", \r
5687                     GE_XBOARD );\r
5688 \r
5689                 return;\r
5690             }\r
5691         }\r
5692 \r
5693         if( gameMode == TwoMachinesPlay ) {\r
5694           // [HGM] some adjudications useful with buggy engines\r
5695             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
5696           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {\r
5697 \r
5698 \r
5699             if( appData.testLegality )\r
5700             {   /* [HGM] Some more adjudications for obstinate engines */\r
5701                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5702                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,\r
5703                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;\r
5704                 static int moveCount = 6;\r
5705                 ChessMove result;\r
5706                 char *reason = NULL;\r
5707 \r
5708                 /* Count what is on board. */\r
5709                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5710                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5711                     int m=i;\r
5712 \r
5713                     switch((int) p)\r
5714                     {   /* count B,N,R and other of each side */\r
5715                         case WhiteKing:\r
5716                         case BlackKing:\r
5717                              NrK++; break; // [HGM] atomic: count Kings\r
5718                         case WhiteKnight:\r
5719                              NrWN++; break;\r
5720                         case WhiteBishop:\r
5721                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5722                              bishopsColor |= 1 << ((i^j)&1);\r
5723                              NrWB++; break;\r
5724                         case BlackKnight:\r
5725                              NrBN++; break;\r
5726                         case BlackBishop:\r
5727                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5728                              bishopsColor |= 1 << ((i^j)&1);\r
5729                              NrBB++; break;\r
5730                         case WhiteRook:\r
5731                              NrWR++; break;\r
5732                         case BlackRook:\r
5733                              NrBR++; break;\r
5734                         case WhiteQueen:\r
5735                              NrWQ++; break;\r
5736                         case BlackQueen:\r
5737                              NrBQ++; break;\r
5738                         case EmptySquare: \r
5739                              break;\r
5740                         case BlackPawn:\r
5741                              m = 7-i;\r
5742                         case WhitePawn:\r
5743                              PawnAdvance += m; NrPawns++;\r
5744                     }\r
5745                     NrPieces += (p != EmptySquare);\r
5746                     NrW += ((int)p < (int)BlackPawn);\r
5747                     if(gameInfo.variant == VariantXiangqi && \r
5748                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {\r
5749                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces\r
5750                         NrW -= ((int)p < (int)BlackPawn);\r
5751                     }\r
5752                 }\r
5753 \r
5754                 /* Some material-based adjudications that have to be made before stalemate test */\r
5755                 if(gameInfo.variant == VariantAtomic && NrK < 2) {\r
5756                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal\r
5757                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated\r
5758                      if(appData.checkMates) {\r
5759                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move\r
5760                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5761                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5762                                                         "Xboard adjudication: King destroyed", GE_XBOARD );\r
5763                          return;\r
5764                      }\r
5765                 }\r
5766 \r
5767                 /* Bare King in Shatranj (loses) or Losers (wins) */\r
5768                 if( NrW == 1 || NrPieces - NrW == 1) {\r
5769                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)\r
5770                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable\r
5771                      if(appData.checkMates) {\r
5772                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move\r
5773                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5774                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5775                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5776                          return;\r
5777                      }\r
5778                   } else\r
5779                   if( gameInfo.variant == VariantShatranj && --bare < 0)\r
5780                   {    /* bare King */\r
5781                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm\r
5782                         if(appData.checkMates) {\r
5783                             /* but only adjudicate if adjudication enabled */\r
5784                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move\r
5785                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5786                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
5787                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5788                             return;\r
5789                         }\r
5790                   }\r
5791                 } else bare = 1;\r
5792 \r
5793 \r
5794             // don't wait for engine to announce game end if we can judge ourselves\r
5795             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,\r
5796                                        castlingRights[forwardMostMove]) ) {\r
5797               case MT_NONE:\r
5798               case MT_CHECK:\r
5799               default:\r
5800                 break;\r
5801               case MT_STALEMATE:\r
5802               case MT_STAINMATE:\r
5803                 reason = "Xboard adjudication: Stalemate";\r
5804                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt\r
5805                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw\r
5806                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:\r
5807                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win\r
5808                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends\r
5809                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :\r
5810                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?\r
5811                                                                         EP_CHECKMATE : EP_WINS);\r
5812                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)\r
5813                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses\r
5814                 }\r
5815                 break;\r
5816               case MT_CHECKMATE:\r
5817                 reason = "Xboard adjudication: Checkmate";\r
5818                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);\r
5819                 break;\r
5820             }\r
5821 \r
5822                 switch(i = epStatus[forwardMostMove]) {\r
5823                     case EP_STALEMATE:\r
5824                         result = GameIsDrawn; break;\r
5825                     case EP_CHECKMATE:\r
5826                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;\r
5827                     case EP_WINS:\r
5828                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;\r
5829                     default:\r
5830                         result = (ChessMove) 0;\r
5831                 }\r
5832                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested\r
5833                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5834                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5835                     GameEnds( result, reason, GE_XBOARD );\r
5836                     return;\r
5837                 }\r
5838 \r
5839                 /* Next absolutely insufficient mating material. */\r
5840                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && \r
5841                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible\r
5842                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||\r
5843                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color\r
5844                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5845 \r
5846                      /* always flag draws, for judging claims */\r
5847                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5848 \r
5849                      if(appData.materialDraws) {\r
5850                          /* but only adjudicate them if adjudication enabled */\r
5851                          SendToProgram("force\n", cps->other); // suppress reply\r
5852                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */\r
5853                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5854                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5855                          return;\r
5856                      }\r
5857                 }\r
5858 \r
5859                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5860                 if(NrPieces == 4 && \r
5861                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5862                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5863                    || NrWN==2 || NrBN==2     /* KNNK */\r
5864                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5865                   ) ) {\r
5866                      if(--moveCount < 0 && appData.trivialDraws)\r
5867                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5868                           SendToProgram("force\n", cps->other); // suppress reply\r
5869                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5870                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5871                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5872                           return;\r
5873                      }\r
5874                 } else moveCount = 6;\r
5875             }\r
5876           }\r
5877 #if 1\r
5878     if (appData.debugMode) { int i;\r
5879       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5880               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5881               appData.drawRepeats);\r
5882       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5883            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5884 \r
5885     }\r
5886 #endif\r
5887                 /* Check for rep-draws */\r
5888                 count = 0;\r
5889                 for(k = forwardMostMove-2;\r
5890                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5891                         epStatus[k] < EP_UNKNOWN &&\r
5892                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5893                     k-=2)\r
5894                 {   int rights=0;\r
5895 #if 0\r
5896     if (appData.debugMode) {\r
5897       fprintf(debugFP, " loop\n");\r
5898     }\r
5899 #endif\r
5900                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5901 #if 0\r
5902     if (appData.debugMode) {\r
5903       fprintf(debugFP, "match\n");\r
5904     }\r
5905 #endif\r
5906                         /* compare castling rights */\r
5907                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5908                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5909                                 rights++; /* King lost rights, while rook still had them */\r
5910                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5911                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5912                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5913                                    rights++; /* but at least one rook lost them */\r
5914                         }\r
5915                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5916                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5917                                 rights++; \r
5918                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5919                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5920                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5921                                    rights++;\r
5922                         }\r
5923 #if 0\r
5924     if (appData.debugMode) {\r
5925       for(i=0; i<nrCastlingRights; i++)\r
5926       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5927     }\r
5928 \r
5929     if (appData.debugMode) {\r
5930       fprintf(debugFP, " %d %d\n", rights, k);\r
5931     }\r
5932 #endif\r
5933                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5934                             && appData.drawRepeats > 1) {\r
5935                              /* adjudicate after user-specified nr of repeats */\r
5936                              SendToProgram("force\n", cps->other); // suppress reply\r
5937                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5938                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5939                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { \r
5940                                 // [HGM] xiangqi: check for forbidden perpetuals\r
5941                                 int m, ourPerpetual = 1, hisPerpetual = 1;\r
5942                                 for(m=forwardMostMove; m>k; m-=2) {\r
5943                                     if(MateTest(boards[m], PosFlags(m), \r
5944                                                         EP_NONE, castlingRights[m]) != MT_CHECK)\r
5945                                         ourPerpetual = 0; // the current mover did not always check\r
5946                                     if(MateTest(boards[m-1], PosFlags(m-1), \r
5947                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)\r
5948                                         hisPerpetual = 0; // the opponent did not always check\r
5949                                 }\r
5950                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",\r
5951                                                                         ourPerpetual, hisPerpetual);\r
5952                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
5953                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5954                                            "Xboard adjudication: perpetual checking", GE_XBOARD );\r
5955                                     return;\r
5956                                 }\r
5957                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet\r
5958                                     break; // (or we would have caught him before). Abort repetition-checking loop.\r
5959                                 // Now check for perpetual chases\r
5960                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase\r
5961                                     hisPerpetual = PerpetualChase(k, forwardMostMove);\r
5962                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);\r
5963                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit\r
5964                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5965                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );\r
5966                                         return;\r
5967                                     }\r
5968                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet\r
5969                                         break; // Abort repetition-checking loop.\r
5970                                 }\r
5971                                 // if neither of us is checking or chasing all the time, or both are, it is draw\r
5972                              }\r
5973                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
5974                              return;\r
5975                         }\r
5976                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
5977                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
5978                     }\r
5979                 }\r
5980 \r
5981                 /* Now we test for 50-move draws. Determine ply count */\r
5982                 count = forwardMostMove;\r
5983                 /* look for last irreversble move */\r
5984                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
5985                     count--;\r
5986                 /* if we hit starting position, add initial plies */\r
5987                 if( count == backwardMostMove )\r
5988                     count -= initialRulePlies;\r
5989                 count = forwardMostMove - count; \r
5990                 if( count >= 100)\r
5991                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
5992                          /* this is used to judge if draw claims are legal */\r
5993                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
5994                          SendToProgram("force\n", cps->other); // suppress reply\r
5995                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5996                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5997                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
5998                          return;\r
5999                 }\r
6000 \r
6001                 /* if draw offer is pending, treat it as a draw claim\r
6002                  * when draw condition present, to allow engines a way to\r
6003                  * claim draws before making their move to avoid a race\r
6004                  * condition occurring after their move\r
6005                  */\r
6006                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
6007                          char *p = NULL;\r
6008                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
6009                              p = "Draw claim: 50-move rule";\r
6010                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
6011                              p = "Draw claim: 3-fold repetition";\r
6012                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
6013                              p = "Draw claim: insufficient mating material";\r
6014                          if( p != NULL ) {\r
6015                              SendToProgram("force\n", cps->other); // suppress reply\r
6016                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6017                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
6018                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6019                              return;\r
6020                          }\r
6021                 }\r
6022 \r
6023 \r
6024                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
6025                     SendToProgram("force\n", cps->other); // suppress reply\r
6026                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6027                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6028 \r
6029                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
6030 \r
6031                     return;\r
6032                 }\r
6033         }\r
6034 \r
6035         bookHit = NULL;\r
6036         if (gameMode == TwoMachinesPlay) {\r
6037             /* [HGM] relaying draw offers moved to after reception of move */\r
6038             /* and interpreting offer as claim if it brings draw condition */\r
6039             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
6040                 SendToProgram("draw\n", cps->other);\r
6041             }\r
6042             if (cps->other->sendTime) {\r
6043                 SendTimeRemaining(cps->other,\r
6044                                   cps->other->twoMachinesColor[0] == 'w');\r
6045             }\r
6046             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);\r
6047             if (firstMove && !bookHit) {\r
6048                 firstMove = FALSE;\r
6049                 if (cps->other->useColors) {\r
6050                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
6051                 }\r
6052                 SendToProgram("go\n", cps->other);\r
6053             }\r
6054             cps->other->maybeThinking = TRUE;\r
6055         }\r
6056 \r
6057         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6058         \r
6059         if (!pausing && appData.ringBellAfterMoves) {\r
6060             RingBell();\r
6061         }\r
6062 \r
6063         /* \r
6064          * Reenable menu items that were disabled while\r
6065          * machine was thinking\r
6066          */\r
6067         if (gameMode != TwoMachinesPlay)\r
6068             SetUserThinkingEnables();\r
6069 \r
6070         // [HGM] book: after book hit opponent has received move and is now in force mode\r
6071         // force the book reply into it, and then fake that it outputted this move by jumping\r
6072         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move\r
6073         if(bookHit) {\r
6074                 static char bookMove[MSG_SIZ]; // a bit generous?\r
6075 \r
6076                 strcpy(bookMove, "move ");\r
6077                 strcat(bookMove, bookHit);\r
6078                 message = bookMove;\r
6079                 cps = cps->other;\r
6080                 programStats.nodes = programStats.depth = programStats.time = \r
6081                 programStats.score = programStats.got_only_move = 0;\r
6082                 sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
6083 \r
6084                 if(cps->lastPing != cps->lastPong) {\r
6085                     savedMessage = message; // args for deferred call\r
6086                     savedState = cps;\r
6087                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
6088                     return;\r
6089                 }\r
6090                 goto FakeBookMove;\r
6091         }\r
6092 \r
6093         return;\r
6094     }\r
6095 \r
6096     /* Set special modes for chess engines.  Later something general\r
6097      *  could be added here; for now there is just one kludge feature,\r
6098      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
6099      *  when "xboard" is given as an interactive command.\r
6100      */\r
6101     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
6102         cps->useSigint = FALSE;\r
6103         cps->useSigterm = FALSE;\r
6104     }\r
6105 \r
6106     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
6107      * want this, I was asked to put it in, and obliged.\r
6108      */\r
6109     if (!strncmp(message, "setboard ", 9)) {\r
6110         Board initial_position; int i;\r
6111 \r
6112         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
6113 \r
6114         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
6115             DisplayError(_("Bad FEN received from engine"), 0);\r
6116             return ;\r
6117         } else {\r
6118            Reset(FALSE, FALSE);\r
6119            CopyBoard(boards[0], initial_position);\r
6120            initialRulePlies = FENrulePlies;\r
6121            epStatus[0] = FENepStatus;\r
6122            for( i=0; i<nrCastlingRights; i++ )\r
6123                 castlingRights[0][i] = FENcastlingRights[i];\r
6124            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
6125            else gameMode = MachinePlaysBlack;                 \r
6126            DrawPosition(FALSE, boards[currentMove]);\r
6127         }\r
6128         return;\r
6129     }\r
6130 \r
6131     /*\r
6132      * Look for communication commands\r
6133      */\r
6134     if (!strncmp(message, "telluser ", 9)) {\r
6135         DisplayNote(message + 9);\r
6136         return;\r
6137     }\r
6138     if (!strncmp(message, "tellusererror ", 14)) {\r
6139         DisplayError(message + 14, 0);\r
6140         return;\r
6141     }\r
6142     if (!strncmp(message, "tellopponent ", 13)) {\r
6143       if (appData.icsActive) {\r
6144         if (loggedOn) {\r
6145           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
6146           SendToICS(buf1);\r
6147         }\r
6148       } else {\r
6149         DisplayNote(message + 13);\r
6150       }\r
6151       return;\r
6152     }\r
6153     if (!strncmp(message, "tellothers ", 11)) {\r
6154       if (appData.icsActive) {\r
6155         if (loggedOn) {\r
6156           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
6157           SendToICS(buf1);\r
6158         }\r
6159       }\r
6160       return;\r
6161     }\r
6162     if (!strncmp(message, "tellall ", 8)) {\r
6163       if (appData.icsActive) {\r
6164         if (loggedOn) {\r
6165           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
6166           SendToICS(buf1);\r
6167         }\r
6168       } else {\r
6169         DisplayNote(message + 8);\r
6170       }\r
6171       return;\r
6172     }\r
6173     if (strncmp(message, "warning", 7) == 0) {\r
6174         /* Undocumented feature, use tellusererror in new code */\r
6175         DisplayError(message, 0);\r
6176         return;\r
6177     }\r
6178     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
6179         strcpy(realname, cps->tidy);\r
6180         strcat(realname, " query");\r
6181         AskQuestion(realname, buf2, buf1, cps->pr);\r
6182         return;\r
6183     }\r
6184     /* Commands from the engine directly to ICS.  We don't allow these to be \r
6185      *  sent until we are logged on. Crafty kibitzes have been known to \r
6186      *  interfere with the login process.\r
6187      */\r
6188     if (loggedOn) {\r
6189         if (!strncmp(message, "tellics ", 8)) {\r
6190             SendToICS(message + 8);\r
6191             SendToICS("\n");\r
6192             return;\r
6193         }\r
6194         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
6195             SendToICS(ics_prefix);\r
6196             SendToICS(message + 15);\r
6197             SendToICS("\n");\r
6198             return;\r
6199         }\r
6200         /* The following are for backward compatibility only */\r
6201         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
6202             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
6203             SendToICS(ics_prefix);\r
6204             SendToICS(message);\r
6205             SendToICS("\n");\r
6206             return;\r
6207         }\r
6208     }\r
6209     if (strncmp(message, "feature ", 8) == 0) {\r
6210       ParseFeatures(message+8, cps);\r
6211     }\r
6212     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
6213         return;\r
6214     }\r
6215     /*\r
6216      * If the move is illegal, cancel it and redraw the board.\r
6217      * Also deal with other error cases.  Matching is rather loose\r
6218      * here to accommodate engines written before the spec.\r
6219      */\r
6220     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
6221         strncmp(message, "Error", 5) == 0) {\r
6222         if (StrStr(message, "name") || \r
6223             StrStr(message, "rating") || StrStr(message, "?") ||\r
6224             StrStr(message, "result") || StrStr(message, "board") ||\r
6225             StrStr(message, "bk") || StrStr(message, "computer") ||\r
6226             StrStr(message, "variant") || StrStr(message, "hint") ||\r
6227             StrStr(message, "random") || StrStr(message, "depth") ||\r
6228             StrStr(message, "accepted")) {\r
6229             return;\r
6230         }\r
6231         if (StrStr(message, "protover")) {\r
6232           /* Program is responding to input, so it's apparently done\r
6233              initializing, and this error message indicates it is\r
6234              protocol version 1.  So we don't need to wait any longer\r
6235              for it to initialize and send feature commands. */\r
6236           FeatureDone(cps, 1);\r
6237           cps->protocolVersion = 1;\r
6238           return;\r
6239         }\r
6240         cps->maybeThinking = FALSE;\r
6241 \r
6242         if (StrStr(message, "draw")) {\r
6243             /* Program doesn't have "draw" command */\r
6244             cps->sendDrawOffers = 0;\r
6245             return;\r
6246         }\r
6247         if (cps->sendTime != 1 &&\r
6248             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
6249           /* Program apparently doesn't have "time" or "otim" command */\r
6250           cps->sendTime = 0;\r
6251           return;\r
6252         }\r
6253         if (StrStr(message, "analyze")) {\r
6254             cps->analysisSupport = FALSE;\r
6255             cps->analyzing = FALSE;\r
6256             Reset(FALSE, TRUE);\r
6257             sprintf(buf2, _("%s does not support analysis"), cps->tidy);\r
6258             DisplayError(buf2, 0);\r
6259             return;\r
6260         }\r
6261         if (StrStr(message, "(no matching move)st")) {\r
6262           /* Special kludge for GNU Chess 4 only */\r
6263           cps->stKludge = TRUE;\r
6264           SendTimeControl(cps, movesPerSession, timeControl,\r
6265                           timeIncrement, appData.searchDepth,\r
6266                           searchTime);\r
6267           return;\r
6268         }\r
6269         if (StrStr(message, "(no matching move)sd")) {\r
6270           /* Special kludge for GNU Chess 4 only */\r
6271           cps->sdKludge = TRUE;\r
6272           SendTimeControl(cps, movesPerSession, timeControl,\r
6273                           timeIncrement, appData.searchDepth,\r
6274                           searchTime);\r
6275           return;\r
6276         }\r
6277         if (!StrStr(message, "llegal")) {\r
6278             return;\r
6279         }\r
6280         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6281             gameMode == IcsIdle) return;\r
6282         if (forwardMostMove <= backwardMostMove) return;\r
6283 #if 0\r
6284         /* Following removed: it caused a bug where a real illegal move\r
6285            message in analyze mored would be ignored. */\r
6286         if (cps == &first && programStats.ok_to_send == 0) {\r
6287             /* Bogus message from Crafty responding to "."  This filtering\r
6288                can miss some of the bad messages, but fortunately the bug \r
6289                is fixed in current Crafty versions, so it doesn't matter. */\r
6290             return;\r
6291         }\r
6292 #endif\r
6293         if (pausing) PauseEvent();\r
6294         if (gameMode == PlayFromGameFile) {\r
6295             /* Stop reading this game file */\r
6296             gameMode = EditGame;\r
6297             ModeHighlight();\r
6298         }\r
6299         currentMove = --forwardMostMove;\r
6300         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
6301         SwitchClocks();\r
6302         DisplayBothClocks();\r
6303         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),\r
6304                 parseList[currentMove], cps->which);\r
6305         DisplayMoveError(buf1);\r
6306         DrawPosition(FALSE, boards[currentMove]);\r
6307 \r
6308         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
6309         /* only passes fully legal moves                            */\r
6310         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
6311             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
6312                                 "False illegal-move claim", GE_XBOARD );\r
6313         }\r
6314         return;\r
6315     }\r
6316     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
6317         /* Program has a broken "time" command that\r
6318            outputs a string not ending in newline.\r
6319            Don't use it. */\r
6320         cps->sendTime = 0;\r
6321     }\r
6322     \r
6323     /*\r
6324      * If chess program startup fails, exit with an error message.\r
6325      * Attempts to recover here are futile.\r
6326      */\r
6327     if ((StrStr(message, "unknown host") != NULL)\r
6328         || (StrStr(message, "No remote directory") != NULL)\r
6329         || (StrStr(message, "not found") != NULL)\r
6330         || (StrStr(message, "No such file") != NULL)\r
6331         || (StrStr(message, "can't alloc") != NULL)\r
6332         || (StrStr(message, "Permission denied") != NULL)) {\r
6333 \r
6334         cps->maybeThinking = FALSE;\r
6335         sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),\r
6336                 cps->which, cps->program, cps->host, message);\r
6337         RemoveInputSource(cps->isr);\r
6338         DisplayFatalError(buf1, 0, 1);\r
6339         return;\r
6340     }\r
6341     \r
6342     /* \r
6343      * Look for hint output\r
6344      */\r
6345     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
6346         if (cps == &first && hintRequested) {\r
6347             hintRequested = FALSE;\r
6348             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
6349                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
6350                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
6351                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
6352                                     fromY, fromX, toY, toX, promoChar, buf1);\r
6353                 sprintf(buf2, _("Hint: %s"), buf1);\r
6354                 DisplayInformation(buf2);\r
6355             } else {\r
6356                 /* Hint move could not be parsed!? */\r
6357                 sprintf(buf2,\r
6358                         _("Illegal hint move \"%s\"\nfrom %s chess program"),\r
6359                         buf1, cps->which);\r
6360                 DisplayError(buf2, 0);\r
6361             }\r
6362         } else {\r
6363             strcpy(lastHint, buf1);\r
6364         }\r
6365         return;\r
6366     }\r
6367 \r
6368     /*\r
6369      * Ignore other messages if game is not in progress\r
6370      */\r
6371     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6372         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
6373 \r
6374     /*\r
6375      * look for win, lose, draw, or draw offer\r
6376      */\r
6377     if (strncmp(message, "1-0", 3) == 0) {\r
6378         char *p, *q, *r = "";\r
6379         p = strchr(message, '{');\r
6380         if (p) {\r
6381             q = strchr(p, '}');\r
6382             if (q) {\r
6383                 *q = NULLCHAR;\r
6384                 r = p + 1;\r
6385             }\r
6386         }\r
6387         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
6388         return;\r
6389     } else if (strncmp(message, "0-1", 3) == 0) {\r
6390         char *p, *q, *r = "";\r
6391         p = strchr(message, '{');\r
6392         if (p) {\r
6393             q = strchr(p, '}');\r
6394             if (q) {\r
6395                 *q = NULLCHAR;\r
6396                 r = p + 1;\r
6397             }\r
6398         }\r
6399         /* Kludge for Arasan 4.1 bug */\r
6400         if (strcmp(r, "Black resigns") == 0) {\r
6401             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
6402             return;\r
6403         }\r
6404         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
6405         return;\r
6406     } else if (strncmp(message, "1/2", 3) == 0) {\r
6407         char *p, *q, *r = "";\r
6408         p = strchr(message, '{');\r
6409         if (p) {\r
6410             q = strchr(p, '}');\r
6411             if (q) {\r
6412                 *q = NULLCHAR;\r
6413                 r = p + 1;\r
6414             }\r
6415         }\r
6416             \r
6417         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
6418         return;\r
6419 \r
6420     } else if (strncmp(message, "White resign", 12) == 0) {\r
6421         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6422         return;\r
6423     } else if (strncmp(message, "Black resign", 12) == 0) {\r
6424         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6425         return;\r
6426     } else if (strncmp(message, "White matches", 13) == 0 ||\r
6427                strncmp(message, "Black matches", 13) == 0   ) {\r
6428         /* [HGM] ignore GNUShogi noises */\r
6429         return;\r
6430     } else if (strncmp(message, "White", 5) == 0 &&\r
6431                message[5] != '(' &&\r
6432                StrStr(message, "Black") == NULL) {\r
6433         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6434         return;\r
6435     } else if (strncmp(message, "Black", 5) == 0 &&\r
6436                message[5] != '(') {\r
6437         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6438         return;\r
6439     } else if (strcmp(message, "resign") == 0 ||\r
6440                strcmp(message, "computer resigns") == 0) {\r
6441         switch (gameMode) {\r
6442           case MachinePlaysBlack:\r
6443           case IcsPlayingBlack:\r
6444             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
6445             break;\r
6446           case MachinePlaysWhite:\r
6447           case IcsPlayingWhite:\r
6448             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
6449             break;\r
6450           case TwoMachinesPlay:\r
6451             if (cps->twoMachinesColor[0] == 'w')\r
6452               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6453             else\r
6454               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6455             break;\r
6456           default:\r
6457             /* can't happen */\r
6458             break;\r
6459         }\r
6460         return;\r
6461     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
6462         switch (gameMode) {\r
6463           case MachinePlaysBlack:\r
6464           case IcsPlayingBlack:\r
6465             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6466             break;\r
6467           case MachinePlaysWhite:\r
6468           case IcsPlayingWhite:\r
6469             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
6470             break;\r
6471           case TwoMachinesPlay:\r
6472             if (cps->twoMachinesColor[0] == 'w')\r
6473               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6474             else\r
6475               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6476             break;\r
6477           default:\r
6478             /* can't happen */\r
6479             break;\r
6480         }\r
6481         return;\r
6482     } else if (strncmp(message, "computer mates", 14) == 0) {\r
6483         switch (gameMode) {\r
6484           case MachinePlaysBlack:\r
6485           case IcsPlayingBlack:\r
6486             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
6487             break;\r
6488           case MachinePlaysWhite:\r
6489           case IcsPlayingWhite:\r
6490             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6491             break;\r
6492           case TwoMachinesPlay:\r
6493             if (cps->twoMachinesColor[0] == 'w')\r
6494               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6495             else\r
6496               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6497             break;\r
6498           default:\r
6499             /* can't happen */\r
6500             break;\r
6501         }\r
6502         return;\r
6503     } else if (strncmp(message, "checkmate", 9) == 0) {\r
6504         if (WhiteOnMove(forwardMostMove)) {\r
6505             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6506         } else {\r
6507             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6508         }\r
6509         return;\r
6510     } else if (strstr(message, "Draw") != NULL ||\r
6511                strstr(message, "game is a draw") != NULL) {\r
6512         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
6513         return;\r
6514     } else if (strstr(message, "offer") != NULL &&\r
6515                strstr(message, "draw") != NULL) {\r
6516 #if ZIPPY\r
6517         if (appData.zippyPlay && first.initDone) {\r
6518             /* Relay offer to ICS */\r
6519             SendToICS(ics_prefix);\r
6520             SendToICS("draw\n");\r
6521         }\r
6522 #endif\r
6523         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
6524         if (gameMode == TwoMachinesPlay) {\r
6525             if (cps->other->offeredDraw) {\r
6526                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6527             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
6528             /* until after we also have move, to see if it is really claim */\r
6529             }\r
6530 #if 0\r
6531               else {\r
6532                 if (cps->other->sendDrawOffers) {\r
6533                     SendToProgram("draw\n", cps->other);\r
6534                 }\r
6535             }\r
6536 #endif\r
6537         } else if (gameMode == MachinePlaysWhite ||\r
6538                    gameMode == MachinePlaysBlack) {\r
6539           if (userOfferedDraw) {\r
6540             DisplayInformation(_("Machine accepts your draw offer"));\r
6541             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6542           } else {\r
6543             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));\r
6544           }\r
6545         }\r
6546     }\r
6547 \r
6548     \r
6549     /*\r
6550      * Look for thinking output\r
6551      */\r
6552     if ( appData.showThinking // [HGM] thinking: test all options that cause this output\r
6553           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
6554                                 ) {\r
6555         int plylev, mvleft, mvtot, curscore, time;\r
6556         char mvname[MOVE_LEN];\r
6557         u64 nodes; // [DM]\r
6558         char plyext;\r
6559         int ignore = FALSE;\r
6560         int prefixHint = FALSE;\r
6561         mvname[0] = NULLCHAR;\r
6562 \r
6563         switch (gameMode) {\r
6564           case MachinePlaysBlack:\r
6565           case IcsPlayingBlack:\r
6566             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6567             break;\r
6568           case MachinePlaysWhite:\r
6569           case IcsPlayingWhite:\r
6570             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6571             break;\r
6572           case AnalyzeMode:\r
6573           case AnalyzeFile:\r
6574             break;\r
6575           case IcsObserving: /* [DM] icsEngineAnalyze */\r
6576             if (!appData.icsEngineAnalyze) ignore = TRUE;\r
6577             break;\r
6578           case TwoMachinesPlay:\r
6579             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
6580                 ignore = TRUE;\r
6581             }\r
6582             break;\r
6583           default:\r
6584             ignore = TRUE;\r
6585             break;\r
6586         }\r
6587 \r
6588         if (!ignore) {\r
6589             buf1[0] = NULLCHAR;\r
6590             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6591                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
6592 \r
6593                 if (plyext != ' ' && plyext != '\t') {\r
6594                     time *= 100;\r
6595                 }\r
6596 \r
6597                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6598                 if( cps->scoreIsAbsolute && \r
6599                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
6600                 {\r
6601                     curscore = -curscore;\r
6602                 }\r
6603 \r
6604 \r
6605                 programStats.depth = plylev;\r
6606                 programStats.nodes = nodes;\r
6607                 programStats.time = time;\r
6608                 programStats.score = curscore;\r
6609                 programStats.got_only_move = 0;\r
6610 \r
6611                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
6612                         int ticklen;\r
6613 \r
6614                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time\r
6615                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time\r
6616                         if(WhiteOnMove(forwardMostMove)) \r
6617                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
6618                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
6619                 }\r
6620 \r
6621                 /* Buffer overflow protection */\r
6622                 if (buf1[0] != NULLCHAR) {\r
6623                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
6624                         && appData.debugMode) {\r
6625                         fprintf(debugFP,\r
6626                                 "PV is too long; using the first %d bytes.\n",\r
6627                                 sizeof(programStats.movelist) - 1);\r
6628                     }\r
6629 \r
6630                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
6631                 } else {\r
6632                     sprintf(programStats.movelist, " no PV\n");\r
6633                 }\r
6634 \r
6635                 if (programStats.seen_stat) {\r
6636                     programStats.ok_to_send = 1;\r
6637                 }\r
6638 \r
6639                 if (strchr(programStats.movelist, '(') != NULL) {\r
6640                     programStats.line_is_book = 1;\r
6641                     programStats.nr_moves = 0;\r
6642                     programStats.moves_left = 0;\r
6643                 } else {\r
6644                     programStats.line_is_book = 0;\r
6645                 }\r
6646 \r
6647                 SendProgramStatsToFrontend( cps, &programStats );\r
6648 \r
6649                 /* \r
6650                     [AS] Protect the thinkOutput buffer from overflow... this\r
6651                     is only useful if buf1 hasn't overflowed first!\r
6652                 */\r
6653                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
6654                         plylev, \r
6655                         (gameMode == TwoMachinesPlay ?\r
6656                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
6657                         ((double) curscore) / 100.0,\r
6658                         prefixHint ? lastHint : "",\r
6659                         prefixHint ? " " : "" );\r
6660 \r
6661                 if( buf1[0] != NULLCHAR ) {\r
6662                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
6663 \r
6664                     if( strlen(buf1) > max_len ) {\r
6665                         if( appData.debugMode) {\r
6666                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
6667                         }\r
6668                         buf1[max_len+1] = '\0';\r
6669                     }\r
6670 \r
6671                     strcat( thinkOutput, buf1 );\r
6672                 }\r
6673 \r
6674                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode\r
6675                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6676                     DisplayMove(currentMove - 1);\r
6677                     DisplayAnalysis();\r
6678                 }\r
6679                 return;\r
6680 \r
6681             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6682                 /* crafty (9.25+) says "(only move) <move>"\r
6683                  * if there is only 1 legal move\r
6684                  */\r
6685                 sscanf(p, "(only move) %s", buf1);\r
6686                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6687                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6688                 programStats.depth = 1;\r
6689                 programStats.nr_moves = 1;\r
6690                 programStats.moves_left = 1;\r
6691                 programStats.nodes = 1;\r
6692                 programStats.time = 1;\r
6693                 programStats.got_only_move = 1;\r
6694 \r
6695                 /* Not really, but we also use this member to\r
6696                    mean "line isn't going to change" (Crafty\r
6697                    isn't searching, so stats won't change) */\r
6698                 programStats.line_is_book = 1;\r
6699 \r
6700                 SendProgramStatsToFrontend( cps, &programStats );\r
6701                 \r
6702                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || \r
6703                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6704                     DisplayMove(currentMove - 1);\r
6705                     DisplayAnalysis();\r
6706                 }\r
6707                 return;\r
6708             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",\r
6709                               &time, &nodes, &plylev, &mvleft,\r
6710                               &mvtot, mvname) >= 5) {\r
6711                 /* The stat01: line is from Crafty (9.29+) in response\r
6712                    to the "." command */\r
6713                 programStats.seen_stat = 1;\r
6714                 cps->maybeThinking = TRUE;\r
6715 \r
6716                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6717                   return;\r
6718 \r
6719                 programStats.depth = plylev;\r
6720                 programStats.time = time;\r
6721                 programStats.nodes = nodes;\r
6722                 programStats.moves_left = mvleft;\r
6723                 programStats.nr_moves = mvtot;\r
6724                 strcpy(programStats.move_name, mvname);\r
6725                 programStats.ok_to_send = 1;\r
6726                 programStats.movelist[0] = '\0';\r
6727 \r
6728                 SendProgramStatsToFrontend( cps, &programStats );\r
6729 \r
6730                 DisplayAnalysis();\r
6731                 return;\r
6732 \r
6733             } else if (strncmp(message,"++",2) == 0) {\r
6734                 /* Crafty 9.29+ outputs this */\r
6735                 programStats.got_fail = 2;\r
6736                 return;\r
6737 \r
6738             } else if (strncmp(message,"--",2) == 0) {\r
6739                 /* Crafty 9.29+ outputs this */\r
6740                 programStats.got_fail = 1;\r
6741                 return;\r
6742 \r
6743             } else if (thinkOutput[0] != NULLCHAR &&\r
6744                        strncmp(message, "    ", 4) == 0) {\r
6745                 unsigned message_len;\r
6746 \r
6747                 p = message;\r
6748                 while (*p && *p == ' ') p++;\r
6749 \r
6750                 message_len = strlen( p );\r
6751 \r
6752                 /* [AS] Avoid buffer overflow */\r
6753                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6754                     strcat(thinkOutput, " ");\r
6755                     strcat(thinkOutput, p);\r
6756                 }\r
6757 \r
6758                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6759                     strcat(programStats.movelist, " ");\r
6760                     strcat(programStats.movelist, p);\r
6761                 }\r
6762 \r
6763                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||\r
6764                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6765                     DisplayMove(currentMove - 1);\r
6766                     DisplayAnalysis();\r
6767                 }\r
6768                 return;\r
6769             }\r
6770         }\r
6771         else {\r
6772             buf1[0] = NULLCHAR;\r
6773 \r
6774             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6775                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6776             {\r
6777                 ChessProgramStats cpstats;\r
6778 \r
6779                 if (plyext != ' ' && plyext != '\t') {\r
6780                     time *= 100;\r
6781                 }\r
6782 \r
6783                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6784                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6785                     curscore = -curscore;\r
6786                 }\r
6787 \r
6788                 cpstats.depth = plylev;\r
6789                 cpstats.nodes = nodes;\r
6790                 cpstats.time = time;\r
6791                 cpstats.score = curscore;\r
6792                 cpstats.got_only_move = 0;\r
6793                 cpstats.movelist[0] = '\0';\r
6794 \r
6795                 if (buf1[0] != NULLCHAR) {\r
6796                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6797                 }\r
6798 \r
6799                 cpstats.ok_to_send = 0;\r
6800                 cpstats.line_is_book = 0;\r
6801                 cpstats.nr_moves = 0;\r
6802                 cpstats.moves_left = 0;\r
6803 \r
6804                 SendProgramStatsToFrontend( cps, &cpstats );\r
6805             }\r
6806         }\r
6807     }\r
6808 }\r
6809 \r
6810 \r
6811 /* Parse a game score from the character string "game", and\r
6812    record it as the history of the current game.  The game\r
6813    score is NOT assumed to start from the standard position. \r
6814    The display is not updated in any way.\r
6815    */\r
6816 void\r
6817 ParseGameHistory(game)\r
6818      char *game;\r
6819 {\r
6820     ChessMove moveType;\r
6821     int fromX, fromY, toX, toY, boardIndex;\r
6822     char promoChar;\r
6823     char *p, *q;\r
6824     char buf[MSG_SIZ];\r
6825 \r
6826     if (appData.debugMode)\r
6827       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6828 \r
6829     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6830     gameInfo.site = StrSave(appData.icsHost);\r
6831     gameInfo.date = PGNDate();\r
6832     gameInfo.round = StrSave("-");\r
6833 \r
6834     /* Parse out names of players */\r
6835     while (*game == ' ') game++;\r
6836     p = buf;\r
6837     while (*game != ' ') *p++ = *game++;\r
6838     *p = NULLCHAR;\r
6839     gameInfo.white = StrSave(buf);\r
6840     while (*game == ' ') game++;\r
6841     p = buf;\r
6842     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6843     *p = NULLCHAR;\r
6844     gameInfo.black = StrSave(buf);\r
6845 \r
6846     /* Parse moves */\r
6847     boardIndex = blackPlaysFirst ? 1 : 0;\r
6848     yynewstr(game);\r
6849     for (;;) {\r
6850         yyboardindex = boardIndex;\r
6851         moveType = (ChessMove) yylex();\r
6852         switch (moveType) {\r
6853           case IllegalMove:             /* maybe suicide chess, etc. */\r
6854   if (appData.debugMode) {\r
6855     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6856     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6857     setbuf(debugFP, NULL);\r
6858   }\r
6859           case WhitePromotionChancellor:\r
6860           case BlackPromotionChancellor:\r
6861           case WhitePromotionArchbishop:\r
6862           case BlackPromotionArchbishop:\r
6863           case WhitePromotionQueen:\r
6864           case BlackPromotionQueen:\r
6865           case WhitePromotionRook:\r
6866           case BlackPromotionRook:\r
6867           case WhitePromotionBishop:\r
6868           case BlackPromotionBishop:\r
6869           case WhitePromotionKnight:\r
6870           case BlackPromotionKnight:\r
6871           case WhitePromotionKing:\r
6872           case BlackPromotionKing:\r
6873           case NormalMove:\r
6874           case WhiteCapturesEnPassant:\r
6875           case BlackCapturesEnPassant:\r
6876           case WhiteKingSideCastle:\r
6877           case WhiteQueenSideCastle:\r
6878           case BlackKingSideCastle:\r
6879           case BlackQueenSideCastle:\r
6880           case WhiteKingSideCastleWild:\r
6881           case WhiteQueenSideCastleWild:\r
6882           case BlackKingSideCastleWild:\r
6883           case BlackQueenSideCastleWild:\r
6884           /* PUSH Fabien */\r
6885           case WhiteHSideCastleFR:\r
6886           case WhiteASideCastleFR:\r
6887           case BlackHSideCastleFR:\r
6888           case BlackASideCastleFR:\r
6889           /* POP Fabien */\r
6890             fromX = currentMoveString[0] - AAA;\r
6891             fromY = currentMoveString[1] - ONE;\r
6892             toX = currentMoveString[2] - AAA;\r
6893             toY = currentMoveString[3] - ONE;\r
6894             promoChar = currentMoveString[4];\r
6895             break;\r
6896           case WhiteDrop:\r
6897           case BlackDrop:\r
6898             fromX = moveType == WhiteDrop ?\r
6899               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6900             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6901             fromY = DROP_RANK;\r
6902             toX = currentMoveString[2] - AAA;\r
6903             toY = currentMoveString[3] - ONE;\r
6904             promoChar = NULLCHAR;\r
6905             break;\r
6906           case AmbiguousMove:\r
6907             /* bug? */\r
6908             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);\r
6909   if (appData.debugMode) {\r
6910     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
6911     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6912     setbuf(debugFP, NULL);\r
6913   }\r
6914             DisplayError(buf, 0);\r
6915             return;\r
6916           case ImpossibleMove:\r
6917             /* bug? */\r
6918             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);\r
6919   if (appData.debugMode) {\r
6920     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
6921     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6922     setbuf(debugFP, NULL);\r
6923   }\r
6924             DisplayError(buf, 0);\r
6925             return;\r
6926           case (ChessMove) 0:   /* end of file */\r
6927             if (boardIndex < backwardMostMove) {\r
6928                 /* Oops, gap.  How did that happen? */\r
6929                 DisplayError(_("Gap in move list"), 0);\r
6930                 return;\r
6931             }\r
6932             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
6933             if (boardIndex > forwardMostMove) {\r
6934                 forwardMostMove = boardIndex;\r
6935             }\r
6936             return;\r
6937           case ElapsedTime:\r
6938             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
6939                 strcat(parseList[boardIndex-1], " ");\r
6940                 strcat(parseList[boardIndex-1], yy_text);\r
6941             }\r
6942             continue;\r
6943           case Comment:\r
6944           case PGNTag:\r
6945           case NAG:\r
6946           default:\r
6947             /* ignore */\r
6948             continue;\r
6949           case WhiteWins:\r
6950           case BlackWins:\r
6951           case GameIsDrawn:\r
6952           case GameUnfinished:\r
6953             if (gameMode == IcsExamining) {\r
6954                 if (boardIndex < backwardMostMove) {\r
6955                     /* Oops, gap.  How did that happen? */\r
6956                     return;\r
6957                 }\r
6958                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6959                 return;\r
6960             }\r
6961             gameInfo.result = moveType;\r
6962             p = strchr(yy_text, '{');\r
6963             if (p == NULL) p = strchr(yy_text, '(');\r
6964             if (p == NULL) {\r
6965                 p = yy_text;\r
6966                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
6967             } else {\r
6968                 q = strchr(p, *p == '{' ? '}' : ')');\r
6969                 if (q != NULL) *q = NULLCHAR;\r
6970                 p++;\r
6971             }\r
6972             gameInfo.resultDetails = StrSave(p);\r
6973             continue;\r
6974         }\r
6975         if (boardIndex >= forwardMostMove &&\r
6976             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
6977             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6978             return;\r
6979         }\r
6980         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
6981                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
6982                                  parseList[boardIndex]);\r
6983         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
6984         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}\r
6985         /* currentMoveString is set as a side-effect of yylex */\r
6986         strcpy(moveList[boardIndex], currentMoveString);\r
6987         strcat(moveList[boardIndex], "\n");\r
6988         boardIndex++;\r
6989         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], \r
6990                                         castlingRights[boardIndex], &epStatus[boardIndex]);\r
6991         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
6992                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
6993           case MT_NONE:\r
6994           case MT_STALEMATE:\r
6995           default:\r
6996             break;\r
6997           case MT_CHECK:\r
6998             if(gameInfo.variant != VariantShogi)\r
6999                 strcat(parseList[boardIndex - 1], "+");\r
7000             break;\r
7001           case MT_CHECKMATE:\r
7002           case MT_STAINMATE:\r
7003             strcat(parseList[boardIndex - 1], "#");\r
7004             break;\r
7005         }\r
7006     }\r
7007 }\r
7008 \r
7009 \r
7010 /* Apply a move to the given board  */\r
7011 void\r
7012 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)\r
7013      int fromX, fromY, toX, toY;\r
7014      int promoChar;\r
7015      Board board;\r
7016      char *castling;\r
7017      char *ep;\r
7018 {\r
7019   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
7020 \r
7021     /* [HGM] compute & store e.p. status and castling rights for new position */\r
7022     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */\r
7023     { int i;\r
7024 \r
7025       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
7026       oldEP = *ep;\r
7027       *ep = EP_NONE;\r
7028 \r
7029       if( board[toY][toX] != EmptySquare ) \r
7030            *ep = EP_CAPTURE;  \r
7031 \r
7032       if( board[fromY][fromX] == WhitePawn ) {\r
7033            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7034                *ep = EP_PAWN_MOVE;\r
7035            if( toY-fromY==2) {\r
7036                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
7037                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7038                       *ep = toX | berolina;\r
7039                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
7040                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7041                       *ep = toX;\r
7042            }\r
7043       } else \r
7044       if( board[fromY][fromX] == BlackPawn ) {\r
7045            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7046                *ep = EP_PAWN_MOVE; \r
7047            if( toY-fromY== -2) {\r
7048                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
7049                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7050                       *ep = toX | berolina;\r
7051                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
7052                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7053                       *ep = toX;\r
7054            }\r
7055        }\r
7056 \r
7057        for(i=0; i<nrCastlingRights; i++) {\r
7058            if(castling[i] == fromX && castlingRank[i] == fromY ||\r
7059               castling[i] == toX   && castlingRank[i] == toY   \r
7060              ) castling[i] = -1; // revoke for moved or captured piece\r
7061        }\r
7062 \r
7063     }\r
7064 \r
7065   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
7066   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
7067        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
7068          \r
7069   if (fromX == toX && fromY == toY) return;\r
7070 \r
7071   if (fromY == DROP_RANK) {\r
7072         /* must be first */\r
7073         piece = board[toY][toX] = (ChessSquare) fromX;\r
7074   } else {\r
7075      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
7076      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
7077      if(gameInfo.variant == VariantKnightmate)\r
7078          king += (int) WhiteUnicorn - (int) WhiteKing;\r
7079 \r
7080     /* Code added by Tord: */\r
7081     /* FRC castling assumed when king captures friendly rook. */\r
7082     if (board[fromY][fromX] == WhiteKing &&\r
7083              board[toY][toX] == WhiteRook) {\r
7084       board[fromY][fromX] = EmptySquare;\r
7085       board[toY][toX] = EmptySquare;\r
7086       if(toX > fromX) {\r
7087         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
7088       } else {\r
7089         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
7090       }\r
7091     } else if (board[fromY][fromX] == BlackKing &&\r
7092                board[toY][toX] == BlackRook) {\r
7093       board[fromY][fromX] = EmptySquare;\r
7094       board[toY][toX] = EmptySquare;\r
7095       if(toX > fromX) {\r
7096         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
7097       } else {\r
7098         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
7099       }\r
7100     /* End of code added by Tord */\r
7101 \r
7102     } else if (board[fromY][fromX] == king\r
7103         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7104         && toY == fromY && toX > fromX+1) {\r
7105         board[fromY][fromX] = EmptySquare;\r
7106         board[toY][toX] = king;\r
7107         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7108         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7109     } else if (board[fromY][fromX] == king\r
7110         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7111                && toY == fromY && toX < fromX-1) {\r
7112         board[fromY][fromX] = EmptySquare;\r
7113         board[toY][toX] = king;\r
7114         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7115         board[fromY][BOARD_LEFT] = EmptySquare;\r
7116     } else if (board[fromY][fromX] == WhitePawn\r
7117                && toY == BOARD_HEIGHT-1\r
7118                && gameInfo.variant != VariantXiangqi\r
7119                ) {\r
7120         /* white pawn promotion */\r
7121         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
7122         if (board[toY][toX] == EmptySquare) {\r
7123             board[toY][toX] = WhiteQueen;\r
7124         }\r
7125         if(gameInfo.variant==VariantBughouse ||\r
7126            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7127             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7128         board[fromY][fromX] = EmptySquare;\r
7129     } else if ((fromY == BOARD_HEIGHT-4)\r
7130                && (toX != fromX)\r
7131                && gameInfo.variant != VariantXiangqi\r
7132                && gameInfo.variant != VariantBerolina\r
7133                && (board[fromY][fromX] == WhitePawn)\r
7134                && (board[toY][toX] == EmptySquare)) {\r
7135         board[fromY][fromX] = EmptySquare;\r
7136         board[toY][toX] = WhitePawn;\r
7137         captured = board[toY - 1][toX];\r
7138         board[toY - 1][toX] = EmptySquare;\r
7139     } else if ((fromY == BOARD_HEIGHT-4)\r
7140                && (toX == fromX)\r
7141                && gameInfo.variant == VariantBerolina\r
7142                && (board[fromY][fromX] == WhitePawn)\r
7143                && (board[toY][toX] == EmptySquare)) {\r
7144         board[fromY][fromX] = EmptySquare;\r
7145         board[toY][toX] = WhitePawn;\r
7146         if(oldEP & EP_BEROLIN_A) {\r
7147                 captured = board[fromY][fromX-1];\r
7148                 board[fromY][fromX-1] = EmptySquare;\r
7149         }else{  captured = board[fromY][fromX+1];\r
7150                 board[fromY][fromX+1] = EmptySquare;\r
7151         }\r
7152     } else if (board[fromY][fromX] == king\r
7153         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7154                && toY == fromY && toX > fromX+1) {\r
7155         board[fromY][fromX] = EmptySquare;\r
7156         board[toY][toX] = king;\r
7157         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7158         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7159     } else if (board[fromY][fromX] == king\r
7160         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7161                && toY == fromY && toX < fromX-1) {\r
7162         board[fromY][fromX] = EmptySquare;\r
7163         board[toY][toX] = king;\r
7164         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7165         board[fromY][BOARD_LEFT] = EmptySquare;\r
7166     } else if (fromY == 7 && fromX == 3\r
7167                && board[fromY][fromX] == BlackKing\r
7168                && toY == 7 && toX == 5) {\r
7169         board[fromY][fromX] = EmptySquare;\r
7170         board[toY][toX] = BlackKing;\r
7171         board[fromY][7] = EmptySquare;\r
7172         board[toY][4] = BlackRook;\r
7173     } else if (fromY == 7 && fromX == 3\r
7174                && board[fromY][fromX] == BlackKing\r
7175                && toY == 7 && toX == 1) {\r
7176         board[fromY][fromX] = EmptySquare;\r
7177         board[toY][toX] = BlackKing;\r
7178         board[fromY][0] = EmptySquare;\r
7179         board[toY][2] = BlackRook;\r
7180     } else if (board[fromY][fromX] == BlackPawn\r
7181                && toY == 0\r
7182                && gameInfo.variant != VariantXiangqi\r
7183                ) {\r
7184         /* black pawn promotion */\r
7185         board[0][toX] = CharToPiece(ToLower(promoChar));\r
7186         if (board[0][toX] == EmptySquare) {\r
7187             board[0][toX] = BlackQueen;\r
7188         }\r
7189         if(gameInfo.variant==VariantBughouse ||\r
7190            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7191             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7192         board[fromY][fromX] = EmptySquare;\r
7193     } else if ((fromY == 3)\r
7194                && (toX != fromX)\r
7195                && gameInfo.variant != VariantXiangqi\r
7196                && gameInfo.variant != VariantBerolina\r
7197                && (board[fromY][fromX] == BlackPawn)\r
7198                && (board[toY][toX] == EmptySquare)) {\r
7199         board[fromY][fromX] = EmptySquare;\r
7200         board[toY][toX] = BlackPawn;\r
7201         captured = board[toY + 1][toX];\r
7202         board[toY + 1][toX] = EmptySquare;\r
7203     } else if ((fromY == 3)\r
7204                && (toX == fromX)\r
7205                && gameInfo.variant == VariantBerolina\r
7206                && (board[fromY][fromX] == BlackPawn)\r
7207                && (board[toY][toX] == EmptySquare)) {\r
7208         board[fromY][fromX] = EmptySquare;\r
7209         board[toY][toX] = BlackPawn;\r
7210         if(oldEP & EP_BEROLIN_A) {\r
7211                 captured = board[fromY][fromX-1];\r
7212                 board[fromY][fromX-1] = EmptySquare;\r
7213         }else{  captured = board[fromY][fromX+1];\r
7214                 board[fromY][fromX+1] = EmptySquare;\r
7215         }\r
7216     } else {\r
7217         board[toY][toX] = board[fromY][fromX];\r
7218         board[fromY][fromX] = EmptySquare;\r
7219     }\r
7220 \r
7221     /* [HGM] now we promote for Shogi, if needed */\r
7222     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
7223         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7224   }\r
7225 \r
7226     if (gameInfo.holdingsWidth != 0) {\r
7227 \r
7228       /* !!A lot more code needs to be written to support holdings  */\r
7229       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
7230       /* penultimate board files, so they are automaticlly stored   */\r
7231       /* in the game history.                                       */\r
7232       if (fromY == DROP_RANK) {\r
7233         /* Delete from holdings, by decreasing count */\r
7234         /* and erasing image if necessary            */\r
7235         p = (int) fromX;\r
7236         if(p < (int) BlackPawn) { /* white drop */\r
7237              p -= (int)WhitePawn;\r
7238              if(p >= gameInfo.holdingsSize) p = 0;\r
7239              if(--board[p][BOARD_WIDTH-2] == 0)\r
7240                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
7241         } else {                  /* black drop */\r
7242              p -= (int)BlackPawn;\r
7243              if(p >= gameInfo.holdingsSize) p = 0;\r
7244              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
7245                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
7246         }\r
7247       }\r
7248       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
7249           && gameInfo.variant != VariantBughouse        ) {\r
7250         /* [HGM] holdings: Add to holdings, if holdings exist */\r
7251         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { \r
7252                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
7253                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
7254         }\r
7255         p = (int) captured;\r
7256         if (p >= (int) BlackPawn) {\r
7257           p -= (int)BlackPawn;\r
7258           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7259                   /* in Shogi restore piece to its original  first */\r
7260                   captured = (ChessSquare) (DEMOTED captured);\r
7261                   p = DEMOTED p;\r
7262           }\r
7263           p = PieceToNumber((ChessSquare)p);\r
7264           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
7265           board[p][BOARD_WIDTH-2]++;\r
7266           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;\r
7267         } else {\r
7268           p -= (int)WhitePawn;\r
7269           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7270                   captured = (ChessSquare) (DEMOTED captured);\r
7271                   p = DEMOTED p;\r
7272           }\r
7273           p = PieceToNumber((ChessSquare)p);\r
7274           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
7275           board[BOARD_HEIGHT-1-p][1]++;\r
7276           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;\r
7277         }\r
7278       }\r
7279 \r
7280     } else if (gameInfo.variant == VariantAtomic) {\r
7281       if (captured != EmptySquare) {\r
7282         int y, x;\r
7283         for (y = toY-1; y <= toY+1; y++) {\r
7284           for (x = toX-1; x <= toX+1; x++) {\r
7285             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
7286                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
7287               board[y][x] = EmptySquare;\r
7288             }\r
7289           }\r
7290         }\r
7291         board[toY][toX] = EmptySquare;\r
7292       }\r
7293     }\r
7294     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
7295         /* [HGM] Shogi promotions */\r
7296         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7297     }\r
7298 \r
7299     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) \r
7300                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
7301         // [HGM] superchess: take promotion piece out of holdings\r
7302         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
7303         if((int)piece < (int)BlackPawn) { // determine stm from piece color\r
7304             if(!--board[k][BOARD_WIDTH-2])\r
7305                 board[k][BOARD_WIDTH-1] = EmptySquare;\r
7306         } else {\r
7307             if(!--board[BOARD_HEIGHT-1-k][1])\r
7308                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;\r
7309         }\r
7310     }\r
7311 \r
7312 }\r
7313 \r
7314 /* Updates forwardMostMove */\r
7315 void\r
7316 MakeMove(fromX, fromY, toX, toY, promoChar)\r
7317      int fromX, fromY, toX, toY;\r
7318      int promoChar;\r
7319 {\r
7320 //    forwardMostMove++; // [HGM] bare: moved downstream\r
7321 \r
7322     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */\r
7323         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
7324         piece = boards[forwardMostMove][fromY][fromX];\r
7325         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
7326         if(gameInfo.variant == VariantKnightmate)\r
7327             king += (int) WhiteUnicorn - (int) WhiteKing;\r
7328         if(forwardMostMove == 0) {\r
7329             if(blackPlaysFirst) \r
7330                 fprintf(serverMoves, "%s;", second.tidy);\r
7331             fprintf(serverMoves, "%s;", first.tidy);\r
7332             if(!blackPlaysFirst) \r
7333                 fprintf(serverMoves, "%s;", second.tidy);\r
7334         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
7335         lastLoadFlag = loadFlag;\r
7336         // print base move\r
7337         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
7338         // print castling suffix\r
7339         if( toY == fromY && piece == king ) {\r
7340             if(toX-fromX > 1)\r
7341                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
7342             if(fromX-toX >1)\r
7343                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
7344         }\r
7345         // e.p. suffix\r
7346         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||\r
7347              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&\r
7348              boards[forwardMostMove][toY][toX] == EmptySquare\r
7349              && fromX != toX )\r
7350                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
7351         // promotion suffix\r
7352         if(promoChar != NULLCHAR)\r
7353                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
7354         if(!loadFlag) {\r
7355             fprintf(serverMoves, "/%d/%d",\r
7356                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);\r
7357             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;\r
7358             else                      timeLeft = blackTimeRemaining/1000;\r
7359             fprintf(serverMoves, "/%d", timeLeft);\r
7360         }\r
7361         fflush(serverMoves);\r
7362     }\r
7363 \r
7364     if (forwardMostMove+1 >= MAX_MOVES) {\r
7365       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
7366                         0, 1);\r
7367       return;\r
7368     }\r
7369     SwitchClocks();\r
7370     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;\r
7371     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;\r
7372     if (commentList[forwardMostMove+1] != NULL) {\r
7373         free(commentList[forwardMostMove+1]);\r
7374         commentList[forwardMostMove+1] = NULL;\r
7375     }\r
7376     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);\r
7377     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}\r
7378     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], \r
7379                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);\r
7380     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board\r
7381     gameInfo.result = GameUnfinished;\r
7382     if (gameInfo.resultDetails != NULL) {\r
7383         free(gameInfo.resultDetails);\r
7384         gameInfo.resultDetails = NULL;\r
7385     }\r
7386     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
7387                               moveList[forwardMostMove - 1]);\r
7388     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
7389                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
7390                              fromY, fromX, toY, toX, promoChar,\r
7391                              parseList[forwardMostMove - 1]);\r
7392     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
7393                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
7394                             castlingRights[forwardMostMove]) ) {\r
7395       case MT_NONE:\r
7396       case MT_STALEMATE:\r
7397       default:\r
7398         break;\r
7399       case MT_CHECK:\r
7400         if(gameInfo.variant != VariantShogi)\r
7401             strcat(parseList[forwardMostMove - 1], "+");\r
7402         break;\r
7403       case MT_CHECKMATE:\r
7404       case MT_STAINMATE:\r
7405         strcat(parseList[forwardMostMove - 1], "#");\r
7406         break;\r
7407     }\r
7408     if (appData.debugMode) {\r
7409         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
7410     }\r
7411 \r
7412 }\r
7413 \r
7414 /* Updates currentMove if not pausing */\r
7415 void\r
7416 ShowMove(fromX, fromY, toX, toY)\r
7417 {\r
7418     int instant = (gameMode == PlayFromGameFile) ?\r
7419         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
7420     if(appData.noGUI) return;\r
7421     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
7422         if (!instant) {\r
7423             if (forwardMostMove == currentMove + 1) {\r
7424                 AnimateMove(boards[forwardMostMove - 1],\r
7425                             fromX, fromY, toX, toY);\r
7426             }\r
7427             if (appData.highlightLastMove) {\r
7428                 SetHighlights(fromX, fromY, toX, toY);\r
7429             }\r
7430         }\r
7431         currentMove = forwardMostMove;\r
7432     }\r
7433 \r
7434     if (instant) return;\r
7435 \r
7436     DisplayMove(currentMove - 1);\r
7437     DrawPosition(FALSE, boards[currentMove]);\r
7438     DisplayBothClocks();\r
7439     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
7440 }\r
7441 \r
7442 void SendEgtPath(ChessProgramState *cps)\r
7443 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */\r
7444         char buf[MSG_SIZ], name[MSG_SIZ], *p;\r
7445 \r
7446         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;\r
7447 \r
7448         while(*p) {\r
7449             char c, *q = name+1, *r, *s;\r
7450 \r
7451             name[0] = ','; // extract next format name from feature and copy with prefixed ','\r
7452             while(*p && *p != ',') *q++ = *p++;\r
7453             *q++ = ':'; *q = 0;\r
7454             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && \r
7455                 strcmp(name, ",nalimov:") == 0 ) {\r
7456                 // take nalimov path from the menu-changeable option first, if it is defined\r
7457                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);\r
7458                 SendToProgram(buf,cps);     // send egtbpath command for nalimov\r
7459             } else\r
7460             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||\r
7461                 (s = StrStr(appData.egtFormats, name)) != NULL) {\r
7462                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma\r
7463                 s = r = StrStr(s, ":") + 1; // beginning of path info\r
7464                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string\r
7465                 c = *r; *r = 0;             // temporarily null-terminate path info\r
7466                     *--q = 0;               // strip of trailig ':' from name\r
7467                     sprintf(buf, "egtbpath %s %s\n", name+1, s);\r
7468                 *r = c;\r
7469                 SendToProgram(buf,cps);     // send egtbpath command for this format\r
7470             }\r
7471             if(*p == ',') p++; // read away comma to position for next format name\r
7472         }\r
7473 }\r
7474 \r
7475 void\r
7476 InitChessProgram(cps, setup)\r
7477      ChessProgramState *cps;\r
7478      int setup; /* [HGM] needed to setup FRC opening position */\r
7479 {\r
7480     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
7481     if (appData.noChessProgram) return;\r
7482     hintRequested = FALSE;\r
7483     bookRequested = FALSE;\r
7484 \r
7485     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */\r
7486     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */\r
7487     if(cps->memSize) { /* [HGM] memory */\r
7488         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);\r
7489         SendToProgram(buf, cps);\r
7490     }\r
7491     SendEgtPath(cps); /* [HGM] EGT */\r
7492     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */\r
7493         sprintf(buf, "cores %d\n", appData.smpCores);\r
7494         SendToProgram(buf, cps);\r
7495     }\r
7496 \r
7497     SendToProgram(cps->initString, cps);\r
7498     if (gameInfo.variant != VariantNormal &&\r
7499         gameInfo.variant != VariantLoadable\r
7500         /* [HGM] also send variant if board size non-standard */\r
7501         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
7502                                             ) {\r
7503       char *v = VariantName(gameInfo.variant);\r
7504       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
7505         /* [HGM] in protocol 1 we have to assume all variants valid */\r
7506         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);\r
7507         DisplayFatalError(buf, 0, 1);\r
7508         return;\r
7509       }\r
7510 \r
7511       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
7512       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7513       if( gameInfo.variant == VariantXiangqi )\r
7514            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
7515       if( gameInfo.variant == VariantShogi )\r
7516            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
7517       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
7518            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
7519       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
7520                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
7521            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7522       if( gameInfo.variant == VariantCourier )\r
7523            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7524       if( gameInfo.variant == VariantSuper )\r
7525            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7526       if( gameInfo.variant == VariantGreat )\r
7527            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7528 \r
7529       if(overruled) {\r
7530            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
7531                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
7532            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
7533            if(StrStr(cps->variants, b) == NULL) { \r
7534                // specific sized variant not known, check if general sizing allowed\r
7535                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
7536                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
7537                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
7538                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
7539                        DisplayFatalError(buf, 0, 1);\r
7540                        return;\r
7541                    }\r
7542                    /* [HGM] here we really should compare with the maximum supported board size */\r
7543                }\r
7544            }\r
7545       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
7546       sprintf(buf, "variant %s\n", b);\r
7547       SendToProgram(buf, cps);\r
7548     }\r
7549     currentlyInitializedVariant = gameInfo.variant;\r
7550 \r
7551     /* [HGM] send opening position in FRC to first engine */\r
7552     if(setup) {\r
7553           SendToProgram("force\n", cps);\r
7554           SendBoard(cps, 0);\r
7555           /* engine is now in force mode! Set flag to wake it up after first move. */\r
7556           setboardSpoiledMachineBlack = 1;\r
7557     }\r
7558 \r
7559     if (cps->sendICS) {\r
7560       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
7561       SendToProgram(buf, cps);\r
7562     }\r
7563     cps->maybeThinking = FALSE;\r
7564     cps->offeredDraw = 0;\r
7565     if (!appData.icsActive) {\r
7566         SendTimeControl(cps, movesPerSession, timeControl,\r
7567                         timeIncrement, appData.searchDepth,\r
7568                         searchTime);\r
7569     }\r
7570     if (appData.showThinking \r
7571         // [HGM] thinking: four options require thinking output to be sent\r
7572         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
7573                                 ) {\r
7574         SendToProgram("post\n", cps);\r
7575     }\r
7576     SendToProgram("hard\n", cps);\r
7577     if (!appData.ponderNextMove) {\r
7578         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
7579            it without being sure what state we are in first.  "hard"\r
7580            is not a toggle, so that one is OK.\r
7581          */\r
7582         SendToProgram("easy\n", cps);\r
7583     }\r
7584     if (cps->usePing) {\r
7585       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
7586       SendToProgram(buf, cps);\r
7587     }\r
7588     cps->initDone = TRUE;\r
7589 }   \r
7590 \r
7591 \r
7592 void\r
7593 StartChessProgram(cps)\r
7594      ChessProgramState *cps;\r
7595 {\r
7596     char buf[MSG_SIZ];\r
7597     int err;\r
7598 \r
7599     if (appData.noChessProgram) return;\r
7600     cps->initDone = FALSE;\r
7601 \r
7602     if (strcmp(cps->host, "localhost") == 0) {\r
7603         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
7604     } else if (*appData.remoteShell == NULLCHAR) {\r
7605         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
7606     } else {\r
7607         if (*appData.remoteUser == NULLCHAR) {\r
7608             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
7609                     cps->program);\r
7610         } else {\r
7611             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
7612                     cps->host, appData.remoteUser, cps->program);\r
7613         }\r
7614         err = StartChildProcess(buf, "", &cps->pr);\r
7615     }\r
7616     \r
7617     if (err != 0) {\r
7618         sprintf(buf, _("Startup failure on '%s'"), cps->program);\r
7619         DisplayFatalError(buf, err, 1);\r
7620         cps->pr = NoProc;\r
7621         cps->isr = NULL;\r
7622         return;\r
7623     }\r
7624     \r
7625     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
7626     if (cps->protocolVersion > 1) {\r
7627       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
7628       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options\r
7629       cps->comboCnt = 0;  //                and values of combo boxes\r
7630       SendToProgram(buf, cps);\r
7631     } else {\r
7632       SendToProgram("xboard\n", cps);\r
7633     }\r
7634 }\r
7635 \r
7636 \r
7637 void\r
7638 TwoMachinesEventIfReady P((void))\r
7639 {\r
7640   if (first.lastPing != first.lastPong) {\r
7641     DisplayMessage("", _("Waiting for first chess program"));\r
7642     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7643     return;\r
7644   }\r
7645   if (second.lastPing != second.lastPong) {\r
7646     DisplayMessage("", _("Waiting for second chess program"));\r
7647     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7648     return;\r
7649   }\r
7650   ThawUI();\r
7651   TwoMachinesEvent();\r
7652 }\r
7653 \r
7654 void\r
7655 NextMatchGame P((void))\r
7656 {\r
7657     int index; /* [HGM] autoinc: step lod index during match */\r
7658     Reset(FALSE, TRUE);\r
7659     if (*appData.loadGameFile != NULLCHAR) {\r
7660         index = appData.loadGameIndex;\r
7661         if(index < 0) { // [HGM] autoinc\r
7662             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7663             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7664         } \r
7665         LoadGameFromFile(appData.loadGameFile,\r
7666                          index,\r
7667                          appData.loadGameFile, FALSE);\r
7668     } else if (*appData.loadPositionFile != NULLCHAR) {\r
7669         index = appData.loadPositionIndex;\r
7670         if(index < 0) { // [HGM] autoinc\r
7671             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7672             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7673         } \r
7674         LoadPositionFromFile(appData.loadPositionFile,\r
7675                              index,\r
7676                              appData.loadPositionFile);\r
7677     }\r
7678     TwoMachinesEventIfReady();\r
7679 }\r
7680 \r
7681 void UserAdjudicationEvent( int result )\r
7682 {\r
7683     ChessMove gameResult = GameIsDrawn;\r
7684 \r
7685     if( result > 0 ) {\r
7686         gameResult = WhiteWins;\r
7687     }\r
7688     else if( result < 0 ) {\r
7689         gameResult = BlackWins;\r
7690     }\r
7691 \r
7692     if( gameMode == TwoMachinesPlay ) {\r
7693         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
7694     }\r
7695 }\r
7696 \r
7697 \r
7698 void\r
7699 GameEnds(result, resultDetails, whosays)\r
7700      ChessMove result;\r
7701      char *resultDetails;\r
7702      int whosays;\r
7703 {\r
7704     GameMode nextGameMode;\r
7705     int isIcsGame;\r
7706     char buf[MSG_SIZ];\r
7707 \r
7708     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
7709     endingGame = 1;\r
7710 \r
7711     if (appData.debugMode) {\r
7712       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
7713               result, resultDetails ? resultDetails : "(null)", whosays);\r
7714     }\r
7715 \r
7716     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
7717         /* If we are playing on ICS, the server decides when the\r
7718            game is over, but the engine can offer to draw, claim \r
7719            a draw, or resign. \r
7720          */\r
7721 #if ZIPPY\r
7722         if (appData.zippyPlay && first.initDone) {\r
7723             if (result == GameIsDrawn) {\r
7724                 /* In case draw still needs to be claimed */\r
7725                 SendToICS(ics_prefix);\r
7726                 SendToICS("draw\n");\r
7727             } else if (StrCaseStr(resultDetails, "resign")) {\r
7728                 SendToICS(ics_prefix);\r
7729                 SendToICS("resign\n");\r
7730             }\r
7731         }\r
7732 #endif\r
7733         endingGame = 0; /* [HGM] crash */\r
7734         return;\r
7735     }\r
7736 \r
7737     /* If we're loading the game from a file, stop */\r
7738     if (whosays == GE_FILE) {\r
7739       (void) StopLoadGameTimer();\r
7740       gameFileFP = NULL;\r
7741     }\r
7742 \r
7743     /* Cancel draw offers */\r
7744     first.offeredDraw = second.offeredDraw = 0;\r
7745 \r
7746     /* If this is an ICS game, only ICS can really say it's done;\r
7747        if not, anyone can. */\r
7748     isIcsGame = (gameMode == IcsPlayingWhite || \r
7749                  gameMode == IcsPlayingBlack || \r
7750                  gameMode == IcsObserving    || \r
7751                  gameMode == IcsExamining);\r
7752 \r
7753     if (!isIcsGame || whosays == GE_ICS) {\r
7754         /* OK -- not an ICS game, or ICS said it was done */\r
7755         StopClocks();\r
7756         if (!isIcsGame && !appData.noChessProgram) \r
7757           SetUserThinkingEnables();\r
7758     \r
7759         /* [HGM] if a machine claims the game end we verify this claim */\r
7760         if(gameMode == TwoMachinesPlay && appData.testClaims) {\r
7761             if(appData.testLegality && whosays >= GE_ENGINE1 ) {\r
7762                 char claimer;\r
7763                 ChessMove trueResult = (ChessMove) -1;\r
7764 \r
7765                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
7766                                             first.twoMachinesColor[0] :\r
7767                                             second.twoMachinesColor[0] ;\r
7768 \r
7769                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first\r
7770                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {\r
7771                     /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7772                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;\r
7773                 } else\r
7774                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win\r
7775                     /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7776                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;\r
7777                 } else\r
7778                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now\r
7779                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE\r
7780                 }\r
7781 \r
7782                 // now verify win claims, but not in drop games, as we don't understand those yet\r
7783                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper\r
7784                                                  || gameInfo.variant == VariantGreat) &&\r
7785                     (result == WhiteWins && claimer == 'w' ||\r
7786                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win\r
7787                       if (appData.debugMode) {\r
7788                         fprintf(debugFP, "result=%d sp=%d move=%d\n",\r
7789                                 result, epStatus[forwardMostMove], forwardMostMove);\r
7790                       }\r
7791                       if(result != trueResult) {\r
7792                               sprintf(buf, "False win claim: '%s'", resultDetails);\r
7793                               result = claimer == 'w' ? BlackWins : WhiteWins;\r
7794                               resultDetails = buf;\r
7795                       }\r
7796                 } else\r
7797                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
7798                     && (forwardMostMove <= backwardMostMove ||\r
7799                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
7800                         (claimer=='b')==(forwardMostMove&1))\r
7801                                                                                   ) {\r
7802                       /* [HGM] verify: draws that were not flagged are false claims */\r
7803                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
7804                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7805                       resultDetails = buf;\r
7806                 }\r
7807                 /* (Claiming a loss is accepted no questions asked!) */\r
7808             }\r
7809             /* [HGM] bare: don't allow bare King to win */\r
7810             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)\r
7811                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway \r
7812                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...\r
7813                && result != GameIsDrawn)\r
7814             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
7815                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
7816                         int p = (int)boards[forwardMostMove][i][j] - color;\r
7817                         if(p >= 0 && p <= (int)WhiteKing) k++;\r
7818                 }\r
7819                 if (appData.debugMode) {\r
7820                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",\r
7821                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);\r
7822                 }\r
7823                 if(k <= 1) {\r
7824                         result = GameIsDrawn;\r
7825                         sprintf(buf, "%s but bare king", resultDetails);\r
7826                         resultDetails = buf;\r
7827                 }\r
7828             }\r
7829         }\r
7830 \r
7831 \r
7832         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
7833             if(result==WhiteWins) c = '+';\r
7834             if(result==BlackWins) c = '-';\r
7835             if(resultDetails != NULL)\r
7836                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
7837         }\r
7838         if (resultDetails != NULL) {\r
7839             gameInfo.result = result;\r
7840             gameInfo.resultDetails = StrSave(resultDetails);\r
7841 \r
7842             /* display last move only if game was not loaded from file */\r
7843             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7844                 DisplayMove(currentMove - 1);\r
7845     \r
7846             if (forwardMostMove != 0) {\r
7847                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7848                     if (*appData.saveGameFile != NULLCHAR) {\r
7849                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7850                     } else if (appData.autoSaveGames) {\r
7851                         AutoSaveGame();\r
7852                     }\r
7853                     if (*appData.savePositionFile != NULLCHAR) {\r
7854                         SavePositionToFile(appData.savePositionFile);\r
7855                     }\r
7856                 }\r
7857             }\r
7858 \r
7859             /* Tell program how game ended in case it is learning */\r
7860             /* [HGM] Moved this to after saving the PGN, just in case */\r
7861             /* engine died and we got here through time loss. In that */\r
7862             /* case we will get a fatal error writing the pipe, which */\r
7863             /* would otherwise lose us the PGN.                       */\r
7864             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7865             /* output during GameEnds should never be fatal anymore   */\r
7866             if (gameMode == MachinePlaysWhite ||\r
7867                 gameMode == MachinePlaysBlack ||\r
7868                 gameMode == TwoMachinesPlay ||\r
7869                 gameMode == IcsPlayingWhite ||\r
7870                 gameMode == IcsPlayingBlack ||\r
7871                 gameMode == BeginningOfGame) {\r
7872                 char buf[MSG_SIZ];\r
7873                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7874                         resultDetails);\r
7875                 if (first.pr != NoProc) {\r
7876                     SendToProgram(buf, &first);\r
7877                 }\r
7878                 if (second.pr != NoProc &&\r
7879                     gameMode == TwoMachinesPlay) {\r
7880                     SendToProgram(buf, &second);\r
7881                 }\r
7882             }\r
7883         }\r
7884 \r
7885         if (appData.icsActive) {\r
7886             if (appData.quietPlay &&\r
7887                 (gameMode == IcsPlayingWhite ||\r
7888                  gameMode == IcsPlayingBlack)) {\r
7889                 SendToICS(ics_prefix);\r
7890                 SendToICS("set shout 1\n");\r
7891             }\r
7892             nextGameMode = IcsIdle;\r
7893             ics_user_moved = FALSE;\r
7894             /* clean up premove.  It's ugly when the game has ended and the\r
7895              * premove highlights are still on the board.\r
7896              */\r
7897             if (gotPremove) {\r
7898               gotPremove = FALSE;\r
7899               ClearPremoveHighlights();\r
7900               DrawPosition(FALSE, boards[currentMove]);\r
7901             }\r
7902             if (whosays == GE_ICS) {\r
7903                 switch (result) {\r
7904                 case WhiteWins:\r
7905                     if (gameMode == IcsPlayingWhite)\r
7906                         PlayIcsWinSound();\r
7907                     else if(gameMode == IcsPlayingBlack)\r
7908                         PlayIcsLossSound();\r
7909                     break;\r
7910                 case BlackWins:\r
7911                     if (gameMode == IcsPlayingBlack)\r
7912                         PlayIcsWinSound();\r
7913                     else if(gameMode == IcsPlayingWhite)\r
7914                         PlayIcsLossSound();\r
7915                     break;\r
7916                 case GameIsDrawn:\r
7917                     PlayIcsDrawSound();\r
7918                     break;\r
7919                 default:\r
7920                     PlayIcsUnfinishedSound();\r
7921                 }\r
7922             }\r
7923         } else if (gameMode == EditGame ||\r
7924                    gameMode == PlayFromGameFile || \r
7925                    gameMode == AnalyzeMode || \r
7926                    gameMode == AnalyzeFile) {\r
7927             nextGameMode = gameMode;\r
7928         } else {\r
7929             nextGameMode = EndOfGame;\r
7930         }\r
7931         pausing = FALSE;\r
7932         ModeHighlight();\r
7933     } else {\r
7934         nextGameMode = gameMode;\r
7935     }\r
7936 \r
7937     if (appData.noChessProgram) {\r
7938         gameMode = nextGameMode;\r
7939         ModeHighlight();\r
7940         endingGame = 0; /* [HGM] crash */\r
7941         return;\r
7942     }\r
7943 \r
7944     if (first.reuse) {\r
7945         /* Put first chess program into idle state */\r
7946         if (first.pr != NoProc &&\r
7947             (gameMode == MachinePlaysWhite ||\r
7948              gameMode == MachinePlaysBlack ||\r
7949              gameMode == TwoMachinesPlay ||\r
7950              gameMode == IcsPlayingWhite ||\r
7951              gameMode == IcsPlayingBlack ||\r
7952              gameMode == BeginningOfGame)) {\r
7953             SendToProgram("force\n", &first);\r
7954             if (first.usePing) {\r
7955               char buf[MSG_SIZ];\r
7956               sprintf(buf, "ping %d\n", ++first.lastPing);\r
7957               SendToProgram(buf, &first);\r
7958             }\r
7959         }\r
7960     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7961         /* Kill off first chess program */\r
7962         if (first.isr != NULL)\r
7963           RemoveInputSource(first.isr);\r
7964         first.isr = NULL;\r
7965     \r
7966         if (first.pr != NoProc) {\r
7967             ExitAnalyzeMode();\r
7968             DoSleep( appData.delayBeforeQuit );\r
7969             SendToProgram("quit\n", &first);\r
7970             DoSleep( appData.delayAfterQuit );\r
7971             DestroyChildProcess(first.pr, first.useSigterm);\r
7972         }\r
7973         first.pr = NoProc;\r
7974     }\r
7975     if (second.reuse) {\r
7976         /* Put second chess program into idle state */\r
7977         if (second.pr != NoProc &&\r
7978             gameMode == TwoMachinesPlay) {\r
7979             SendToProgram("force\n", &second);\r
7980             if (second.usePing) {\r
7981               char buf[MSG_SIZ];\r
7982               sprintf(buf, "ping %d\n", ++second.lastPing);\r
7983               SendToProgram(buf, &second);\r
7984             }\r
7985         }\r
7986     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7987         /* Kill off second chess program */\r
7988         if (second.isr != NULL)\r
7989           RemoveInputSource(second.isr);\r
7990         second.isr = NULL;\r
7991     \r
7992         if (second.pr != NoProc) {\r
7993             DoSleep( appData.delayBeforeQuit );\r
7994             SendToProgram("quit\n", &second);\r
7995             DoSleep( appData.delayAfterQuit );\r
7996             DestroyChildProcess(second.pr, second.useSigterm);\r
7997         }\r
7998         second.pr = NoProc;\r
7999     }\r
8000 \r
8001     if (matchMode && gameMode == TwoMachinesPlay) {\r
8002         switch (result) {\r
8003         case WhiteWins:\r
8004           if (first.twoMachinesColor[0] == 'w') {\r
8005             first.matchWins++;\r
8006           } else {\r
8007             second.matchWins++;\r
8008           }\r
8009           break;\r
8010         case BlackWins:\r
8011           if (first.twoMachinesColor[0] == 'b') {\r
8012             first.matchWins++;\r
8013           } else {\r
8014             second.matchWins++;\r
8015           }\r
8016           break;\r
8017         default:\r
8018           break;\r
8019         }\r
8020         if (matchGame < appData.matchGames) {\r
8021             char *tmp;\r
8022             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */\r
8023                 tmp = first.twoMachinesColor;\r
8024                 first.twoMachinesColor = second.twoMachinesColor;\r
8025                 second.twoMachinesColor = tmp;\r
8026             }\r
8027             gameMode = nextGameMode;\r
8028             matchGame++;\r
8029             if(appData.matchPause>10000 || appData.matchPause<10)\r
8030                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
8031             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
8032             endingGame = 0; /* [HGM] crash */\r
8033             return;\r
8034         } else {\r
8035             char buf[MSG_SIZ];\r
8036             gameMode = nextGameMode;\r
8037             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),\r
8038                     first.tidy, second.tidy,\r
8039                     first.matchWins, second.matchWins,\r
8040                     appData.matchGames - (first.matchWins + second.matchWins));\r
8041             DisplayFatalError(buf, 0, 0);\r
8042         }\r
8043     }\r
8044     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
8045         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
8046       ExitAnalyzeMode();\r
8047     gameMode = nextGameMode;\r
8048     ModeHighlight();\r
8049     endingGame = 0;  /* [HGM] crash */\r
8050 }\r
8051 \r
8052 /* Assumes program was just initialized (initString sent).\r
8053    Leaves program in force mode. */\r
8054 void\r
8055 FeedMovesToProgram(cps, upto) \r
8056      ChessProgramState *cps;\r
8057      int upto;\r
8058 {\r
8059     int i;\r
8060     \r
8061     if (appData.debugMode)\r
8062       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
8063               startedFromSetupPosition ? "position and " : "",\r
8064               backwardMostMove, upto, cps->which);\r
8065     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
8066         // [HGM] variantswitch: make engine aware of new variant\r
8067         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
8068                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
8069         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
8070         SendToProgram(buf, cps);\r
8071         currentlyInitializedVariant = gameInfo.variant;\r
8072     }\r
8073     SendToProgram("force\n", cps);\r
8074     if (startedFromSetupPosition) {\r
8075         SendBoard(cps, backwardMostMove);\r
8076     if (appData.debugMode) {\r
8077         fprintf(debugFP, "feedMoves\n");\r
8078     }\r
8079     }\r
8080     for (i = backwardMostMove; i < upto; i++) {\r
8081         SendMoveToProgram(i, cps);\r
8082     }\r
8083 }\r
8084 \r
8085 \r
8086 void\r
8087 ResurrectChessProgram()\r
8088 {\r
8089      /* The chess program may have exited.\r
8090         If so, restart it and feed it all the moves made so far. */\r
8091 \r
8092     if (appData.noChessProgram || first.pr != NoProc) return;\r
8093     \r
8094     StartChessProgram(&first);\r
8095     InitChessProgram(&first, FALSE);\r
8096     FeedMovesToProgram(&first, currentMove);\r
8097 \r
8098     if (!first.sendTime) {\r
8099         /* can't tell gnuchess what its clock should read,\r
8100            so we bow to its notion. */\r
8101         ResetClocks();\r
8102         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
8103         timeRemaining[1][currentMove] = blackTimeRemaining;\r
8104     }\r
8105 \r
8106     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||\r
8107                 appData.icsEngineAnalyze) && first.analysisSupport) {\r
8108       SendToProgram("analyze\n", &first);\r
8109       first.analyzing = TRUE;\r
8110     }\r
8111 }\r
8112 \r
8113 /*\r
8114  * Button procedures\r
8115  */\r
8116 void\r
8117 Reset(redraw, init)\r
8118      int redraw, init;\r
8119 {\r
8120     int i;\r
8121 \r
8122     if (appData.debugMode) {\r
8123         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
8124                 redraw, init, gameMode);\r
8125     }\r
8126     pausing = pauseExamInvalid = FALSE;\r
8127     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
8128     firstMove = TRUE;\r
8129     whiteFlag = blackFlag = FALSE;\r
8130     userOfferedDraw = FALSE;\r
8131     hintRequested = bookRequested = FALSE;\r
8132     first.maybeThinking = FALSE;\r
8133     second.maybeThinking = FALSE;\r
8134     first.bookSuspend = FALSE; // [HGM] book\r
8135     second.bookSuspend = FALSE;\r
8136     thinkOutput[0] = NULLCHAR;\r
8137     lastHint[0] = NULLCHAR;\r
8138     ClearGameInfo(&gameInfo);\r
8139     gameInfo.variant = StringToVariant(appData.variant);\r
8140     ics_user_moved = ics_clock_paused = FALSE;\r
8141     ics_getting_history = H_FALSE;\r
8142     ics_gamenum = -1;\r
8143     white_holding[0] = black_holding[0] = NULLCHAR;\r
8144     ClearProgramStats();\r
8145     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode\r
8146     \r
8147     ResetFrontEnd();\r
8148     ClearHighlights();\r
8149     flipView = appData.flipView;\r
8150     ClearPremoveHighlights();\r
8151     gotPremove = FALSE;\r
8152     alarmSounded = FALSE;\r
8153 \r
8154     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8155     if(appData.serverMovesName != NULL) {\r
8156         /* [HGM] prepare to make moves file for broadcasting */\r
8157         clock_t t = clock();\r
8158         if(serverMoves != NULL) fclose(serverMoves);\r
8159         serverMoves = fopen(appData.serverMovesName, "r");\r
8160         if(serverMoves != NULL) {\r
8161             fclose(serverMoves);\r
8162             /* delay 15 sec before overwriting, so all clients can see end */\r
8163             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
8164         }\r
8165         serverMoves = fopen(appData.serverMovesName, "w");\r
8166     }\r
8167 \r
8168     ExitAnalyzeMode();\r
8169     gameMode = BeginningOfGame;\r
8170     ModeHighlight();\r
8171     if(appData.icsActive) gameInfo.variant = VariantNormal;\r
8172     InitPosition(redraw);\r
8173     for (i = 0; i < MAX_MOVES; i++) {\r
8174         if (commentList[i] != NULL) {\r
8175             free(commentList[i]);\r
8176             commentList[i] = NULL;\r
8177         }\r
8178     }\r
8179     ResetClocks();\r
8180     timeRemaining[0][0] = whiteTimeRemaining;\r
8181     timeRemaining[1][0] = blackTimeRemaining;\r
8182     if (first.pr == NULL) {\r
8183         StartChessProgram(&first);\r
8184     }\r
8185     if (init) {\r
8186             InitChessProgram(&first, startedFromSetupPosition);\r
8187     }\r
8188     DisplayTitle("");\r
8189     DisplayMessage("", "");\r
8190     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8191 }\r
8192 \r
8193 void\r
8194 AutoPlayGameLoop()\r
8195 {\r
8196     for (;;) {\r
8197         if (!AutoPlayOneMove())\r
8198           return;\r
8199         if (matchMode || appData.timeDelay == 0)\r
8200           continue;\r
8201         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
8202           return;\r
8203         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
8204         break;\r
8205     }\r
8206 }\r
8207 \r
8208 \r
8209 int\r
8210 AutoPlayOneMove()\r
8211 {\r
8212     int fromX, fromY, toX, toY;\r
8213 \r
8214     if (appData.debugMode) {\r
8215       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
8216     }\r
8217 \r
8218     if (gameMode != PlayFromGameFile)\r
8219       return FALSE;\r
8220 \r
8221     if (currentMove >= forwardMostMove) {\r
8222       gameMode = EditGame;\r
8223       ModeHighlight();\r
8224 \r
8225       /* [AS] Clear current move marker at the end of a game */\r
8226       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
8227 \r
8228       return FALSE;\r
8229     }\r
8230     \r
8231     toX = moveList[currentMove][2] - AAA;\r
8232     toY = moveList[currentMove][3] - ONE;\r
8233 \r
8234     if (moveList[currentMove][1] == '@') {\r
8235         if (appData.highlightLastMove) {\r
8236             SetHighlights(-1, -1, toX, toY);\r
8237         }\r
8238     } else {\r
8239         fromX = moveList[currentMove][0] - AAA;\r
8240         fromY = moveList[currentMove][1] - ONE;\r
8241 \r
8242         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
8243 \r
8244         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
8245 \r
8246         if (appData.highlightLastMove) {\r
8247             SetHighlights(fromX, fromY, toX, toY);\r
8248         }\r
8249     }\r
8250     DisplayMove(currentMove);\r
8251     SendMoveToProgram(currentMove++, &first);\r
8252     DisplayBothClocks();\r
8253     DrawPosition(FALSE, boards[currentMove]);\r
8254     // [HGM] PV info: always display, routine tests if empty\r
8255     DisplayComment(currentMove - 1, commentList[currentMove]);\r
8256     return TRUE;\r
8257 }\r
8258 \r
8259 \r
8260 int\r
8261 LoadGameOneMove(readAhead)\r
8262      ChessMove readAhead;\r
8263 {\r
8264     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
8265     char promoChar = NULLCHAR;\r
8266     ChessMove moveType;\r
8267     char move[MSG_SIZ];\r
8268     char *p, *q;\r
8269     \r
8270     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
8271         gameMode != AnalyzeMode && gameMode != Training) {\r
8272         gameFileFP = NULL;\r
8273         return FALSE;\r
8274     }\r
8275     \r
8276     yyboardindex = forwardMostMove;\r
8277     if (readAhead != (ChessMove)0) {\r
8278       moveType = readAhead;\r
8279     } else {\r
8280       if (gameFileFP == NULL)\r
8281           return FALSE;\r
8282       moveType = (ChessMove) yylex();\r
8283     }\r
8284     \r
8285     done = FALSE;\r
8286     switch (moveType) {\r
8287       case Comment:\r
8288         if (appData.debugMode) \r
8289           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8290         p = yy_text;\r
8291         if (*p == '{' || *p == '[' || *p == '(') {\r
8292             p[strlen(p) - 1] = NULLCHAR;\r
8293             p++;\r
8294         }\r
8295 \r
8296         /* append the comment but don't display it */\r
8297         while (*p == '\n') p++;\r
8298         AppendComment(currentMove, p);\r
8299         return TRUE;\r
8300 \r
8301       case WhiteCapturesEnPassant:\r
8302       case BlackCapturesEnPassant:\r
8303       case WhitePromotionChancellor:\r
8304       case BlackPromotionChancellor:\r
8305       case WhitePromotionArchbishop:\r
8306       case BlackPromotionArchbishop:\r
8307       case WhitePromotionCentaur:\r
8308       case BlackPromotionCentaur:\r
8309       case WhitePromotionQueen:\r
8310       case BlackPromotionQueen:\r
8311       case WhitePromotionRook:\r
8312       case BlackPromotionRook:\r
8313       case WhitePromotionBishop:\r
8314       case BlackPromotionBishop:\r
8315       case WhitePromotionKnight:\r
8316       case BlackPromotionKnight:\r
8317       case WhitePromotionKing:\r
8318       case BlackPromotionKing:\r
8319       case NormalMove:\r
8320       case WhiteKingSideCastle:\r
8321       case WhiteQueenSideCastle:\r
8322       case BlackKingSideCastle:\r
8323       case BlackQueenSideCastle:\r
8324       case WhiteKingSideCastleWild:\r
8325       case WhiteQueenSideCastleWild:\r
8326       case BlackKingSideCastleWild:\r
8327       case BlackQueenSideCastleWild:\r
8328       /* PUSH Fabien */\r
8329       case WhiteHSideCastleFR:\r
8330       case WhiteASideCastleFR:\r
8331       case BlackHSideCastleFR:\r
8332       case BlackASideCastleFR:\r
8333       /* POP Fabien */\r
8334         if (appData.debugMode)\r
8335           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8336         fromX = currentMoveString[0] - AAA;\r
8337         fromY = currentMoveString[1] - ONE;\r
8338         toX = currentMoveString[2] - AAA;\r
8339         toY = currentMoveString[3] - ONE;\r
8340         promoChar = currentMoveString[4];\r
8341         break;\r
8342 \r
8343       case WhiteDrop:\r
8344       case BlackDrop:\r
8345         if (appData.debugMode)\r
8346           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8347         fromX = moveType == WhiteDrop ?\r
8348           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
8349         (int) CharToPiece(ToLower(currentMoveString[0]));\r
8350         fromY = DROP_RANK;\r
8351         toX = currentMoveString[2] - AAA;\r
8352         toY = currentMoveString[3] - ONE;\r
8353         break;\r
8354 \r
8355       case WhiteWins:\r
8356       case BlackWins:\r
8357       case GameIsDrawn:\r
8358       case GameUnfinished:\r
8359         if (appData.debugMode)\r
8360           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
8361         p = strchr(yy_text, '{');\r
8362         if (p == NULL) p = strchr(yy_text, '(');\r
8363         if (p == NULL) {\r
8364             p = yy_text;\r
8365             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
8366         } else {\r
8367             q = strchr(p, *p == '{' ? '}' : ')');\r
8368             if (q != NULL) *q = NULLCHAR;\r
8369             p++;\r
8370         }\r
8371         GameEnds(moveType, p, GE_FILE);\r
8372         done = TRUE;\r
8373         if (cmailMsgLoaded) {\r
8374             ClearHighlights();\r
8375             flipView = WhiteOnMove(currentMove);\r
8376             if (moveType == GameUnfinished) flipView = !flipView;\r
8377             if (appData.debugMode)\r
8378               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
8379         }\r
8380         break;\r
8381 \r
8382       case (ChessMove) 0:       /* end of file */\r
8383         if (appData.debugMode)\r
8384           fprintf(debugFP, "Parser hit end of file\n");\r
8385         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8386                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8387           case MT_NONE:\r
8388           case MT_CHECK:\r
8389             break;\r
8390           case MT_CHECKMATE:\r
8391           case MT_STAINMATE:\r
8392             if (WhiteOnMove(currentMove)) {\r
8393                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8394             } else {\r
8395                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8396             }\r
8397             break;\r
8398           case MT_STALEMATE:\r
8399             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8400             break;\r
8401         }\r
8402         done = TRUE;\r
8403         break;\r
8404 \r
8405       case MoveNumberOne:\r
8406         if (lastLoadGameStart == GNUChessGame) {\r
8407             /* GNUChessGames have numbers, but they aren't move numbers */\r
8408             if (appData.debugMode)\r
8409               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8410                       yy_text, (int) moveType);\r
8411             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8412         }\r
8413         /* else fall thru */\r
8414 \r
8415       case XBoardGame:\r
8416       case GNUChessGame:\r
8417       case PGNTag:\r
8418         /* Reached start of next game in file */\r
8419         if (appData.debugMode)\r
8420           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
8421         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8422                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8423           case MT_NONE:\r
8424           case MT_CHECK:\r
8425             break;\r
8426           case MT_CHECKMATE:\r
8427           case MT_STAINMATE:\r
8428             if (WhiteOnMove(currentMove)) {\r
8429                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8430             } else {\r
8431                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8432             }\r
8433             break;\r
8434           case MT_STALEMATE:\r
8435             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8436             break;\r
8437         }\r
8438         done = TRUE;\r
8439         break;\r
8440 \r
8441       case PositionDiagram:     /* should not happen; ignore */\r
8442       case ElapsedTime:         /* ignore */\r
8443       case NAG:                 /* ignore */\r
8444         if (appData.debugMode)\r
8445           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8446                   yy_text, (int) moveType);\r
8447         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8448 \r
8449       case IllegalMove:\r
8450         if (appData.testLegality) {\r
8451             if (appData.debugMode)\r
8452               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
8453             sprintf(move, _("Illegal move: %d.%s%s"),\r
8454                     (forwardMostMove / 2) + 1,\r
8455                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8456             DisplayError(move, 0);\r
8457             done = TRUE;\r
8458         } else {\r
8459             if (appData.debugMode)\r
8460               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
8461                       yy_text, currentMoveString);\r
8462             fromX = currentMoveString[0] - AAA;\r
8463             fromY = currentMoveString[1] - ONE;\r
8464             toX = currentMoveString[2] - AAA;\r
8465             toY = currentMoveString[3] - ONE;\r
8466             promoChar = currentMoveString[4];\r
8467         }\r
8468         break;\r
8469 \r
8470       case AmbiguousMove:\r
8471         if (appData.debugMode)\r
8472           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
8473         sprintf(move, _("Ambiguous move: %d.%s%s"),\r
8474                 (forwardMostMove / 2) + 1,\r
8475                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8476         DisplayError(move, 0);\r
8477         done = TRUE;\r
8478         break;\r
8479 \r
8480       default:\r
8481       case ImpossibleMove:\r
8482         if (appData.debugMode)\r
8483           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);\r
8484         sprintf(move, _("Illegal move: %d.%s%s"),\r
8485                 (forwardMostMove / 2) + 1,\r
8486                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8487         DisplayError(move, 0);\r
8488         done = TRUE;\r
8489         break;\r
8490     }\r
8491 \r
8492     if (done) {\r
8493         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
8494             DrawPosition(FALSE, boards[currentMove]);\r
8495             DisplayBothClocks();\r
8496             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
8497               DisplayComment(currentMove - 1, commentList[currentMove]);\r
8498         }\r
8499         (void) StopLoadGameTimer();\r
8500         gameFileFP = NULL;\r
8501         cmailOldMove = forwardMostMove;\r
8502         return FALSE;\r
8503     } else {\r
8504         /* currentMoveString is set as a side-effect of yylex */\r
8505         strcat(currentMoveString, "\n");\r
8506         strcpy(moveList[forwardMostMove], currentMoveString);\r
8507         \r
8508         thinkOutput[0] = NULLCHAR;\r
8509         MakeMove(fromX, fromY, toX, toY, promoChar);\r
8510         currentMove = forwardMostMove;\r
8511         return TRUE;\r
8512     }\r
8513 }\r
8514 \r
8515 /* Load the nth game from the given file */\r
8516 int\r
8517 LoadGameFromFile(filename, n, title, useList)\r
8518      char *filename;\r
8519      int n;\r
8520      char *title;\r
8521      /*Boolean*/ int useList;\r
8522 {\r
8523     FILE *f;\r
8524     char buf[MSG_SIZ];\r
8525 \r
8526     if (strcmp(filename, "-") == 0) {\r
8527         f = stdin;\r
8528         title = "stdin";\r
8529     } else {\r
8530         f = fopen(filename, "rb");\r
8531         if (f == NULL) {\r
8532             sprintf(buf, _("Can't open \"%s\""), filename);\r
8533             DisplayError(buf, errno);\r
8534             return FALSE;\r
8535         }\r
8536     }\r
8537     if (fseek(f, 0, 0) == -1) {\r
8538         /* f is not seekable; probably a pipe */\r
8539         useList = FALSE;\r
8540     }\r
8541     if (useList && n == 0) {\r
8542         int error = GameListBuild(f);\r
8543         if (error) {\r
8544             DisplayError(_("Cannot build game list"), error);\r
8545         } else if (!ListEmpty(&gameList) &&\r
8546                    ((ListGame *) gameList.tailPred)->number > 1) {\r
8547             GameListPopUp(f, title);\r
8548             return TRUE;\r
8549         }\r
8550         GameListDestroy();\r
8551         n = 1;\r
8552     }\r
8553     if (n == 0) n = 1;\r
8554     return LoadGame(f, n, title, FALSE);\r
8555 }\r
8556 \r
8557 \r
8558 void\r
8559 MakeRegisteredMove()\r
8560 {\r
8561     int fromX, fromY, toX, toY;\r
8562     char promoChar;\r
8563     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8564         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
8565           case CMAIL_MOVE:\r
8566           case CMAIL_DRAW:\r
8567             if (appData.debugMode)\r
8568               fprintf(debugFP, "Restoring %s for game %d\n",\r
8569                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8570     \r
8571             thinkOutput[0] = NULLCHAR;\r
8572             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
8573             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
8574             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
8575             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
8576             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
8577             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
8578             MakeMove(fromX, fromY, toX, toY, promoChar);\r
8579             ShowMove(fromX, fromY, toX, toY);\r
8580               \r
8581             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8582                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8583               case MT_NONE:\r
8584               case MT_CHECK:\r
8585                 break;\r
8586                 \r
8587               case MT_CHECKMATE:\r
8588               case MT_STAINMATE:\r
8589                 if (WhiteOnMove(currentMove)) {\r
8590                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
8591                 } else {\r
8592                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
8593                 }\r
8594                 break;\r
8595                 \r
8596               case MT_STALEMATE:\r
8597                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
8598                 break;\r
8599             }\r
8600 \r
8601             break;\r
8602             \r
8603           case CMAIL_RESIGN:\r
8604             if (WhiteOnMove(currentMove)) {\r
8605                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
8606             } else {\r
8607                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
8608             }\r
8609             break;\r
8610             \r
8611           case CMAIL_ACCEPT:\r
8612             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
8613             break;\r
8614               \r
8615           default:\r
8616             break;\r
8617         }\r
8618     }\r
8619 \r
8620     return;\r
8621 }\r
8622 \r
8623 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
8624 int\r
8625 CmailLoadGame(f, gameNumber, title, useList)\r
8626      FILE *f;\r
8627      int gameNumber;\r
8628      char *title;\r
8629      int useList;\r
8630 {\r
8631     int retVal;\r
8632 \r
8633     if (gameNumber > nCmailGames) {\r
8634         DisplayError(_("No more games in this message"), 0);\r
8635         return FALSE;\r
8636     }\r
8637     if (f == lastLoadGameFP) {\r
8638         int offset = gameNumber - lastLoadGameNumber;\r
8639         if (offset == 0) {\r
8640             cmailMsg[0] = NULLCHAR;\r
8641             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8642                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8643                 nCmailMovesRegistered--;\r
8644             }\r
8645             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
8646             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
8647                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
8648             }\r
8649         } else {\r
8650             if (! RegisterMove()) return FALSE;\r
8651         }\r
8652     }\r
8653 \r
8654     retVal = LoadGame(f, gameNumber, title, useList);\r
8655 \r
8656     /* Make move registered during previous look at this game, if any */\r
8657     MakeRegisteredMove();\r
8658 \r
8659     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
8660         commentList[currentMove]\r
8661           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
8662         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8663     }\r
8664 \r
8665     return retVal;\r
8666 }\r
8667 \r
8668 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
8669 int\r
8670 ReloadGame(offset)\r
8671      int offset;\r
8672 {\r
8673     int gameNumber = lastLoadGameNumber + offset;\r
8674     if (lastLoadGameFP == NULL) {\r
8675         DisplayError(_("No game has been loaded yet"), 0);\r
8676         return FALSE;\r
8677     }\r
8678     if (gameNumber <= 0) {\r
8679         DisplayError(_("Can't back up any further"), 0);\r
8680         return FALSE;\r
8681     }\r
8682     if (cmailMsgLoaded) {\r
8683         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
8684                              lastLoadGameTitle, lastLoadGameUseList);\r
8685     } else {\r
8686         return LoadGame(lastLoadGameFP, gameNumber,\r
8687                         lastLoadGameTitle, lastLoadGameUseList);\r
8688     }\r
8689 }\r
8690 \r
8691 \r
8692 \r
8693 /* Load the nth game from open file f */\r
8694 int\r
8695 LoadGame(f, gameNumber, title, useList)\r
8696      FILE *f;\r
8697      int gameNumber;\r
8698      char *title;\r
8699      int useList;\r
8700 {\r
8701     ChessMove cm;\r
8702     char buf[MSG_SIZ];\r
8703     int gn = gameNumber;\r
8704     ListGame *lg = NULL;\r
8705     int numPGNTags = 0;\r
8706     int err;\r
8707     GameMode oldGameMode;\r
8708     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
8709 \r
8710     if (appData.debugMode) \r
8711         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
8712 \r
8713     if (gameMode == Training )\r
8714         SetTrainingModeOff();\r
8715 \r
8716     oldGameMode = gameMode;\r
8717     if (gameMode != BeginningOfGame) {\r
8718       Reset(FALSE, TRUE);\r
8719     }\r
8720 \r
8721     gameFileFP = f;\r
8722     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
8723         fclose(lastLoadGameFP);\r
8724     }\r
8725 \r
8726     if (useList) {\r
8727         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
8728         \r
8729         if (lg) {\r
8730             fseek(f, lg->offset, 0);\r
8731             GameListHighlight(gameNumber);\r
8732             gn = 1;\r
8733         }\r
8734         else {\r
8735             DisplayError(_("Game number out of range"), 0);\r
8736             return FALSE;\r
8737         }\r
8738     } else {\r
8739         GameListDestroy();\r
8740         if (fseek(f, 0, 0) == -1) {\r
8741             if (f == lastLoadGameFP ?\r
8742                 gameNumber == lastLoadGameNumber + 1 :\r
8743                 gameNumber == 1) {\r
8744                 gn = 1;\r
8745             } else {\r
8746                 DisplayError(_("Can't seek on game file"), 0);\r
8747                 return FALSE;\r
8748             }\r
8749         }\r
8750     }\r
8751     lastLoadGameFP = f;\r
8752     lastLoadGameNumber = gameNumber;\r
8753     strcpy(lastLoadGameTitle, title);\r
8754     lastLoadGameUseList = useList;\r
8755 \r
8756     yynewfile(f);\r
8757 \r
8758     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
8759         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
8760                 lg->gameInfo.black);\r
8761             DisplayTitle(buf);\r
8762     } else if (*title != NULLCHAR) {\r
8763         if (gameNumber > 1) {\r
8764             sprintf(buf, "%s %d", title, gameNumber);\r
8765             DisplayTitle(buf);\r
8766         } else {\r
8767             DisplayTitle(title);\r
8768         }\r
8769     }\r
8770 \r
8771     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
8772         gameMode = PlayFromGameFile;\r
8773         ModeHighlight();\r
8774     }\r
8775 \r
8776     currentMove = forwardMostMove = backwardMostMove = 0;\r
8777     CopyBoard(boards[0], initialPosition);\r
8778     StopClocks();\r
8779 \r
8780     /*\r
8781      * Skip the first gn-1 games in the file.\r
8782      * Also skip over anything that precedes an identifiable \r
8783      * start of game marker, to avoid being confused by \r
8784      * garbage at the start of the file.  Currently \r
8785      * recognized start of game markers are the move number "1",\r
8786      * the pattern "gnuchess .* game", the pattern\r
8787      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
8788      * A game that starts with one of the latter two patterns\r
8789      * will also have a move number 1, possibly\r
8790      * following a position diagram.\r
8791      * 5-4-02: Let's try being more lenient and allowing a game to\r
8792      * start with an unnumbered move.  Does that break anything?\r
8793      */\r
8794     cm = lastLoadGameStart = (ChessMove) 0;\r
8795     while (gn > 0) {\r
8796         yyboardindex = forwardMostMove;\r
8797         cm = (ChessMove) yylex();\r
8798         switch (cm) {\r
8799           case (ChessMove) 0:\r
8800             if (cmailMsgLoaded) {\r
8801                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
8802             } else {\r
8803                 Reset(TRUE, TRUE);\r
8804                 DisplayError(_("Game not found in file"), 0);\r
8805             }\r
8806             return FALSE;\r
8807 \r
8808           case GNUChessGame:\r
8809           case XBoardGame:\r
8810             gn--;\r
8811             lastLoadGameStart = cm;\r
8812             break;\r
8813             \r
8814           case MoveNumberOne:\r
8815             switch (lastLoadGameStart) {\r
8816               case GNUChessGame:\r
8817               case XBoardGame:\r
8818               case PGNTag:\r
8819                 break;\r
8820               case MoveNumberOne:\r
8821               case (ChessMove) 0:\r
8822                 gn--;           /* count this game */\r
8823                 lastLoadGameStart = cm;\r
8824                 break;\r
8825               default:\r
8826                 /* impossible */\r
8827                 break;\r
8828             }\r
8829             break;\r
8830 \r
8831           case PGNTag:\r
8832             switch (lastLoadGameStart) {\r
8833               case GNUChessGame:\r
8834               case PGNTag:\r
8835               case MoveNumberOne:\r
8836               case (ChessMove) 0:\r
8837                 gn--;           /* count this game */\r
8838                 lastLoadGameStart = cm;\r
8839                 break;\r
8840               case XBoardGame:\r
8841                 lastLoadGameStart = cm; /* game counted already */\r
8842                 break;\r
8843               default:\r
8844                 /* impossible */\r
8845                 break;\r
8846             }\r
8847             if (gn > 0) {\r
8848                 do {\r
8849                     yyboardindex = forwardMostMove;\r
8850                     cm = (ChessMove) yylex();\r
8851                 } while (cm == PGNTag || cm == Comment);\r
8852             }\r
8853             break;\r
8854 \r
8855           case WhiteWins:\r
8856           case BlackWins:\r
8857           case GameIsDrawn:\r
8858             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8859                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8860                     != CMAIL_OLD_RESULT) {\r
8861                     nCmailResults ++ ;\r
8862                     cmailResult[  CMAIL_MAX_GAMES\r
8863                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8864                 }\r
8865             }\r
8866             break;\r
8867 \r
8868           case NormalMove:\r
8869             /* Only a NormalMove can be at the start of a game\r
8870              * without a position diagram. */\r
8871             if (lastLoadGameStart == (ChessMove) 0) {\r
8872               gn--;\r
8873               lastLoadGameStart = MoveNumberOne;\r
8874             }\r
8875             break;\r
8876 \r
8877           default:\r
8878             break;\r
8879         }\r
8880     }\r
8881     \r
8882     if (appData.debugMode)\r
8883       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8884 \r
8885     if (cm == XBoardGame) {\r
8886         /* Skip any header junk before position diagram and/or move 1 */\r
8887         for (;;) {\r
8888             yyboardindex = forwardMostMove;\r
8889             cm = (ChessMove) yylex();\r
8890 \r
8891             if (cm == (ChessMove) 0 ||\r
8892                 cm == GNUChessGame || cm == XBoardGame) {\r
8893                 /* Empty game; pretend end-of-file and handle later */\r
8894                 cm = (ChessMove) 0;\r
8895                 break;\r
8896             }\r
8897 \r
8898             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8899                 cm == PGNTag || cm == Comment)\r
8900               break;\r
8901         }\r
8902     } else if (cm == GNUChessGame) {\r
8903         if (gameInfo.event != NULL) {\r
8904             free(gameInfo.event);\r
8905         }\r
8906         gameInfo.event = StrSave(yy_text);\r
8907     }   \r
8908 \r
8909     startedFromSetupPosition = FALSE;\r
8910     while (cm == PGNTag) {\r
8911         if (appData.debugMode) \r
8912           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8913         err = ParsePGNTag(yy_text, &gameInfo);\r
8914         if (!err) numPGNTags++;\r
8915 \r
8916         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8917         if(gameInfo.variant != oldVariant) {\r
8918             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8919             InitPosition(TRUE);\r
8920             oldVariant = gameInfo.variant;\r
8921             if (appData.debugMode) \r
8922               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8923         }\r
8924 \r
8925 \r
8926         if (gameInfo.fen != NULL) {\r
8927           Board initial_position;\r
8928           startedFromSetupPosition = TRUE;\r
8929           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8930             Reset(TRUE, TRUE);\r
8931             DisplayError(_("Bad FEN position in file"), 0);\r
8932             return FALSE;\r
8933           }\r
8934           CopyBoard(boards[0], initial_position);\r
8935           if (blackPlaysFirst) {\r
8936             currentMove = forwardMostMove = backwardMostMove = 1;\r
8937             CopyBoard(boards[1], initial_position);\r
8938             strcpy(moveList[0], "");\r
8939             strcpy(parseList[0], "");\r
8940             timeRemaining[0][1] = whiteTimeRemaining;\r
8941             timeRemaining[1][1] = blackTimeRemaining;\r
8942             if (commentList[0] != NULL) {\r
8943               commentList[1] = commentList[0];\r
8944               commentList[0] = NULL;\r
8945             }\r
8946           } else {\r
8947             currentMove = forwardMostMove = backwardMostMove = 0;\r
8948           }\r
8949           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */\r
8950           {   int i;\r
8951               initialRulePlies = FENrulePlies;\r
8952               epStatus[forwardMostMove] = FENepStatus;\r
8953               for( i=0; i< nrCastlingRights; i++ )\r
8954                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
8955           }\r
8956           yyboardindex = forwardMostMove;\r
8957           free(gameInfo.fen);\r
8958           gameInfo.fen = NULL;\r
8959         }\r
8960 \r
8961         yyboardindex = forwardMostMove;\r
8962         cm = (ChessMove) yylex();\r
8963 \r
8964         /* Handle comments interspersed among the tags */\r
8965         while (cm == Comment) {\r
8966             char *p;\r
8967             if (appData.debugMode) \r
8968               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8969             p = yy_text;\r
8970             if (*p == '{' || *p == '[' || *p == '(') {\r
8971                 p[strlen(p) - 1] = NULLCHAR;\r
8972                 p++;\r
8973             }\r
8974             while (*p == '\n') p++;\r
8975             AppendComment(currentMove, p);\r
8976             yyboardindex = forwardMostMove;\r
8977             cm = (ChessMove) yylex();\r
8978         }\r
8979     }\r
8980 \r
8981     /* don't rely on existence of Event tag since if game was\r
8982      * pasted from clipboard the Event tag may not exist\r
8983      */\r
8984     if (numPGNTags > 0){\r
8985         char *tags;\r
8986         if (gameInfo.variant == VariantNormal) {\r
8987           gameInfo.variant = StringToVariant(gameInfo.event);\r
8988         }\r
8989         if (!matchMode) {\r
8990           if( appData.autoDisplayTags ) {\r
8991             tags = PGNTags(&gameInfo);\r
8992             TagsPopUp(tags, CmailMsg());\r
8993             free(tags);\r
8994           }\r
8995         }\r
8996     } else {\r
8997         /* Make something up, but don't display it now */\r
8998         SetGameInfo();\r
8999         TagsPopDown();\r
9000     }\r
9001 \r
9002     if (cm == PositionDiagram) {\r
9003         int i, j;\r
9004         char *p;\r
9005         Board initial_position;\r
9006 \r
9007         if (appData.debugMode)\r
9008           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
9009 \r
9010         if (!startedFromSetupPosition) {\r
9011             p = yy_text;\r
9012             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
9013               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
9014                 switch (*p) {\r
9015                   case '[':\r
9016                   case '-':\r
9017                   case ' ':\r
9018                   case '\t':\r
9019                   case '\n':\r
9020                   case '\r':\r
9021                     break;\r
9022                   default:\r
9023                     initial_position[i][j++] = CharToPiece(*p);\r
9024                     break;\r
9025                 }\r
9026             while (*p == ' ' || *p == '\t' ||\r
9027                    *p == '\n' || *p == '\r') p++;\r
9028         \r
9029             if (strncmp(p, "black", strlen("black"))==0)\r
9030               blackPlaysFirst = TRUE;\r
9031             else\r
9032               blackPlaysFirst = FALSE;\r
9033             startedFromSetupPosition = TRUE;\r
9034         \r
9035             CopyBoard(boards[0], initial_position);\r
9036             if (blackPlaysFirst) {\r
9037                 currentMove = forwardMostMove = backwardMostMove = 1;\r
9038                 CopyBoard(boards[1], initial_position);\r
9039                 strcpy(moveList[0], "");\r
9040                 strcpy(parseList[0], "");\r
9041                 timeRemaining[0][1] = whiteTimeRemaining;\r
9042                 timeRemaining[1][1] = blackTimeRemaining;\r
9043                 if (commentList[0] != NULL) {\r
9044                     commentList[1] = commentList[0];\r
9045                     commentList[0] = NULL;\r
9046                 }\r
9047             } else {\r
9048                 currentMove = forwardMostMove = backwardMostMove = 0;\r
9049             }\r
9050         }\r
9051         yyboardindex = forwardMostMove;\r
9052         cm = (ChessMove) yylex();\r
9053     }\r
9054 \r
9055     if (first.pr == NoProc) {\r
9056         StartChessProgram(&first);\r
9057     }\r
9058     InitChessProgram(&first, FALSE);\r
9059     SendToProgram("force\n", &first);\r
9060     if (startedFromSetupPosition) {\r
9061         SendBoard(&first, forwardMostMove);\r
9062     if (appData.debugMode) {\r
9063         fprintf(debugFP, "Load Game\n");\r
9064     }\r
9065         DisplayBothClocks();\r
9066     }      \r
9067 \r
9068     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
9069     loadFlag = appData.suppressLoadMoves;\r
9070 \r
9071     while (cm == Comment) {\r
9072         char *p;\r
9073         if (appData.debugMode) \r
9074           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
9075         p = yy_text;\r
9076         if (*p == '{' || *p == '[' || *p == '(') {\r
9077             p[strlen(p) - 1] = NULLCHAR;\r
9078             p++;\r
9079         }\r
9080         while (*p == '\n') p++;\r
9081         AppendComment(currentMove, p);\r
9082         yyboardindex = forwardMostMove;\r
9083         cm = (ChessMove) yylex();\r
9084     }\r
9085 \r
9086     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
9087         cm == WhiteWins || cm == BlackWins ||\r
9088         cm == GameIsDrawn || cm == GameUnfinished) {\r
9089         DisplayMessage("", _("No moves in game"));\r
9090         if (cmailMsgLoaded) {\r
9091             if (appData.debugMode)\r
9092               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
9093             ClearHighlights();\r
9094             flipView = FALSE;\r
9095         }\r
9096         DrawPosition(FALSE, boards[currentMove]);\r
9097         DisplayBothClocks();\r
9098         gameMode = EditGame;\r
9099         ModeHighlight();\r
9100         gameFileFP = NULL;\r
9101         cmailOldMove = 0;\r
9102         return TRUE;\r
9103     }\r
9104 \r
9105     // [HGM] PV info: routine tests if comment empty\r
9106     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
9107         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9108     }\r
9109     if (!matchMode && appData.timeDelay != 0) \r
9110       DrawPosition(FALSE, boards[currentMove]);\r
9111 \r
9112     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
9113       programStats.ok_to_send = 1;\r
9114     }\r
9115 \r
9116     /* if the first token after the PGN tags is a move\r
9117      * and not move number 1, retrieve it from the parser \r
9118      */\r
9119     if (cm != MoveNumberOne)\r
9120         LoadGameOneMove(cm);\r
9121 \r
9122     /* load the remaining moves from the file */\r
9123     while (LoadGameOneMove((ChessMove)0)) {\r
9124       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9125       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9126     }\r
9127 \r
9128     /* rewind to the start of the game */\r
9129     currentMove = backwardMostMove;\r
9130 \r
9131     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9132 \r
9133     if (oldGameMode == AnalyzeFile ||\r
9134         oldGameMode == AnalyzeMode) {\r
9135       AnalyzeFileEvent();\r
9136     }\r
9137 \r
9138     if (matchMode || appData.timeDelay == 0) {\r
9139       ToEndEvent();\r
9140       gameMode = EditGame;\r
9141       ModeHighlight();\r
9142     } else if (appData.timeDelay > 0) {\r
9143       AutoPlayGameLoop();\r
9144     }\r
9145 \r
9146     if (appData.debugMode) \r
9147         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
9148 \r
9149     loadFlag = 0; /* [HGM] true game starts */\r
9150     return TRUE;\r
9151 }\r
9152 \r
9153 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
9154 int\r
9155 ReloadPosition(offset)\r
9156      int offset;\r
9157 {\r
9158     int positionNumber = lastLoadPositionNumber + offset;\r
9159     if (lastLoadPositionFP == NULL) {\r
9160         DisplayError(_("No position has been loaded yet"), 0);\r
9161         return FALSE;\r
9162     }\r
9163     if (positionNumber <= 0) {\r
9164         DisplayError(_("Can't back up any further"), 0);\r
9165         return FALSE;\r
9166     }\r
9167     return LoadPosition(lastLoadPositionFP, positionNumber,\r
9168                         lastLoadPositionTitle);\r
9169 }\r
9170 \r
9171 /* Load the nth position from the given file */\r
9172 int\r
9173 LoadPositionFromFile(filename, n, title)\r
9174      char *filename;\r
9175      int n;\r
9176      char *title;\r
9177 {\r
9178     FILE *f;\r
9179     char buf[MSG_SIZ];\r
9180 \r
9181     if (strcmp(filename, "-") == 0) {\r
9182         return LoadPosition(stdin, n, "stdin");\r
9183     } else {\r
9184         f = fopen(filename, "rb");\r
9185         if (f == NULL) {\r
9186             sprintf(buf, _("Can't open \"%s\""), filename);\r
9187             DisplayError(buf, errno);\r
9188             return FALSE;\r
9189         } else {\r
9190             return LoadPosition(f, n, title);\r
9191         }\r
9192     }\r
9193 }\r
9194 \r
9195 /* Load the nth position from the given open file, and close it */\r
9196 int\r
9197 LoadPosition(f, positionNumber, title)\r
9198      FILE *f;\r
9199      int positionNumber;\r
9200      char *title;\r
9201 {\r
9202     char *p, line[MSG_SIZ];\r
9203     Board initial_position;\r
9204     int i, j, fenMode, pn;\r
9205     \r
9206     if (gameMode == Training )\r
9207         SetTrainingModeOff();\r
9208 \r
9209     if (gameMode != BeginningOfGame) {\r
9210         Reset(FALSE, TRUE);\r
9211     }\r
9212     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
9213         fclose(lastLoadPositionFP);\r
9214     }\r
9215     if (positionNumber == 0) positionNumber = 1;\r
9216     lastLoadPositionFP = f;\r
9217     lastLoadPositionNumber = positionNumber;\r
9218     strcpy(lastLoadPositionTitle, title);\r
9219     if (first.pr == NoProc) {\r
9220       StartChessProgram(&first);\r
9221       InitChessProgram(&first, FALSE);\r
9222     }    \r
9223     pn = positionNumber;\r
9224     if (positionNumber < 0) {\r
9225         /* Negative position number means to seek to that byte offset */\r
9226         if (fseek(f, -positionNumber, 0) == -1) {\r
9227             DisplayError(_("Can't seek on position file"), 0);\r
9228             return FALSE;\r
9229         };\r
9230         pn = 1;\r
9231     } else {\r
9232         if (fseek(f, 0, 0) == -1) {\r
9233             if (f == lastLoadPositionFP ?\r
9234                 positionNumber == lastLoadPositionNumber + 1 :\r
9235                 positionNumber == 1) {\r
9236                 pn = 1;\r
9237             } else {\r
9238                 DisplayError(_("Can't seek on position file"), 0);\r
9239                 return FALSE;\r
9240             }\r
9241         }\r
9242     }\r
9243     /* See if this file is FEN or old-style xboard */\r
9244     if (fgets(line, MSG_SIZ, f) == NULL) {\r
9245         DisplayError(_("Position not found in file"), 0);\r
9246         return FALSE;\r
9247     }\r
9248 #if 0\r
9249     switch (line[0]) {\r
9250       case '#':  case 'x':\r
9251       default:\r
9252         fenMode = FALSE;\r
9253         break;\r
9254       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
9255       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
9256       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
9257       case '7':  case '8':  case '9':\r
9258       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
9259       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
9260       case 'C':  case 'W':             case 'c':  case 'w': \r
9261         fenMode = TRUE;\r
9262         break;\r
9263     }\r
9264 #else\r
9265     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
9266     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
9267 #endif\r
9268 \r
9269     if (pn >= 2) {\r
9270         if (fenMode || line[0] == '#') pn--;\r
9271         while (pn > 0) {\r
9272             /* skip positions before number pn */\r
9273             if (fgets(line, MSG_SIZ, f) == NULL) {\r
9274                 Reset(TRUE, TRUE);\r
9275                 DisplayError(_("Position not found in file"), 0);\r
9276                 return FALSE;\r
9277             }\r
9278             if (fenMode || line[0] == '#') pn--;\r
9279         }\r
9280     }\r
9281 \r
9282     if (fenMode) {\r
9283         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
9284             DisplayError(_("Bad FEN position in file"), 0);\r
9285             return FALSE;\r
9286         }\r
9287     } else {\r
9288         (void) fgets(line, MSG_SIZ, f);\r
9289         (void) fgets(line, MSG_SIZ, f);\r
9290     \r
9291         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
9292             (void) fgets(line, MSG_SIZ, f);\r
9293             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
9294                 if (*p == ' ')\r
9295                   continue;\r
9296                 initial_position[i][j++] = CharToPiece(*p);\r
9297             }\r
9298         }\r
9299     \r
9300         blackPlaysFirst = FALSE;\r
9301         if (!feof(f)) {\r
9302             (void) fgets(line, MSG_SIZ, f);\r
9303             if (strncmp(line, "black", strlen("black"))==0)\r
9304               blackPlaysFirst = TRUE;\r
9305         }\r
9306     }\r
9307     startedFromSetupPosition = TRUE;\r
9308     \r
9309     SendToProgram("force\n", &first);\r
9310     CopyBoard(boards[0], initial_position);\r
9311     if (blackPlaysFirst) {\r
9312         currentMove = forwardMostMove = backwardMostMove = 1;\r
9313         strcpy(moveList[0], "");\r
9314         strcpy(parseList[0], "");\r
9315         CopyBoard(boards[1], initial_position);\r
9316         DisplayMessage("", _("Black to play"));\r
9317     } else {\r
9318         currentMove = forwardMostMove = backwardMostMove = 0;\r
9319         DisplayMessage("", _("White to play"));\r
9320     }\r
9321           /* [HGM] copy FEN attributes as well */\r
9322           {   int i;\r
9323               initialRulePlies = FENrulePlies;\r
9324               epStatus[forwardMostMove] = FENepStatus;\r
9325               for( i=0; i< nrCastlingRights; i++ )\r
9326                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
9327           }\r
9328     SendBoard(&first, forwardMostMove);\r
9329     if (appData.debugMode) {\r
9330 int i, j;\r
9331   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}\r
9332   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");\r
9333         fprintf(debugFP, "Load Position\n");\r
9334     }\r
9335 \r
9336     if (positionNumber > 1) {\r
9337         sprintf(line, "%s %d", title, positionNumber);\r
9338         DisplayTitle(line);\r
9339     } else {\r
9340         DisplayTitle(title);\r
9341     }\r
9342     gameMode = EditGame;\r
9343     ModeHighlight();\r
9344     ResetClocks();\r
9345     timeRemaining[0][1] = whiteTimeRemaining;\r
9346     timeRemaining[1][1] = blackTimeRemaining;\r
9347     DrawPosition(FALSE, boards[currentMove]);\r
9348    \r
9349     return TRUE;\r
9350 }\r
9351 \r
9352 \r
9353 void\r
9354 CopyPlayerNameIntoFileName(dest, src)\r
9355      char **dest, *src;\r
9356 {\r
9357     while (*src != NULLCHAR && *src != ',') {\r
9358         if (*src == ' ') {\r
9359             *(*dest)++ = '_';\r
9360             src++;\r
9361         } else {\r
9362             *(*dest)++ = *src++;\r
9363         }\r
9364     }\r
9365 }\r
9366 \r
9367 char *DefaultFileName(ext)\r
9368      char *ext;\r
9369 {\r
9370     static char def[MSG_SIZ];\r
9371     char *p;\r
9372 \r
9373     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
9374         p = def;\r
9375         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
9376         *p++ = '-';\r
9377         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
9378         *p++ = '.';\r
9379         strcpy(p, ext);\r
9380     } else {\r
9381         def[0] = NULLCHAR;\r
9382     }\r
9383     return def;\r
9384 }\r
9385 \r
9386 /* Save the current game to the given file */\r
9387 int\r
9388 SaveGameToFile(filename, append)\r
9389      char *filename;\r
9390      int append;\r
9391 {\r
9392     FILE *f;\r
9393     char buf[MSG_SIZ];\r
9394 \r
9395     if (strcmp(filename, "-") == 0) {\r
9396         return SaveGame(stdout, 0, NULL);\r
9397     } else {\r
9398         f = fopen(filename, append ? "a" : "w");\r
9399         if (f == NULL) {\r
9400             sprintf(buf, _("Can't open \"%s\""), filename);\r
9401             DisplayError(buf, errno);\r
9402             return FALSE;\r
9403         } else {\r
9404             return SaveGame(f, 0, NULL);\r
9405         }\r
9406     }\r
9407 }\r
9408 \r
9409 char *\r
9410 SavePart(str)\r
9411      char *str;\r
9412 {\r
9413     static char buf[MSG_SIZ];\r
9414     char *p;\r
9415     \r
9416     p = strchr(str, ' ');\r
9417     if (p == NULL) return str;\r
9418     strncpy(buf, str, p - str);\r
9419     buf[p - str] = NULLCHAR;\r
9420     return buf;\r
9421 }\r
9422 \r
9423 #define PGN_MAX_LINE 75\r
9424 \r
9425 #define PGN_SIDE_WHITE  0\r
9426 #define PGN_SIDE_BLACK  1\r
9427 \r
9428 /* [AS] */\r
9429 static int FindFirstMoveOutOfBook( int side )\r
9430 {\r
9431     int result = -1;\r
9432 \r
9433     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
9434         int index = backwardMostMove;\r
9435         int has_book_hit = 0;\r
9436 \r
9437         if( (index % 2) != side ) {\r
9438             index++;\r
9439         }\r
9440 \r
9441         while( index < forwardMostMove ) {\r
9442             /* Check to see if engine is in book */\r
9443             int depth = pvInfoList[index].depth;\r
9444             int score = pvInfoList[index].score;\r
9445             int in_book = 0;\r
9446 \r
9447             if( depth <= 2 ) {\r
9448                 in_book = 1;\r
9449             }\r
9450             else if( score == 0 && depth == 63 ) {\r
9451                 in_book = 1; /* Zappa */\r
9452             }\r
9453             else if( score == 2 && depth == 99 ) {\r
9454                 in_book = 1; /* Abrok */\r
9455             }\r
9456 \r
9457             has_book_hit += in_book;\r
9458 \r
9459             if( ! in_book ) {\r
9460                 result = index;\r
9461 \r
9462                 break;\r
9463             }\r
9464 \r
9465             index += 2;\r
9466         }\r
9467     }\r
9468 \r
9469     return result;\r
9470 }\r
9471 \r
9472 /* [AS] */\r
9473 void GetOutOfBookInfo( char * buf )\r
9474 {\r
9475     int oob[2];\r
9476     int i;\r
9477     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9478 \r
9479     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
9480     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
9481 \r
9482     *buf = '\0';\r
9483 \r
9484     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
9485         for( i=0; i<2; i++ ) {\r
9486             int idx = oob[i];\r
9487 \r
9488             if( idx >= 0 ) {\r
9489                 if( i > 0 && oob[0] >= 0 ) {\r
9490                     strcat( buf, "   " );\r
9491                 }\r
9492 \r
9493                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
9494                 sprintf( buf+strlen(buf), "%s%.2f", \r
9495                     pvInfoList[idx].score >= 0 ? "+" : "",\r
9496                     pvInfoList[idx].score / 100.0 );\r
9497             }\r
9498         }\r
9499     }\r
9500 }\r
9501 \r
9502 /* Save game in PGN style and close the file */\r
9503 int\r
9504 SaveGamePGN(f)\r
9505      FILE *f;\r
9506 {\r
9507     int i, offset, linelen, newblock;\r
9508     time_t tm;\r
9509 //    char *movetext;\r
9510     char numtext[32];\r
9511     int movelen, numlen, blank;\r
9512     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
9513 \r
9514     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9515     \r
9516     tm = time((time_t *) NULL);\r
9517     \r
9518     PrintPGNTags(f, &gameInfo);\r
9519     \r
9520     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9521         char *fen = PositionToFEN(backwardMostMove, NULL);\r
9522         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
9523         fprintf(f, "\n{--------------\n");\r
9524         PrintPosition(f, backwardMostMove);\r
9525         fprintf(f, "--------------}\n");\r
9526         free(fen);\r
9527     }\r
9528     else {\r
9529         /* [AS] Out of book annotation */\r
9530         if( appData.saveOutOfBookInfo ) {\r
9531             char buf[64];\r
9532 \r
9533             GetOutOfBookInfo( buf );\r
9534 \r
9535             if( buf[0] != '\0' ) {\r
9536                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
9537             }\r
9538         }\r
9539 \r
9540         fprintf(f, "\n");\r
9541     }\r
9542 \r
9543     i = backwardMostMove;\r
9544     linelen = 0;\r
9545     newblock = TRUE;\r
9546 \r
9547     while (i < forwardMostMove) {\r
9548         /* Print comments preceding this move */\r
9549         if (commentList[i] != NULL) {\r
9550             if (linelen > 0) fprintf(f, "\n");\r
9551             fprintf(f, "{\n%s}\n", commentList[i]);\r
9552             linelen = 0;\r
9553             newblock = TRUE;\r
9554         }\r
9555 \r
9556         /* Format move number */\r
9557         if ((i % 2) == 0) {\r
9558             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
9559         } else {\r
9560             if (newblock) {\r
9561                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
9562             } else {\r
9563                 numtext[0] = NULLCHAR;\r
9564             }\r
9565         }\r
9566         numlen = strlen(numtext);\r
9567         newblock = FALSE;\r
9568 \r
9569         /* Print move number */\r
9570         blank = linelen > 0 && numlen > 0;\r
9571         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
9572             fprintf(f, "\n");\r
9573             linelen = 0;\r
9574             blank = 0;\r
9575         }\r
9576         if (blank) {\r
9577             fprintf(f, " ");\r
9578             linelen++;\r
9579         }\r
9580         fprintf(f, numtext);\r
9581         linelen += numlen;\r
9582 \r
9583         /* Get move */\r
9584         strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited\r
9585         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */\r
9586         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9587                 int p = movelen - 1;\r
9588                 if(move_buffer[p] == ' ') p--;\r
9589                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info\r
9590                     while(p && move_buffer[--p] != '(');\r
9591                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;\r
9592                 }\r
9593         }\r
9594 \r
9595         /* Print move */\r
9596         blank = linelen > 0 && movelen > 0;\r
9597         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9598             fprintf(f, "\n");\r
9599             linelen = 0;\r
9600             blank = 0;\r
9601         }\r
9602         if (blank) {\r
9603             fprintf(f, " ");\r
9604             linelen++;\r
9605         }\r
9606         fprintf(f, move_buffer);\r
9607         linelen += movelen;\r
9608 \r
9609         /* [AS] Add PV info if present */\r
9610         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9611             /* [HGM] add time */\r
9612             char buf[MSG_SIZ]; int seconds = 0;\r
9613 \r
9614 #if 1\r
9615             if(i >= backwardMostMove) {\r
9616                 if(WhiteOnMove(i))\r
9617                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]\r
9618                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);\r
9619                 else\r
9620                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]\r
9621                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);\r
9622             }\r
9623             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest\r
9624 #else\r
9625             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time\r
9626 #endif\r
9627 \r
9628             if( seconds <= 0) buf[0] = 0; else\r
9629             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {\r
9630                 seconds = (seconds + 4)/10; // round to full seconds\r
9631                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else\r
9632                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
9633             }\r
9634 \r
9635             sprintf( move_buffer, "{%s%.2f/%d%s}", \r
9636                 pvInfoList[i].score >= 0 ? "+" : "",\r
9637                 pvInfoList[i].score / 100.0,\r
9638                 pvInfoList[i].depth,\r
9639                 buf );\r
9640 \r
9641             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */\r
9642 \r
9643             /* Print score/depth */\r
9644             blank = linelen > 0 && movelen > 0;\r
9645             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9646                 fprintf(f, "\n");\r
9647                 linelen = 0;\r
9648                 blank = 0;\r
9649             }\r
9650             if (blank) {\r
9651                 fprintf(f, " ");\r
9652                 linelen++;\r
9653             }\r
9654             fprintf(f, move_buffer);\r
9655             linelen += movelen;\r
9656         }\r
9657 \r
9658         i++;\r
9659     }\r
9660     \r
9661     /* Start a new line */\r
9662     if (linelen > 0) fprintf(f, "\n");\r
9663 \r
9664     /* Print comments after last move */\r
9665     if (commentList[i] != NULL) {\r
9666         fprintf(f, "{\n%s}\n", commentList[i]);\r
9667     }\r
9668 \r
9669     /* Print result */\r
9670     if (gameInfo.resultDetails != NULL &&\r
9671         gameInfo.resultDetails[0] != NULLCHAR) {\r
9672         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
9673                 PGNResult(gameInfo.result));\r
9674     } else {\r
9675         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9676     }\r
9677 \r
9678     fclose(f);\r
9679     return TRUE;\r
9680 }\r
9681 \r
9682 /* Save game in old style and close the file */\r
9683 int\r
9684 SaveGameOldStyle(f)\r
9685      FILE *f;\r
9686 {\r
9687     int i, offset;\r
9688     time_t tm;\r
9689     \r
9690     tm = time((time_t *) NULL);\r
9691     \r
9692     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
9693     PrintOpponents(f);\r
9694     \r
9695     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9696         fprintf(f, "\n[--------------\n");\r
9697         PrintPosition(f, backwardMostMove);\r
9698         fprintf(f, "--------------]\n");\r
9699     } else {\r
9700         fprintf(f, "\n");\r
9701     }\r
9702 \r
9703     i = backwardMostMove;\r
9704     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9705 \r
9706     while (i < forwardMostMove) {\r
9707         if (commentList[i] != NULL) {\r
9708             fprintf(f, "[%s]\n", commentList[i]);\r
9709         }\r
9710 \r
9711         if ((i % 2) == 1) {\r
9712             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
9713             i++;\r
9714         } else {\r
9715             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
9716             i++;\r
9717             if (commentList[i] != NULL) {\r
9718                 fprintf(f, "\n");\r
9719                 continue;\r
9720             }\r
9721             if (i >= forwardMostMove) {\r
9722                 fprintf(f, "\n");\r
9723                 break;\r
9724             }\r
9725             fprintf(f, "%s\n", parseList[i]);\r
9726             i++;\r
9727         }\r
9728     }\r
9729     \r
9730     if (commentList[i] != NULL) {\r
9731         fprintf(f, "[%s]\n", commentList[i]);\r
9732     }\r
9733 \r
9734     /* This isn't really the old style, but it's close enough */\r
9735     if (gameInfo.resultDetails != NULL &&\r
9736         gameInfo.resultDetails[0] != NULLCHAR) {\r
9737         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
9738                 gameInfo.resultDetails);\r
9739     } else {\r
9740         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9741     }\r
9742 \r
9743     fclose(f);\r
9744     return TRUE;\r
9745 }\r
9746 \r
9747 /* Save the current game to open file f and close the file */\r
9748 int\r
9749 SaveGame(f, dummy, dummy2)\r
9750      FILE *f;\r
9751      int dummy;\r
9752      char *dummy2;\r
9753 {\r
9754     if (gameMode == EditPosition) EditPositionDone();\r
9755     if (appData.oldSaveStyle)\r
9756       return SaveGameOldStyle(f);\r
9757     else\r
9758       return SaveGamePGN(f);\r
9759 }\r
9760 \r
9761 /* Save the current position to the given file */\r
9762 int\r
9763 SavePositionToFile(filename)\r
9764      char *filename;\r
9765 {\r
9766     FILE *f;\r
9767     char buf[MSG_SIZ];\r
9768 \r
9769     if (strcmp(filename, "-") == 0) {\r
9770         return SavePosition(stdout, 0, NULL);\r
9771     } else {\r
9772         f = fopen(filename, "a");\r
9773         if (f == NULL) {\r
9774             sprintf(buf, _("Can't open \"%s\""), filename);\r
9775             DisplayError(buf, errno);\r
9776             return FALSE;\r
9777         } else {\r
9778             SavePosition(f, 0, NULL);\r
9779             return TRUE;\r
9780         }\r
9781     }\r
9782 }\r
9783 \r
9784 /* Save the current position to the given open file and close the file */\r
9785 int\r
9786 SavePosition(f, dummy, dummy2)\r
9787      FILE *f;\r
9788      int dummy;\r
9789      char *dummy2;\r
9790 {\r
9791     time_t tm;\r
9792     char *fen;\r
9793     \r
9794     if (appData.oldSaveStyle) {\r
9795         tm = time((time_t *) NULL);\r
9796     \r
9797         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
9798         PrintOpponents(f);\r
9799         fprintf(f, "[--------------\n");\r
9800         PrintPosition(f, currentMove);\r
9801         fprintf(f, "--------------]\n");\r
9802     } else {\r
9803         fen = PositionToFEN(currentMove, NULL);\r
9804         fprintf(f, "%s\n", fen);\r
9805         free(fen);\r
9806     }\r
9807     fclose(f);\r
9808     return TRUE;\r
9809 }\r
9810 \r
9811 void\r
9812 ReloadCmailMsgEvent(unregister)\r
9813      int unregister;\r
9814 {\r
9815 #if !WIN32\r
9816     static char *inFilename = NULL;\r
9817     static char *outFilename;\r
9818     int i;\r
9819     struct stat inbuf, outbuf;\r
9820     int status;\r
9821     \r
9822     /* Any registered moves are unregistered if unregister is set, */\r
9823     /* i.e. invoked by the signal handler */\r
9824     if (unregister) {\r
9825         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9826             cmailMoveRegistered[i] = FALSE;\r
9827             if (cmailCommentList[i] != NULL) {\r
9828                 free(cmailCommentList[i]);\r
9829                 cmailCommentList[i] = NULL;\r
9830             }\r
9831         }\r
9832         nCmailMovesRegistered = 0;\r
9833     }\r
9834 \r
9835     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9836         cmailResult[i] = CMAIL_NOT_RESULT;\r
9837     }\r
9838     nCmailResults = 0;\r
9839 \r
9840     if (inFilename == NULL) {\r
9841         /* Because the filenames are static they only get malloced once  */\r
9842         /* and they never get freed                                      */\r
9843         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
9844         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
9845 \r
9846         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
9847         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
9848     }\r
9849     \r
9850     status = stat(outFilename, &outbuf);\r
9851     if (status < 0) {\r
9852         cmailMailedMove = FALSE;\r
9853     } else {\r
9854         status = stat(inFilename, &inbuf);\r
9855         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
9856     }\r
9857     \r
9858     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
9859        counts the games, notes how each one terminated, etc.\r
9860        \r
9861        It would be nice to remove this kludge and instead gather all\r
9862        the information while building the game list.  (And to keep it\r
9863        in the game list nodes instead of having a bunch of fixed-size\r
9864        parallel arrays.)  Note this will require getting each game's\r
9865        termination from the PGN tags, as the game list builder does\r
9866        not process the game moves.  --mann\r
9867        */\r
9868     cmailMsgLoaded = TRUE;\r
9869     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
9870     \r
9871     /* Load first game in the file or popup game menu */\r
9872     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
9873 \r
9874 #endif /* !WIN32 */\r
9875     return;\r
9876 }\r
9877 \r
9878 int\r
9879 RegisterMove()\r
9880 {\r
9881     FILE *f;\r
9882     char string[MSG_SIZ];\r
9883 \r
9884     if (   cmailMailedMove\r
9885         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9886         return TRUE;            /* Allow free viewing  */\r
9887     }\r
9888 \r
9889     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9890     /* with the move registered when the conditions for registering no   */\r
9891     /* longer hold                                                       */\r
9892     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9893         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9894         nCmailMovesRegistered --;\r
9895 \r
9896         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9897           {\r
9898               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9899               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9900           }\r
9901     }\r
9902 \r
9903     if (cmailOldMove == -1) {\r
9904         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);\r
9905         return FALSE;\r
9906     }\r
9907 \r
9908     if (currentMove > cmailOldMove + 1) {\r
9909         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);\r
9910         return FALSE;\r
9911     }\r
9912 \r
9913     if (currentMove < cmailOldMove) {\r
9914         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);\r
9915         return FALSE;\r
9916     }\r
9917 \r
9918     if (forwardMostMove > currentMove) {\r
9919         /* Silently truncate extra moves */\r
9920         TruncateGame();\r
9921     }\r
9922 \r
9923     if (   (currentMove == cmailOldMove + 1)\r
9924         || (   (currentMove == cmailOldMove)\r
9925             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9926                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9927         if (gameInfo.result != GameUnfinished) {\r
9928             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9929         }\r
9930 \r
9931         if (commentList[currentMove] != NULL) {\r
9932             cmailCommentList[lastLoadGameNumber - 1]\r
9933               = StrSave(commentList[currentMove]);\r
9934         }\r
9935         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9936 \r
9937         if (appData.debugMode)\r
9938           fprintf(debugFP, "Saving %s for game %d\n",\r
9939                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9940 \r
9941         sprintf(string,\r
9942                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
9943         \r
9944         f = fopen(string, "w");\r
9945         if (appData.oldSaveStyle) {\r
9946             SaveGameOldStyle(f); /* also closes the file */\r
9947             \r
9948             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
9949             f = fopen(string, "w");\r
9950             SavePosition(f, 0, NULL); /* also closes the file */\r
9951         } else {\r
9952             fprintf(f, "{--------------\n");\r
9953             PrintPosition(f, currentMove);\r
9954             fprintf(f, "--------------}\n\n");\r
9955             \r
9956             SaveGame(f, 0, NULL); /* also closes the file*/\r
9957         }\r
9958         \r
9959         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
9960         nCmailMovesRegistered ++;\r
9961     } else if (nCmailGames == 1) {\r
9962         DisplayError(_("You have not made a move yet"), 0);\r
9963         return FALSE;\r
9964     }\r
9965 \r
9966     return TRUE;\r
9967 }\r
9968 \r
9969 void\r
9970 MailMoveEvent()\r
9971 {\r
9972 #if !WIN32\r
9973     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
9974     FILE *commandOutput;\r
9975     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
9976     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
9977     int nBuffers;\r
9978     int i;\r
9979     int archived;\r
9980     char *arcDir;\r
9981 \r
9982     if (! cmailMsgLoaded) {\r
9983         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);\r
9984         return;\r
9985     }\r
9986 \r
9987     if (nCmailGames == nCmailResults) {\r
9988         DisplayError(_("No unfinished games"), 0);\r
9989         return;\r
9990     }\r
9991 \r
9992 #if CMAIL_PROHIBIT_REMAIL\r
9993     if (cmailMailedMove) {\r
9994         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
9995         DisplayError(msg, 0);\r
9996         return;\r
9997     }\r
9998 #endif\r
9999 \r
10000     if (! (cmailMailedMove || RegisterMove())) return;\r
10001     \r
10002     if (   cmailMailedMove\r
10003         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
10004         sprintf(string, partCommandString,\r
10005                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
10006         commandOutput = popen(string, "r");\r
10007 \r
10008         if (commandOutput == NULL) {\r
10009             DisplayError(_("Failed to invoke cmail"), 0);\r
10010         } else {\r
10011             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
10012                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
10013             }\r
10014             if (nBuffers > 1) {\r
10015                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
10016                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
10017                 nBytes = MSG_SIZ - 1;\r
10018             } else {\r
10019                 (void) memcpy(msg, buffer, nBytes);\r
10020             }\r
10021             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
10022 \r
10023             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
10024                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
10025 \r
10026                 archived = TRUE;\r
10027                 for (i = 0; i < nCmailGames; i ++) {\r
10028                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
10029                         archived = FALSE;\r
10030                     }\r
10031                 }\r
10032                 if (   archived\r
10033                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
10034                         != NULL)) {\r
10035                     sprintf(buffer, "%s/%s.%s.archive",\r
10036                             arcDir,\r
10037                             appData.cmailGameName,\r
10038                             gameInfo.date);\r
10039                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
10040                     cmailMsgLoaded = FALSE;\r
10041                 }\r
10042             }\r
10043 \r
10044             DisplayInformation(msg);\r
10045             pclose(commandOutput);\r
10046         }\r
10047     } else {\r
10048         if ((*cmailMsg) != '\0') {\r
10049             DisplayInformation(cmailMsg);\r
10050         }\r
10051     }\r
10052 \r
10053     return;\r
10054 #endif /* !WIN32 */\r
10055 }\r
10056 \r
10057 char *\r
10058 CmailMsg()\r
10059 {\r
10060 #if WIN32\r
10061     return NULL;\r
10062 #else\r
10063     int  prependComma = 0;\r
10064     char number[5];\r
10065     char string[MSG_SIZ];       /* Space for game-list */\r
10066     int  i;\r
10067     \r
10068     if (!cmailMsgLoaded) return "";\r
10069 \r
10070     if (cmailMailedMove) {\r
10071         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));\r
10072     } else {\r
10073         /* Create a list of games left */\r
10074         sprintf(string, "[");\r
10075         for (i = 0; i < nCmailGames; i ++) {\r
10076             if (! (   cmailMoveRegistered[i]\r
10077                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
10078                 if (prependComma) {\r
10079                     sprintf(number, ",%d", i + 1);\r
10080                 } else {\r
10081                     sprintf(number, "%d", i + 1);\r
10082                     prependComma = 1;\r
10083                 }\r
10084                 \r
10085                 strcat(string, number);\r
10086             }\r
10087         }\r
10088         strcat(string, "]");\r
10089 \r
10090         if (nCmailMovesRegistered + nCmailResults == 0) {\r
10091             switch (nCmailGames) {\r
10092               case 1:\r
10093                 sprintf(cmailMsg,\r
10094                         _("Still need to make move for game\n"));\r
10095                 break;\r
10096                 \r
10097               case 2:\r
10098                 sprintf(cmailMsg,\r
10099                         _("Still need to make moves for both games\n"));\r
10100                 break;\r
10101                 \r
10102               default:\r
10103                 sprintf(cmailMsg,\r
10104                         _("Still need to make moves for all %d games\n"),\r
10105                         nCmailGames);\r
10106                 break;\r
10107             }\r
10108         } else {\r
10109             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
10110               case 1:\r
10111                 sprintf(cmailMsg,\r
10112                         _("Still need to make a move for game %s\n"),\r
10113                         string);\r
10114                 break;\r
10115                 \r
10116               case 0:\r
10117                 if (nCmailResults == nCmailGames) {\r
10118                     sprintf(cmailMsg, _("No unfinished games\n"));\r
10119                 } else {\r
10120                     sprintf(cmailMsg, _("Ready to send mail\n"));\r
10121                 }\r
10122                 break;\r
10123                 \r
10124               default:\r
10125                 sprintf(cmailMsg,\r
10126                         _("Still need to make moves for games %s\n"),\r
10127                         string);\r
10128             }\r
10129         }\r
10130     }\r
10131     return cmailMsg;\r
10132 #endif /* WIN32 */\r
10133 }\r
10134 \r
10135 void\r
10136 ResetGameEvent()\r
10137 {\r
10138     if (gameMode == Training)\r
10139       SetTrainingModeOff();\r
10140 \r
10141     Reset(TRUE, TRUE);\r
10142     cmailMsgLoaded = FALSE;\r
10143     if (appData.icsActive) {\r
10144       SendToICS(ics_prefix);\r
10145       SendToICS("refresh\n");\r
10146     }\r
10147 }\r
10148 \r
10149 void\r
10150 ExitEvent(status)\r
10151      int status;\r
10152 {\r
10153     exiting++;\r
10154     if (exiting > 2) {\r
10155       /* Give up on clean exit */\r
10156       exit(status);\r
10157     }\r
10158     if (exiting > 1) {\r
10159       /* Keep trying for clean exit */\r
10160       return;\r
10161     }\r
10162 \r
10163     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
10164 \r
10165     if (telnetISR != NULL) {\r
10166       RemoveInputSource(telnetISR);\r
10167     }\r
10168     if (icsPR != NoProc) {\r
10169       DestroyChildProcess(icsPR, TRUE);\r
10170     }\r
10171 #if 0\r
10172     /* Save game if resource set and not already saved by GameEnds() */\r
10173     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
10174                              && forwardMostMove > 0) {\r
10175       if (*appData.saveGameFile != NULLCHAR) {\r
10176         SaveGameToFile(appData.saveGameFile, TRUE);\r
10177       } else if (appData.autoSaveGames) {\r
10178         AutoSaveGame();\r
10179       }\r
10180       if (*appData.savePositionFile != NULLCHAR) {\r
10181         SavePositionToFile(appData.savePositionFile);\r
10182       }\r
10183     }\r
10184     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10185 #else\r
10186     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
10187     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);\r
10188 #endif\r
10189     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
10190     /* make sure this other one finishes before killing it!                  */\r
10191     if(endingGame) { int count = 0;\r
10192         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
10193         while(endingGame && count++ < 10) DoSleep(1);\r
10194         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
10195     }\r
10196 \r
10197     /* Kill off chess programs */\r
10198     if (first.pr != NoProc) {\r
10199         ExitAnalyzeMode();\r
10200         \r
10201         DoSleep( appData.delayBeforeQuit );\r
10202         SendToProgram("quit\n", &first);\r
10203         DoSleep( appData.delayAfterQuit );\r
10204         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
10205     }\r
10206     if (second.pr != NoProc) {\r
10207         DoSleep( appData.delayBeforeQuit );\r
10208         SendToProgram("quit\n", &second);\r
10209         DoSleep( appData.delayAfterQuit );\r
10210         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
10211     }\r
10212     if (first.isr != NULL) {\r
10213         RemoveInputSource(first.isr);\r
10214     }\r
10215     if (second.isr != NULL) {\r
10216         RemoveInputSource(second.isr);\r
10217     }\r
10218 \r
10219     ShutDownFrontEnd();\r
10220     exit(status);\r
10221 }\r
10222 \r
10223 void\r
10224 PauseEvent()\r
10225 {\r
10226     if (appData.debugMode)\r
10227         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
10228     if (pausing) {\r
10229         pausing = FALSE;\r
10230         ModeHighlight();\r
10231         if (gameMode == MachinePlaysWhite ||\r
10232             gameMode == MachinePlaysBlack) {\r
10233             StartClocks();\r
10234         } else {\r
10235             DisplayBothClocks();\r
10236         }\r
10237         if (gameMode == PlayFromGameFile) {\r
10238             if (appData.timeDelay >= 0) \r
10239                 AutoPlayGameLoop();\r
10240         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
10241             Reset(FALSE, TRUE);\r
10242             SendToICS(ics_prefix);\r
10243             SendToICS("refresh\n");\r
10244         } else if (currentMove < forwardMostMove) {\r
10245             ForwardInner(forwardMostMove);\r
10246         }\r
10247         pauseExamInvalid = FALSE;\r
10248     } else {\r
10249         switch (gameMode) {\r
10250           default:\r
10251             return;\r
10252           case IcsExamining:\r
10253             pauseExamForwardMostMove = forwardMostMove;\r
10254             pauseExamInvalid = FALSE;\r
10255             /* fall through */\r
10256           case IcsObserving:\r
10257           case IcsPlayingWhite:\r
10258           case IcsPlayingBlack:\r
10259             pausing = TRUE;\r
10260             ModeHighlight();\r
10261             return;\r
10262           case PlayFromGameFile:\r
10263             (void) StopLoadGameTimer();\r
10264             pausing = TRUE;\r
10265             ModeHighlight();\r
10266             break;\r
10267           case BeginningOfGame:\r
10268             if (appData.icsActive) return;\r
10269             /* else fall through */\r
10270           case MachinePlaysWhite:\r
10271           case MachinePlaysBlack:\r
10272           case TwoMachinesPlay:\r
10273             if (forwardMostMove == 0)\r
10274               return;           /* don't pause if no one has moved */\r
10275             if ((gameMode == MachinePlaysWhite &&\r
10276                  !WhiteOnMove(forwardMostMove)) ||\r
10277                 (gameMode == MachinePlaysBlack &&\r
10278                  WhiteOnMove(forwardMostMove))) {\r
10279                 StopClocks();\r
10280             }\r
10281             pausing = TRUE;\r
10282             ModeHighlight();\r
10283             break;\r
10284         }\r
10285     }\r
10286 }\r
10287 \r
10288 void\r
10289 EditCommentEvent()\r
10290 {\r
10291     char title[MSG_SIZ];\r
10292 \r
10293     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
10294         strcpy(title, _("Edit comment"));\r
10295     } else {\r
10296         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,\r
10297                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
10298                 parseList[currentMove - 1]);\r
10299     }\r
10300 \r
10301     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
10302 }\r
10303 \r
10304 \r
10305 void\r
10306 EditTagsEvent()\r
10307 {\r
10308     char *tags = PGNTags(&gameInfo);\r
10309     EditTagsPopUp(tags);\r
10310     free(tags);\r
10311 }\r
10312 \r
10313 void\r
10314 AnalyzeModeEvent()\r
10315 {\r
10316     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
10317       return;\r
10318 \r
10319     if (gameMode != AnalyzeFile) {\r
10320         if (!appData.icsEngineAnalyze) {\r
10321                EditGameEvent();\r
10322                if (gameMode != EditGame) return;\r
10323         }\r
10324         ResurrectChessProgram();\r
10325         SendToProgram("analyze\n", &first);\r
10326         first.analyzing = TRUE;\r
10327         /*first.maybeThinking = TRUE;*/\r
10328         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10329         AnalysisPopUp(_("Analysis"),\r
10330                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10331     }\r
10332     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;\r
10333     pausing = FALSE;\r
10334     ModeHighlight();\r
10335     SetGameInfo();\r
10336 \r
10337     StartAnalysisClock();\r
10338     GetTimeMark(&lastNodeCountTime);\r
10339     lastNodeCount = 0;\r
10340 }\r
10341 \r
10342 void\r
10343 AnalyzeFileEvent()\r
10344 {\r
10345     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
10346       return;\r
10347 \r
10348     if (gameMode != AnalyzeMode) {\r
10349         EditGameEvent();\r
10350         if (gameMode != EditGame) return;\r
10351         ResurrectChessProgram();\r
10352         SendToProgram("analyze\n", &first);\r
10353         first.analyzing = TRUE;\r
10354         /*first.maybeThinking = TRUE;*/\r
10355         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10356         AnalysisPopUp(_("Analysis"),\r
10357                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10358     }\r
10359     gameMode = AnalyzeFile;\r
10360     pausing = FALSE;\r
10361     ModeHighlight();\r
10362     SetGameInfo();\r
10363 \r
10364     StartAnalysisClock();\r
10365     GetTimeMark(&lastNodeCountTime);\r
10366     lastNodeCount = 0;\r
10367 }\r
10368 \r
10369 void\r
10370 MachineWhiteEvent()\r
10371 {\r
10372     char buf[MSG_SIZ];\r
10373     char *bookHit = NULL;\r
10374 \r
10375     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
10376       return;\r
10377 \r
10378 \r
10379     if (gameMode == PlayFromGameFile || \r
10380         gameMode == TwoMachinesPlay  || \r
10381         gameMode == Training         || \r
10382         gameMode == AnalyzeMode      || \r
10383         gameMode == EndOfGame)\r
10384         EditGameEvent();\r
10385 \r
10386     if (gameMode == EditPosition) \r
10387         EditPositionDone();\r
10388 \r
10389     if (!WhiteOnMove(currentMove)) {\r
10390         DisplayError(_("It is not White's turn"), 0);\r
10391         return;\r
10392     }\r
10393   \r
10394     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10395       ExitAnalyzeMode();\r
10396 \r
10397     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10398         gameMode == AnalyzeFile)\r
10399         TruncateGame();\r
10400 \r
10401     ResurrectChessProgram();    /* in case it isn't running */\r
10402     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */\r
10403         gameMode = MachinePlaysWhite;\r
10404         ResetClocks();\r
10405     } else\r
10406     gameMode = MachinePlaysWhite;\r
10407     pausing = FALSE;\r
10408     ModeHighlight();\r
10409     SetGameInfo();\r
10410     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10411     DisplayTitle(buf);\r
10412     if (first.sendName) {\r
10413       sprintf(buf, "name %s\n", gameInfo.black);\r
10414       SendToProgram(buf, &first);\r
10415     }\r
10416     if (first.sendTime) {\r
10417       if (first.useColors) {\r
10418         SendToProgram("black\n", &first); /*gnu kludge*/\r
10419       }\r
10420       SendTimeRemaining(&first, TRUE);\r
10421     }\r
10422     if (first.useColors) {\r
10423       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately\r
10424     }\r
10425     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10426     SetMachineThinkingEnables();\r
10427     first.maybeThinking = TRUE;\r
10428     StartClocks();\r
10429 \r
10430     if (appData.autoFlipView && !flipView) {\r
10431       flipView = !flipView;\r
10432       DrawPosition(FALSE, NULL);\r
10433       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10434     }\r
10435 \r
10436     if(bookHit) { // [HGM] book: simulate book reply\r
10437         static char bookMove[MSG_SIZ]; // a bit generous?\r
10438 \r
10439         programStats.nodes = programStats.depth = programStats.time = \r
10440         programStats.score = programStats.got_only_move = 0;\r
10441         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10442 \r
10443         strcpy(bookMove, "move ");\r
10444         strcat(bookMove, bookHit);\r
10445         HandleMachineMove(bookMove, &first);\r
10446     }\r
10447 }\r
10448 \r
10449 void\r
10450 MachineBlackEvent()\r
10451 {\r
10452     char buf[MSG_SIZ];\r
10453    char *bookHit = NULL;\r
10454 \r
10455     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
10456         return;\r
10457 \r
10458 \r
10459     if (gameMode == PlayFromGameFile || \r
10460         gameMode == TwoMachinesPlay  || \r
10461         gameMode == Training         || \r
10462         gameMode == AnalyzeMode      || \r
10463         gameMode == EndOfGame)\r
10464         EditGameEvent();\r
10465 \r
10466     if (gameMode == EditPosition) \r
10467         EditPositionDone();\r
10468 \r
10469     if (WhiteOnMove(currentMove)) {\r
10470         DisplayError(_("It is not Black's turn"), 0);\r
10471         return;\r
10472     }\r
10473     \r
10474     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10475       ExitAnalyzeMode();\r
10476 \r
10477     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10478         gameMode == AnalyzeFile)\r
10479         TruncateGame();\r
10480 \r
10481     ResurrectChessProgram();    /* in case it isn't running */\r
10482     gameMode = MachinePlaysBlack;\r
10483     pausing = FALSE;\r
10484     ModeHighlight();\r
10485     SetGameInfo();\r
10486     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10487     DisplayTitle(buf);\r
10488     if (first.sendName) {\r
10489       sprintf(buf, "name %s\n", gameInfo.white);\r
10490       SendToProgram(buf, &first);\r
10491     }\r
10492     if (first.sendTime) {\r
10493       if (first.useColors) {\r
10494         SendToProgram("white\n", &first); /*gnu kludge*/\r
10495       }\r
10496       SendTimeRemaining(&first, FALSE);\r
10497     }\r
10498     if (first.useColors) {\r
10499       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately\r
10500     }\r
10501     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10502     SetMachineThinkingEnables();\r
10503     first.maybeThinking = TRUE;\r
10504     StartClocks();\r
10505 \r
10506     if (appData.autoFlipView && flipView) {\r
10507       flipView = !flipView;\r
10508       DrawPosition(FALSE, NULL);\r
10509       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10510     }\r
10511     if(bookHit) { // [HGM] book: simulate book reply\r
10512         static char bookMove[MSG_SIZ]; // a bit generous?\r
10513 \r
10514         programStats.nodes = programStats.depth = programStats.time = \r
10515         programStats.score = programStats.got_only_move = 0;\r
10516         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10517 \r
10518         strcpy(bookMove, "move ");\r
10519         strcat(bookMove, bookHit);\r
10520         HandleMachineMove(bookMove, &first);\r
10521     }\r
10522 }\r
10523 \r
10524 \r
10525 void\r
10526 DisplayTwoMachinesTitle()\r
10527 {\r
10528     char buf[MSG_SIZ];\r
10529     if (appData.matchGames > 0) {\r
10530         if (first.twoMachinesColor[0] == 'w') {\r
10531             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10532                     gameInfo.white, gameInfo.black,\r
10533                     first.matchWins, second.matchWins,\r
10534                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10535         } else {\r
10536             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10537                     gameInfo.white, gameInfo.black,\r
10538                     second.matchWins, first.matchWins,\r
10539                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10540         }\r
10541     } else {\r
10542         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10543     }\r
10544     DisplayTitle(buf);\r
10545 }\r
10546 \r
10547 void\r
10548 TwoMachinesEvent P((void))\r
10549 {\r
10550     int i;\r
10551     char buf[MSG_SIZ];\r
10552     ChessProgramState *onmove;\r
10553     char *bookHit = NULL;\r
10554     \r
10555     if (appData.noChessProgram) return;\r
10556 \r
10557     switch (gameMode) {\r
10558       case TwoMachinesPlay:\r
10559         return;\r
10560       case MachinePlaysWhite:\r
10561       case MachinePlaysBlack:\r
10562         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10563             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
10564             return;\r
10565         }\r
10566         /* fall through */\r
10567       case BeginningOfGame:\r
10568       case PlayFromGameFile:\r
10569       case EndOfGame:\r
10570         EditGameEvent();\r
10571         if (gameMode != EditGame) return;\r
10572         break;\r
10573       case EditPosition:\r
10574         EditPositionDone();\r
10575         break;\r
10576       case AnalyzeMode:\r
10577       case AnalyzeFile:\r
10578         ExitAnalyzeMode();\r
10579         break;\r
10580       case EditGame:\r
10581       default:\r
10582         break;\r
10583     }\r
10584 \r
10585     forwardMostMove = currentMove;\r
10586     ResurrectChessProgram();    /* in case first program isn't running */\r
10587 \r
10588     if (second.pr == NULL) {\r
10589         StartChessProgram(&second);\r
10590         if (second.protocolVersion == 1) {\r
10591           TwoMachinesEventIfReady();\r
10592         } else {\r
10593           /* kludge: allow timeout for initial "feature" command */\r
10594           FreezeUI();\r
10595           DisplayMessage("", _("Starting second chess program"));\r
10596           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
10597         }\r
10598         return;\r
10599     }\r
10600     DisplayMessage("", "");\r
10601     InitChessProgram(&second, FALSE);\r
10602     SendToProgram("force\n", &second);\r
10603     if (startedFromSetupPosition) {\r
10604         SendBoard(&second, backwardMostMove);\r
10605     if (appData.debugMode) {\r
10606         fprintf(debugFP, "Two Machines\n");\r
10607     }\r
10608     }\r
10609     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
10610         SendMoveToProgram(i, &second);\r
10611     }\r
10612 \r
10613     gameMode = TwoMachinesPlay;\r
10614     pausing = FALSE;\r
10615     ModeHighlight();\r
10616     SetGameInfo();\r
10617     DisplayTwoMachinesTitle();\r
10618     firstMove = TRUE;\r
10619     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
10620         onmove = &first;\r
10621     } else {\r
10622         onmove = &second;\r
10623     }\r
10624 \r
10625     SendToProgram(first.computerString, &first);\r
10626     if (first.sendName) {\r
10627       sprintf(buf, "name %s\n", second.tidy);\r
10628       SendToProgram(buf, &first);\r
10629     }\r
10630     SendToProgram(second.computerString, &second);\r
10631     if (second.sendName) {\r
10632       sprintf(buf, "name %s\n", first.tidy);\r
10633       SendToProgram(buf, &second);\r
10634     }\r
10635 \r
10636     ResetClocks();\r
10637     if (!first.sendTime || !second.sendTime) {\r
10638         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10639         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10640     }\r
10641     if (onmove->sendTime) {\r
10642       if (onmove->useColors) {\r
10643         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
10644       }\r
10645       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
10646     }\r
10647     if (onmove->useColors) {\r
10648       SendToProgram(onmove->twoMachinesColor, onmove);\r
10649     }\r
10650     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move\r
10651 //    SendToProgram("go\n", onmove);\r
10652     onmove->maybeThinking = TRUE;\r
10653     SetMachineThinkingEnables();\r
10654 \r
10655     StartClocks();\r
10656 \r
10657     if(bookHit) { // [HGM] book: simulate book reply\r
10658         static char bookMove[MSG_SIZ]; // a bit generous?\r
10659 \r
10660         programStats.nodes = programStats.depth = programStats.time = \r
10661         programStats.score = programStats.got_only_move = 0;\r
10662         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10663 \r
10664         strcpy(bookMove, "move ");\r
10665         strcat(bookMove, bookHit);\r
10666         HandleMachineMove(bookMove, &first);\r
10667     }\r
10668 }\r
10669 \r
10670 void\r
10671 TrainingEvent()\r
10672 {\r
10673     if (gameMode == Training) {\r
10674       SetTrainingModeOff();\r
10675       gameMode = PlayFromGameFile;\r
10676       DisplayMessage("", _("Training mode off"));\r
10677     } else {\r
10678       gameMode = Training;\r
10679       animateTraining = appData.animate;\r
10680 \r
10681       /* make sure we are not already at the end of the game */\r
10682       if (currentMove < forwardMostMove) {\r
10683         SetTrainingModeOn();\r
10684         DisplayMessage("", _("Training mode on"));\r
10685       } else {\r
10686         gameMode = PlayFromGameFile;\r
10687         DisplayError(_("Already at end of game"), 0);\r
10688       }\r
10689     }\r
10690     ModeHighlight();\r
10691 }\r
10692 \r
10693 void\r
10694 IcsClientEvent()\r
10695 {\r
10696     if (!appData.icsActive) return;\r
10697     switch (gameMode) {\r
10698       case IcsPlayingWhite:\r
10699       case IcsPlayingBlack:\r
10700       case IcsObserving:\r
10701       case IcsIdle:\r
10702       case BeginningOfGame:\r
10703       case IcsExamining:\r
10704         return;\r
10705 \r
10706       case EditGame:\r
10707         break;\r
10708 \r
10709       case EditPosition:\r
10710         EditPositionDone();\r
10711         break;\r
10712 \r
10713       case AnalyzeMode:\r
10714       case AnalyzeFile:\r
10715         ExitAnalyzeMode();\r
10716         break;\r
10717         \r
10718       default:\r
10719         EditGameEvent();\r
10720         break;\r
10721     }\r
10722 \r
10723     gameMode = IcsIdle;\r
10724     ModeHighlight();\r
10725     return;\r
10726 }\r
10727 \r
10728 \r
10729 void\r
10730 EditGameEvent()\r
10731 {\r
10732     int i;\r
10733 \r
10734     switch (gameMode) {\r
10735       case Training:\r
10736         SetTrainingModeOff();\r
10737         break;\r
10738       case MachinePlaysWhite:\r
10739       case MachinePlaysBlack:\r
10740       case BeginningOfGame:\r
10741         SendToProgram("force\n", &first);\r
10742         SetUserThinkingEnables();\r
10743         break;\r
10744       case PlayFromGameFile:\r
10745         (void) StopLoadGameTimer();\r
10746         if (gameFileFP != NULL) {\r
10747             gameFileFP = NULL;\r
10748         }\r
10749         break;\r
10750       case EditPosition:\r
10751         EditPositionDone();\r
10752         break;\r
10753       case AnalyzeMode:\r
10754       case AnalyzeFile:\r
10755         ExitAnalyzeMode();\r
10756         SendToProgram("force\n", &first);\r
10757         break;\r
10758       case TwoMachinesPlay:\r
10759         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10760         ResurrectChessProgram();\r
10761         SetUserThinkingEnables();\r
10762         break;\r
10763       case EndOfGame:\r
10764         ResurrectChessProgram();\r
10765         break;\r
10766       case IcsPlayingBlack:\r
10767       case IcsPlayingWhite:\r
10768         DisplayError(_("Warning: You are still playing a game"), 0);\r
10769         break;\r
10770       case IcsObserving:\r
10771         DisplayError(_("Warning: You are still observing a game"), 0);\r
10772         break;\r
10773       case IcsExamining:\r
10774         DisplayError(_("Warning: You are still examining a game"), 0);\r
10775         break;\r
10776       case IcsIdle:\r
10777         break;\r
10778       case EditGame:\r
10779       default:\r
10780         return;\r
10781     }\r
10782     \r
10783     pausing = FALSE;\r
10784     StopClocks();\r
10785     first.offeredDraw = second.offeredDraw = 0;\r
10786 \r
10787     if (gameMode == PlayFromGameFile) {\r
10788         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10789         blackTimeRemaining = timeRemaining[1][currentMove];\r
10790         DisplayTitle("");\r
10791     }\r
10792 \r
10793     if (gameMode == MachinePlaysWhite ||\r
10794         gameMode == MachinePlaysBlack ||\r
10795         gameMode == TwoMachinesPlay ||\r
10796         gameMode == EndOfGame) {\r
10797         i = forwardMostMove;\r
10798         while (i > currentMove) {\r
10799             SendToProgram("undo\n", &first);\r
10800             i--;\r
10801         }\r
10802         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10803         blackTimeRemaining = timeRemaining[1][currentMove];\r
10804         DisplayBothClocks();\r
10805         if (whiteFlag || blackFlag) {\r
10806             whiteFlag = blackFlag = 0;\r
10807         }\r
10808         DisplayTitle("");\r
10809     }           \r
10810     \r
10811     gameMode = EditGame;\r
10812     ModeHighlight();\r
10813     SetGameInfo();\r
10814 }\r
10815 \r
10816 \r
10817 void\r
10818 EditPositionEvent()\r
10819 {\r
10820     if (gameMode == EditPosition) {\r
10821         EditGameEvent();\r
10822         return;\r
10823     }\r
10824     \r
10825     EditGameEvent();\r
10826     if (gameMode != EditGame) return;\r
10827     \r
10828     gameMode = EditPosition;\r
10829     ModeHighlight();\r
10830     SetGameInfo();\r
10831     if (currentMove > 0)\r
10832       CopyBoard(boards[0], boards[currentMove]);\r
10833     \r
10834     blackPlaysFirst = !WhiteOnMove(currentMove);\r
10835     ResetClocks();\r
10836     currentMove = forwardMostMove = backwardMostMove = 0;\r
10837     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10838     DisplayMove(-1);\r
10839 }\r
10840 \r
10841 void\r
10842 ExitAnalyzeMode()\r
10843 {\r
10844     /* [DM] icsEngineAnalyze - possible call from other functions */\r
10845     if (appData.icsEngineAnalyze) {\r
10846         appData.icsEngineAnalyze = FALSE;\r
10847 \r
10848         DisplayMessage("",_("Close ICS engine analyze..."));\r
10849     }\r
10850     if (first.analysisSupport && first.analyzing) {\r
10851       SendToProgram("exit\n", &first);\r
10852       first.analyzing = FALSE;\r
10853     }\r
10854     AnalysisPopDown();\r
10855     thinkOutput[0] = NULLCHAR;\r
10856 }\r
10857 \r
10858 void\r
10859 EditPositionDone()\r
10860 {\r
10861     startedFromSetupPosition = TRUE;\r
10862     InitChessProgram(&first, FALSE);\r
10863     SendToProgram("force\n", &first);\r
10864     if (blackPlaysFirst) {\r
10865         strcpy(moveList[0], "");\r
10866         strcpy(parseList[0], "");\r
10867         currentMove = forwardMostMove = backwardMostMove = 1;\r
10868         CopyBoard(boards[1], boards[0]);\r
10869         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */\r
10870         { int i;\r
10871           epStatus[1] = epStatus[0];\r
10872           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];\r
10873         }\r
10874     } else {\r
10875         currentMove = forwardMostMove = backwardMostMove = 0;\r
10876     }\r
10877     SendBoard(&first, forwardMostMove);\r
10878     if (appData.debugMode) {\r
10879         fprintf(debugFP, "EditPosDone\n");\r
10880     }\r
10881     DisplayTitle("");\r
10882     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10883     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10884     gameMode = EditGame;\r
10885     ModeHighlight();\r
10886     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10887     ClearHighlights(); /* [AS] */\r
10888 }\r
10889 \r
10890 /* Pause for `ms' milliseconds */\r
10891 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10892 void\r
10893 TimeDelay(ms)\r
10894      long ms;\r
10895 {\r
10896     TimeMark m1, m2;\r
10897 \r
10898     GetTimeMark(&m1);\r
10899     do {\r
10900         GetTimeMark(&m2);\r
10901     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
10902 }\r
10903 \r
10904 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10905 void\r
10906 SendMultiLineToICS(buf)\r
10907      char *buf;\r
10908 {\r
10909     char temp[MSG_SIZ+1], *p;\r
10910     int len;\r
10911 \r
10912     len = strlen(buf);\r
10913     if (len > MSG_SIZ)\r
10914       len = MSG_SIZ;\r
10915   \r
10916     strncpy(temp, buf, len);\r
10917     temp[len] = 0;\r
10918 \r
10919     p = temp;\r
10920     while (*p) {\r
10921         if (*p == '\n' || *p == '\r')\r
10922           *p = ' ';\r
10923         ++p;\r
10924     }\r
10925 \r
10926     strcat(temp, "\n");\r
10927     SendToICS(temp);\r
10928     SendToPlayer(temp, strlen(temp));\r
10929 }\r
10930 \r
10931 void\r
10932 SetWhiteToPlayEvent()\r
10933 {\r
10934     if (gameMode == EditPosition) {\r
10935         blackPlaysFirst = FALSE;\r
10936         DisplayBothClocks();    /* works because currentMove is 0 */\r
10937     } else if (gameMode == IcsExamining) {\r
10938         SendToICS(ics_prefix);\r
10939         SendToICS("tomove white\n");\r
10940     }\r
10941 }\r
10942 \r
10943 void\r
10944 SetBlackToPlayEvent()\r
10945 {\r
10946     if (gameMode == EditPosition) {\r
10947         blackPlaysFirst = TRUE;\r
10948         currentMove = 1;        /* kludge */\r
10949         DisplayBothClocks();\r
10950         currentMove = 0;\r
10951     } else if (gameMode == IcsExamining) {\r
10952         SendToICS(ics_prefix);\r
10953         SendToICS("tomove black\n");\r
10954     }\r
10955 }\r
10956 \r
10957 void\r
10958 EditPositionMenuEvent(selection, x, y)\r
10959      ChessSquare selection;\r
10960      int x, y;\r
10961 {\r
10962     char buf[MSG_SIZ];\r
10963     ChessSquare piece = boards[0][y][x];\r
10964 \r
10965     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
10966 \r
10967     switch (selection) {\r
10968       case ClearBoard:\r
10969         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
10970             SendToICS(ics_prefix);\r
10971             SendToICS("bsetup clear\n");\r
10972         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
10973             SendToICS(ics_prefix);\r
10974             SendToICS("clearboard\n");\r
10975         } else {\r
10976             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;\r
10977                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */\r
10978                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
10979                     if (gameMode == IcsExamining) {\r
10980                         if (boards[currentMove][y][x] != EmptySquare) {\r
10981                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
10982                                     AAA + x, ONE + y);\r
10983                             SendToICS(buf);\r
10984                         }\r
10985                     } else {\r
10986                         boards[0][y][x] = p;\r
10987                     }\r
10988                 }\r
10989             }\r
10990         }\r
10991         if (gameMode == EditPosition) {\r
10992             DrawPosition(FALSE, boards[0]);\r
10993         }\r
10994         break;\r
10995 \r
10996       case WhitePlay:\r
10997         SetWhiteToPlayEvent();\r
10998         break;\r
10999 \r
11000       case BlackPlay:\r
11001         SetBlackToPlayEvent();\r
11002         break;\r
11003 \r
11004       case EmptySquare:\r
11005         if (gameMode == IcsExamining) {\r
11006             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
11007             SendToICS(buf);\r
11008         } else {\r
11009             boards[0][y][x] = EmptySquare;\r
11010             DrawPosition(FALSE, boards[0]);\r
11011         }\r
11012         break;\r
11013 \r
11014       case PromotePiece:\r
11015         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
11016            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
11017             selection = (ChessSquare) (PROMOTED piece);\r
11018         } else if(piece == EmptySquare) selection = WhiteSilver;\r
11019         else selection = (ChessSquare)((int)piece - 1);\r
11020         goto defaultlabel;\r
11021 \r
11022       case DemotePiece:\r
11023         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
11024            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
11025             selection = (ChessSquare) (DEMOTED piece);\r
11026         } else if(piece == EmptySquare) selection = BlackSilver;\r
11027         else selection = (ChessSquare)((int)piece + 1);       \r
11028         goto defaultlabel;\r
11029 \r
11030       case WhiteQueen:\r
11031       case BlackQueen:\r
11032         if(gameInfo.variant == VariantShatranj ||\r
11033            gameInfo.variant == VariantXiangqi  ||\r
11034            gameInfo.variant == VariantCourier    )\r
11035             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
11036         goto defaultlabel;\r
11037 \r
11038       case WhiteKing:\r
11039       case BlackKing:\r
11040         if(gameInfo.variant == VariantXiangqi)\r
11041             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
11042         if(gameInfo.variant == VariantKnightmate)\r
11043             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
11044       default:\r
11045         defaultlabel:\r
11046         if (gameMode == IcsExamining) {\r
11047             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
11048                     PieceToChar(selection), AAA + x, ONE + y);\r
11049             SendToICS(buf);\r
11050         } else {\r
11051             boards[0][y][x] = selection;\r
11052             DrawPosition(FALSE, boards[0]);\r
11053         }\r
11054         break;\r
11055     }\r
11056 }\r
11057 \r
11058 \r
11059 void\r
11060 DropMenuEvent(selection, x, y)\r
11061      ChessSquare selection;\r
11062      int x, y;\r
11063 {\r
11064     ChessMove moveType;\r
11065 \r
11066     switch (gameMode) {\r
11067       case IcsPlayingWhite:\r
11068       case MachinePlaysBlack:\r
11069         if (!WhiteOnMove(currentMove)) {\r
11070             DisplayMoveError(_("It is Black's turn"));\r
11071             return;\r
11072         }\r
11073         moveType = WhiteDrop;\r
11074         break;\r
11075       case IcsPlayingBlack:\r
11076       case MachinePlaysWhite:\r
11077         if (WhiteOnMove(currentMove)) {\r
11078             DisplayMoveError(_("It is White's turn"));\r
11079             return;\r
11080         }\r
11081         moveType = BlackDrop;\r
11082         break;\r
11083       case EditGame:\r
11084         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
11085         break;\r
11086       default:\r
11087         return;\r
11088     }\r
11089 \r
11090     if (moveType == BlackDrop && selection < BlackPawn) {\r
11091       selection = (ChessSquare) ((int) selection\r
11092                                  + (int) BlackPawn - (int) WhitePawn);\r
11093     }\r
11094     if (boards[currentMove][y][x] != EmptySquare) {\r
11095         DisplayMoveError(_("That square is occupied"));\r
11096         return;\r
11097     }\r
11098 \r
11099     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
11100 }\r
11101 \r
11102 void\r
11103 AcceptEvent()\r
11104 {\r
11105     /* Accept a pending offer of any kind from opponent */\r
11106     \r
11107     if (appData.icsActive) {\r
11108         SendToICS(ics_prefix);\r
11109         SendToICS("accept\n");\r
11110     } else if (cmailMsgLoaded) {\r
11111         if (currentMove == cmailOldMove &&\r
11112             commentList[cmailOldMove] != NULL &&\r
11113             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11114                    "Black offers a draw" : "White offers a draw")) {\r
11115             TruncateGame();\r
11116             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11117             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11118         } else {\r
11119             DisplayError(_("There is no pending offer on this move"), 0);\r
11120             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11121         }\r
11122     } else {\r
11123         /* Not used for offers from chess program */\r
11124     }\r
11125 }\r
11126 \r
11127 void\r
11128 DeclineEvent()\r
11129 {\r
11130     /* Decline a pending offer of any kind from opponent */\r
11131     \r
11132     if (appData.icsActive) {\r
11133         SendToICS(ics_prefix);\r
11134         SendToICS("decline\n");\r
11135     } else if (cmailMsgLoaded) {\r
11136         if (currentMove == cmailOldMove &&\r
11137             commentList[cmailOldMove] != NULL &&\r
11138             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11139                    "Black offers a draw" : "White offers a draw")) {\r
11140 #ifdef NOTDEF\r
11141             AppendComment(cmailOldMove, "Draw declined");\r
11142             DisplayComment(cmailOldMove - 1, "Draw declined");\r
11143 #endif /*NOTDEF*/\r
11144         } else {\r
11145             DisplayError(_("There is no pending offer on this move"), 0);\r
11146         }\r
11147     } else {\r
11148         /* Not used for offers from chess program */\r
11149     }\r
11150 }\r
11151 \r
11152 void\r
11153 RematchEvent()\r
11154 {\r
11155     /* Issue ICS rematch command */\r
11156     if (appData.icsActive) {\r
11157         SendToICS(ics_prefix);\r
11158         SendToICS("rematch\n");\r
11159     }\r
11160 }\r
11161 \r
11162 void\r
11163 CallFlagEvent()\r
11164 {\r
11165     /* Call your opponent's flag (claim a win on time) */\r
11166     if (appData.icsActive) {\r
11167         SendToICS(ics_prefix);\r
11168         SendToICS("flag\n");\r
11169     } else {\r
11170         switch (gameMode) {\r
11171           default:\r
11172             return;\r
11173           case MachinePlaysWhite:\r
11174             if (whiteFlag) {\r
11175                 if (blackFlag)\r
11176                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11177                            GE_PLAYER);\r
11178                 else\r
11179                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
11180             } else {\r
11181                 DisplayError(_("Your opponent is not out of time"), 0);\r
11182             }\r
11183             break;\r
11184           case MachinePlaysBlack:\r
11185             if (blackFlag) {\r
11186                 if (whiteFlag)\r
11187                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11188                            GE_PLAYER);\r
11189                 else\r
11190                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
11191             } else {\r
11192                 DisplayError(_("Your opponent is not out of time"), 0);\r
11193             }\r
11194             break;\r
11195         }\r
11196     }\r
11197 }\r
11198 \r
11199 void\r
11200 DrawEvent()\r
11201 {\r
11202     /* Offer draw or accept pending draw offer from opponent */\r
11203     \r
11204     if (appData.icsActive) {\r
11205         /* Note: tournament rules require draw offers to be\r
11206            made after you make your move but before you punch\r
11207            your clock.  Currently ICS doesn't let you do that;\r
11208            instead, you immediately punch your clock after making\r
11209            a move, but you can offer a draw at any time. */\r
11210         \r
11211         SendToICS(ics_prefix);\r
11212         SendToICS("draw\n");\r
11213     } else if (cmailMsgLoaded) {\r
11214         if (currentMove == cmailOldMove &&\r
11215             commentList[cmailOldMove] != NULL &&\r
11216             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11217                    "Black offers a draw" : "White offers a draw")) {\r
11218             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11219             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11220         } else if (currentMove == cmailOldMove + 1) {\r
11221             char *offer = WhiteOnMove(cmailOldMove) ?\r
11222               "White offers a draw" : "Black offers a draw";\r
11223             AppendComment(currentMove, offer);\r
11224             DisplayComment(currentMove - 1, offer);\r
11225             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
11226         } else {\r
11227             DisplayError(_("You must make your move before offering a draw"), 0);\r
11228             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11229         }\r
11230     } else if (first.offeredDraw) {\r
11231         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
11232     } else {\r
11233         if (first.sendDrawOffers) {\r
11234             SendToProgram("draw\n", &first);\r
11235             userOfferedDraw = TRUE;\r
11236         }\r
11237     }\r
11238 }\r
11239 \r
11240 void\r
11241 AdjournEvent()\r
11242 {\r
11243     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
11244     \r
11245     if (appData.icsActive) {\r
11246         SendToICS(ics_prefix);\r
11247         SendToICS("adjourn\n");\r
11248     } else {\r
11249         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
11250     }\r
11251 }\r
11252 \r
11253 \r
11254 void\r
11255 AbortEvent()\r
11256 {\r
11257     /* Offer Abort or accept pending Abort offer from opponent */\r
11258     \r
11259     if (appData.icsActive) {\r
11260         SendToICS(ics_prefix);\r
11261         SendToICS("abort\n");\r
11262     } else {\r
11263         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
11264     }\r
11265 }\r
11266 \r
11267 void\r
11268 ResignEvent()\r
11269 {\r
11270     /* Resign.  You can do this even if it's not your turn. */\r
11271     \r
11272     if (appData.icsActive) {\r
11273         SendToICS(ics_prefix);\r
11274         SendToICS("resign\n");\r
11275     } else {\r
11276         switch (gameMode) {\r
11277           case MachinePlaysWhite:\r
11278             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11279             break;\r
11280           case MachinePlaysBlack:\r
11281             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11282             break;\r
11283           case EditGame:\r
11284             if (cmailMsgLoaded) {\r
11285                 TruncateGame();\r
11286                 if (WhiteOnMove(cmailOldMove)) {\r
11287                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11288                 } else {\r
11289                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11290                 }\r
11291                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
11292             }\r
11293             break;\r
11294           default:\r
11295             break;\r
11296         }\r
11297     }\r
11298 }\r
11299 \r
11300 \r
11301 void\r
11302 StopObservingEvent()\r
11303 {\r
11304     /* Stop observing current games */\r
11305     SendToICS(ics_prefix);\r
11306     SendToICS("unobserve\n");\r
11307 }\r
11308 \r
11309 void\r
11310 StopExaminingEvent()\r
11311 {\r
11312     /* Stop observing current game */\r
11313     SendToICS(ics_prefix);\r
11314     SendToICS("unexamine\n");\r
11315 }\r
11316 \r
11317 void\r
11318 ForwardInner(target)\r
11319      int target;\r
11320 {\r
11321     int limit;\r
11322 \r
11323     if (appData.debugMode)\r
11324         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
11325                 target, currentMove, forwardMostMove);\r
11326 \r
11327     if (gameMode == EditPosition)\r
11328       return;\r
11329 \r
11330     if (gameMode == PlayFromGameFile && !pausing)\r
11331       PauseEvent();\r
11332     \r
11333     if (gameMode == IcsExamining && pausing)\r
11334       limit = pauseExamForwardMostMove;\r
11335     else\r
11336       limit = forwardMostMove;\r
11337     \r
11338     if (target > limit) target = limit;\r
11339 \r
11340     if (target > 0 && moveList[target - 1][0]) {\r
11341         int fromX, fromY, toX, toY;\r
11342         toX = moveList[target - 1][2] - AAA;\r
11343         toY = moveList[target - 1][3] - ONE;\r
11344         if (moveList[target - 1][1] == '@') {\r
11345             if (appData.highlightLastMove) {\r
11346                 SetHighlights(-1, -1, toX, toY);\r
11347             }\r
11348         } else {\r
11349             fromX = moveList[target - 1][0] - AAA;\r
11350             fromY = moveList[target - 1][1] - ONE;\r
11351             if (target == currentMove + 1) {\r
11352                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
11353             }\r
11354             if (appData.highlightLastMove) {\r
11355                 SetHighlights(fromX, fromY, toX, toY);\r
11356             }\r
11357         }\r
11358     }\r
11359     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
11360         gameMode == Training || gameMode == PlayFromGameFile || \r
11361         gameMode == AnalyzeFile) {\r
11362         while (currentMove < target) {\r
11363             SendMoveToProgram(currentMove++, &first);\r
11364         }\r
11365     } else {\r
11366         currentMove = target;\r
11367     }\r
11368     \r
11369     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11370         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11371         blackTimeRemaining = timeRemaining[1][currentMove];\r
11372     }\r
11373     DisplayBothClocks();\r
11374     DisplayMove(currentMove - 1);\r
11375     DrawPosition(FALSE, boards[currentMove]);\r
11376     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11377     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
11378         DisplayComment(currentMove - 1, commentList[currentMove]);\r
11379     }\r
11380 }\r
11381 \r
11382 \r
11383 void\r
11384 ForwardEvent()\r
11385 {\r
11386     if (gameMode == IcsExamining && !pausing) {\r
11387         SendToICS(ics_prefix);\r
11388         SendToICS("forward\n");\r
11389     } else {\r
11390         ForwardInner(currentMove + 1);\r
11391     }\r
11392 }\r
11393 \r
11394 void\r
11395 ToEndEvent()\r
11396 {\r
11397     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11398         /* to optimze, we temporarily turn off analysis mode while we feed\r
11399          * the remaining moves to the engine. Otherwise we get analysis output\r
11400          * after each move.\r
11401          */ \r
11402         if (first.analysisSupport) {\r
11403           SendToProgram("exit\nforce\n", &first);\r
11404           first.analyzing = FALSE;\r
11405         }\r
11406     }\r
11407         \r
11408     if (gameMode == IcsExamining && !pausing) {\r
11409         SendToICS(ics_prefix);\r
11410         SendToICS("forward 999999\n");\r
11411     } else {\r
11412         ForwardInner(forwardMostMove);\r
11413     }\r
11414 \r
11415     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11416         /* we have fed all the moves, so reactivate analysis mode */\r
11417         SendToProgram("analyze\n", &first);\r
11418         first.analyzing = TRUE;\r
11419         /*first.maybeThinking = TRUE;*/\r
11420         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11421     }\r
11422 }\r
11423 \r
11424 void\r
11425 BackwardInner(target)\r
11426      int target;\r
11427 {\r
11428     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
11429 \r
11430     if (appData.debugMode)\r
11431         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
11432                 target, currentMove, forwardMostMove);\r
11433 \r
11434     if (gameMode == EditPosition) return;\r
11435     if (currentMove <= backwardMostMove) {\r
11436         ClearHighlights();\r
11437         DrawPosition(full_redraw, boards[currentMove]);\r
11438         return;\r
11439     }\r
11440     if (gameMode == PlayFromGameFile && !pausing)\r
11441       PauseEvent();\r
11442     \r
11443     if (moveList[target][0]) {\r
11444         int fromX, fromY, toX, toY;\r
11445         toX = moveList[target][2] - AAA;\r
11446         toY = moveList[target][3] - ONE;\r
11447         if (moveList[target][1] == '@') {\r
11448             if (appData.highlightLastMove) {\r
11449                 SetHighlights(-1, -1, toX, toY);\r
11450             }\r
11451         } else {\r
11452             fromX = moveList[target][0] - AAA;\r
11453             fromY = moveList[target][1] - ONE;\r
11454             if (target == currentMove - 1) {\r
11455                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
11456             }\r
11457             if (appData.highlightLastMove) {\r
11458                 SetHighlights(fromX, fromY, toX, toY);\r
11459             }\r
11460         }\r
11461     }\r
11462     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
11463         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
11464         while (currentMove > target) {\r
11465             SendToProgram("undo\n", &first);\r
11466             currentMove--;\r
11467         }\r
11468     } else {\r
11469         currentMove = target;\r
11470     }\r
11471     \r
11472     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11473         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11474         blackTimeRemaining = timeRemaining[1][currentMove];\r
11475     }\r
11476     DisplayBothClocks();\r
11477     DisplayMove(currentMove - 1);\r
11478     DrawPosition(full_redraw, boards[currentMove]);\r
11479     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11480     // [HGM] PV info: routine tests if comment empty\r
11481     DisplayComment(currentMove - 1, commentList[currentMove]);\r
11482 }\r
11483 \r
11484 void\r
11485 BackwardEvent()\r
11486 {\r
11487     if (gameMode == IcsExamining && !pausing) {\r
11488         SendToICS(ics_prefix);\r
11489         SendToICS("backward\n");\r
11490     } else {\r
11491         BackwardInner(currentMove - 1);\r
11492     }\r
11493 }\r
11494 \r
11495 void\r
11496 ToStartEvent()\r
11497 {\r
11498     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11499         /* to optimze, we temporarily turn off analysis mode while we undo\r
11500          * all the moves. Otherwise we get analysis output after each undo.\r
11501          */ \r
11502         if (first.analysisSupport) {\r
11503           SendToProgram("exit\nforce\n", &first);\r
11504           first.analyzing = FALSE;\r
11505         }\r
11506     }\r
11507 \r
11508     if (gameMode == IcsExamining && !pausing) {\r
11509         SendToICS(ics_prefix);\r
11510         SendToICS("backward 999999\n");\r
11511     } else {\r
11512         BackwardInner(backwardMostMove);\r
11513     }\r
11514 \r
11515     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11516         /* we have fed all the moves, so reactivate analysis mode */\r
11517         SendToProgram("analyze\n", &first);\r
11518         first.analyzing = TRUE;\r
11519         /*first.maybeThinking = TRUE;*/\r
11520         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11521     }\r
11522 }\r
11523 \r
11524 void\r
11525 ToNrEvent(int to)\r
11526 {\r
11527   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
11528   if (to >= forwardMostMove) to = forwardMostMove;\r
11529   if (to <= backwardMostMove) to = backwardMostMove;\r
11530   if (to < currentMove) {\r
11531     BackwardInner(to);\r
11532   } else {\r
11533     ForwardInner(to);\r
11534   }\r
11535 }\r
11536 \r
11537 void\r
11538 RevertEvent()\r
11539 {\r
11540     if (gameMode != IcsExamining) {\r
11541         DisplayError(_("You are not examining a game"), 0);\r
11542         return;\r
11543     }\r
11544     if (pausing) {\r
11545         DisplayError(_("You can't revert while pausing"), 0);\r
11546         return;\r
11547     }\r
11548     SendToICS(ics_prefix);\r
11549     SendToICS("revert\n");\r
11550 }\r
11551 \r
11552 void\r
11553 RetractMoveEvent()\r
11554 {\r
11555     switch (gameMode) {\r
11556       case MachinePlaysWhite:\r
11557       case MachinePlaysBlack:\r
11558         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
11559             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
11560             return;\r
11561         }\r
11562         if (forwardMostMove < 2) return;\r
11563         currentMove = forwardMostMove = forwardMostMove - 2;\r
11564         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11565         blackTimeRemaining = timeRemaining[1][currentMove];\r
11566         DisplayBothClocks();\r
11567         DisplayMove(currentMove - 1);\r
11568         ClearHighlights();/*!! could figure this out*/\r
11569         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
11570         SendToProgram("remove\n", &first);\r
11571         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
11572         break;\r
11573 \r
11574       case BeginningOfGame:\r
11575       default:\r
11576         break;\r
11577 \r
11578       case IcsPlayingWhite:\r
11579       case IcsPlayingBlack:\r
11580         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
11581             SendToICS(ics_prefix);\r
11582             SendToICS("takeback 2\n");\r
11583         } else {\r
11584             SendToICS(ics_prefix);\r
11585             SendToICS("takeback 1\n");\r
11586         }\r
11587         break;\r
11588     }\r
11589 }\r
11590 \r
11591 void\r
11592 MoveNowEvent()\r
11593 {\r
11594     ChessProgramState *cps;\r
11595 \r
11596     switch (gameMode) {\r
11597       case MachinePlaysWhite:\r
11598         if (!WhiteOnMove(forwardMostMove)) {\r
11599             DisplayError(_("It is your turn"), 0);\r
11600             return;\r
11601         }\r
11602         cps = &first;\r
11603         break;\r
11604       case MachinePlaysBlack:\r
11605         if (WhiteOnMove(forwardMostMove)) {\r
11606             DisplayError(_("It is your turn"), 0);\r
11607             return;\r
11608         }\r
11609         cps = &first;\r
11610         break;\r
11611       case TwoMachinesPlay:\r
11612         if (WhiteOnMove(forwardMostMove) ==\r
11613             (first.twoMachinesColor[0] == 'w')) {\r
11614             cps = &first;\r
11615         } else {\r
11616             cps = &second;\r
11617         }\r
11618         break;\r
11619       case BeginningOfGame:\r
11620       default:\r
11621         return;\r
11622     }\r
11623     SendToProgram("?\n", cps);\r
11624 }\r
11625 \r
11626 void\r
11627 TruncateGameEvent()\r
11628 {\r
11629     EditGameEvent();\r
11630     if (gameMode != EditGame) return;\r
11631     TruncateGame();\r
11632 }\r
11633 \r
11634 void\r
11635 TruncateGame()\r
11636 {\r
11637     if (forwardMostMove > currentMove) {\r
11638         if (gameInfo.resultDetails != NULL) {\r
11639             free(gameInfo.resultDetails);\r
11640             gameInfo.resultDetails = NULL;\r
11641             gameInfo.result = GameUnfinished;\r
11642         }\r
11643         forwardMostMove = currentMove;\r
11644         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
11645                    currentMove-1);\r
11646     }\r
11647 }\r
11648 \r
11649 void\r
11650 HintEvent()\r
11651 {\r
11652     if (appData.noChessProgram) return;\r
11653     switch (gameMode) {\r
11654       case MachinePlaysWhite:\r
11655         if (WhiteOnMove(forwardMostMove)) {\r
11656             DisplayError(_("Wait until your turn"), 0);\r
11657             return;\r
11658         }\r
11659         break;\r
11660       case BeginningOfGame:\r
11661       case MachinePlaysBlack:\r
11662         if (!WhiteOnMove(forwardMostMove)) {\r
11663             DisplayError(_("Wait until your turn"), 0);\r
11664             return;\r
11665         }\r
11666         break;\r
11667       default:\r
11668         DisplayError(_("No hint available"), 0);\r
11669         return;\r
11670     }\r
11671     SendToProgram("hint\n", &first);\r
11672     hintRequested = TRUE;\r
11673 }\r
11674 \r
11675 void\r
11676 BookEvent()\r
11677 {\r
11678     if (appData.noChessProgram) return;\r
11679     switch (gameMode) {\r
11680       case MachinePlaysWhite:\r
11681         if (WhiteOnMove(forwardMostMove)) {\r
11682             DisplayError(_("Wait until your turn"), 0);\r
11683             return;\r
11684         }\r
11685         break;\r
11686       case BeginningOfGame:\r
11687       case MachinePlaysBlack:\r
11688         if (!WhiteOnMove(forwardMostMove)) {\r
11689             DisplayError(_("Wait until your turn"), 0);\r
11690             return;\r
11691         }\r
11692         break;\r
11693       case EditPosition:\r
11694         EditPositionDone();\r
11695         break;\r
11696       case TwoMachinesPlay:\r
11697         return;\r
11698       default:\r
11699         break;\r
11700     }\r
11701     SendToProgram("bk\n", &first);\r
11702     bookOutput[0] = NULLCHAR;\r
11703     bookRequested = TRUE;\r
11704 }\r
11705 \r
11706 void\r
11707 AboutGameEvent()\r
11708 {\r
11709     char *tags = PGNTags(&gameInfo);\r
11710     TagsPopUp(tags, CmailMsg());\r
11711     free(tags);\r
11712 }\r
11713 \r
11714 /* end button procedures */\r
11715 \r
11716 void\r
11717 PrintPosition(fp, move)\r
11718      FILE *fp;\r
11719      int move;\r
11720 {\r
11721     int i, j;\r
11722     \r
11723     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11724         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11725             char c = PieceToChar(boards[move][i][j]);\r
11726             fputc(c == 'x' ? '.' : c, fp);\r
11727             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
11728         }\r
11729     }\r
11730     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
11731       fprintf(fp, "white to play\n");\r
11732     else\r
11733       fprintf(fp, "black to play\n");\r
11734 }\r
11735 \r
11736 void\r
11737 PrintOpponents(fp)\r
11738      FILE *fp;\r
11739 {\r
11740     if (gameInfo.white != NULL) {\r
11741         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
11742     } else {\r
11743         fprintf(fp, "\n");\r
11744     }\r
11745 }\r
11746 \r
11747 /* Find last component of program's own name, using some heuristics */\r
11748 void\r
11749 TidyProgramName(prog, host, buf)\r
11750      char *prog, *host, buf[MSG_SIZ];\r
11751 {\r
11752     char *p, *q;\r
11753     int local = (strcmp(host, "localhost") == 0);\r
11754     while (!local && (p = strchr(prog, ';')) != NULL) {\r
11755         p++;\r
11756         while (*p == ' ') p++;\r
11757         prog = p;\r
11758     }\r
11759     if (*prog == '"' || *prog == '\'') {\r
11760         q = strchr(prog + 1, *prog);\r
11761     } else {\r
11762         q = strchr(prog, ' ');\r
11763     }\r
11764     if (q == NULL) q = prog + strlen(prog);\r
11765     p = q;\r
11766     while (p >= prog && *p != '/' && *p != '\\') p--;\r
11767     p++;\r
11768     if(p == prog && *p == '"') p++;\r
11769     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
11770     memcpy(buf, p, q - p);\r
11771     buf[q - p] = NULLCHAR;\r
11772     if (!local) {\r
11773         strcat(buf, "@");\r
11774         strcat(buf, host);\r
11775     }\r
11776 }\r
11777 \r
11778 char *\r
11779 TimeControlTagValue()\r
11780 {\r
11781     char buf[MSG_SIZ];\r
11782     if (!appData.clockMode) {\r
11783         strcpy(buf, "-");\r
11784     } else if (movesPerSession > 0) {\r
11785         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
11786     } else if (timeIncrement == 0) {\r
11787         sprintf(buf, "%ld", timeControl/1000);\r
11788     } else {\r
11789         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
11790     }\r
11791     return StrSave(buf);\r
11792 }\r
11793 \r
11794 void\r
11795 SetGameInfo()\r
11796 {\r
11797     /* This routine is used only for certain modes */\r
11798     VariantClass v = gameInfo.variant;\r
11799     ClearGameInfo(&gameInfo);\r
11800     gameInfo.variant = v;\r
11801 \r
11802     switch (gameMode) {\r
11803       case MachinePlaysWhite:\r
11804         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11805         gameInfo.site = StrSave(HostName());\r
11806         gameInfo.date = PGNDate();\r
11807         gameInfo.round = StrSave("-");\r
11808         gameInfo.white = StrSave(first.tidy);\r
11809         gameInfo.black = StrSave(UserName());\r
11810         gameInfo.timeControl = TimeControlTagValue();\r
11811         break;\r
11812 \r
11813       case MachinePlaysBlack:\r
11814         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11815         gameInfo.site = StrSave(HostName());\r
11816         gameInfo.date = PGNDate();\r
11817         gameInfo.round = StrSave("-");\r
11818         gameInfo.white = StrSave(UserName());\r
11819         gameInfo.black = StrSave(first.tidy);\r
11820         gameInfo.timeControl = TimeControlTagValue();\r
11821         break;\r
11822 \r
11823       case TwoMachinesPlay:\r
11824         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11825         gameInfo.site = StrSave(HostName());\r
11826         gameInfo.date = PGNDate();\r
11827         if (matchGame > 0) {\r
11828             char buf[MSG_SIZ];\r
11829             sprintf(buf, "%d", matchGame);\r
11830             gameInfo.round = StrSave(buf);\r
11831         } else {\r
11832             gameInfo.round = StrSave("-");\r
11833         }\r
11834         if (first.twoMachinesColor[0] == 'w') {\r
11835             gameInfo.white = StrSave(first.tidy);\r
11836             gameInfo.black = StrSave(second.tidy);\r
11837         } else {\r
11838             gameInfo.white = StrSave(second.tidy);\r
11839             gameInfo.black = StrSave(first.tidy);\r
11840         }\r
11841         gameInfo.timeControl = TimeControlTagValue();\r
11842         break;\r
11843 \r
11844       case EditGame:\r
11845         gameInfo.event = StrSave("Edited game");\r
11846         gameInfo.site = StrSave(HostName());\r
11847         gameInfo.date = PGNDate();\r
11848         gameInfo.round = StrSave("-");\r
11849         gameInfo.white = StrSave("-");\r
11850         gameInfo.black = StrSave("-");\r
11851         break;\r
11852 \r
11853       case EditPosition:\r
11854         gameInfo.event = StrSave("Edited position");\r
11855         gameInfo.site = StrSave(HostName());\r
11856         gameInfo.date = PGNDate();\r
11857         gameInfo.round = StrSave("-");\r
11858         gameInfo.white = StrSave("-");\r
11859         gameInfo.black = StrSave("-");\r
11860         break;\r
11861 \r
11862       case IcsPlayingWhite:\r
11863       case IcsPlayingBlack:\r
11864       case IcsObserving:\r
11865       case IcsExamining:\r
11866         break;\r
11867 \r
11868       case PlayFromGameFile:\r
11869         gameInfo.event = StrSave("Game from non-PGN file");\r
11870         gameInfo.site = StrSave(HostName());\r
11871         gameInfo.date = PGNDate();\r
11872         gameInfo.round = StrSave("-");\r
11873         gameInfo.white = StrSave("?");\r
11874         gameInfo.black = StrSave("?");\r
11875         break;\r
11876 \r
11877       default:\r
11878         break;\r
11879     }\r
11880 }\r
11881 \r
11882 void\r
11883 ReplaceComment(index, text)\r
11884      int index;\r
11885      char *text;\r
11886 {\r
11887     int len;\r
11888 \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 (commentList[index] != NULL)\r
11894       free(commentList[index]);\r
11895 \r
11896     if (len == 0) {\r
11897         commentList[index] = NULL;\r
11898         return;\r
11899     }\r
11900     commentList[index] = (char *) malloc(len + 2);\r
11901     strncpy(commentList[index], text, len);\r
11902     commentList[index][len] = '\n';\r
11903     commentList[index][len + 1] = NULLCHAR;\r
11904 }\r
11905 \r
11906 void\r
11907 CrushCRs(text)\r
11908      char *text;\r
11909 {\r
11910   char *p = text;\r
11911   char *q = text;\r
11912   char ch;\r
11913 \r
11914   do {\r
11915     ch = *p++;\r
11916     if (ch == '\r') continue;\r
11917     *q++ = ch;\r
11918   } while (ch != '\0');\r
11919 }\r
11920 \r
11921 void\r
11922 AppendComment(index, text)\r
11923      int index;\r
11924      char *text;\r
11925 {\r
11926     int oldlen, len;\r
11927     char *old;\r
11928 \r
11929     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11930 \r
11931     CrushCRs(text);\r
11932     while (*text == '\n') text++;\r
11933     len = strlen(text);\r
11934     while (len > 0 && text[len - 1] == '\n') len--;\r
11935 \r
11936     if (len == 0) return;\r
11937 \r
11938     if (commentList[index] != NULL) {\r
11939         old = commentList[index];\r
11940         oldlen = strlen(old);\r
11941         commentList[index] = (char *) malloc(oldlen + len + 2);\r
11942         strcpy(commentList[index], old);\r
11943         free(old);\r
11944         strncpy(&commentList[index][oldlen], text, len);\r
11945         commentList[index][oldlen + len] = '\n';\r
11946         commentList[index][oldlen + len + 1] = NULLCHAR;\r
11947     } else {\r
11948         commentList[index] = (char *) malloc(len + 2);\r
11949         strncpy(commentList[index], text, len);\r
11950         commentList[index][len] = '\n';\r
11951         commentList[index][len + 1] = NULLCHAR;\r
11952     }\r
11953 }\r
11954 \r
11955 static char * FindStr( char * text, char * sub_text )\r
11956 {\r
11957     char * result = strstr( text, sub_text );\r
11958 \r
11959     if( result != NULL ) {\r
11960         result += strlen( sub_text );\r
11961     }\r
11962 \r
11963     return result;\r
11964 }\r
11965 \r
11966 /* [AS] Try to extract PV info from PGN comment */\r
11967 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
11968 char *GetInfoFromComment( int index, char * text )\r
11969 {\r
11970     char * sep = text;\r
11971 \r
11972     if( text != NULL && index > 0 ) {\r
11973         int score = 0;\r
11974         int depth = 0;\r
11975         int time = -1, sec = 0, deci;\r
11976         char * s_eval = FindStr( text, "[%eval " );\r
11977         char * s_emt = FindStr( text, "[%emt " );\r
11978 \r
11979         if( s_eval != NULL || s_emt != NULL ) {\r
11980             /* New style */\r
11981             char delim;\r
11982 \r
11983             if( s_eval != NULL ) {\r
11984                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
11985                     return text;\r
11986                 }\r
11987 \r
11988                 if( delim != ']' ) {\r
11989                     return text;\r
11990                 }\r
11991             }\r
11992 \r
11993             if( s_emt != NULL ) {\r
11994             }\r
11995         }\r
11996         else {\r
11997             /* We expect something like: [+|-]nnn.nn/dd */\r
11998             int score_lo = 0;\r
11999 \r
12000             sep = strchr( text, '/' );\r
12001             if( sep == NULL || sep < (text+4) ) {\r
12002                 return text;\r
12003             }\r
12004 \r
12005             time = -1; sec = -1; deci = -1;\r
12006             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
12007                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&\r
12008                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
12009                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
12010                 return text;\r
12011             }\r
12012 \r
12013             if( score_lo < 0 || score_lo >= 100 ) {\r
12014                 return text;\r
12015             }\r
12016 \r
12017             if(sec >= 0) time = 600*time + 10*sec; else\r
12018             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec\r
12019 \r
12020             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
12021 \r
12022             /* [HGM] PV time: now locate end of PV info */\r
12023             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
12024             if(time >= 0)\r
12025             while( *++sep >= '0' && *sep <= '9'); // strip time\r
12026             if(sec >= 0)\r
12027             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
12028             if(deci >= 0)\r
12029             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds\r
12030             while(*sep == ' ') sep++;\r
12031         }\r
12032 \r
12033         if( depth <= 0 ) {\r
12034             return text;\r
12035         }\r
12036 \r
12037         if( time < 0 ) {\r
12038             time = -1;\r
12039         }\r
12040 \r
12041         pvInfoList[index-1].depth = depth;\r
12042         pvInfoList[index-1].score = score;\r
12043         pvInfoList[index-1].time  = 10*time; // centi-sec\r
12044     }\r
12045     return sep;\r
12046 }\r
12047 \r
12048 void\r
12049 SendToProgram(message, cps)\r
12050      char *message;\r
12051      ChessProgramState *cps;\r
12052 {\r
12053     int count, outCount, error;\r
12054     char buf[MSG_SIZ];\r
12055 \r
12056     if (cps->pr == NULL) return;\r
12057     Attention(cps);\r
12058     \r
12059     if (appData.debugMode) {\r
12060         TimeMark now;\r
12061         GetTimeMark(&now);\r
12062         fprintf(debugFP, "%ld >%-6s: %s", \r
12063                 SubtractTimeMarks(&now, &programStartTime),\r
12064                 cps->which, message);\r
12065     }\r
12066     \r
12067     count = strlen(message);\r
12068     outCount = OutputToProcess(cps->pr, message, count, &error);\r
12069     if (outCount < count && !exiting \r
12070                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
12071         sprintf(buf, _("Error writing to %s chess program"), cps->which);\r
12072         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12073             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12074                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12075                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
12076             } else {\r
12077                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12078             }\r
12079             gameInfo.resultDetails = buf;\r
12080         }\r
12081         DisplayFatalError(buf, error, 1);\r
12082     }\r
12083 }\r
12084 \r
12085 void\r
12086 ReceiveFromProgram(isr, closure, message, count, error)\r
12087      InputSourceRef isr;\r
12088      VOIDSTAR closure;\r
12089      char *message;\r
12090      int count;\r
12091      int error;\r
12092 {\r
12093     char *end_str;\r
12094     char buf[MSG_SIZ];\r
12095     ChessProgramState *cps = (ChessProgramState *)closure;\r
12096 \r
12097     if (isr != cps->isr) return; /* Killed intentionally */\r
12098     if (count <= 0) {\r
12099         if (count == 0) {\r
12100             sprintf(buf,\r
12101                     _("Error: %s chess program (%s) exited unexpectedly"),\r
12102                     cps->which, cps->program);\r
12103         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12104                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12105                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12106                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);\r
12107                 } else {\r
12108                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12109                 }\r
12110                 gameInfo.resultDetails = buf;\r
12111             }\r
12112             RemoveInputSource(cps->isr);\r
12113             DisplayFatalError(buf, 0, 1);\r
12114         } else {\r
12115             sprintf(buf,\r
12116                     _("Error reading from %s chess program (%s)"),\r
12117                     cps->which, cps->program);\r
12118             RemoveInputSource(cps->isr);\r
12119 \r
12120             /* [AS] Program is misbehaving badly... kill it */\r
12121             if( count == -2 ) {\r
12122                 DestroyChildProcess( cps->pr, 9 );\r
12123                 cps->pr = NoProc;\r
12124             }\r
12125 \r
12126             DisplayFatalError(buf, error, 1);\r
12127         }\r
12128         return;\r
12129     }\r
12130     \r
12131     if ((end_str = strchr(message, '\r')) != NULL)\r
12132       *end_str = NULLCHAR;\r
12133     if ((end_str = strchr(message, '\n')) != NULL)\r
12134       *end_str = NULLCHAR;\r
12135     \r
12136     if (appData.debugMode) {\r
12137         TimeMark now; int print = 1;\r
12138         char *quote = ""; char c; int i;\r
12139 \r
12140         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
12141                 char start = message[0];\r
12142                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
12143                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
12144                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&\r
12145                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
12146                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
12147                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&\r
12148                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
12149                         { quote = "# "; print = (appData.engineComments == 2); }\r
12150                 message[0] = start; // restore original message\r
12151         }\r
12152         if(print) {\r
12153                 GetTimeMark(&now);\r
12154                 fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
12155                         SubtractTimeMarks(&now, &programStartTime), cps->which, \r
12156                         quote,\r
12157                         message);\r
12158         }\r
12159     }\r
12160 \r
12161     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */\r
12162     if (appData.icsEngineAnalyze) {\r
12163         if (strstr(message, "whisper") != NULL ||\r
12164              strstr(message, "kibitz") != NULL || \r
12165             strstr(message, "tellics") != NULL) return;\r
12166     }\r
12167 \r
12168     HandleMachineMove(message, cps);\r
12169 }\r
12170 \r
12171 \r
12172 void\r
12173 SendTimeControl(cps, mps, tc, inc, sd, st)\r
12174      ChessProgramState *cps;\r
12175      int mps, inc, sd, st;\r
12176      long tc;\r
12177 {\r
12178     char buf[MSG_SIZ];\r
12179     int seconds;\r
12180 \r
12181     if( timeControl_2 > 0 ) {\r
12182         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
12183             tc = timeControl_2;\r
12184         }\r
12185     }\r
12186     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
12187     inc /= cps->timeOdds;\r
12188     st  /= cps->timeOdds;\r
12189 \r
12190     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
12191 \r
12192     if (st > 0) {\r
12193       /* Set exact time per move, normally using st command */\r
12194       if (cps->stKludge) {\r
12195         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
12196         seconds = st % 60;\r
12197         if (seconds == 0) {\r
12198           sprintf(buf, "level 1 %d\n", st/60);\r
12199         } else {\r
12200           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
12201         }\r
12202       } else {\r
12203         sprintf(buf, "st %d\n", st);\r
12204       }\r
12205     } else {\r
12206       /* Set conventional or incremental time control, using level command */\r
12207       if (seconds == 0) {\r
12208         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
12209            Fixed in later versions, but still avoid :seconds\r
12210            when seconds is 0. */\r
12211         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
12212       } else {\r
12213         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
12214                 seconds, inc/1000);\r
12215       }\r
12216     }\r
12217     SendToProgram(buf, cps);\r
12218 \r
12219     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
12220     /* Orthogonally, limit search to given depth */\r
12221     if (sd > 0) {\r
12222       if (cps->sdKludge) {\r
12223         sprintf(buf, "depth\n%d\n", sd);\r
12224       } else {\r
12225         sprintf(buf, "sd %d\n", sd);\r
12226       }\r
12227       SendToProgram(buf, cps);\r
12228     }\r
12229 \r
12230     if(cps->nps > 0) { /* [HGM] nps */\r
12231         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
12232         else {\r
12233                 sprintf(buf, "nps %d\n", cps->nps);\r
12234               SendToProgram(buf, cps);\r
12235         }\r
12236     }\r
12237 }\r
12238 \r
12239 ChessProgramState *WhitePlayer()\r
12240 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
12241 {\r
12242     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || \r
12243        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)\r
12244         return &second;\r
12245     return &first;\r
12246 }\r
12247 \r
12248 void\r
12249 SendTimeRemaining(cps, machineWhite)\r
12250      ChessProgramState *cps;\r
12251      int /*boolean*/ machineWhite;\r
12252 {\r
12253     char message[MSG_SIZ];\r
12254     long time, otime;\r
12255 \r
12256     /* Note: this routine must be called when the clocks are stopped\r
12257        or when they have *just* been set or switched; otherwise\r
12258        it will be off by the time since the current tick started.\r
12259     */\r
12260     if (machineWhite) {\r
12261         time = whiteTimeRemaining / 10;\r
12262         otime = blackTimeRemaining / 10;\r
12263     } else {\r
12264         time = blackTimeRemaining / 10;\r
12265         otime = whiteTimeRemaining / 10;\r
12266     }\r
12267     /* [HGM] translate opponent's time by time-odds factor */\r
12268     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
12269     if (appData.debugMode) {\r
12270         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
12271     }\r
12272 \r
12273     if (time <= 0) time = 1;\r
12274     if (otime <= 0) otime = 1;\r
12275     \r
12276     sprintf(message, "time %ld\n", time);\r
12277     SendToProgram(message, cps);\r
12278 \r
12279     sprintf(message, "otim %ld\n", otime);\r
12280     SendToProgram(message, cps);\r
12281 }\r
12282 \r
12283 int\r
12284 BoolFeature(p, name, loc, cps)\r
12285      char **p;\r
12286      char *name;\r
12287      int *loc;\r
12288      ChessProgramState *cps;\r
12289 {\r
12290   char buf[MSG_SIZ];\r
12291   int len = strlen(name);\r
12292   int val;\r
12293   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12294     (*p) += len + 1;\r
12295     sscanf(*p, "%d", &val);\r
12296     *loc = (val != 0);\r
12297     while (**p && **p != ' ') (*p)++;\r
12298     sprintf(buf, "accepted %s\n", name);\r
12299     SendToProgram(buf, cps);\r
12300     return TRUE;\r
12301   }\r
12302   return FALSE;\r
12303 }\r
12304 \r
12305 int\r
12306 IntFeature(p, name, loc, cps)\r
12307      char **p;\r
12308      char *name;\r
12309      int *loc;\r
12310      ChessProgramState *cps;\r
12311 {\r
12312   char buf[MSG_SIZ];\r
12313   int len = strlen(name);\r
12314   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12315     (*p) += len + 1;\r
12316     sscanf(*p, "%d", loc);\r
12317     while (**p && **p != ' ') (*p)++;\r
12318     sprintf(buf, "accepted %s\n", name);\r
12319     SendToProgram(buf, cps);\r
12320     return TRUE;\r
12321   }\r
12322   return FALSE;\r
12323 }\r
12324 \r
12325 int\r
12326 StringFeature(p, name, loc, cps)\r
12327      char **p;\r
12328      char *name;\r
12329      char loc[];\r
12330      ChessProgramState *cps;\r
12331 {\r
12332   char buf[MSG_SIZ];\r
12333   int len = strlen(name);\r
12334   if (strncmp((*p), name, len) == 0\r
12335       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
12336     (*p) += len + 2;\r
12337     sscanf(*p, "%[^\"]", loc);\r
12338     while (**p && **p != '\"') (*p)++;\r
12339     if (**p == '\"') (*p)++;\r
12340     sprintf(buf, "accepted %s\n", name);\r
12341     SendToProgram(buf, cps);\r
12342     return TRUE;\r
12343   }\r
12344   return FALSE;\r
12345 }\r
12346 \r
12347 int \r
12348 ParseOption(Option *opt, ChessProgramState *cps)\r
12349 // [HGM] options: process the string that defines an engine option, and determine\r
12350 // name, type, default value, and allowed value range\r
12351 {\r
12352         char *p, *q, buf[MSG_SIZ];\r
12353         int n, min = (-1)<<31, max = 1<<31, def;\r
12354 \r
12355         if(p = strstr(opt->name, " -spin ")) {\r
12356             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;\r
12357             if(max < min) max = min; // enforce consistency\r
12358             if(def < min) def = min;\r
12359             if(def > max) def = max;\r
12360             opt->value = def;\r
12361             opt->min = min;\r
12362             opt->max = max;\r
12363             opt->type = Spin;\r
12364         } else if(p = strstr(opt->name, " -string ")) {\r
12365             opt->textValue = p+9;\r
12366             opt->type = TextBox;\r
12367         } else if(p = strstr(opt->name, " -check ")) {\r
12368             if(sscanf(p, " -check %d", &def) < 1) return FALSE;\r
12369             opt->value = (def != 0);\r
12370             opt->type = CheckBox;\r
12371         } else if(p = strstr(opt->name, " -combo ")) {\r
12372             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type\r
12373             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices\r
12374             opt->value = n = 0;\r
12375             while(q = StrStr(q, " /// ")) {\r
12376                 n++; *q = 0;    // count choices, and null-terminate each of them\r
12377                 q += 5;\r
12378                 if(*q == '*') { // remember default, which is marked with * prefix\r
12379                     q++;\r
12380                     opt->value = n;\r
12381                 }\r
12382                 cps->comboList[cps->comboCnt++] = q;\r
12383             }\r
12384             cps->comboList[cps->comboCnt++] = NULL;\r
12385             opt->max = n + 1;\r
12386             opt->type = ComboBox;\r
12387         } else if(p = strstr(opt->name, " -button")) {\r
12388             opt->type = Button;\r
12389         } else if(p = strstr(opt->name, " -save")) {\r
12390             opt->type = SaveButton;\r
12391         } else return FALSE;\r
12392         *p = 0; // terminate option name\r
12393         // now look if the command-line options define a setting for this engine option.\r
12394         if(cps->optionSettings && cps->optionSettings[0])\r
12395             p = strstr(cps->optionSettings, opt->name); else p = NULL;\r
12396         if(p && (p == cps->optionSettings || p[-1] == ',')) {\r
12397                 sprintf(buf, "option %s", p);\r
12398                 if(p = strstr(buf, ",")) *p = 0;\r
12399                 strcat(buf, "\n");\r
12400                 SendToProgram(buf, cps);\r
12401         }\r
12402         return TRUE;\r
12403 }\r
12404 \r
12405 void\r
12406 FeatureDone(cps, val)\r
12407      ChessProgramState* cps;\r
12408      int val;\r
12409 {\r
12410   DelayedEventCallback cb = GetDelayedEvent();\r
12411   if ((cb == InitBackEnd3 && cps == &first) ||\r
12412       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
12413     CancelDelayedEvent();\r
12414     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
12415   }\r
12416   cps->initDone = val;\r
12417 }\r
12418 \r
12419 /* Parse feature command from engine */\r
12420 void\r
12421 ParseFeatures(args, cps)\r
12422      char* args;\r
12423      ChessProgramState *cps;  \r
12424 {\r
12425   char *p = args;\r
12426   char *q;\r
12427   int val;\r
12428   char buf[MSG_SIZ];\r
12429 \r
12430   for (;;) {\r
12431     while (*p == ' ') p++;\r
12432     if (*p == NULLCHAR) return;\r
12433 \r
12434     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
12435     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
12436     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
12437     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
12438     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
12439     if (BoolFeature(&p, "reuse", &val, cps)) {\r
12440       /* Engine can disable reuse, but can't enable it if user said no */\r
12441       if (!val) cps->reuse = FALSE;\r
12442       continue;\r
12443     }\r
12444     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
12445     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
12446       if (gameMode == TwoMachinesPlay) {\r
12447         DisplayTwoMachinesTitle();\r
12448       } else {\r
12449         DisplayTitle("");\r
12450       }\r
12451       continue;\r
12452     }\r
12453     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
12454     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
12455     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
12456     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
12457     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
12458     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
12459     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
12460     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
12461     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
12462     if (IntFeature(&p, "done", &val, cps)) {\r
12463       FeatureDone(cps, val);\r
12464       continue;\r
12465     }\r
12466     /* Added by Tord: */\r
12467     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
12468     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
12469     /* End of additions by Tord */\r
12470 \r
12471     /* [HGM] added features: */\r
12472     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
12473     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
12474     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
12475     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
12476     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12477     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
12478     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {\r
12479         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature\r
12480         if(cps->nrOptions >= MAX_OPTIONS) {\r
12481             cps->nrOptions--;\r
12482             sprintf(buf, "%s engine has too many options\n", cps->which);\r
12483             DisplayError(buf, 0);\r
12484         }\r
12485         continue;\r
12486     }\r
12487     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12488     /* End of additions by HGM */\r
12489 \r
12490     /* unknown feature: complain and skip */\r
12491     q = p;\r
12492     while (*q && *q != '=') q++;\r
12493     sprintf(buf, "rejected %.*s\n", q-p, p);\r
12494     SendToProgram(buf, cps);\r
12495     p = q;\r
12496     if (*p == '=') {\r
12497       p++;\r
12498       if (*p == '\"') {\r
12499         p++;\r
12500         while (*p && *p != '\"') p++;\r
12501         if (*p == '\"') p++;\r
12502       } else {\r
12503         while (*p && *p != ' ') p++;\r
12504       }\r
12505     }\r
12506   }\r
12507 \r
12508 }\r
12509 \r
12510 void\r
12511 PeriodicUpdatesEvent(newState)\r
12512      int newState;\r
12513 {\r
12514     if (newState == appData.periodicUpdates)\r
12515       return;\r
12516 \r
12517     appData.periodicUpdates=newState;\r
12518 \r
12519     /* Display type changes, so update it now */\r
12520     DisplayAnalysis();\r
12521 \r
12522     /* Get the ball rolling again... */\r
12523     if (newState) {\r
12524         AnalysisPeriodicEvent(1);\r
12525         StartAnalysisClock();\r
12526     }\r
12527 }\r
12528 \r
12529 void\r
12530 PonderNextMoveEvent(newState)\r
12531      int newState;\r
12532 {\r
12533     if (newState == appData.ponderNextMove) return;\r
12534     if (gameMode == EditPosition) EditPositionDone();\r
12535     if (newState) {\r
12536         SendToProgram("hard\n", &first);\r
12537         if (gameMode == TwoMachinesPlay) {\r
12538             SendToProgram("hard\n", &second);\r
12539         }\r
12540     } else {\r
12541         SendToProgram("easy\n", &first);\r
12542         thinkOutput[0] = NULLCHAR;\r
12543         if (gameMode == TwoMachinesPlay) {\r
12544             SendToProgram("easy\n", &second);\r
12545         }\r
12546     }\r
12547     appData.ponderNextMove = newState;\r
12548 }\r
12549 \r
12550 void\r
12551 NewSettingEvent(option, command, value)\r
12552      char *command;\r
12553      int option, value;\r
12554 {\r
12555     char buf[MSG_SIZ];\r
12556 \r
12557     if (gameMode == EditPosition) EditPositionDone();\r
12558     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);\r
12559     SendToProgram(buf, &first);\r
12560     if (gameMode == TwoMachinesPlay) {\r
12561         SendToProgram(buf, &second);\r
12562     }\r
12563 }\r
12564 \r
12565 void\r
12566 ShowThinkingEvent()\r
12567 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup\r
12568 {\r
12569     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated\r
12570     int newState = appData.showThinking\r
12571         // [HGM] thinking: other features now need thinking output as well\r
12572         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();\r
12573     \r
12574     if (oldState == newState) return;\r
12575     oldState = newState;\r
12576     if (gameMode == EditPosition) EditPositionDone();\r
12577     if (oldState) {\r
12578         SendToProgram("post\n", &first);\r
12579         if (gameMode == TwoMachinesPlay) {\r
12580             SendToProgram("post\n", &second);\r
12581         }\r
12582     } else {\r
12583         SendToProgram("nopost\n", &first);\r
12584         thinkOutput[0] = NULLCHAR;\r
12585         if (gameMode == TwoMachinesPlay) {\r
12586             SendToProgram("nopost\n", &second);\r
12587         }\r
12588     }\r
12589 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!\r
12590 }\r
12591 \r
12592 void\r
12593 AskQuestionEvent(title, question, replyPrefix, which)\r
12594      char *title; char *question; char *replyPrefix; char *which;\r
12595 {\r
12596   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
12597   if (pr == NoProc) return;\r
12598   AskQuestion(title, question, replyPrefix, pr);\r
12599 }\r
12600 \r
12601 void\r
12602 DisplayMove(moveNumber)\r
12603      int moveNumber;\r
12604 {\r
12605     char message[MSG_SIZ];\r
12606     char res[MSG_SIZ];\r
12607     char cpThinkOutput[MSG_SIZ];\r
12608 \r
12609     if(appData.noGUI) return; // [HGM] fast: suppress display of moves\r
12610     \r
12611     if (moveNumber == forwardMostMove - 1 || \r
12612         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12613 \r
12614         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
12615 \r
12616         if (strchr(cpThinkOutput, '\n')) {\r
12617             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
12618         }\r
12619     } else {\r
12620         *cpThinkOutput = NULLCHAR;\r
12621     }\r
12622 \r
12623     /* [AS] Hide thinking from human user */\r
12624     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
12625         *cpThinkOutput = NULLCHAR;\r
12626         if( thinkOutput[0] != NULLCHAR ) {\r
12627             int i;\r
12628 \r
12629             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
12630                 cpThinkOutput[i] = '.';\r
12631             }\r
12632             cpThinkOutput[i] = NULLCHAR;\r
12633             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
12634         }\r
12635     }\r
12636 \r
12637     if (moveNumber == forwardMostMove - 1 &&\r
12638         gameInfo.resultDetails != NULL) {\r
12639         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
12640             sprintf(res, " %s", PGNResult(gameInfo.result));\r
12641         } else {\r
12642             sprintf(res, " {%s} %s",\r
12643                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
12644         }\r
12645     } else {\r
12646         res[0] = NULLCHAR;\r
12647     }\r
12648 \r
12649     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12650         DisplayMessage(res, cpThinkOutput);\r
12651     } else {\r
12652         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
12653                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
12654                 parseList[moveNumber], res);\r
12655         DisplayMessage(message, cpThinkOutput);\r
12656     }\r
12657 }\r
12658 \r
12659 void\r
12660 DisplayAnalysisText(text)\r
12661      char *text;\r
12662 {\r
12663     char buf[MSG_SIZ];\r
12664 \r
12665     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile \r
12666                || appData.icsEngineAnalyze) {\r
12667         sprintf(buf, "Analysis (%s)", first.tidy);\r
12668         AnalysisPopUp(buf, text);\r
12669     }\r
12670 }\r
12671 \r
12672 static int\r
12673 only_one_move(str)\r
12674      char *str;\r
12675 {\r
12676     while (*str && isspace(*str)) ++str;\r
12677     while (*str && !isspace(*str)) ++str;\r
12678     if (!*str) return 1;\r
12679     while (*str && isspace(*str)) ++str;\r
12680     if (!*str) return 1;\r
12681     return 0;\r
12682 }\r
12683 \r
12684 void\r
12685 DisplayAnalysis()\r
12686 {\r
12687     char buf[MSG_SIZ];\r
12688     char lst[MSG_SIZ / 2];\r
12689     double nps;\r
12690     static char *xtra[] = { "", " (--)", " (++)" };\r
12691     int h, m, s, cs;\r
12692   \r
12693     if (programStats.time == 0) {\r
12694         programStats.time = 1;\r
12695     }\r
12696   \r
12697     if (programStats.got_only_move) {\r
12698         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
12699     } else {\r
12700         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
12701 \r
12702         nps = (u64ToDouble(programStats.nodes) /\r
12703              ((double)programStats.time /100.0));\r
12704 \r
12705         cs = programStats.time % 100;\r
12706         s = programStats.time / 100;\r
12707         h = (s / (60*60));\r
12708         s = s - h*60*60;\r
12709         m = (s/60);\r
12710         s = s - m*60;\r
12711 \r
12712         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
12713           if (programStats.move_name[0] != NULLCHAR) {\r
12714             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12715                     programStats.depth,\r
12716                     programStats.nr_moves-programStats.moves_left,\r
12717                     programStats.nr_moves, programStats.move_name,\r
12718                     ((float)programStats.score)/100.0, lst,\r
12719                     only_one_move(lst)?\r
12720                     xtra[programStats.got_fail] : "",\r
12721                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12722           } else {\r
12723             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12724                     programStats.depth,\r
12725                     programStats.nr_moves-programStats.moves_left,\r
12726                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
12727                     lst,\r
12728                     only_one_move(lst)?\r
12729                     xtra[programStats.got_fail] : "",\r
12730                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12731           }\r
12732         } else {\r
12733             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12734                     programStats.depth,\r
12735                     ((float)programStats.score)/100.0,\r
12736                     lst,\r
12737                     only_one_move(lst)?\r
12738                     xtra[programStats.got_fail] : "",\r
12739                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12740         }\r
12741     }\r
12742     DisplayAnalysisText(buf);\r
12743 }\r
12744 \r
12745 void\r
12746 DisplayComment(moveNumber, text)\r
12747      int moveNumber;\r
12748      char *text;\r
12749 {\r
12750     char title[MSG_SIZ];\r
12751     char buf[8000]; // comment can be long!\r
12752     int score, depth;\r
12753 \r
12754     if( appData.autoDisplayComment ) {\r
12755         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12756             strcpy(title, "Comment");\r
12757         } else {\r
12758             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
12759                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
12760                     parseList[moveNumber]);\r
12761         }\r
12762         // [HGM] PV info: display PV info together with (or as) comment\r
12763         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
12764             if(text == NULL) text = "";                                           \r
12765             score = pvInfoList[moveNumber].score;\r
12766             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
12767                               depth, (pvInfoList[moveNumber].time+50)/100, text);\r
12768             text = buf;\r
12769         }\r
12770     } else title[0] = 0;\r
12771 \r
12772     if (text != NULL)\r
12773         CommentPopUp(title, text);\r
12774 }\r
12775 \r
12776 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
12777  * might be busy thinking or pondering.  It can be omitted if your\r
12778  * gnuchess is configured to stop thinking immediately on any user\r
12779  * input.  However, that gnuchess feature depends on the FIONREAD\r
12780  * ioctl, which does not work properly on some flavors of Unix.\r
12781  */\r
12782 void\r
12783 Attention(cps)\r
12784      ChessProgramState *cps;\r
12785 {\r
12786 #if ATTENTION\r
12787     if (!cps->useSigint) return;\r
12788     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
12789     switch (gameMode) {\r
12790       case MachinePlaysWhite:\r
12791       case MachinePlaysBlack:\r
12792       case TwoMachinesPlay:\r
12793       case IcsPlayingWhite:\r
12794       case IcsPlayingBlack:\r
12795       case AnalyzeMode:\r
12796       case AnalyzeFile:\r
12797         /* Skip if we know it isn't thinking */\r
12798         if (!cps->maybeThinking) return;\r
12799         if (appData.debugMode)\r
12800           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
12801         InterruptChildProcess(cps->pr);\r
12802         cps->maybeThinking = FALSE;\r
12803         break;\r
12804       default:\r
12805         break;\r
12806     }\r
12807 #endif /*ATTENTION*/\r
12808 }\r
12809 \r
12810 int\r
12811 CheckFlags()\r
12812 {\r
12813     if (whiteTimeRemaining <= 0) {\r
12814         if (!whiteFlag) {\r
12815             whiteFlag = TRUE;\r
12816             if (appData.icsActive) {\r
12817                 if (appData.autoCallFlag &&\r
12818                     gameMode == IcsPlayingBlack && !blackFlag) {\r
12819                   SendToICS(ics_prefix);\r
12820                   SendToICS("flag\n");\r
12821                 }\r
12822             } else {\r
12823                 if (blackFlag) {\r
12824                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12825                 } else {\r
12826                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));\r
12827                     if (appData.autoCallFlag) {\r
12828                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
12829                         return TRUE;\r
12830                     }\r
12831                 }\r
12832             }\r
12833         }\r
12834     }\r
12835     if (blackTimeRemaining <= 0) {\r
12836         if (!blackFlag) {\r
12837             blackFlag = TRUE;\r
12838             if (appData.icsActive) {\r
12839                 if (appData.autoCallFlag &&\r
12840                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
12841                   SendToICS(ics_prefix);\r
12842                   SendToICS("flag\n");\r
12843                 }\r
12844             } else {\r
12845                 if (whiteFlag) {\r
12846                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12847                 } else {\r
12848                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));\r
12849                     if (appData.autoCallFlag) {\r
12850                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
12851                         return TRUE;\r
12852                     }\r
12853                 }\r
12854             }\r
12855         }\r
12856     }\r
12857     return FALSE;\r
12858 }\r
12859 \r
12860 void\r
12861 CheckTimeControl()\r
12862 {\r
12863     if (!appData.clockMode || appData.icsActive ||\r
12864         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
12865 \r
12866     /*\r
12867      * add time to clocks when time control is achieved ([HGM] now also used for increment)\r
12868      */\r
12869     if ( !WhiteOnMove(forwardMostMove) )\r
12870         /* White made time control */\r
12871         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12872         /* [HGM] time odds: correct new time quota for time odds! */\r
12873                                             / WhitePlayer()->timeOdds;\r
12874       else\r
12875         /* Black made time control */\r
12876         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12877                                             / WhitePlayer()->other->timeOdds;\r
12878 }\r
12879 \r
12880 void\r
12881 DisplayBothClocks()\r
12882 {\r
12883     int wom = gameMode == EditPosition ?\r
12884       !blackPlaysFirst : WhiteOnMove(currentMove);\r
12885     DisplayWhiteClock(whiteTimeRemaining, wom);\r
12886     DisplayBlackClock(blackTimeRemaining, !wom);\r
12887 }\r
12888 \r
12889 \r
12890 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
12891    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
12892    to use other calls if you don't.  Clocks will be less accurate if\r
12893    you have neither ftime nor gettimeofday.\r
12894 */\r
12895 \r
12896 /* VS 2008 requires the #include outside of the function */\r
12897 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME\r
12898 #include <sys/timeb.h>\r
12899 #endif\r
12900 \r
12901 /* Get the current time as a TimeMark */\r
12902 void\r
12903 GetTimeMark(tm)\r
12904      TimeMark *tm;\r
12905 {\r
12906 #if HAVE_GETTIMEOFDAY\r
12907 \r
12908     struct timeval timeVal;\r
12909     struct timezone timeZone;\r
12910 \r
12911     gettimeofday(&timeVal, &timeZone);\r
12912     tm->sec = (long) timeVal.tv_sec; \r
12913     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
12914 \r
12915 #else /*!HAVE_GETTIMEOFDAY*/\r
12916 #if HAVE_FTIME\r
12917 \r
12918 // include <sys/timeb.h> / moved to just above start of function\r
12919     struct timeb timeB;\r
12920 \r
12921     ftime(&timeB);\r
12922     tm->sec = (long) timeB.time;\r
12923     tm->ms = (int) timeB.millitm;\r
12924 \r
12925 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
12926     tm->sec = (long) time(NULL);\r
12927     tm->ms = 0;\r
12928 #endif\r
12929 #endif\r
12930 }\r
12931 \r
12932 /* Return the difference in milliseconds between two\r
12933    time marks.  We assume the difference will fit in a long!\r
12934 */\r
12935 long\r
12936 SubtractTimeMarks(tm2, tm1)\r
12937      TimeMark *tm2, *tm1;\r
12938 {\r
12939     return 1000L*(tm2->sec - tm1->sec) +\r
12940            (long) (tm2->ms - tm1->ms);\r
12941 }\r
12942 \r
12943 \r
12944 /*\r
12945  * Code to manage the game clocks.\r
12946  *\r
12947  * In tournament play, black starts the clock and then white makes a move.\r
12948  * We give the human user a slight advantage if he is playing white---the\r
12949  * clocks don't run until he makes his first move, so it takes zero time.\r
12950  * Also, we don't account for network lag, so we could get out of sync\r
12951  * with GNU Chess's clock -- but then, referees are always right.  \r
12952  */\r
12953 \r
12954 static TimeMark tickStartTM;\r
12955 static long intendedTickLength;\r
12956 \r
12957 long\r
12958 NextTickLength(timeRemaining)\r
12959      long timeRemaining;\r
12960 {\r
12961     long nominalTickLength, nextTickLength;\r
12962 \r
12963     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
12964       nominalTickLength = 100L;\r
12965     else\r
12966       nominalTickLength = 1000L;\r
12967     nextTickLength = timeRemaining % nominalTickLength;\r
12968     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
12969 \r
12970     return nextTickLength;\r
12971 }\r
12972 \r
12973 /* Adjust clock one minute up or down */\r
12974 void\r
12975 AdjustClock(Boolean which, int dir)\r
12976 {\r
12977     if(which) blackTimeRemaining += 60000*dir;\r
12978     else      whiteTimeRemaining += 60000*dir;\r
12979     DisplayBothClocks();\r
12980 }\r
12981 \r
12982 /* Stop clocks and reset to a fresh time control */\r
12983 void\r
12984 ResetClocks() \r
12985 {\r
12986     (void) StopClockTimer();\r
12987     if (appData.icsActive) {\r
12988         whiteTimeRemaining = blackTimeRemaining = 0;\r
12989     } else { /* [HGM] correct new time quote for time odds */\r
12990         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
12991         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
12992     }\r
12993     if (whiteFlag || blackFlag) {\r
12994         DisplayTitle("");\r
12995         whiteFlag = blackFlag = FALSE;\r
12996     }\r
12997     DisplayBothClocks();\r
12998 }\r
12999 \r
13000 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
13001 \r
13002 /* Decrement running clock by amount of time that has passed */\r
13003 void\r
13004 DecrementClocks()\r
13005 {\r
13006     long timeRemaining;\r
13007     long lastTickLength, fudge;\r
13008     TimeMark now;\r
13009 \r
13010     if (!appData.clockMode) return;\r
13011     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
13012         \r
13013     GetTimeMark(&now);\r
13014 \r
13015     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13016 \r
13017     /* Fudge if we woke up a little too soon */\r
13018     fudge = intendedTickLength - lastTickLength;\r
13019     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
13020 \r
13021     if (WhiteOnMove(forwardMostMove)) {\r
13022         if(whiteNPS >= 0) lastTickLength = 0;\r
13023         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
13024         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
13025                           WhiteOnMove(currentMove));\r
13026     } else {\r
13027         if(blackNPS >= 0) lastTickLength = 0;\r
13028         timeRemaining = blackTimeRemaining -= lastTickLength;\r
13029         DisplayBlackClock(blackTimeRemaining - fudge,\r
13030                           !WhiteOnMove(currentMove));\r
13031     }\r
13032 \r
13033     if (CheckFlags()) return;\r
13034         \r
13035     tickStartTM = now;\r
13036     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
13037     StartClockTimer(intendedTickLength);\r
13038 \r
13039     /* if the time remaining has fallen below the alarm threshold, sound the\r
13040      * alarm. if the alarm has sounded and (due to a takeback or time control\r
13041      * with increment) the time remaining has increased to a level above the\r
13042      * threshold, reset the alarm so it can sound again. \r
13043      */\r
13044     \r
13045     if (appData.icsActive && appData.icsAlarm) {\r
13046 \r
13047         /* make sure we are dealing with the user's clock */\r
13048         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
13049                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
13050            )) return;\r
13051 \r
13052         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
13053             alarmSounded = FALSE;\r
13054         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
13055             PlayAlarmSound();\r
13056             alarmSounded = TRUE;\r
13057         }\r
13058     }\r
13059 }\r
13060 \r
13061 \r
13062 /* A player has just moved, so stop the previously running\r
13063    clock and (if in clock mode) start the other one.\r
13064    We redisplay both clocks in case we're in ICS mode, because\r
13065    ICS gives us an update to both clocks after every move.\r
13066    Note that this routine is called *after* forwardMostMove\r
13067    is updated, so the last fractional tick must be subtracted\r
13068    from the color that is *not* on move now.\r
13069 */\r
13070 void\r
13071 SwitchClocks()\r
13072 {\r
13073     long lastTickLength;\r
13074     TimeMark now;\r
13075     int flagged = FALSE;\r
13076 \r
13077     GetTimeMark(&now);\r
13078 \r
13079     if (StopClockTimer() && appData.clockMode) {\r
13080         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13081         if (WhiteOnMove(forwardMostMove)) {\r
13082             if(blackNPS >= 0) lastTickLength = 0;\r
13083             blackTimeRemaining -= lastTickLength;\r
13084            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13085 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13086                  pvInfoList[forwardMostMove-1].time =               // use GUI time\r
13087                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
13088         } else {\r
13089            if(whiteNPS >= 0) lastTickLength = 0;\r
13090            whiteTimeRemaining -= lastTickLength;\r
13091            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13092 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13093                  pvInfoList[forwardMostMove-1].time = \r
13094                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
13095         }\r
13096         flagged = CheckFlags();\r
13097     }\r
13098     CheckTimeControl();\r
13099 \r
13100     if (flagged || !appData.clockMode) return;\r
13101 \r
13102     switch (gameMode) {\r
13103       case MachinePlaysBlack:\r
13104       case MachinePlaysWhite:\r
13105       case BeginningOfGame:\r
13106         if (pausing) return;\r
13107         break;\r
13108 \r
13109       case EditGame:\r
13110       case PlayFromGameFile:\r
13111       case IcsExamining:\r
13112         return;\r
13113 \r
13114       default:\r
13115         break;\r
13116     }\r
13117 \r
13118     tickStartTM = now;\r
13119     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13120       whiteTimeRemaining : blackTimeRemaining);\r
13121     StartClockTimer(intendedTickLength);\r
13122 }\r
13123         \r
13124 \r
13125 /* Stop both clocks */\r
13126 void\r
13127 StopClocks()\r
13128 {       \r
13129     long lastTickLength;\r
13130     TimeMark now;\r
13131 \r
13132     if (!StopClockTimer()) return;\r
13133     if (!appData.clockMode) return;\r
13134 \r
13135     GetTimeMark(&now);\r
13136 \r
13137     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13138     if (WhiteOnMove(forwardMostMove)) {\r
13139         if(whiteNPS >= 0) lastTickLength = 0;\r
13140         whiteTimeRemaining -= lastTickLength;\r
13141         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
13142     } else {\r
13143         if(blackNPS >= 0) lastTickLength = 0;\r
13144         blackTimeRemaining -= lastTickLength;\r
13145         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
13146     }\r
13147     CheckFlags();\r
13148 }\r
13149         \r
13150 /* Start clock of player on move.  Time may have been reset, so\r
13151    if clock is already running, stop and restart it. */\r
13152 void\r
13153 StartClocks()\r
13154 {\r
13155     (void) StopClockTimer(); /* in case it was running already */\r
13156     DisplayBothClocks();\r
13157     if (CheckFlags()) return;\r
13158 \r
13159     if (!appData.clockMode) return;\r
13160     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
13161 \r
13162     GetTimeMark(&tickStartTM);\r
13163     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13164       whiteTimeRemaining : blackTimeRemaining);\r
13165 \r
13166    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
13167     whiteNPS = blackNPS = -1; \r
13168     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
13169        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
13170         whiteNPS = first.nps;\r
13171     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
13172        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
13173         blackNPS = first.nps;\r
13174     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
13175         whiteNPS = second.nps;\r
13176     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
13177         blackNPS = second.nps;\r
13178     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
13179 \r
13180     StartClockTimer(intendedTickLength);\r
13181 }\r
13182 \r
13183 char *\r
13184 TimeString(ms)\r
13185      long ms;\r
13186 {\r
13187     long second, minute, hour, day;\r
13188     char *sign = "";\r
13189     static char buf[32];\r
13190     \r
13191     if (ms > 0 && ms <= 9900) {\r
13192       /* convert milliseconds to tenths, rounding up */\r
13193       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
13194 \r
13195       sprintf(buf, " %03.1f ", tenths/10.0);\r
13196       return buf;\r
13197     }\r
13198 \r
13199     /* convert milliseconds to seconds, rounding up */\r
13200     /* use floating point to avoid strangeness of integer division\r
13201        with negative dividends on many machines */\r
13202     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
13203 \r
13204     if (second < 0) {\r
13205         sign = "-";\r
13206         second = -second;\r
13207     }\r
13208     \r
13209     day = second / (60 * 60 * 24);\r
13210     second = second % (60 * 60 * 24);\r
13211     hour = second / (60 * 60);\r
13212     second = second % (60 * 60);\r
13213     minute = second / 60;\r
13214     second = second % 60;\r
13215     \r
13216     if (day > 0)\r
13217       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
13218               sign, day, hour, minute, second);\r
13219     else if (hour > 0)\r
13220       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
13221     else\r
13222       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
13223     \r
13224     return buf;\r
13225 }\r
13226 \r
13227 \r
13228 /*\r
13229  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
13230  */\r
13231 char *\r
13232 StrStr(string, match)\r
13233      char *string, *match;\r
13234 {\r
13235     int i, length;\r
13236     \r
13237     length = strlen(match);\r
13238     \r
13239     for (i = strlen(string) - length; i >= 0; i--, string++)\r
13240       if (!strncmp(match, string, length))\r
13241         return string;\r
13242     \r
13243     return NULL;\r
13244 }\r
13245 \r
13246 char *\r
13247 StrCaseStr(string, match)\r
13248      char *string, *match;\r
13249 {\r
13250     int i, j, length;\r
13251     \r
13252     length = strlen(match);\r
13253     \r
13254     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
13255         for (j = 0; j < length; j++) {\r
13256             if (ToLower(match[j]) != ToLower(string[j]))\r
13257               break;\r
13258         }\r
13259         if (j == length) return string;\r
13260     }\r
13261 \r
13262     return NULL;\r
13263 }\r
13264 \r
13265 #ifndef _amigados\r
13266 int\r
13267 StrCaseCmp(s1, s2)\r
13268      char *s1, *s2;\r
13269 {\r
13270     char c1, c2;\r
13271     \r
13272     for (;;) {\r
13273         c1 = ToLower(*s1++);\r
13274         c2 = ToLower(*s2++);\r
13275         if (c1 > c2) return 1;\r
13276         if (c1 < c2) return -1;\r
13277         if (c1 == NULLCHAR) return 0;\r
13278     }\r
13279 }\r
13280 \r
13281 \r
13282 int\r
13283 ToLower(c)\r
13284      int c;\r
13285 {\r
13286     return isupper(c) ? tolower(c) : c;\r
13287 }\r
13288 \r
13289 \r
13290 int\r
13291 ToUpper(c)\r
13292      int c;\r
13293 {\r
13294     return islower(c) ? toupper(c) : c;\r
13295 }\r
13296 #endif /* !_amigados    */\r
13297 \r
13298 char *\r
13299 StrSave(s)\r
13300      char *s;\r
13301 {\r
13302     char *ret;\r
13303 \r
13304     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
13305         strcpy(ret, s);\r
13306     }\r
13307     return ret;\r
13308 }\r
13309 \r
13310 char *\r
13311 StrSavePtr(s, savePtr)\r
13312      char *s, **savePtr;\r
13313 {\r
13314     if (*savePtr) {\r
13315         free(*savePtr);\r
13316     }\r
13317     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
13318         strcpy(*savePtr, s);\r
13319     }\r
13320     return(*savePtr);\r
13321 }\r
13322 \r
13323 char *\r
13324 PGNDate()\r
13325 {\r
13326     time_t clock;\r
13327     struct tm *tm;\r
13328     char buf[MSG_SIZ];\r
13329 \r
13330     clock = time((time_t *)NULL);\r
13331     tm = localtime(&clock);\r
13332     sprintf(buf, "%04d.%02d.%02d",\r
13333             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
13334     return StrSave(buf);\r
13335 }\r
13336 \r
13337 \r
13338 char *\r
13339 PositionToFEN(move, overrideCastling)\r
13340      int move;\r
13341      char *overrideCastling;\r
13342 {\r
13343     int i, j, fromX, fromY, toX, toY;\r
13344     int whiteToPlay;\r
13345     char buf[128];\r
13346     char *p, *q;\r
13347     int emptycount;\r
13348     ChessSquare piece;\r
13349 \r
13350     whiteToPlay = (gameMode == EditPosition) ?\r
13351       !blackPlaysFirst : (move % 2 == 0);\r
13352     p = buf;\r
13353 \r
13354     /* Piece placement data */\r
13355     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13356         emptycount = 0;\r
13357         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
13358             if (boards[move][i][j] == EmptySquare) {\r
13359                 emptycount++;\r
13360             } else { ChessSquare piece = boards[move][i][j];\r
13361                 if (emptycount > 0) {\r
13362                     if(emptycount<10) /* [HGM] can be >= 10 */\r
13363                         *p++ = '0' + emptycount;\r
13364                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13365                     emptycount = 0;\r
13366                 }\r
13367                 if(PieceToChar(piece) == '+') {\r
13368                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
13369                     *p++ = '+';\r
13370                     piece = (ChessSquare)(DEMOTED piece);\r
13371                 } \r
13372                 *p++ = PieceToChar(piece);\r
13373                 if(p[-1] == '~') {\r
13374                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
13375                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
13376                     *p++ = '~';\r
13377                 }\r
13378             }\r
13379         }\r
13380         if (emptycount > 0) {\r
13381             if(emptycount<10) /* [HGM] can be >= 10 */\r
13382                 *p++ = '0' + emptycount;\r
13383             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13384             emptycount = 0;\r
13385         }\r
13386         *p++ = '/';\r
13387     }\r
13388     *(p - 1) = ' ';\r
13389 \r
13390     /* [HGM] print Crazyhouse or Shogi holdings */\r
13391     if( gameInfo.holdingsWidth ) {\r
13392         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
13393         q = p;\r
13394         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
13395             piece = boards[move][i][BOARD_WIDTH-1];\r
13396             if( piece != EmptySquare )\r
13397               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
13398                   *p++ = PieceToChar(piece);\r
13399         }\r
13400         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
13401             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
13402             if( piece != EmptySquare )\r
13403               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
13404                   *p++ = PieceToChar(piece);\r
13405         }\r
13406 \r
13407         if( q == p ) *p++ = '-';\r
13408         *p++ = ']';\r
13409         *p++ = ' ';\r
13410     }\r
13411 \r
13412     /* Active color */\r
13413     *p++ = whiteToPlay ? 'w' : 'b';\r
13414     *p++ = ' ';\r
13415 \r
13416   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines\r
13417     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';\r
13418   } else {\r
13419   if(nrCastlingRights) {\r
13420      q = p;\r
13421      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
13422        /* [HGM] write directly from rights */\r
13423            if(castlingRights[move][2] >= 0 &&\r
13424               castlingRights[move][0] >= 0   )\r
13425                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
13426            if(castlingRights[move][2] >= 0 &&\r
13427               castlingRights[move][1] >= 0   )\r
13428                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
13429            if(castlingRights[move][5] >= 0 &&\r
13430               castlingRights[move][3] >= 0   )\r
13431                 *p++ = castlingRights[move][3] + AAA;\r
13432            if(castlingRights[move][5] >= 0 &&\r
13433               castlingRights[move][4] >= 0   )\r
13434                 *p++ = castlingRights[move][4] + AAA;\r
13435      } else {\r
13436 \r
13437         /* [HGM] write true castling rights */\r
13438         if( nrCastlingRights == 6 ) {\r
13439             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
13440                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
13441             if(castlingRights[move][1] == BOARD_LEFT &&\r
13442                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
13443             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
13444                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
13445             if(castlingRights[move][4] == BOARD_LEFT &&\r
13446                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
13447         }\r
13448      }\r
13449      if (q == p) *p++ = '-'; /* No castling rights */\r
13450      *p++ = ' ';\r
13451   }\r
13452 \r
13453   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13454      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13455     /* En passant target square */\r
13456     if (move > backwardMostMove) {\r
13457         fromX = moveList[move - 1][0] - AAA;\r
13458         fromY = moveList[move - 1][1] - ONE;\r
13459         toX = moveList[move - 1][2] - AAA;\r
13460         toY = moveList[move - 1][3] - ONE;\r
13461         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
13462             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
13463             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
13464             fromX == toX) {\r
13465             /* 2-square pawn move just happened */\r
13466             *p++ = toX + AAA;\r
13467             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
13468         } else {\r
13469             *p++ = '-';\r
13470         }\r
13471     } else {\r
13472         *p++ = '-';\r
13473     }\r
13474     *p++ = ' ';\r
13475   }\r
13476   }\r
13477 \r
13478     /* [HGM] find reversible plies */\r
13479     {   int i = 0, j=move;\r
13480 \r
13481         if (appData.debugMode) { int k;\r
13482             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
13483             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
13484                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
13485 \r
13486         }\r
13487 \r
13488         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
13489         if( j == backwardMostMove ) i += initialRulePlies;\r
13490         sprintf(p, "%d ", i);\r
13491         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
13492     }\r
13493     /* Fullmove number */\r
13494     sprintf(p, "%d", (move / 2) + 1);\r
13495     \r
13496     return StrSave(buf);\r
13497 }\r
13498 \r
13499 Boolean\r
13500 ParseFEN(board, blackPlaysFirst, fen)\r
13501     Board board;\r
13502      int *blackPlaysFirst;\r
13503      char *fen;\r
13504 {\r
13505     int i, j;\r
13506     char *p;\r
13507     int emptycount;\r
13508     ChessSquare piece;\r
13509 \r
13510     p = fen;\r
13511 \r
13512     /* [HGM] by default clear Crazyhouse holdings, if present */\r
13513     if(gameInfo.holdingsWidth) {\r
13514        for(i=0; i<BOARD_HEIGHT; i++) {\r
13515            board[i][0]             = EmptySquare; /* black holdings */\r
13516            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
13517            board[i][1]             = (ChessSquare) 0; /* black counts */\r
13518            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
13519        }\r
13520     }\r
13521 \r
13522     /* Piece placement data */\r
13523     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13524         j = 0;\r
13525         for (;;) {\r
13526             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
13527                 if (*p == '/') p++;\r
13528                 emptycount = gameInfo.boardWidth - j;\r
13529                 while (emptycount--)\r
13530                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13531                 break;\r
13532 #if(BOARD_SIZE >= 10)\r
13533             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
13534                 p++; emptycount=10;\r
13535                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13536                 while (emptycount--)\r
13537                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13538 #endif\r
13539             } else if (isdigit(*p)) {\r
13540                 emptycount = *p++ - '0';\r
13541                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
13542                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13543                 while (emptycount--)\r
13544                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13545             } else if (*p == '+' || isalpha(*p)) {\r
13546                 if (j >= gameInfo.boardWidth) return FALSE;\r
13547                 if(*p=='+') {\r
13548                     piece = CharToPiece(*++p);\r
13549                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
13550                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
13551                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
13552                 } else piece = CharToPiece(*p++);\r
13553 \r
13554                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
13555                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
13556                     piece = (ChessSquare) (PROMOTED piece);\r
13557                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
13558                     p++;\r
13559                 }\r
13560                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
13561             } else {\r
13562                 return FALSE;\r
13563             }\r
13564         }\r
13565     }\r
13566     while (*p == '/' || *p == ' ') p++;\r
13567 \r
13568     /* [HGM] look for Crazyhouse holdings here */\r
13569     while(*p==' ') p++;\r
13570     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
13571         if(*p == '[') p++;\r
13572         if(*p == '-' ) *p++; /* empty holdings */ else {\r
13573             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
13574             /* if we would allow FEN reading to set board size, we would   */\r
13575             /* have to add holdings and shift the board read so far here   */\r
13576             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
13577                 *p++;\r
13578                 if((int) piece >= (int) BlackPawn ) {\r
13579                     i = (int)piece - (int)BlackPawn;\r
13580                     i = PieceToNumber((ChessSquare)i);\r
13581                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13582                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
13583                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
13584                 } else {\r
13585                     i = (int)piece - (int)WhitePawn;\r
13586                     i = PieceToNumber((ChessSquare)i);\r
13587                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13588                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
13589                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
13590                 }\r
13591             }\r
13592         }\r
13593         if(*p == ']') *p++;\r
13594     }\r
13595 \r
13596     while(*p == ' ') p++;\r
13597 \r
13598     /* Active color */\r
13599     switch (*p++) {\r
13600       case 'w':\r
13601         *blackPlaysFirst = FALSE;\r
13602         break;\r
13603       case 'b': \r
13604         *blackPlaysFirst = TRUE;\r
13605         break;\r
13606       default:\r
13607         return FALSE;\r
13608     }\r
13609 \r
13610     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
13611     /* return the extra info in global variiables             */\r
13612 \r
13613     /* set defaults in case FEN is incomplete */\r
13614     FENepStatus = EP_UNKNOWN;\r
13615     for(i=0; i<nrCastlingRights; i++ ) {\r
13616         FENcastlingRights[i] =\r
13617             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
13618     }   /* assume possible unless obviously impossible */\r
13619     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
13620     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
13621     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
13622     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
13623     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
13624     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
13625     FENrulePlies = 0;\r
13626 \r
13627     while(*p==' ') p++;\r
13628     if(nrCastlingRights) {\r
13629       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
13630           /* castling indicator present, so default becomes no castlings */\r
13631           for(i=0; i<nrCastlingRights; i++ ) {\r
13632                  FENcastlingRights[i] = -1;\r
13633           }\r
13634       }\r
13635       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
13636              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
13637              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
13638              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
13639         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
13640 \r
13641         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
13642             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
13643             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
13644         }\r
13645         switch(c) {\r
13646           case'K':\r
13647               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
13648               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
13649               FENcastlingRights[2] = whiteKingFile;\r
13650               break;\r
13651           case'Q':\r
13652               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
13653               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
13654               FENcastlingRights[2] = whiteKingFile;\r
13655               break;\r
13656           case'k':\r
13657               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
13658               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
13659               FENcastlingRights[5] = blackKingFile;\r
13660               break;\r
13661           case'q':\r
13662               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
13663               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
13664               FENcastlingRights[5] = blackKingFile;\r
13665           case '-':\r
13666               break;\r
13667           default: /* FRC castlings */\r
13668               if(c >= 'a') { /* black rights */\r
13669                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13670                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
13671                   if(i == BOARD_RGHT) break;\r
13672                   FENcastlingRights[5] = i;\r
13673                   c -= AAA;\r
13674                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
13675                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
13676                   if(c > i)\r
13677                       FENcastlingRights[3] = c;\r
13678                   else\r
13679                       FENcastlingRights[4] = c;\r
13680               } else { /* white rights */\r
13681                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13682                     if(board[0][i] == WhiteKing) break;\r
13683                   if(i == BOARD_RGHT) break;\r
13684                   FENcastlingRights[2] = i;\r
13685                   c -= AAA - 'a' + 'A';\r
13686                   if(board[0][c] >= WhiteKing) break;\r
13687                   if(c > i)\r
13688                       FENcastlingRights[0] = c;\r
13689                   else\r
13690                       FENcastlingRights[1] = c;\r
13691               }\r
13692         }\r
13693       }\r
13694     if (appData.debugMode) {\r
13695         fprintf(debugFP, "FEN castling rights:");\r
13696         for(i=0; i<nrCastlingRights; i++)\r
13697         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
13698         fprintf(debugFP, "\n");\r
13699     }\r
13700 \r
13701       while(*p==' ') p++;\r
13702     }\r
13703 \r
13704     /* read e.p. field in games that know e.p. capture */\r
13705     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13706        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13707       if(*p=='-') {\r
13708         p++; FENepStatus = EP_NONE;\r
13709       } else {\r
13710          char c = *p++ - AAA;\r
13711 \r
13712          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
13713          if(*p >= '0' && *p <='9') *p++;\r
13714          FENepStatus = c;\r
13715       }\r
13716     }\r
13717 \r
13718 \r
13719     if(sscanf(p, "%d", &i) == 1) {\r
13720         FENrulePlies = i; /* 50-move ply counter */\r
13721         /* (The move number is still ignored)    */\r
13722     }\r
13723 \r
13724     return TRUE;\r
13725 }\r
13726       \r
13727 void\r
13728 EditPositionPasteFEN(char *fen)\r
13729 {\r
13730   if (fen != NULL) {\r
13731     Board initial_position;\r
13732 \r
13733     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
13734       DisplayError(_("Bad FEN position in clipboard"), 0);\r
13735       return ;\r
13736     } else {\r
13737       int savedBlackPlaysFirst = blackPlaysFirst;\r
13738       EditPositionEvent();\r
13739       blackPlaysFirst = savedBlackPlaysFirst;\r
13740       CopyBoard(boards[0], initial_position);\r
13741           /* [HGM] copy FEN attributes as well */\r
13742           {   int i;\r
13743               initialRulePlies = FENrulePlies;\r
13744               epStatus[0] = FENepStatus;\r
13745               for( i=0; i<nrCastlingRights; i++ )\r
13746                   castlingRights[0][i] = FENcastlingRights[i];\r
13747           }\r
13748       EditPositionDone();\r
13749       DisplayBothClocks();\r
13750       DrawPosition(FALSE, boards[currentMove]);\r
13751     }\r
13752   }\r
13753 }\r