Awareness of winning conditions for atomic and giveaway variants; two bugfixes
[xboard.git] / backend.c
1 /*\r
2  * backend.c -- Common back end for X and Windows NT versions of\r
3  * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $\r
4  *\r
5  * Copyright 1991 by Digital Equipment Corporation, Maynard,\r
6  * Massachusetts.  Enhancements Copyright\r
7  * 1992-2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software\r
8  * Foundation, Inc.\r
9  *\r
10  * The following terms apply to Digital Equipment Corporation's copyright\r
11  * interest in XBoard:\r
12  * ------------------------------------------------------------------------\r
13  * All Rights Reserved\r
14  *\r
15  * Permission to use, copy, modify, and distribute this software and its\r
16  * documentation for any purpose and without fee is hereby granted,\r
17  * provided that the above copyright notice appear in all copies and that\r
18  * both that copyright notice and this permission notice appear in\r
19  * supporting documentation, and that the name of Digital not be\r
20  * used in advertising or publicity pertaining to distribution of the\r
21  * software without specific, written prior permission.\r
22  *\r
23  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING\r
24  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL\r
25  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR\r
26  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,\r
27  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\r
28  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS\r
29  * SOFTWARE.\r
30  * ------------------------------------------------------------------------\r
31  *\r
32  * The following terms apply to the enhanced version of XBoard\r
33  * distributed by the Free Software Foundation:\r
34  * ------------------------------------------------------------------------\r
35  *\r
36  * GNU XBoard is free software: you can redistribute it and/or modify\r
37  * it under the terms of the GNU General Public License as published by\r
38  * the Free Software Foundation, either version 3 of the License, or (at\r
39  * your option) any later version.\r
40  *\r
41  * GNU XBoard is distributed in the hope that it will be useful, but\r
42  * WITHOUT ANY WARRANTY; without even the implied warranty of\r
43  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
44  * General Public License for more details.\r
45  *\r
46  * You should have received a copy of the GNU General Public License\r
47  * along with this program. If not, see http://www.gnu.org/licenses/.  *\r
48  *\r
49  *------------------------------------------------------------------------\r
50  ** See the file ChangeLog for a revision history.  */\r
51 \r
52 /* [AS] Also useful here for debugging */\r
53 #ifdef WIN32\r
54 #include <windows.h>\r
55 \r
56 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );\r
57 \r
58 #else\r
59 \r
60 #define DoSleep( n ) if( (n) >= 0) sleep(n)\r
61 \r
62 #endif\r
63 \r
64 #include "config.h"\r
65 \r
66 #include <assert.h>\r
67 #include <stdio.h>\r
68 #include <ctype.h>\r
69 #include <errno.h>\r
70 #include <sys/types.h>\r
71 #include <sys/stat.h>\r
72 #include <math.h>\r
73 #include <ctype.h>\r
74 \r
75 #if STDC_HEADERS\r
76 # include <stdlib.h>\r
77 # include <string.h>\r
78 #else /* not STDC_HEADERS */\r
79 # if HAVE_STRING_H\r
80 #  include <string.h>\r
81 # else /* not HAVE_STRING_H */\r
82 #  include <strings.h>\r
83 # endif /* not HAVE_STRING_H */\r
84 #endif /* not STDC_HEADERS */\r
85 \r
86 #if HAVE_SYS_FCNTL_H\r
87 # include <sys/fcntl.h>\r
88 #else /* not HAVE_SYS_FCNTL_H */\r
89 # if HAVE_FCNTL_H\r
90 #  include <fcntl.h>\r
91 # endif /* HAVE_FCNTL_H */\r
92 #endif /* not HAVE_SYS_FCNTL_H */\r
93 \r
94 #if TIME_WITH_SYS_TIME\r
95 # include <sys/time.h>\r
96 # include <time.h>\r
97 #else\r
98 # if HAVE_SYS_TIME_H\r
99 #  include <sys/time.h>\r
100 # else\r
101 #  include <time.h>\r
102 # endif\r
103 #endif\r
104 \r
105 #if defined(_amigados) && !defined(__GNUC__)\r
106 struct timezone {\r
107     int tz_minuteswest;\r
108     int tz_dsttime;\r
109 };\r
110 extern int gettimeofday(struct timeval *, struct timezone *);\r
111 #endif\r
112 \r
113 #if HAVE_UNISTD_H\r
114 # include <unistd.h>\r
115 #endif\r
116 \r
117 #include "common.h"\r
118 #include "frontend.h"\r
119 #include "backend.h"\r
120 #include "parser.h"\r
121 #include "moves.h"\r
122 #if ZIPPY\r
123 # include "zippy.h"\r
124 #endif\r
125 #include "backendz.h"\r
126 #include "gettext.h" \r
127  \r
128 #ifdef ENABLE_NLS \r
129 # define _(s) gettext (s) \r
130 # define N_(s) gettext_noop (s) \r
131 #else \r
132 # define _(s) (s) \r
133 # define N_(s) s \r
134 #endif \r
135 \r
136 \r
137 /* A point in time */\r
138 typedef struct {\r
139     long sec;  /* Assuming this is >= 32 bits */\r
140     int ms;    /* Assuming this is >= 16 bits */\r
141 } TimeMark;\r
142 \r
143 int establish P((void));\r
144 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,\r
145                          char *buf, int count, int error));\r
146 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,\r
147                       char *buf, int count, int error));\r
148 void SendToICS P((char *s));\r
149 void SendToICSDelayed P((char *s, long msdelay));\r
150 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,\r
151                       int toX, int toY));\r
152 void InitPosition P((int redraw));\r
153 void HandleMachineMove P((char *message, ChessProgramState *cps));\r
154 int AutoPlayOneMove P((void));\r
155 int LoadGameOneMove P((ChessMove readAhead));\r
156 int LoadGameFromFile P((char *filename, int n, char *title, int useList));\r
157 int LoadPositionFromFile P((char *filename, int n, char *title));\r
158 int SavePositionToFile P((char *filename));\r
159 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,\r
160                   Board board));\r
161 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));\r
162 void ShowMove P((int fromX, int fromY, int toX, int toY));\r
163 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,\r
164                    /*char*/int promoChar));\r
165 void BackwardInner P((int target));\r
166 void ForwardInner P((int target));\r
167 void GameEnds P((ChessMove result, char *resultDetails, int whosays));\r
168 void EditPositionDone P((void));\r
169 void PrintOpponents P((FILE *fp));\r
170 void PrintPosition P((FILE *fp, int move));\r
171 void StartChessProgram P((ChessProgramState *cps));\r
172 void SendToProgram P((char *message, ChessProgramState *cps));\r
173 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));\r
174 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,\r
175                            char *buf, int count, int error));\r
176 void SendTimeControl P((ChessProgramState *cps,\r
177                         int mps, long tc, int inc, int sd, int st));\r
178 char *TimeControlTagValue P((void));\r
179 void Attention P((ChessProgramState *cps));\r
180 void FeedMovesToProgram P((ChessProgramState *cps, int upto));\r
181 void ResurrectChessProgram P((void));\r
182 void DisplayComment P((int moveNumber, char *text));\r
183 void DisplayMove P((int moveNumber));\r
184 void DisplayAnalysis P((void));\r
185 \r
186 void ParseGameHistory P((char *game));\r
187 void ParseBoard12 P((char *string));\r
188 void StartClocks P((void));\r
189 void SwitchClocks P((void));\r
190 void StopClocks P((void));\r
191 void ResetClocks P((void));\r
192 char *PGNDate P((void));\r
193 void SetGameInfo P((void));\r
194 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));\r
195 int RegisterMove P((void));\r
196 void MakeRegisteredMove P((void));\r
197 void TruncateGame P((void));\r
198 int looking_at P((char *, int *, char *));\r
199 void CopyPlayerNameIntoFileName P((char **, char *));\r
200 char *SavePart P((char *));\r
201 int SaveGameOldStyle P((FILE *));\r
202 int SaveGamePGN P((FILE *));\r
203 void GetTimeMark P((TimeMark *));\r
204 long SubtractTimeMarks P((TimeMark *, TimeMark *));\r
205 int CheckFlags P((void));\r
206 long NextTickLength P((long));\r
207 void CheckTimeControl P((void));\r
208 void show_bytes P((FILE *, char *, int));\r
209 int string_to_rating P((char *str));\r
210 void ParseFeatures P((char* args, ChessProgramState *cps));\r
211 void InitBackEnd3 P((void));\r
212 void FeatureDone P((ChessProgramState* cps, int val));\r
213 void InitChessProgram P((ChessProgramState *cps, int setup));\r
214 void OutputKibitz(int window, char *text);\r
215 int PerpetualChase(int first, int last);\r
216 int EngineOutputIsUp();\r
217 void InitDrawingSizes(int x, int y);\r
218 \r
219 #ifdef WIN32\r
220        extern void ConsoleCreate();\r
221 #endif\r
222 \r
223 ChessProgramState *WhitePlayer();\r
224 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c\r
225 int VerifyDisplayMode P(());\r
226 \r
227 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment\r
228 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c\r
229 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move\r
230 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book\r
231 extern char installDir[MSG_SIZ];\r
232 \r
233 extern int tinyLayout, smallLayout;\r
234 ChessProgramStats programStats;\r
235 static int exiting = 0; /* [HGM] moved to top */\r
236 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;\r
237 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */\r
238 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */\r
239 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */\r
240 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */\r
241 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */\r
242 int opponentKibitzes;\r
243 \r
244 /* States for ics_getting_history */\r
245 #define H_FALSE 0\r
246 #define H_REQUESTED 1\r
247 #define H_GOT_REQ_HEADER 2\r
248 #define H_GOT_UNREQ_HEADER 3\r
249 #define H_GETTING_MOVES 4\r
250 #define H_GOT_UNWANTED_HEADER 5\r
251 \r
252 /* whosays values for GameEnds */\r
253 #define GE_ICS 0\r
254 #define GE_ENGINE 1\r
255 #define GE_PLAYER 2\r
256 #define GE_FILE 3\r
257 #define GE_XBOARD 4\r
258 #define GE_ENGINE1 5\r
259 #define GE_ENGINE2 6\r
260 \r
261 /* Maximum number of games in a cmail message */\r
262 #define CMAIL_MAX_GAMES 20\r
263 \r
264 /* Different types of move when calling RegisterMove */\r
265 #define CMAIL_MOVE   0\r
266 #define CMAIL_RESIGN 1\r
267 #define CMAIL_DRAW   2\r
268 #define CMAIL_ACCEPT 3\r
269 \r
270 /* Different types of result to remember for each game */\r
271 #define CMAIL_NOT_RESULT 0\r
272 #define CMAIL_OLD_RESULT 1\r
273 #define CMAIL_NEW_RESULT 2\r
274 \r
275 /* Telnet protocol constants */\r
276 #define TN_WILL 0373\r
277 #define TN_WONT 0374\r
278 #define TN_DO   0375\r
279 #define TN_DONT 0376\r
280 #define TN_IAC  0377\r
281 #define TN_ECHO 0001\r
282 #define TN_SGA  0003\r
283 #define TN_PORT 23\r
284 \r
285 /* [AS] */\r
286 static char * safeStrCpy( char * dst, const char * src, size_t count )\r
287 {\r
288     assert( dst != NULL );\r
289     assert( src != NULL );\r
290     assert( count > 0 );\r
291 \r
292     strncpy( dst, src, count );\r
293     dst[ count-1 ] = '\0';\r
294     return dst;\r
295 }\r
296 \r
297 #if 0\r
298 //[HGM] for future use? Conditioned out for now to suppress warning.\r
299 static char * safeStrCat( char * dst, const char * src, size_t count )\r
300 {\r
301     size_t  dst_len;\r
302 \r
303     assert( dst != NULL );\r
304     assert( src != NULL );\r
305     assert( count > 0 );\r
306 \r
307     dst_len = strlen(dst);\r
308 \r
309     assert( count > dst_len ); /* Buffer size must be greater than current length */\r
310 \r
311     safeStrCpy( dst + dst_len, src, count - dst_len );\r
312 \r
313     return dst;\r
314 }\r
315 #endif\r
316 \r
317 /* Some compiler can't cast u64 to double\r
318  * This function do the job for us:\r
319 \r
320  * We use the highest bit for cast, this only\r
321  * works if the highest bit is not\r
322  * in use (This should not happen)\r
323  *\r
324  * We used this for all compiler\r
325  */\r
326 double\r
327 u64ToDouble(u64 value)\r
328 {\r
329   double r;\r
330   u64 tmp = value & u64Const(0x7fffffffffffffff);\r
331   r = (double)(s64)tmp;\r
332   if (value & u64Const(0x8000000000000000))\r
333        r +=  9.2233720368547758080e18; /* 2^63 */\r
334  return r;\r
335 }\r
336 \r
337 /* Fake up flags for now, as we aren't keeping track of castling\r
338    availability yet. [HGM] Change of logic: the flag now only\r
339    indicates the type of castlings allowed by the rule of the game.\r
340    The actual rights themselves are maintained in the array\r
341    castlingRights, as part of the game history, and are not probed\r
342    by this function.\r
343  */\r
344 int\r
345 PosFlags(index)\r
346 {\r
347   int flags = F_ALL_CASTLE_OK;\r
348   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;\r
349   switch (gameInfo.variant) {\r
350   case VariantSuicide:\r
351     flags &= ~F_ALL_CASTLE_OK;\r
352   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!\r
353     flags |= F_IGNORE_CHECK;\r
354   case VariantLosers:\r
355     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist\r
356     break;\r
357   case VariantAtomic:\r
358     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;\r
359     break;\r
360   case VariantKriegspiel:\r
361     flags |= F_KRIEGSPIEL_CAPTURE;\r
362     break;\r
363   case VariantCapaRandom: \r
364   case VariantFischeRandom:\r
365     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */\r
366   case VariantNoCastle:\r
367   case VariantShatranj:\r
368   case VariantCourier:\r
369     flags &= ~F_ALL_CASTLE_OK;\r
370     break;\r
371   default:\r
372     break;\r
373   }\r
374   return flags;\r
375 }\r
376 \r
377 FILE *gameFileFP, *debugFP;\r
378 \r
379 /* \r
380     [AS] Note: sometimes, the sscanf() function is used to parse the input\r
381     into a fixed-size buffer. Because of this, we must be prepared to\r
382     receive strings as long as the size of the input buffer, which is currently\r
383     set to 4K for Windows and 8K for the rest.\r
384     So, we must either allocate sufficiently large buffers here, or\r
385     reduce the size of the input buffer in the input reading part.\r
386 */\r
387 \r
388 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];\r
389 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];\r
390 char thinkOutput1[MSG_SIZ*10];\r
391 \r
392 ChessProgramState first, second;\r
393 \r
394 /* premove variables */\r
395 int premoveToX = 0;\r
396 int premoveToY = 0;\r
397 int premoveFromX = 0;\r
398 int premoveFromY = 0;\r
399 int premovePromoChar = 0;\r
400 int gotPremove = 0;\r
401 Boolean alarmSounded;\r
402 /* end premove variables */\r
403 \r
404 char *ics_prefix = "$";\r
405 int ics_type = ICS_GENERIC;\r
406 \r
407 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;\r
408 int pauseExamForwardMostMove = 0;\r
409 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;\r
410 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];\r
411 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;\r
412 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;\r
413 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;\r
414 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;\r
415 int whiteFlag = FALSE, blackFlag = FALSE;\r
416 int userOfferedDraw = FALSE;\r
417 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;\r
418 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;\r
419 int cmailMoveType[CMAIL_MAX_GAMES];\r
420 long ics_clock_paused = 0;\r
421 ProcRef icsPR = NoProc, cmailPR = NoProc;\r
422 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;\r
423 GameMode gameMode = BeginningOfGame;\r
424 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];\r
425 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];\r
426 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */\r
427 int hiddenThinkOutputState = 0; /* [AS] */\r
428 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */\r
429 int adjudicateLossPlies = 6;\r
430 char white_holding[64], black_holding[64];\r
431 TimeMark lastNodeCountTime;\r
432 long lastNodeCount=0;\r
433 int have_sent_ICS_logon = 0;\r
434 int movesPerSession;\r
435 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;\r
436 long timeControl_2; /* [AS] Allow separate time controls */\r
437 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */\r
438 long timeRemaining[2][MAX_MOVES];\r
439 int matchGame = 0;\r
440 TimeMark programStartTime;\r
441 char ics_handle[MSG_SIZ];\r
442 int have_set_title = 0;\r
443 \r
444 /* animateTraining preserves the state of appData.animate\r
445  * when Training mode is activated. This allows the\r
446  * response to be animated when appData.animate == TRUE and\r
447  * appData.animateDragging == TRUE.\r
448  */\r
449 Boolean animateTraining;\r
450 \r
451 GameInfo gameInfo;\r
452 \r
453 AppData appData;\r
454 \r
455 Board boards[MAX_MOVES];\r
456 /* [HGM] Following 7 needed for accurate legality tests: */\r
457 char  epStatus[MAX_MOVES];\r
458 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1\r
459 char  castlingRank[BOARD_SIZE]; // and corresponding ranks\r
460 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];\r
461 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status\r
462 int   initialRulePlies, FENrulePlies;\r
463 char  FENepStatus;\r
464 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)\r
465 int loadFlag = 0; \r
466 int shuffleOpenings;\r
467 \r
468 ChessSquare  FIDEArray[2][BOARD_SIZE] = {\r
469     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
470         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
471     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
472         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
473 };\r
474 \r
475 ChessSquare twoKingsArray[2][BOARD_SIZE] = {\r
476     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
477         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },\r
478     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
479         BlackKing, BlackKing, BlackKnight, BlackRook }\r
480 };\r
481 \r
482 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {\r
483     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,\r
484         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },\r
485     { BlackRook, BlackMan, BlackBishop, BlackQueen,\r
486         BlackUnicorn, BlackBishop, BlackMan, BlackRook }\r
487 };\r
488 \r
489 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */\r
490     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,\r
491         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
492     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,\r
493         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
494 };\r
495 \r
496 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */\r
497     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,\r
498         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
499     { BlackRook, BlackKnight, BlackAlfil, BlackKing,\r
500         BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
501 };\r
502 \r
503 \r
504 #if (BOARD_SIZE>=10)\r
505 ChessSquare ShogiArray[2][BOARD_SIZE] = {\r
506     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,\r
507         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },\r
508     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,\r
509         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }\r
510 };\r
511 \r
512 ChessSquare XiangqiArray[2][BOARD_SIZE] = {\r
513     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,\r
514         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
515     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,\r
516         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
517 };\r
518 \r
519 ChessSquare CapablancaArray[2][BOARD_SIZE] = {\r
520     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, \r
521         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },\r
522     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, \r
523         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
524 };\r
525 \r
526 ChessSquare GreatArray[2][BOARD_SIZE] = {\r
527     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, \r
528         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },\r
529     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, \r
530         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },\r
531 };\r
532 \r
533 ChessSquare JanusArray[2][BOARD_SIZE] = {\r
534     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, \r
535         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },\r
536     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, \r
537         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }\r
538 };\r
539 \r
540 #ifdef GOTHIC\r
541 ChessSquare GothicArray[2][BOARD_SIZE] = {\r
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, \r
543         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },\r
544     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, \r
545         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }\r
546 };\r
547 #else // !GOTHIC\r
548 #define GothicArray CapablancaArray\r
549 #endif // !GOTHIC\r
550 \r
551 #ifdef FALCON\r
552 ChessSquare FalconArray[2][BOARD_SIZE] = {\r
553     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, \r
554         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },\r
555     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, \r
556         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }\r
557 };\r
558 #else // !FALCON\r
559 #define FalconArray CapablancaArray\r
560 #endif // !FALCON\r
561 \r
562 #else // !(BOARD_SIZE>=10)\r
563 #define XiangqiPosition FIDEArray\r
564 #define CapablancaArray FIDEArray\r
565 #define GothicArray FIDEArray\r
566 #define GreatArray FIDEArray\r
567 #endif // !(BOARD_SIZE>=10)\r
568 \r
569 #if (BOARD_SIZE>=12)\r
570 ChessSquare CourierArray[2][BOARD_SIZE] = {\r
571     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,\r
572         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },\r
573     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,\r
574         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }\r
575 };\r
576 #else // !(BOARD_SIZE>=12)\r
577 #define CourierArray CapablancaArray\r
578 #endif // !(BOARD_SIZE>=12)\r
579 \r
580 \r
581 Board initialPosition;\r
582 \r
583 \r
584 /* Convert str to a rating. Checks for special cases of "----",\r
585 \r
586    "++++", etc. Also strips ()'s */\r
587 int\r
588 string_to_rating(str)\r
589   char *str;\r
590 {\r
591   while(*str && !isdigit(*str)) ++str;\r
592   if (!*str)\r
593     return 0;   /* One of the special "no rating" cases */\r
594   else\r
595     return atoi(str);\r
596 }\r
597 \r
598 void\r
599 ClearProgramStats()\r
600 {\r
601     /* Init programStats */\r
602     programStats.movelist[0] = 0;\r
603     programStats.depth = 0;\r
604     programStats.nr_moves = 0;\r
605     programStats.moves_left = 0;\r
606     programStats.nodes = 0;\r
607     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output\r
608     programStats.score = 0;\r
609     programStats.got_only_move = 0;\r
610     programStats.got_fail = 0;\r
611     programStats.line_is_book = 0;\r
612 }\r
613 \r
614 void\r
615 InitBackEnd1()\r
616 {\r
617     int matched, min, sec;\r
618 \r
619     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options\r
620 \r
621     GetTimeMark(&programStartTime);\r
622 \r
623     ClearProgramStats();\r
624     programStats.ok_to_send = 1;\r
625     programStats.seen_stat = 0;\r
626 \r
627     /*\r
628      * Initialize game list\r
629      */\r
630     ListNew(&gameList);\r
631 \r
632 \r
633     /*\r
634      * Internet chess server status\r
635      */\r
636     if (appData.icsActive) {\r
637         appData.matchMode = FALSE;\r
638         appData.matchGames = 0;\r
639 #if ZIPPY       \r
640         appData.noChessProgram = !appData.zippyPlay;\r
641 #else\r
642         appData.zippyPlay = FALSE;\r
643         appData.zippyTalk = FALSE;\r
644         appData.noChessProgram = TRUE;\r
645 #endif\r
646         if (*appData.icsHelper != NULLCHAR) {\r
647             appData.useTelnet = TRUE;\r
648             appData.telnetProgram = appData.icsHelper;\r
649         }\r
650     } else {\r
651         appData.zippyTalk = appData.zippyPlay = FALSE;\r
652     }\r
653 \r
654     /* [AS] Initialize pv info list [HGM] and game state */\r
655     {\r
656         int i, j;\r
657 \r
658         for( i=0; i<MAX_MOVES; i++ ) {\r
659             pvInfoList[i].depth = -1;\r
660             epStatus[i]=EP_NONE;\r
661             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
662         }\r
663     }\r
664 \r
665     /*\r
666      * Parse timeControl resource\r
667      */\r
668     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,\r
669                           appData.movesPerSession)) {\r
670         char buf[MSG_SIZ];\r
671         sprintf(buf, _("bad timeControl option %s"), appData.timeControl);\r
672         DisplayFatalError(buf, 0, 2);\r
673     }\r
674 \r
675     /*\r
676      * Parse searchTime resource\r
677      */\r
678     if (*appData.searchTime != NULLCHAR) {\r
679         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);\r
680         if (matched == 1) {\r
681             searchTime = min * 60;\r
682         } else if (matched == 2) {\r
683             searchTime = min * 60 + sec;\r
684         } else {\r
685             char buf[MSG_SIZ];\r
686             sprintf(buf, _("bad searchTime option %s"), appData.searchTime);\r
687             DisplayFatalError(buf, 0, 2);\r
688         }\r
689     }\r
690 \r
691     /* [AS] Adjudication threshold */\r
692     adjudicateLossThreshold = appData.adjudicateLossThreshold;\r
693     \r
694     first.which = "first";\r
695     second.which = "second";\r
696     first.maybeThinking = second.maybeThinking = FALSE;\r
697     first.pr = second.pr = NoProc;\r
698     first.isr = second.isr = NULL;\r
699     first.sendTime = second.sendTime = 2;\r
700     first.sendDrawOffers = 1;\r
701     if (appData.firstPlaysBlack) {\r
702         first.twoMachinesColor = "black\n";\r
703         second.twoMachinesColor = "white\n";\r
704     } else {\r
705         first.twoMachinesColor = "white\n";\r
706         second.twoMachinesColor = "black\n";\r
707     }\r
708     first.program = appData.firstChessProgram;\r
709     second.program = appData.secondChessProgram;\r
710     first.host = appData.firstHost;\r
711     second.host = appData.secondHost;\r
712     first.dir = appData.firstDirectory;\r
713     second.dir = appData.secondDirectory;\r
714     first.other = &second;\r
715     second.other = &first;\r
716     first.initString = appData.initString;\r
717     second.initString = appData.secondInitString;\r
718     first.computerString = appData.firstComputerString;\r
719     second.computerString = appData.secondComputerString;\r
720     first.useSigint = second.useSigint = TRUE;\r
721     first.useSigterm = second.useSigterm = TRUE;\r
722     first.reuse = appData.reuseFirst;\r
723     second.reuse = appData.reuseSecond;\r
724     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second\r
725     second.nps = appData.secondNPS;\r
726     first.useSetboard = second.useSetboard = FALSE;\r
727     first.useSAN = second.useSAN = FALSE;\r
728     first.usePing = second.usePing = FALSE;\r
729     first.lastPing = second.lastPing = 0;\r
730     first.lastPong = second.lastPong = 0;\r
731     first.usePlayother = second.usePlayother = FALSE;\r
732     first.useColors = second.useColors = TRUE;\r
733     first.useUsermove = second.useUsermove = FALSE;\r
734     first.sendICS = second.sendICS = FALSE;\r
735     first.sendName = second.sendName = appData.icsActive;\r
736     first.sdKludge = second.sdKludge = FALSE;\r
737     first.stKludge = second.stKludge = FALSE;\r
738     TidyProgramName(first.program, first.host, first.tidy);\r
739     TidyProgramName(second.program, second.host, second.tidy);\r
740     first.matchWins = second.matchWins = 0;\r
741     strcpy(first.variants, appData.variant);\r
742     strcpy(second.variants, appData.variant);\r
743     first.analysisSupport = second.analysisSupport = 2; /* detect */\r
744     first.analyzing = second.analyzing = FALSE;\r
745     first.initDone = second.initDone = FALSE;\r
746 \r
747     /* New features added by Tord: */\r
748     first.useFEN960 = FALSE; second.useFEN960 = FALSE;\r
749     first.useOOCastle = TRUE; second.useOOCastle = TRUE;\r
750     /* End of new features added by Tord. */\r
751 \r
752     /* [HGM] time odds: set factor for each machine */\r
753     first.timeOdds  = appData.firstTimeOdds;\r
754     second.timeOdds = appData.secondTimeOdds;\r
755     { int norm = 1;\r
756         if(appData.timeOddsMode) {\r
757             norm = first.timeOdds;\r
758             if(norm > second.timeOdds) norm = second.timeOdds;\r
759         }\r
760         first.timeOdds /= norm;\r
761         second.timeOdds /= norm;\r
762     }\r
763 \r
764     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/\r
765     first.accumulateTC = appData.firstAccumulateTC;\r
766     second.accumulateTC = appData.secondAccumulateTC;\r
767     first.maxNrOfSessions = second.maxNrOfSessions = 1;\r
768 \r
769     /* [HGM] debug */\r
770     first.debug = second.debug = FALSE;\r
771     first.supportsNPS = second.supportsNPS = UNKNOWN;\r
772 \r
773     /* [HGM] options */\r
774     first.optionSettings  = appData.firstOptions;\r
775     second.optionSettings = appData.secondOptions;\r
776 \r
777     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
778     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
779     first.isUCI = appData.firstIsUCI; /* [AS] */\r
780     second.isUCI = appData.secondIsUCI; /* [AS] */\r
781     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
782     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
783 \r
784     if (appData.firstProtocolVersion > PROTOVER ||\r
785         appData.firstProtocolVersion < 1) {\r
786       char buf[MSG_SIZ];\r
787       sprintf(buf, _("protocol version %d not supported"),\r
788               appData.firstProtocolVersion);\r
789       DisplayFatalError(buf, 0, 2);\r
790     } else {\r
791       first.protocolVersion = appData.firstProtocolVersion;\r
792     }\r
793 \r
794     if (appData.secondProtocolVersion > PROTOVER ||\r
795         appData.secondProtocolVersion < 1) {\r
796       char buf[MSG_SIZ];\r
797       sprintf(buf, _("protocol version %d not supported"),\r
798               appData.secondProtocolVersion);\r
799       DisplayFatalError(buf, 0, 2);\r
800     } else {\r
801       second.protocolVersion = appData.secondProtocolVersion;\r
802     }\r
803 \r
804     if (appData.icsActive) {\r
805         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */\r
806     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
807         appData.clockMode = FALSE;\r
808         first.sendTime = second.sendTime = 0;\r
809     }\r
810     \r
811 #if ZIPPY\r
812     /* Override some settings from environment variables, for backward\r
813        compatibility.  Unfortunately it's not feasible to have the env\r
814        vars just set defaults, at least in xboard.  Ugh.\r
815     */\r
816     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
817       ZippyInit();\r
818     }\r
819 #endif\r
820     \r
821     if (appData.noChessProgram) {\r
822         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
823                                         + strlen(PATCHLEVEL));\r
824         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
825     } else {\r
826 #if 0\r
827         char *p, *q;\r
828         q = first.program;\r
829         while (*q != ' ' && *q != NULLCHAR) q++;\r
830         p = q;\r
831         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */\r
832         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
833                                         + strlen(PATCHLEVEL) + (q - p));\r
834         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
835         strncat(programVersion, p, q - p);\r
836 #else\r
837         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */\r
838         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
839                                         + strlen(PATCHLEVEL) + strlen(first.tidy));\r
840         sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);\r
841 #endif\r
842     }\r
843 \r
844     if (!appData.icsActive) {\r
845       char buf[MSG_SIZ];\r
846       /* Check for variants that are supported only in ICS mode,\r
847          or not at all.  Some that are accepted here nevertheless\r
848          have bugs; see comments below.\r
849       */\r
850       VariantClass variant = StringToVariant(appData.variant);\r
851       switch (variant) {\r
852       case VariantBughouse:     /* need four players and two boards */\r
853       case VariantKriegspiel:   /* need to hide pieces and move details */\r
854       /* case VariantFischeRandom: (Fabien: moved below) */\r
855         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);\r
856         DisplayFatalError(buf, 0, 2);\r
857         return;\r
858 \r
859       case VariantUnknown:\r
860       case VariantLoadable:\r
861       case Variant29:\r
862       case Variant30:\r
863       case Variant31:\r
864       case Variant32:\r
865       case Variant33:\r
866       case Variant34:\r
867       case Variant35:\r
868       case Variant36:\r
869       default:\r
870         sprintf(buf, _("Unknown variant name %s"), appData.variant);\r
871         DisplayFatalError(buf, 0, 2);\r
872         return;\r
873 \r
874       case VariantXiangqi:    /* [HGM] repetition rules not implemented */\r
875       case VariantFairy:      /* [HGM] TestLegality definitely off! */\r
876       case VariantGothic:     /* [HGM] should work */\r
877       case VariantCapablanca: /* [HGM] should work */\r
878       case VariantCourier:    /* [HGM] initial forced moves not implemented */\r
879       case VariantShogi:      /* [HGM] drops not tested for legality */\r
880       case VariantKnightmate: /* [HGM] should work */\r
881       case VariantCylinder:   /* [HGM] untested */\r
882       case VariantFalcon:     /* [HGM] untested */\r
883       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)\r
884                                  offboard interposition not understood */\r
885       case VariantNormal:     /* definitely works! */\r
886       case VariantWildCastle: /* pieces not automatically shuffled */\r
887       case VariantNoCastle:   /* pieces not automatically shuffled */\r
888       case VariantFischeRandom: /* [HGM] works and shuffles pieces */\r
889       case VariantLosers:     /* should work except for win condition,\r
890                                  and doesn't know captures are mandatory */\r
891       case VariantSuicide:    /* should work except for win condition,\r
892                                  and doesn't know captures are mandatory */\r
893       case VariantGiveaway:   /* should work except for win condition,\r
894                                  and doesn't know captures are mandatory */\r
895       case VariantTwoKings:   /* should work */\r
896       case VariantAtomic:     /* should work except for win condition */\r
897       case Variant3Check:     /* should work except for win condition */\r
898       case VariantShatranj:   /* should work except for all win conditions */\r
899       case VariantBerolina:   /* might work if TestLegality is off */\r
900       case VariantCapaRandom: /* should work */\r
901       case VariantJanus:      /* should work */\r
902       case VariantSuper:      /* experimental */\r
903       case VariantGreat:      /* experimental, requires legality testing to be off */\r
904         break;\r
905       }\r
906     }\r
907 \r
908     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard\r
909     InitEngineUCI( installDir, &second );\r
910 }\r
911 \r
912 int NextIntegerFromString( char ** str, long * value )\r
913 {\r
914     int result = -1;\r
915     char * s = *str;\r
916 \r
917     while( *s == ' ' || *s == '\t' ) {\r
918         s++;\r
919     }\r
920 \r
921     *value = 0;\r
922 \r
923     if( *s >= '0' && *s <= '9' ) {\r
924         while( *s >= '0' && *s <= '9' ) {\r
925             *value = *value * 10 + (*s - '0');\r
926             s++;\r
927         }\r
928 \r
929         result = 0;\r
930     }\r
931 \r
932     *str = s;\r
933 \r
934     return result;\r
935 }\r
936 \r
937 int NextTimeControlFromString( char ** str, long * value )\r
938 {\r
939     long temp;\r
940     int result = NextIntegerFromString( str, &temp );\r
941 \r
942     if( result == 0 ) {\r
943         *value = temp * 60; /* Minutes */\r
944         if( **str == ':' ) {\r
945             (*str)++;\r
946             result = NextIntegerFromString( str, &temp );\r
947             *value += temp; /* Seconds */\r
948         }\r
949     }\r
950 \r
951     return result;\r
952 }\r
953 \r
954 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)\r
955 {   /* [HGM] routine added to read '+moves/time' for secondary time control */\r
956     int result = -1; long temp, temp2;\r
957 \r
958     if(**str != '+') return -1; // old params remain in force!\r
959     (*str)++;\r
960     if( NextTimeControlFromString( str, &temp ) ) return -1;\r
961 \r
962     if(**str != '/') {\r
963         /* time only: incremental or sudden-death time control */\r
964         if(**str == '+') { /* increment follows; read it */\r
965             (*str)++;\r
966             if(result = NextIntegerFromString( str, &temp2)) return -1;\r
967             *inc = temp2 * 1000;\r
968         } else *inc = 0;\r
969         *moves = 0; *tc = temp * 1000; \r
970         return 0;\r
971     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */\r
972 \r
973     (*str)++; /* classical time control */\r
974     result = NextTimeControlFromString( str, &temp2);\r
975     if(result == 0) {\r
976         *moves = temp/60;\r
977         *tc    = temp2 * 1000;\r
978         *inc   = 0;\r
979     }\r
980     return result;\r
981 }\r
982 \r
983 int GetTimeQuota(int movenr)\r
984 {   /* [HGM] get time to add from the multi-session time-control string */\r
985     int moves=1; /* kludge to force reading of first session */\r
986     long time, increment;\r
987     char *s = fullTimeControlString;\r
988 \r
989     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);\r
990     do {\r
991         if(moves) NextSessionFromString(&s, &moves, &time, &increment);\r
992         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);\r
993         if(movenr == -1) return time;    /* last move before new session     */\r
994         if(!moves) return increment;     /* current session is incremental   */\r
995         if(movenr >= 0) movenr -= moves; /* we already finished this session */\r
996     } while(movenr >= -1);               /* try again for next session       */\r
997 \r
998     return 0; // no new time quota on this move\r
999 }\r
1000 \r
1001 int\r
1002 ParseTimeControl(tc, ti, mps)\r
1003      char *tc;\r
1004      int ti;\r
1005      int mps;\r
1006 {\r
1007 #if 0\r
1008     int matched, min, sec;\r
1009 \r
1010     matched = sscanf(tc, "%d:%d", &min, &sec);\r
1011     if (matched == 1) {\r
1012         timeControl = min * 60 * 1000;\r
1013     } else if (matched == 2) {\r
1014         timeControl = (min * 60 + sec) * 1000;\r
1015     } else {\r
1016         return FALSE;\r
1017     }\r
1018 #else\r
1019     long tc1;\r
1020     long tc2;\r
1021     char buf[MSG_SIZ];\r
1022 \r
1023     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;\r
1024     if(ti > 0) {\r
1025         if(mps)\r
1026              sprintf(buf, "+%d/%s+%d", mps, tc, ti);\r
1027         else sprintf(buf, "+%s+%d", tc, ti);\r
1028     } else {\r
1029         if(mps)\r
1030              sprintf(buf, "+%d/%s", mps, tc);\r
1031         else sprintf(buf, "+%s", tc);\r
1032     }\r
1033     fullTimeControlString = StrSave(buf);\r
1034 \r
1035     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
1036         return FALSE;\r
1037     }\r
1038 \r
1039     if( *tc == '/' ) {\r
1040         /* Parse second time control */\r
1041         tc++;\r
1042 \r
1043         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
1044             return FALSE;\r
1045         }\r
1046 \r
1047         if( tc2 == 0 ) {\r
1048             return FALSE;\r
1049         }\r
1050 \r
1051         timeControl_2 = tc2 * 1000;\r
1052     }\r
1053     else {\r
1054         timeControl_2 = 0;\r
1055     }\r
1056 \r
1057     if( tc1 == 0 ) {\r
1058         return FALSE;\r
1059     }\r
1060 \r
1061     timeControl = tc1 * 1000;\r
1062 #endif\r
1063 \r
1064     if (ti >= 0) {\r
1065         timeIncrement = ti * 1000;  /* convert to ms */\r
1066         movesPerSession = 0;\r
1067     } else {\r
1068         timeIncrement = 0;\r
1069         movesPerSession = mps;\r
1070     }\r
1071     return TRUE;\r
1072 }\r
1073 \r
1074 void\r
1075 InitBackEnd2()\r
1076 {\r
1077     if (appData.debugMode) {\r
1078         fprintf(debugFP, "%s\n", programVersion);\r
1079     }\r
1080 \r
1081     if (appData.matchGames > 0) {\r
1082         appData.matchMode = TRUE;\r
1083     } else if (appData.matchMode) {\r
1084         appData.matchGames = 1;\r
1085     }\r
1086     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */\r
1087         appData.matchGames = appData.sameColorGames;\r
1088     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */\r
1089         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;\r
1090         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;\r
1091     }\r
1092     Reset(TRUE, FALSE);\r
1093     if (appData.noChessProgram || first.protocolVersion == 1) {\r
1094       InitBackEnd3();\r
1095     } else {\r
1096       /* kludge: allow timeout for initial "feature" commands */\r
1097       FreezeUI();\r
1098       DisplayMessage("", _("Starting chess program"));\r
1099       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
1100     }\r
1101 }\r
1102 \r
1103 void\r
1104 InitBackEnd3 P((void))\r
1105 {\r
1106     GameMode initialMode;\r
1107     char buf[MSG_SIZ];\r
1108     int err;\r
1109 \r
1110     InitChessProgram(&first, startedFromSetupPosition);\r
1111 \r
1112 \r
1113     if (appData.icsActive) {\r
1114 #ifdef WIN32\r
1115         /* [DM] Make a console window if needed [HGM] merged ifs */\r
1116         ConsoleCreate(); \r
1117 #endif\r
1118         err = establish();\r
1119         if (err != 0) {\r
1120             if (*appData.icsCommPort != NULLCHAR) {\r
1121                 sprintf(buf, _("Could not open comm port %s"),  \r
1122                         appData.icsCommPort);\r
1123             } else {\r
1124                 sprintf(buf, _("Could not connect to host %s, port %s"),  \r
1125                         appData.icsHost, appData.icsPort);\r
1126             }\r
1127             DisplayFatalError(buf, err, 1);\r
1128             return;\r
1129         }\r
1130         SetICSMode();\r
1131         telnetISR =\r
1132           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
1133         fromUserISR =\r
1134           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
1135     } else if (appData.noChessProgram) {\r
1136         SetNCPMode();\r
1137     } else {\r
1138         SetGNUMode();\r
1139     }\r
1140 \r
1141     if (*appData.cmailGameName != NULLCHAR) {\r
1142         SetCmailMode();\r
1143         OpenLoopback(&cmailPR);\r
1144         cmailISR =\r
1145           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
1146     }\r
1147     \r
1148     ThawUI();\r
1149     DisplayMessage("", "");\r
1150     if (StrCaseCmp(appData.initialMode, "") == 0) {\r
1151       initialMode = BeginningOfGame;\r
1152     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
1153       initialMode = TwoMachinesPlay;\r
1154     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
1155       initialMode = AnalyzeFile; \r
1156     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
1157       initialMode = AnalyzeMode;\r
1158     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
1159       initialMode = MachinePlaysWhite;\r
1160     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
1161       initialMode = MachinePlaysBlack;\r
1162     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
1163       initialMode = EditGame;\r
1164     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
1165       initialMode = EditPosition;\r
1166     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
1167       initialMode = Training;\r
1168     } else {\r
1169       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);\r
1170       DisplayFatalError(buf, 0, 2);\r
1171       return;\r
1172     }\r
1173 \r
1174     if (appData.matchMode) {\r
1175         /* Set up machine vs. machine match */\r
1176         if (appData.noChessProgram) {\r
1177             DisplayFatalError(_("Can't have a match with no chess programs"),\r
1178                               0, 2);\r
1179             return;\r
1180         }\r
1181         matchMode = TRUE;\r
1182         matchGame = 1;\r
1183         if (*appData.loadGameFile != NULLCHAR) {\r
1184             int index = appData.loadGameIndex; // [HGM] autoinc\r
1185             if(index<0) lastIndex = index = 1;\r
1186             if (!LoadGameFromFile(appData.loadGameFile,\r
1187                                   index,\r
1188                                   appData.loadGameFile, FALSE)) {\r
1189                 DisplayFatalError(_("Bad game file"), 0, 1);\r
1190                 return;\r
1191             }\r
1192         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1193             int index = appData.loadPositionIndex; // [HGM] autoinc\r
1194             if(index<0) lastIndex = index = 1;\r
1195             if (!LoadPositionFromFile(appData.loadPositionFile,\r
1196                                       index,\r
1197                                       appData.loadPositionFile)) {\r
1198                 DisplayFatalError(_("Bad position file"), 0, 1);\r
1199                 return;\r
1200             }\r
1201         }\r
1202         TwoMachinesEvent();\r
1203     } else if (*appData.cmailGameName != NULLCHAR) {\r
1204         /* Set up cmail mode */\r
1205         ReloadCmailMsgEvent(TRUE);\r
1206     } else {\r
1207         /* Set up other modes */\r
1208         if (initialMode == AnalyzeFile) {\r
1209           if (*appData.loadGameFile == NULLCHAR) {\r
1210             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);\r
1211             return;\r
1212           }\r
1213         }\r
1214         if (*appData.loadGameFile != NULLCHAR) {\r
1215             (void) LoadGameFromFile(appData.loadGameFile,\r
1216                                     appData.loadGameIndex,\r
1217                                     appData.loadGameFile, TRUE);\r
1218         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1219             (void) LoadPositionFromFile(appData.loadPositionFile,\r
1220                                         appData.loadPositionIndex,\r
1221                                         appData.loadPositionFile);\r
1222             /* [HGM] try to make self-starting even after FEN load */\r
1223             /* to allow automatic setup of fairy variants with wtm */\r
1224             if(initialMode == BeginningOfGame && !blackPlaysFirst) {\r
1225                 gameMode = BeginningOfGame;\r
1226                 setboardSpoiledMachineBlack = 1;\r
1227             }\r
1228             /* [HGM] loadPos: make that every new game uses the setup */\r
1229             /* from file as long as we do not switch variant          */\r
1230             if(!blackPlaysFirst) { int i;\r
1231                 startedFromPositionFile = TRUE;\r
1232                 CopyBoard(filePosition, boards[0]);\r
1233                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];\r
1234             }\r
1235         }\r
1236         if (initialMode == AnalyzeMode) {\r
1237           if (appData.noChessProgram) {\r
1238             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);\r
1239             return;\r
1240           }\r
1241           if (appData.icsActive) {\r
1242             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);\r
1243             return;\r
1244           }\r
1245           AnalyzeModeEvent();\r
1246         } else if (initialMode == AnalyzeFile) {\r
1247           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent\r
1248           ShowThinkingEvent();\r
1249           AnalyzeFileEvent();\r
1250           AnalysisPeriodicEvent(1);\r
1251         } else if (initialMode == MachinePlaysWhite) {\r
1252           if (appData.noChessProgram) {\r
1253             DisplayFatalError(_("MachineWhite mode requires a chess engine"),\r
1254                               0, 2);\r
1255             return;\r
1256           }\r
1257           if (appData.icsActive) {\r
1258             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),\r
1259                               0, 2);\r
1260             return;\r
1261           }\r
1262           MachineWhiteEvent();\r
1263         } else if (initialMode == MachinePlaysBlack) {\r
1264           if (appData.noChessProgram) {\r
1265             DisplayFatalError(_("MachineBlack mode requires a chess engine"),\r
1266                               0, 2);\r
1267             return;\r
1268           }\r
1269           if (appData.icsActive) {\r
1270             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),\r
1271                               0, 2);\r
1272             return;\r
1273           }\r
1274           MachineBlackEvent();\r
1275         } else if (initialMode == TwoMachinesPlay) {\r
1276           if (appData.noChessProgram) {\r
1277             DisplayFatalError(_("TwoMachines mode requires a chess engine"),\r
1278                               0, 2);\r
1279             return;\r
1280           }\r
1281           if (appData.icsActive) {\r
1282             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),\r
1283                               0, 2);\r
1284             return;\r
1285           }\r
1286           TwoMachinesEvent();\r
1287         } else if (initialMode == EditGame) {\r
1288           EditGameEvent();\r
1289         } else if (initialMode == EditPosition) {\r
1290           EditPositionEvent();\r
1291         } else if (initialMode == Training) {\r
1292           if (*appData.loadGameFile == NULLCHAR) {\r
1293             DisplayFatalError(_("Training mode requires a game file"), 0, 2);\r
1294             return;\r
1295           }\r
1296           TrainingEvent();\r
1297         }\r
1298     }\r
1299 }\r
1300 \r
1301 /*\r
1302  * Establish will establish a contact to a remote host.port.\r
1303  * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
1304  *  used to talk to the host.\r
1305  * Returns 0 if okay, error code if not.\r
1306  */\r
1307 int\r
1308 establish()\r
1309 {\r
1310     char buf[MSG_SIZ];\r
1311 \r
1312     if (*appData.icsCommPort != NULLCHAR) {\r
1313         /* Talk to the host through a serial comm port */\r
1314         return OpenCommPort(appData.icsCommPort, &icsPR);\r
1315 \r
1316     } else if (*appData.gateway != NULLCHAR) {\r
1317         if (*appData.remoteShell == NULLCHAR) {\r
1318             /* Use the rcmd protocol to run telnet program on a gateway host */\r
1319             sprintf(buf, "%s %s %s",\r
1320                     appData.telnetProgram, appData.icsHost, appData.icsPort);\r
1321             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
1322 \r
1323         } else {\r
1324             /* Use the rsh program to run telnet program on a gateway host */\r
1325             if (*appData.remoteUser == NULLCHAR) {\r
1326                 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,\r
1327                         appData.gateway, appData.telnetProgram,\r
1328                         appData.icsHost, appData.icsPort);\r
1329             } else {\r
1330                 sprintf(buf, "%s %s -l %s %s %s %s",\r
1331                         appData.remoteShell, appData.gateway, \r
1332                         appData.remoteUser, appData.telnetProgram,\r
1333                         appData.icsHost, appData.icsPort);\r
1334             }\r
1335             return StartChildProcess(buf, "", &icsPR);\r
1336 \r
1337         }\r
1338     } else if (appData.useTelnet) {\r
1339         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
1340 \r
1341     } else {\r
1342         /* TCP socket interface differs somewhat between\r
1343            Unix and NT; handle details in the front end.\r
1344            */\r
1345         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
1346     }\r
1347 }\r
1348 \r
1349 void\r
1350 show_bytes(fp, buf, count)\r
1351      FILE *fp;\r
1352      char *buf;\r
1353      int count;\r
1354 {\r
1355     while (count--) {\r
1356         if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
1357             fprintf(fp, "\\%03o", *buf & 0xff);\r
1358         } else {\r
1359             putc(*buf, fp);\r
1360         }\r
1361         buf++;\r
1362     }\r
1363     fflush(fp);\r
1364 }\r
1365 \r
1366 /* Returns an errno value */\r
1367 int\r
1368 OutputMaybeTelnet(pr, message, count, outError)\r
1369      ProcRef pr;\r
1370      char *message;\r
1371      int count;\r
1372      int *outError;\r
1373 {\r
1374     char buf[8192], *p, *q, *buflim;\r
1375     int left, newcount, outcount;\r
1376 \r
1377     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
1378         *appData.gateway != NULLCHAR) {\r
1379         if (appData.debugMode) {\r
1380             fprintf(debugFP, ">ICS: ");\r
1381             show_bytes(debugFP, message, count);\r
1382             fprintf(debugFP, "\n");\r
1383         }\r
1384         return OutputToProcess(pr, message, count, outError);\r
1385     }\r
1386 \r
1387     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
1388     p = message;\r
1389     q = buf;\r
1390     left = count;\r
1391     newcount = 0;\r
1392     while (left) {\r
1393         if (q >= buflim) {\r
1394             if (appData.debugMode) {\r
1395                 fprintf(debugFP, ">ICS: ");\r
1396                 show_bytes(debugFP, buf, newcount);\r
1397                 fprintf(debugFP, "\n");\r
1398             }\r
1399             outcount = OutputToProcess(pr, buf, newcount, outError);\r
1400             if (outcount < newcount) return -1; /* to be sure */\r
1401             q = buf;\r
1402             newcount = 0;\r
1403         }\r
1404         if (*p == '\n') {\r
1405             *q++ = '\r';\r
1406             newcount++;\r
1407         } else if (((unsigned char) *p) == TN_IAC) {\r
1408             *q++ = (char) TN_IAC;\r
1409             newcount ++;\r
1410         }\r
1411         *q++ = *p++;\r
1412         newcount++;\r
1413         left--;\r
1414     }\r
1415     if (appData.debugMode) {\r
1416         fprintf(debugFP, ">ICS: ");\r
1417         show_bytes(debugFP, buf, newcount);\r
1418         fprintf(debugFP, "\n");\r
1419     }\r
1420     outcount = OutputToProcess(pr, buf, newcount, outError);\r
1421     if (outcount < newcount) return -1; /* to be sure */\r
1422     return count;\r
1423 }\r
1424 \r
1425 void\r
1426 read_from_player(isr, closure, message, count, error)\r
1427      InputSourceRef isr;\r
1428      VOIDSTAR closure;\r
1429      char *message;\r
1430      int count;\r
1431      int error;\r
1432 {\r
1433     int outError, outCount;\r
1434     static int gotEof = 0;\r
1435 \r
1436     /* Pass data read from player on to ICS */\r
1437     if (count > 0) {\r
1438         gotEof = 0;\r
1439         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
1440         if (outCount < count) {\r
1441             DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1442         }\r
1443     } else if (count < 0) {\r
1444         RemoveInputSource(isr);\r
1445         DisplayFatalError(_("Error reading from keyboard"), error, 1);\r
1446     } else if (gotEof++ > 0) {\r
1447         RemoveInputSource(isr);\r
1448         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);\r
1449     }\r
1450 }\r
1451 \r
1452 void\r
1453 SendToICS(s)\r
1454      char *s;\r
1455 {\r
1456     int count, outCount, outError;\r
1457 \r
1458     if (icsPR == NULL) return;\r
1459 \r
1460     count = strlen(s);\r
1461     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
1462     if (outCount < count) {\r
1463         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1464     }\r
1465 }\r
1466 \r
1467 /* This is used for sending logon scripts to the ICS. Sending\r
1468    without a delay causes problems when using timestamp on ICC\r
1469    (at least on my machine). */\r
1470 void\r
1471 SendToICSDelayed(s,msdelay)\r
1472      char *s;\r
1473      long msdelay;\r
1474 {\r
1475     int count, outCount, outError;\r
1476 \r
1477     if (icsPR == NULL) return;\r
1478 \r
1479     count = strlen(s);\r
1480     if (appData.debugMode) {\r
1481         fprintf(debugFP, ">ICS: ");\r
1482         show_bytes(debugFP, s, count);\r
1483         fprintf(debugFP, "\n");\r
1484     }\r
1485     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
1486                                       msdelay);\r
1487     if (outCount < count) {\r
1488         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1489     }\r
1490 }\r
1491 \r
1492 \r
1493 /* Remove all highlighting escape sequences in s\r
1494    Also deletes any suffix starting with '(' \r
1495    */\r
1496 char *\r
1497 StripHighlightAndTitle(s)\r
1498      char *s;\r
1499 {\r
1500     static char retbuf[MSG_SIZ];\r
1501     char *p = retbuf;\r
1502 \r
1503     while (*s != NULLCHAR) {\r
1504         while (*s == '\033') {\r
1505             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1506             if (*s != NULLCHAR) s++;\r
1507         }\r
1508         while (*s != NULLCHAR && *s != '\033') {\r
1509             if (*s == '(' || *s == '[') {\r
1510                 *p = NULLCHAR;\r
1511                 return retbuf;\r
1512             }\r
1513             *p++ = *s++;\r
1514         }\r
1515     }\r
1516     *p = NULLCHAR;\r
1517     return retbuf;\r
1518 }\r
1519 \r
1520 /* Remove all highlighting escape sequences in s */\r
1521 char *\r
1522 StripHighlight(s)\r
1523      char *s;\r
1524 {\r
1525     static char retbuf[MSG_SIZ];\r
1526     char *p = retbuf;\r
1527 \r
1528     while (*s != NULLCHAR) {\r
1529         while (*s == '\033') {\r
1530             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1531             if (*s != NULLCHAR) s++;\r
1532         }\r
1533         while (*s != NULLCHAR && *s != '\033') {\r
1534             *p++ = *s++;\r
1535         }\r
1536     }\r
1537     *p = NULLCHAR;\r
1538     return retbuf;\r
1539 }\r
1540 \r
1541 char *variantNames[] = VARIANT_NAMES;\r
1542 char *\r
1543 VariantName(v)\r
1544      VariantClass v;\r
1545 {\r
1546     return variantNames[v];\r
1547 }\r
1548 \r
1549 \r
1550 /* Identify a variant from the strings the chess servers use or the\r
1551    PGN Variant tag names we use. */\r
1552 VariantClass\r
1553 StringToVariant(e)\r
1554      char *e;\r
1555 {\r
1556     char *p;\r
1557     int wnum = -1;\r
1558     VariantClass v = VariantNormal;\r
1559     int i, found = FALSE;\r
1560     char buf[MSG_SIZ];\r
1561 \r
1562     if (!e) return v;\r
1563 \r
1564     /* [HGM] skip over optional board-size prefixes */\r
1565     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||\r
1566         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
1567         while( *e++ != '_');\r
1568     }\r
1569 \r
1570     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1571       if (StrCaseStr(e, variantNames[i])) {\r
1572         v = (VariantClass) i;\r
1573         found = TRUE;\r
1574         break;\r
1575       }\r
1576     }\r
1577 \r
1578     if (!found) {\r
1579       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1580           || StrCaseStr(e, "wild/fr") \r
1581           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {\r
1582         v = VariantFischeRandom;\r
1583       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1584                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1585         p += i;\r
1586         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1587         if (isdigit(*p)) {\r
1588           wnum = atoi(p);\r
1589         } else {\r
1590           wnum = -1;\r
1591         }\r
1592         switch (wnum) {\r
1593         case 0: /* FICS only, actually */\r
1594         case 1:\r
1595           /* Castling legal even if K starts on d-file */\r
1596           v = VariantWildCastle;\r
1597           break;\r
1598         case 2:\r
1599         case 3:\r
1600         case 4:\r
1601           /* Castling illegal even if K & R happen to start in\r
1602              normal positions. */\r
1603           v = VariantNoCastle;\r
1604           break;\r
1605         case 5:\r
1606         case 7:\r
1607         case 8:\r
1608         case 10:\r
1609         case 11:\r
1610         case 12:\r
1611         case 13:\r
1612         case 14:\r
1613         case 15:\r
1614         case 18:\r
1615         case 19:\r
1616           /* Castling legal iff K & R start in normal positions */\r
1617           v = VariantNormal;\r
1618           break;\r
1619         case 6:\r
1620         case 20:\r
1621         case 21:\r
1622           /* Special wilds for position setup; unclear what to do here */\r
1623           v = VariantLoadable;\r
1624           break;\r
1625         case 9:\r
1626           /* Bizarre ICC game */\r
1627           v = VariantTwoKings;\r
1628           break;\r
1629         case 16:\r
1630           v = VariantKriegspiel;\r
1631           break;\r
1632         case 17:\r
1633           v = VariantLosers;\r
1634           break;\r
1635         case 22:\r
1636           v = VariantFischeRandom;\r
1637           break;\r
1638         case 23:\r
1639           v = VariantCrazyhouse;\r
1640           break;\r
1641         case 24:\r
1642           v = VariantBughouse;\r
1643           break;\r
1644         case 25:\r
1645           v = Variant3Check;\r
1646           break;\r
1647         case 26:\r
1648           /* Not quite the same as FICS suicide! */\r
1649           v = VariantGiveaway;\r
1650           break;\r
1651         case 27:\r
1652           v = VariantAtomic;\r
1653           break;\r
1654         case 28:\r
1655           v = VariantShatranj;\r
1656           break;\r
1657 \r
1658         /* Temporary names for future ICC types.  The name *will* change in \r
1659            the next xboard/WinBoard release after ICC defines it. */\r
1660         case 29:\r
1661           v = Variant29;\r
1662           break;\r
1663         case 30:\r
1664           v = Variant30;\r
1665           break;\r
1666         case 31:\r
1667           v = Variant31;\r
1668           break;\r
1669         case 32:\r
1670           v = Variant32;\r
1671           break;\r
1672         case 33:\r
1673           v = Variant33;\r
1674           break;\r
1675         case 34:\r
1676           v = Variant34;\r
1677           break;\r
1678         case 35:\r
1679           v = Variant35;\r
1680           break;\r
1681         case 36:\r
1682           v = Variant36;\r
1683           break;\r
1684         case 37:\r
1685           v = VariantShogi;\r
1686           break;\r
1687         case 38:\r
1688           v = VariantXiangqi;\r
1689           break;\r
1690         case 39:\r
1691           v = VariantCourier;\r
1692           break;\r
1693         case 40:\r
1694           v = VariantGothic;\r
1695           break;\r
1696         case 41:\r
1697           v = VariantCapablanca;\r
1698           break;\r
1699         case 42:\r
1700           v = VariantKnightmate;\r
1701           break;\r
1702         case 43:\r
1703           v = VariantFairy;\r
1704           break;\r
1705         case 44:\r
1706           v = VariantCylinder;\r
1707           break;\r
1708         case 45:\r
1709           v = VariantFalcon;\r
1710           break;\r
1711         case 46:\r
1712           v = VariantCapaRandom;\r
1713           break;\r
1714         case 47:\r
1715           v = VariantBerolina;\r
1716           break;\r
1717         case 48:\r
1718           v = VariantJanus;\r
1719           break;\r
1720         case 49:\r
1721           v = VariantSuper;\r
1722           break;\r
1723         case 50:\r
1724           v = VariantGreat;\r
1725           break;\r
1726         case -1:\r
1727           /* Found "wild" or "w" in the string but no number;\r
1728              must assume it's normal chess. */\r
1729           v = VariantNormal;\r
1730           break;\r
1731         default:\r
1732           sprintf(buf, _("Unknown wild type %d"), wnum);\r
1733           DisplayError(buf, 0);\r
1734           v = VariantUnknown;\r
1735           break;\r
1736         }\r
1737       }\r
1738     }\r
1739     if (appData.debugMode) {\r
1740       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),\r
1741               e, wnum, VariantName(v));\r
1742     }\r
1743     return v;\r
1744 }\r
1745 \r
1746 static int leftover_start = 0, leftover_len = 0;\r
1747 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1748 \r
1749 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1750    advance *index beyond it, and set leftover_start to the new value of\r
1751    *index; else return FALSE.  If pattern contains the character '*', it\r
1752    matches any sequence of characters not containing '\r', '\n', or the\r
1753    character following the '*' (if any), and the matched sequence(s) are\r
1754    copied into star_match.\r
1755    */\r
1756 int\r
1757 looking_at(buf, index, pattern)\r
1758      char *buf;\r
1759      int *index;\r
1760      char *pattern;\r
1761 {\r
1762     char *bufp = &buf[*index], *patternp = pattern;\r
1763     int star_count = 0;\r
1764     char *matchp = star_match[0];\r
1765     \r
1766     for (;;) {\r
1767         if (*patternp == NULLCHAR) {\r
1768             *index = leftover_start = bufp - buf;\r
1769             *matchp = NULLCHAR;\r
1770             return TRUE;\r
1771         }\r
1772         if (*bufp == NULLCHAR) return FALSE;\r
1773         if (*patternp == '*') {\r
1774             if (*bufp == *(patternp + 1)) {\r
1775                 *matchp = NULLCHAR;\r
1776                 matchp = star_match[++star_count];\r
1777                 patternp += 2;\r
1778                 bufp++;\r
1779                 continue;\r
1780             } else if (*bufp == '\n' || *bufp == '\r') {\r
1781                 patternp++;\r
1782                 if (*patternp == NULLCHAR)\r
1783                   continue;\r
1784                 else\r
1785                   return FALSE;\r
1786             } else {\r
1787                 *matchp++ = *bufp++;\r
1788                 continue;\r
1789             }\r
1790         }\r
1791         if (*patternp != *bufp) return FALSE;\r
1792         patternp++;\r
1793         bufp++;\r
1794     }\r
1795 }\r
1796 \r
1797 void\r
1798 SendToPlayer(data, length)\r
1799      char *data;\r
1800      int length;\r
1801 {\r
1802     int error, outCount;\r
1803     outCount = OutputToProcess(NoProc, data, length, &error);\r
1804     if (outCount < length) {\r
1805         DisplayFatalError(_("Error writing to display"), error, 1);\r
1806     }\r
1807 }\r
1808 \r
1809 void\r
1810 PackHolding(packed, holding)\r
1811      char packed[];\r
1812      char *holding;\r
1813 {\r
1814     char *p = holding;\r
1815     char *q = packed;\r
1816     int runlength = 0;\r
1817     int curr = 9999;\r
1818     do {\r
1819         if (*p == curr) {\r
1820             runlength++;\r
1821         } else {\r
1822             switch (runlength) {\r
1823               case 0:\r
1824                 break;\r
1825               case 1:\r
1826                 *q++ = curr;\r
1827                 break;\r
1828               case 2:\r
1829                 *q++ = curr;\r
1830                 *q++ = curr;\r
1831                 break;\r
1832               default:\r
1833                 sprintf(q, "%d", runlength);\r
1834                 while (*q) q++;\r
1835                 *q++ = curr;\r
1836                 break;\r
1837             }\r
1838             runlength = 1;\r
1839             curr = *p;\r
1840         }\r
1841     } while (*p++);\r
1842     *q = NULLCHAR;\r
1843 }\r
1844 \r
1845 /* Telnet protocol requests from the front end */\r
1846 void\r
1847 TelnetRequest(ddww, option)\r
1848      unsigned char ddww, option;\r
1849 {\r
1850     unsigned char msg[3];\r
1851     int outCount, outError;\r
1852 \r
1853     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1854 \r
1855     if (appData.debugMode) {\r
1856         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1857         switch (ddww) {\r
1858           case TN_DO:\r
1859             ddwwStr = "DO";\r
1860             break;\r
1861           case TN_DONT:\r
1862             ddwwStr = "DONT";\r
1863             break;\r
1864           case TN_WILL:\r
1865             ddwwStr = "WILL";\r
1866             break;\r
1867           case TN_WONT:\r
1868             ddwwStr = "WONT";\r
1869             break;\r
1870           default:\r
1871             ddwwStr = buf1;\r
1872             sprintf(buf1, "%d", ddww);\r
1873             break;\r
1874         }\r
1875         switch (option) {\r
1876           case TN_ECHO:\r
1877             optionStr = "ECHO";\r
1878             break;\r
1879           default:\r
1880             optionStr = buf2;\r
1881             sprintf(buf2, "%d", option);\r
1882             break;\r
1883         }\r
1884         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1885     }\r
1886     msg[0] = TN_IAC;\r
1887     msg[1] = ddww;\r
1888     msg[2] = option;\r
1889     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1890     if (outCount < 3) {\r
1891         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1892     }\r
1893 }\r
1894 \r
1895 void\r
1896 DoEcho()\r
1897 {\r
1898     if (!appData.icsActive) return;\r
1899     TelnetRequest(TN_DO, TN_ECHO);\r
1900 }\r
1901 \r
1902 void\r
1903 DontEcho()\r
1904 {\r
1905     if (!appData.icsActive) return;\r
1906     TelnetRequest(TN_DONT, TN_ECHO);\r
1907 }\r
1908 \r
1909 void\r
1910 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
1911 {\r
1912     /* put the holdings sent to us by the server on the board holdings area */\r
1913     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
1914     char p;\r
1915     ChessSquare piece;\r
1916 \r
1917     if(gameInfo.holdingsWidth < 2)  return;\r
1918 \r
1919     if( (int)lowestPiece >= BlackPawn ) {\r
1920         holdingsColumn = 0;\r
1921         countsColumn = 1;\r
1922         holdingsStartRow = BOARD_HEIGHT-1;\r
1923         direction = -1;\r
1924     } else {\r
1925         holdingsColumn = BOARD_WIDTH-1;\r
1926         countsColumn = BOARD_WIDTH-2;\r
1927         holdingsStartRow = 0;\r
1928         direction = 1;\r
1929     }\r
1930 \r
1931     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
1932         board[i][holdingsColumn] = EmptySquare;\r
1933         board[i][countsColumn]   = (ChessSquare) 0;\r
1934     }\r
1935     while( (p=*holdings++) != NULLCHAR ) {\r
1936         piece = CharToPiece( ToUpper(p) );\r
1937         if(piece == EmptySquare) continue;\r
1938         /*j = (int) piece - (int) WhitePawn;*/\r
1939         j = PieceToNumber(piece);\r
1940         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1941         if(j < 0) continue;               /* should not happen */\r
1942         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );\r
1943         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
1944         board[holdingsStartRow+j*direction][countsColumn]++;\r
1945     }\r
1946 \r
1947 }\r
1948 \r
1949 \r
1950 void\r
1951 VariantSwitch(Board board, VariantClass newVariant)\r
1952 {\r
1953    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;\r
1954    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;\r
1955 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;\r
1956 \r
1957    startedFromPositionFile = FALSE;\r
1958    if(gameInfo.variant == newVariant) return;\r
1959 \r
1960    /* [HGM] This routine is called each time an assignment is made to\r
1961     * gameInfo.variant during a game, to make sure the board sizes\r
1962     * are set to match the new variant. If that means adding or deleting\r
1963     * holdings, we shift the playing board accordingly\r
1964     * This kludge is needed because in ICS observe mode, we get boards\r
1965     * of an ongoing game without knowing the variant, and learn about the\r
1966     * latter only later. This can be because of the move list we requested,\r
1967     * in which case the game history is refilled from the beginning anyway,\r
1968     * but also when receiving holdings of a crazyhouse game. In the latter\r
1969     * case we want to add those holdings to the already received position.\r
1970     */\r
1971 \r
1972 \r
1973   if (appData.debugMode) {\r
1974     fprintf(debugFP, "Switch board from %s to %s\n",\r
1975                VariantName(gameInfo.variant), VariantName(newVariant));\r
1976     setbuf(debugFP, NULL);\r
1977   }\r
1978     shuffleOpenings = 0;       /* [HGM] shuffle */\r
1979     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
1980     switch(newVariant) {\r
1981             case VariantShogi:\r
1982               newWidth = 9;  newHeight = 9;\r
1983               gameInfo.holdingsSize = 7;\r
1984             case VariantBughouse:\r
1985             case VariantCrazyhouse:\r
1986               newHoldingsWidth = 2; break;\r
1987             default:\r
1988               newHoldingsWidth = gameInfo.holdingsSize = 0;\r
1989     }\r
1990 \r
1991     if(newWidth  != gameInfo.boardWidth  ||\r
1992        newHeight != gameInfo.boardHeight ||\r
1993        newHoldingsWidth != gameInfo.holdingsWidth ) {\r
1994 \r
1995         /* shift position to new playing area, if needed */\r
1996         if(newHoldingsWidth > gameInfo.holdingsWidth) {\r
1997            for(i=0; i<BOARD_HEIGHT; i++) \r
1998                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)\r
1999                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
2000                                                      board[i][j];\r
2001            for(i=0; i<newHeight; i++) {\r
2002                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;\r
2003                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;\r
2004            }\r
2005         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {\r
2006            for(i=0; i<BOARD_HEIGHT; i++)\r
2007                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
2008                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
2009                                                  board[i][j];\r
2010         }\r
2011 \r
2012         gameInfo.boardWidth  = newWidth;\r
2013         gameInfo.boardHeight = newHeight;\r
2014         gameInfo.holdingsWidth = newHoldingsWidth;\r
2015         gameInfo.variant = newVariant;\r
2016         InitDrawingSizes(-2, 0);\r
2017 \r
2018         /* [HGM] The following should definitely be solved in a better way */\r
2019 #if 0\r
2020         CopyBoard(board, tempBoard); /* save position in case it is board[0] */\r
2021         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];\r
2022         saveEP = epStatus[0];\r
2023 #endif\r
2024         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */\r
2025 #if 0\r
2026         epStatus[0] = saveEP;\r
2027         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];\r
2028         CopyBoard(tempBoard, board); /* restore position received from ICS   */\r
2029 #endif\r
2030     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }\r
2031 \r
2032     forwardMostMove = oldForwardMostMove;\r
2033     backwardMostMove = oldBackwardMostMove;\r
2034     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */\r
2035 }\r
2036 \r
2037 static int loggedOn = FALSE;\r
2038 \r
2039 /*-- Game start info cache: --*/\r
2040 int gs_gamenum;\r
2041 char gs_kind[MSG_SIZ];\r
2042 static char player1Name[128] = "";\r
2043 static char player2Name[128] = "";\r
2044 static int player1Rating = -1;\r
2045 static int player2Rating = -1;\r
2046 /*----------------------------*/\r
2047 \r
2048 ColorClass curColor = ColorNormal;\r
2049 int suppressKibitz = 0;\r
2050 \r
2051 void\r
2052 read_from_ics(isr, closure, data, count, error)\r
2053      InputSourceRef isr;\r
2054      VOIDSTAR closure;\r
2055      char *data;\r
2056      int count;\r
2057      int error;\r
2058 {\r
2059 #define BUF_SIZE 8192\r
2060 #define STARTED_NONE 0\r
2061 #define STARTED_MOVES 1\r
2062 #define STARTED_BOARD 2\r
2063 #define STARTED_OBSERVE 3\r
2064 #define STARTED_HOLDINGS 4\r
2065 #define STARTED_CHATTER 5\r
2066 #define STARTED_COMMENT 6\r
2067 #define STARTED_MOVES_NOHIDE 7\r
2068     \r
2069     static int started = STARTED_NONE;\r
2070     static char parse[20000];\r
2071     static int parse_pos = 0;\r
2072     static char buf[BUF_SIZE + 1];\r
2073     static int firstTime = TRUE, intfSet = FALSE;\r
2074     static ColorClass prevColor = ColorNormal;\r
2075     static int savingComment = FALSE;\r
2076     char str[500];\r
2077     int i, oldi;\r
2078     int buf_len;\r
2079     int next_out;\r
2080     int tkind;\r
2081     int backup;    /* [DM] For zippy color lines */\r
2082     char *p;\r
2083 \r
2084     if (appData.debugMode) {\r
2085       if (!error) {\r
2086         fprintf(debugFP, "<ICS: ");\r
2087         show_bytes(debugFP, data, count);\r
2088         fprintf(debugFP, "\n");\r
2089       }\r
2090     }\r
2091 \r
2092     if (appData.debugMode) { int f = forwardMostMove;\r
2093         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,\r
2094                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
2095     }\r
2096     if (count > 0) {\r
2097         /* If last read ended with a partial line that we couldn't parse,\r
2098            prepend it to the new read and try again. */\r
2099         if (leftover_len > 0) {\r
2100             for (i=0; i<leftover_len; i++)\r
2101               buf[i] = buf[leftover_start + i];\r
2102         }\r
2103 \r
2104         /* Copy in new characters, removing nulls and \r's */\r
2105         buf_len = leftover_len;\r
2106         for (i = 0; i < count; i++) {\r
2107             if (data[i] != NULLCHAR && data[i] != '\r')\r
2108               buf[buf_len++] = data[i];\r
2109             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && \r
2110                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') \r
2111                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous\r
2112         }\r
2113 \r
2114         buf[buf_len] = NULLCHAR;\r
2115         next_out = leftover_len;\r
2116         leftover_start = 0;\r
2117         \r
2118         i = 0;\r
2119         while (i < buf_len) {\r
2120             /* Deal with part of the TELNET option negotiation\r
2121                protocol.  We refuse to do anything beyond the\r
2122                defaults, except that we allow the WILL ECHO option,\r
2123                which ICS uses to turn off password echoing when we are\r
2124                directly connected to it.  We reject this option\r
2125                if localLineEditing mode is on (always on in xboard)\r
2126                and we are talking to port 23, which might be a real\r
2127                telnet server that will try to keep WILL ECHO on permanently.\r
2128              */\r
2129             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
2130                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
2131                 unsigned char option;\r
2132                 oldi = i;\r
2133                 switch ((unsigned char) buf[++i]) {\r
2134                   case TN_WILL:\r
2135                     if (appData.debugMode)\r
2136                       fprintf(debugFP, "\n<WILL ");\r
2137                     switch (option = (unsigned char) buf[++i]) {\r
2138                       case TN_ECHO:\r
2139                         if (appData.debugMode)\r
2140                           fprintf(debugFP, "ECHO ");\r
2141                         /* Reply only if this is a change, according\r
2142                            to the protocol rules. */\r
2143                         if (remoteEchoOption) break;\r
2144                         if (appData.localLineEditing &&\r
2145                             atoi(appData.icsPort) == TN_PORT) {\r
2146                             TelnetRequest(TN_DONT, TN_ECHO);\r
2147                         } else {\r
2148                             EchoOff();\r
2149                             TelnetRequest(TN_DO, TN_ECHO);\r
2150                             remoteEchoOption = TRUE;\r
2151                         }\r
2152                         break;\r
2153                       default:\r
2154                         if (appData.debugMode)\r
2155                           fprintf(debugFP, "%d ", option);\r
2156                         /* Whatever this is, we don't want it. */\r
2157                         TelnetRequest(TN_DONT, option);\r
2158                         break;\r
2159                     }\r
2160                     break;\r
2161                   case TN_WONT:\r
2162                     if (appData.debugMode)\r
2163                       fprintf(debugFP, "\n<WONT ");\r
2164                     switch (option = (unsigned char) buf[++i]) {\r
2165                       case TN_ECHO:\r
2166                         if (appData.debugMode)\r
2167                           fprintf(debugFP, "ECHO ");\r
2168                         /* Reply only if this is a change, according\r
2169                            to the protocol rules. */\r
2170                         if (!remoteEchoOption) break;\r
2171                         EchoOn();\r
2172                         TelnetRequest(TN_DONT, TN_ECHO);\r
2173                         remoteEchoOption = FALSE;\r
2174                         break;\r
2175                       default:\r
2176                         if (appData.debugMode)\r
2177                           fprintf(debugFP, "%d ", (unsigned char) option);\r
2178                         /* Whatever this is, it must already be turned\r
2179                            off, because we never agree to turn on\r
2180                            anything non-default, so according to the\r
2181                            protocol rules, we don't reply. */\r
2182                         break;\r
2183                     }\r
2184                     break;\r
2185                   case TN_DO:\r
2186                     if (appData.debugMode)\r
2187                       fprintf(debugFP, "\n<DO ");\r
2188                     switch (option = (unsigned char) buf[++i]) {\r
2189                       default:\r
2190                         /* Whatever this is, we refuse to do it. */\r
2191                         if (appData.debugMode)\r
2192                           fprintf(debugFP, "%d ", option);\r
2193                         TelnetRequest(TN_WONT, option);\r
2194                         break;\r
2195                     }\r
2196                     break;\r
2197                   case TN_DONT:\r
2198                     if (appData.debugMode)\r
2199                       fprintf(debugFP, "\n<DONT ");\r
2200                     switch (option = (unsigned char) buf[++i]) {\r
2201                       default:\r
2202                         if (appData.debugMode)\r
2203                           fprintf(debugFP, "%d ", option);\r
2204                         /* Whatever this is, we are already not doing\r
2205                            it, because we never agree to do anything\r
2206                            non-default, so according to the protocol\r
2207                            rules, we don't reply. */\r
2208                         break;\r
2209                     }\r
2210                     break;\r
2211                   case TN_IAC:\r
2212                     if (appData.debugMode)\r
2213                       fprintf(debugFP, "\n<IAC ");\r
2214                     /* Doubled IAC; pass it through */\r
2215                     i--;\r
2216                     break;\r
2217                   default:\r
2218                     if (appData.debugMode)\r
2219                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
2220                     /* Drop all other telnet commands on the floor */\r
2221                     break;\r
2222                 }\r
2223                 if (oldi > next_out)\r
2224                   SendToPlayer(&buf[next_out], oldi - next_out);\r
2225                 if (++i > next_out)\r
2226                   next_out = i;\r
2227                 continue;\r
2228             }\r
2229                 \r
2230             /* OK, this at least will *usually* work */\r
2231             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
2232                 loggedOn = TRUE;\r
2233             }\r
2234             \r
2235             if (loggedOn && !intfSet) {\r
2236                 if (ics_type == ICS_ICC) {\r
2237                   sprintf(str,\r
2238                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
2239                           programVersion);\r
2240 \r
2241                 } else if (ics_type == ICS_CHESSNET) {\r
2242                   sprintf(str, "/style 12\n");\r
2243                 } else {\r
2244                   strcpy(str, "alias $ @\n$set interface ");\r
2245                   strcat(str, programVersion);\r
2246                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
2247 #ifdef WIN32\r
2248                   strcat(str, "$iset nohighlight 1\n");\r
2249 #endif\r
2250                   strcat(str, "$iset lock 1\n$style 12\n");\r
2251                 }\r
2252                 SendToICS(str);\r
2253                 intfSet = TRUE;\r
2254             }\r
2255 \r
2256             if (started == STARTED_COMMENT) {\r
2257                 /* Accumulate characters in comment */\r
2258                 parse[parse_pos++] = buf[i];\r
2259                 if (buf[i] == '\n') {\r
2260                     parse[parse_pos] = NULLCHAR;\r
2261                     if(!suppressKibitz) // [HGM] kibitz\r
2262                         AppendComment(forwardMostMove, StripHighlight(parse));\r
2263                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window\r
2264                         int nrDigit = 0, nrAlph = 0, i;\r
2265                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input\r
2266                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }\r
2267                         parse[parse_pos] = NULLCHAR;\r
2268                         // try to be smart: if it does not look like search info, it should go to\r
2269                         // ICS interaction window after all, not to engine-output window.\r
2270                         for(i=0; i<parse_pos; i++) { // count letters and digits\r
2271                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');\r
2272                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');\r
2273                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');\r
2274                         }\r
2275                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info\r
2276                             OutputKibitz(suppressKibitz, parse);\r
2277                         } else {\r
2278                             char tmp[MSG_SIZ];\r
2279                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);\r
2280                             SendToPlayer(tmp, strlen(tmp));\r
2281                         }\r
2282                     }\r
2283                     started = STARTED_NONE;\r
2284                 } else {\r
2285                     /* Don't match patterns against characters in chatter */\r
2286                     i++;\r
2287                     continue;\r
2288                 }\r
2289             }\r
2290             if (started == STARTED_CHATTER) {\r
2291                 if (buf[i] != '\n') {\r
2292                     /* Don't match patterns against characters in chatter */\r
2293                     i++;\r
2294                     continue;\r
2295                 }\r
2296                 started = STARTED_NONE;\r
2297             }\r
2298 \r
2299             /* Kludge to deal with rcmd protocol */\r
2300             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2301                 DisplayFatalError(&buf[1], 0, 1);\r
2302                 continue;\r
2303             } else {\r
2304                 firstTime = FALSE;\r
2305             }\r
2306 \r
2307             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2308                 ics_type = ICS_ICC;\r
2309                 ics_prefix = "/";\r
2310                 if (appData.debugMode)\r
2311                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2312                 continue;\r
2313             }\r
2314             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2315                 ics_type = ICS_FICS;\r
2316                 ics_prefix = "$";\r
2317                 if (appData.debugMode)\r
2318                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2319                 continue;\r
2320             }\r
2321             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2322                 ics_type = ICS_CHESSNET;\r
2323                 ics_prefix = "/";\r
2324                 if (appData.debugMode)\r
2325                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2326                 continue;\r
2327             }\r
2328 \r
2329             if (!loggedOn &&\r
2330                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2331                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2332                  looking_at(buf, &i, "will be \"*\""))) {\r
2333               strcpy(ics_handle, star_match[0]);\r
2334               continue;\r
2335             }\r
2336 \r
2337             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2338               char buf[MSG_SIZ];\r
2339               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2340               DisplayIcsInteractionTitle(buf);\r
2341               have_set_title = TRUE;\r
2342             }\r
2343 \r
2344             /* skip finger notes */\r
2345             if (started == STARTED_NONE &&\r
2346                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2347                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2348                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2349               started = STARTED_CHATTER;\r
2350               i += 3;\r
2351               continue;\r
2352             }\r
2353 \r
2354             /* skip formula vars */\r
2355             if (started == STARTED_NONE &&\r
2356                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2357               started = STARTED_CHATTER;\r
2358               i += 3;\r
2359               continue;\r
2360             }\r
2361 \r
2362             oldi = i;\r
2363             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window\r
2364             if (appData.autoKibitz && started == STARTED_NONE && \r
2365                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze\r
2366                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {\r
2367                 if(looking_at(buf, &i, "* kibitzes: ") &&\r
2368                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || \r
2369                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent\r
2370                         suppressKibitz = TRUE;\r
2371                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]\r
2372                                 && (gameMode == IcsPlayingWhite)) ||\r
2373                            (StrStr(star_match[0], gameInfo.black) == star_match[0]\r
2374                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz\r
2375                             started = STARTED_CHATTER; // own kibitz we simply discard\r
2376                         else {\r
2377                             started = STARTED_COMMENT; // make sure it will be collected in parse[]\r
2378                             parse_pos = 0; parse[0] = NULLCHAR;\r
2379                             savingComment = TRUE;\r
2380                             suppressKibitz = gameMode != IcsObserving ? 2 :\r
2381                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;\r
2382                         } \r
2383                         continue;\r
2384                 } else\r
2385                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz\r
2386                     started = STARTED_CHATTER;\r
2387                     suppressKibitz = TRUE;\r
2388                 }\r
2389             } // [HGM] kibitz: end of patch\r
2390 \r
2391             if (appData.zippyTalk || appData.zippyPlay) {\r
2392                 /* [DM] Backup address for color zippy lines */\r
2393                 backup = i;\r
2394 #if ZIPPY\r
2395        #ifdef WIN32\r
2396                if (loggedOn == TRUE)\r
2397                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||\r
2398                           (appData.zippyPlay && ZippyMatch(buf, &backup)));\r
2399        #else\r
2400                 if (ZippyControl(buf, &i) ||\r
2401                     ZippyConverse(buf, &i) ||\r
2402                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2403                       loggedOn = TRUE;\r
2404                       if (!appData.colorize) continue;\r
2405                 }\r
2406        #endif\r
2407 #endif\r
2408             } // [DM] 'else { ' deleted\r
2409                 if (/* Don't color "message" or "messages" output */\r
2410                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2411                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2412                     looking_at(buf, &i, "--* (*:*): ") ||\r
2413                     /* Regular tells and says */\r
2414                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2415                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2416                     looking_at(buf, &i, "* says: ") ||\r
2417                     /* Message notifications (same color as tells) */\r
2418                     looking_at(buf, &i, "* has left a message ") ||\r
2419                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2420                     /* Whispers and kibitzes */\r
2421                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2422                     looking_at(buf, &i, "* kibitzes: ") ||\r
2423                     /* Channel tells */\r
2424                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2425 \r
2426                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2427                       /* Avoid "tells you:" spoofs in channels */\r
2428                      tkind = 3;\r
2429                   }\r
2430                   if (star_match[0][0] == NULLCHAR ||\r
2431                       strchr(star_match[0], ' ') ||\r
2432                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2433                     /* Reject bogus matches */\r
2434                     i = oldi;\r
2435                   } else {\r
2436                     if (appData.colorize) {\r
2437                       if (oldi > next_out) {\r
2438                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2439                         next_out = oldi;\r
2440                       }\r
2441                       switch (tkind) {\r
2442                       case 1:\r
2443                         Colorize(ColorTell, FALSE);\r
2444                         curColor = ColorTell;\r
2445                         break;\r
2446                       case 2:\r
2447                         Colorize(ColorKibitz, FALSE);\r
2448                         curColor = ColorKibitz;\r
2449                         break;\r
2450                       case 3:\r
2451                         p = strrchr(star_match[1], '(');\r
2452                         if (p == NULL) {\r
2453                           p = star_match[1];\r
2454                         } else {\r
2455                           p++;\r
2456                         }\r
2457                         if (atoi(p) == 1) {\r
2458                           Colorize(ColorChannel1, FALSE);\r
2459                           curColor = ColorChannel1;\r
2460                         } else {\r
2461                           Colorize(ColorChannel, FALSE);\r
2462                           curColor = ColorChannel;\r
2463                         }\r
2464                         break;\r
2465                       case 5:\r
2466                         curColor = ColorNormal;\r
2467                         break;\r
2468                       }\r
2469                     }\r
2470                     if (started == STARTED_NONE && appData.autoComment &&\r
2471                         (gameMode == IcsObserving ||\r
2472                          gameMode == IcsPlayingWhite ||\r
2473                          gameMode == IcsPlayingBlack)) {\r
2474                       parse_pos = i - oldi;\r
2475                       memcpy(parse, &buf[oldi], parse_pos);\r
2476                       parse[parse_pos] = NULLCHAR;\r
2477                       started = STARTED_COMMENT;\r
2478                       savingComment = TRUE;\r
2479                     } else {\r
2480                       started = STARTED_CHATTER;\r
2481                       savingComment = FALSE;\r
2482                     }\r
2483                     loggedOn = TRUE;\r
2484                     continue;\r
2485                   }\r
2486                 }\r
2487 \r
2488                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2489                     looking_at(buf, &i, "* c-shouts: ")) {\r
2490                     if (appData.colorize) {\r
2491                         if (oldi > next_out) {\r
2492                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2493                             next_out = oldi;\r
2494                         }\r
2495                         Colorize(ColorSShout, FALSE);\r
2496                         curColor = ColorSShout;\r
2497                     }\r
2498                     loggedOn = TRUE;\r
2499                     started = STARTED_CHATTER;\r
2500                     continue;\r
2501                 }\r
2502 \r
2503                 if (looking_at(buf, &i, "--->")) {\r
2504                     loggedOn = TRUE;\r
2505                     continue;\r
2506                 }\r
2507 \r
2508                 if (looking_at(buf, &i, "* shouts: ") ||\r
2509                     looking_at(buf, &i, "--> ")) {\r
2510                     if (appData.colorize) {\r
2511                         if (oldi > next_out) {\r
2512                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2513                             next_out = oldi;\r
2514                         }\r
2515                         Colorize(ColorShout, FALSE);\r
2516                         curColor = ColorShout;\r
2517                     }\r
2518                     loggedOn = TRUE;\r
2519                     started = STARTED_CHATTER;\r
2520                     continue;\r
2521                 }\r
2522 \r
2523                 if (looking_at( buf, &i, "Challenge:")) {\r
2524                     if (appData.colorize) {\r
2525                         if (oldi > next_out) {\r
2526                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2527                             next_out = oldi;\r
2528                         }\r
2529                         Colorize(ColorChallenge, FALSE);\r
2530                         curColor = ColorChallenge;\r
2531                     }\r
2532                     loggedOn = TRUE;\r
2533                     continue;\r
2534                 }\r
2535 \r
2536                 if (looking_at(buf, &i, "* offers you") ||\r
2537                     looking_at(buf, &i, "* offers to be") ||\r
2538                     looking_at(buf, &i, "* would like to") ||\r
2539                     looking_at(buf, &i, "* requests to") ||\r
2540                     looking_at(buf, &i, "Your opponent offers") ||\r
2541                     looking_at(buf, &i, "Your opponent requests")) {\r
2542 \r
2543                     if (appData.colorize) {\r
2544                         if (oldi > next_out) {\r
2545                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2546                             next_out = oldi;\r
2547                         }\r
2548                         Colorize(ColorRequest, FALSE);\r
2549                         curColor = ColorRequest;\r
2550                     }\r
2551                     continue;\r
2552                 }\r
2553 \r
2554                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2555                     if (appData.colorize) {\r
2556                         if (oldi > next_out) {\r
2557                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2558                             next_out = oldi;\r
2559                         }\r
2560                         Colorize(ColorSeek, FALSE);\r
2561                         curColor = ColorSeek;\r
2562                     }\r
2563                     continue;\r
2564             }\r
2565 \r
2566             if (looking_at(buf, &i, "\\   ")) {\r
2567                 if (prevColor != ColorNormal) {\r
2568                     if (oldi > next_out) {\r
2569                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2570                         next_out = oldi;\r
2571                     }\r
2572                     Colorize(prevColor, TRUE);\r
2573                     curColor = prevColor;\r
2574                 }\r
2575                 if (savingComment) {\r
2576                     parse_pos = i - oldi;\r
2577                     memcpy(parse, &buf[oldi], parse_pos);\r
2578                     parse[parse_pos] = NULLCHAR;\r
2579                     started = STARTED_COMMENT;\r
2580                 } else {\r
2581                     started = STARTED_CHATTER;\r
2582                 }\r
2583                 continue;\r
2584             }\r
2585 \r
2586             if (looking_at(buf, &i, "Black Strength :") ||\r
2587                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2588                 looking_at(buf, &i, "<10>") ||\r
2589                 looking_at(buf, &i, "#@#")) {\r
2590                 /* Wrong board style */\r
2591                 loggedOn = TRUE;\r
2592                 SendToICS(ics_prefix);\r
2593                 SendToICS("set style 12\n");\r
2594                 SendToICS(ics_prefix);\r
2595                 SendToICS("refresh\n");\r
2596                 continue;\r
2597             }\r
2598             \r
2599             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2600                 ICSInitScript();\r
2601                 have_sent_ICS_logon = 1;\r
2602                 continue;\r
2603             }\r
2604               \r
2605             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2606                 (looking_at(buf, &i, "\n<12> ") ||\r
2607                  looking_at(buf, &i, "<12> "))) {\r
2608                 loggedOn = TRUE;\r
2609                 if (oldi > next_out) {\r
2610                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2611                 }\r
2612                 next_out = i;\r
2613                 started = STARTED_BOARD;\r
2614                 parse_pos = 0;\r
2615                 continue;\r
2616             }\r
2617 \r
2618             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2619                 looking_at(buf, &i, "<b1> ")) {\r
2620                 if (oldi > next_out) {\r
2621                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2622                 }\r
2623                 next_out = i;\r
2624                 started = STARTED_HOLDINGS;\r
2625                 parse_pos = 0;\r
2626                 continue;\r
2627             }\r
2628 \r
2629             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2630                 loggedOn = TRUE;\r
2631                 /* Header for a move list -- first line */\r
2632 \r
2633                 switch (ics_getting_history) {\r
2634                   case H_FALSE:\r
2635                     switch (gameMode) {\r
2636                       case IcsIdle:\r
2637                       case BeginningOfGame:\r
2638                         /* User typed "moves" or "oldmoves" while we\r
2639                            were idle.  Pretend we asked for these\r
2640                            moves and soak them up so user can step\r
2641                            through them and/or save them.\r
2642                            */\r
2643                         Reset(FALSE, TRUE);\r
2644                         gameMode = IcsObserving;\r
2645                         ModeHighlight();\r
2646                         ics_gamenum = -1;\r
2647                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2648                         break;\r
2649                       case EditGame: /*?*/\r
2650                       case EditPosition: /*?*/\r
2651                         /* Should above feature work in these modes too? */\r
2652                         /* For now it doesn't */\r
2653                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2654                         break;\r
2655                       default:\r
2656                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2657                         break;\r
2658                     }\r
2659                     break;\r
2660                   case H_REQUESTED:\r
2661                     /* Is this the right one? */\r
2662                     if (gameInfo.white && gameInfo.black &&\r
2663                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2664                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2665                         /* All is well */\r
2666                         ics_getting_history = H_GOT_REQ_HEADER;\r
2667                     }\r
2668                     break;\r
2669                   case H_GOT_REQ_HEADER:\r
2670                   case H_GOT_UNREQ_HEADER:\r
2671                   case H_GOT_UNWANTED_HEADER:\r
2672                   case H_GETTING_MOVES:\r
2673                     /* Should not happen */\r
2674                     DisplayError(_("Error gathering move list: two headers"), 0);\r
2675                     ics_getting_history = H_FALSE;\r
2676                     break;\r
2677                 }\r
2678 \r
2679                 /* Save player ratings into gameInfo if needed */\r
2680                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2681                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2682                     (gameInfo.whiteRating == -1 ||\r
2683                      gameInfo.blackRating == -1)) {\r
2684 \r
2685                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2686                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2687                     if (appData.debugMode)\r
2688                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), \r
2689                               gameInfo.whiteRating, gameInfo.blackRating);\r
2690                 }\r
2691                 continue;\r
2692             }\r
2693 \r
2694             if (looking_at(buf, &i,\r
2695               "* * match, initial time: * minute*, increment: * second")) {\r
2696                 /* Header for a move list -- second line */\r
2697                 /* Initial board will follow if this is a wild game */\r
2698                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2699                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2700                 gameInfo.event = StrSave(str);\r
2701                 /* [HGM] we switched variant. Translate boards if needed. */\r
2702                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2703                 continue;\r
2704             }\r
2705 \r
2706             if (looking_at(buf, &i, "Move  ")) {\r
2707                 /* Beginning of a move list */\r
2708                 switch (ics_getting_history) {\r
2709                   case H_FALSE:\r
2710                     /* Normally should not happen */\r
2711                     /* Maybe user hit reset while we were parsing */\r
2712                     break;\r
2713                   case H_REQUESTED:\r
2714                     /* Happens if we are ignoring a move list that is not\r
2715                      * the one we just requested.  Common if the user\r
2716                      * tries to observe two games without turning off\r
2717                      * getMoveList */\r
2718                     break;\r
2719                   case H_GETTING_MOVES:\r
2720                     /* Should not happen */\r
2721                     DisplayError(_("Error gathering move list: nested"), 0);\r
2722                     ics_getting_history = H_FALSE;\r
2723                     break;\r
2724                   case H_GOT_REQ_HEADER:\r
2725                     ics_getting_history = H_GETTING_MOVES;\r
2726                     started = STARTED_MOVES;\r
2727                     parse_pos = 0;\r
2728                     if (oldi > next_out) {\r
2729                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2730                     }\r
2731                     break;\r
2732                   case H_GOT_UNREQ_HEADER:\r
2733                     ics_getting_history = H_GETTING_MOVES;\r
2734                     started = STARTED_MOVES_NOHIDE;\r
2735                     parse_pos = 0;\r
2736                     break;\r
2737                   case H_GOT_UNWANTED_HEADER:\r
2738                     ics_getting_history = H_FALSE;\r
2739                     break;\r
2740                 }\r
2741                 continue;\r
2742             }                           \r
2743             \r
2744             if (looking_at(buf, &i, "% ") ||\r
2745                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2746                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book\r
2747                 savingComment = FALSE;\r
2748                 switch (started) {\r
2749                   case STARTED_MOVES:\r
2750                   case STARTED_MOVES_NOHIDE:\r
2751                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2752                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2753                     ParseGameHistory(parse);\r
2754 #if ZIPPY\r
2755                     if (appData.zippyPlay && first.initDone) {\r
2756                         FeedMovesToProgram(&first, forwardMostMove);\r
2757                         if (gameMode == IcsPlayingWhite) {\r
2758                             if (WhiteOnMove(forwardMostMove)) {\r
2759                                 if (first.sendTime) {\r
2760                                   if (first.useColors) {\r
2761                                     SendToProgram("black\n", &first); \r
2762                                   }\r
2763                                   SendTimeRemaining(&first, TRUE);\r
2764                                 }\r
2765 #if 0\r
2766                                 if (first.useColors) {\r
2767                                   SendToProgram("white\ngo\n", &first);\r
2768                                 } else {\r
2769                                   SendToProgram("go\n", &first);\r
2770                                 }\r
2771 #else\r
2772                                 if (first.useColors) {\r
2773                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent\r
2774                                 }\r
2775                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos\r
2776 #endif\r
2777                                 first.maybeThinking = TRUE;\r
2778                             } else {\r
2779                                 if (first.usePlayother) {\r
2780                                   if (first.sendTime) {\r
2781                                     SendTimeRemaining(&first, TRUE);\r
2782                                   }\r
2783                                   SendToProgram("playother\n", &first);\r
2784                                   firstMove = FALSE;\r
2785                                 } else {\r
2786                                   firstMove = TRUE;\r
2787                                 }\r
2788                             }\r
2789                         } else if (gameMode == IcsPlayingBlack) {\r
2790                             if (!WhiteOnMove(forwardMostMove)) {\r
2791                                 if (first.sendTime) {\r
2792                                   if (first.useColors) {\r
2793                                     SendToProgram("white\n", &first);\r
2794                                   }\r
2795                                   SendTimeRemaining(&first, FALSE);\r
2796                                 }\r
2797 #if 0\r
2798                                 if (first.useColors) {\r
2799                                   SendToProgram("black\ngo\n", &first);\r
2800                                 } else {\r
2801                                   SendToProgram("go\n", &first);\r
2802                                 }\r
2803 #else\r
2804                                 if (first.useColors) {\r
2805                                   SendToProgram("black\n", &first);\r
2806                                 }\r
2807                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);\r
2808 #endif\r
2809                                 first.maybeThinking = TRUE;\r
2810                             } else {\r
2811                                 if (first.usePlayother) {\r
2812                                   if (first.sendTime) {\r
2813                                     SendTimeRemaining(&first, FALSE);\r
2814                                   }\r
2815                                   SendToProgram("playother\n", &first);\r
2816                                   firstMove = FALSE;\r
2817                                 } else {\r
2818                                   firstMove = TRUE;\r
2819                                 }\r
2820                             }\r
2821                         }                       \r
2822                     }\r
2823 #endif\r
2824                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2825                         /* Moves came from oldmoves or moves command\r
2826                            while we weren't doing anything else.\r
2827                            */\r
2828                         currentMove = forwardMostMove;\r
2829                         ClearHighlights();/*!!could figure this out*/\r
2830                         flipView = appData.flipView;\r
2831                         DrawPosition(FALSE, boards[currentMove]);\r
2832                         DisplayBothClocks();\r
2833                         sprintf(str, "%s vs. %s",\r
2834                                 gameInfo.white, gameInfo.black);\r
2835                         DisplayTitle(str);\r
2836                         gameMode = IcsIdle;\r
2837                     } else {\r
2838                         /* Moves were history of an active game */\r
2839                         if (gameInfo.resultDetails != NULL) {\r
2840                             free(gameInfo.resultDetails);\r
2841                             gameInfo.resultDetails = NULL;\r
2842                         }\r
2843                     }\r
2844                     HistorySet(parseList, backwardMostMove,\r
2845                                forwardMostMove, currentMove-1);\r
2846                     DisplayMove(currentMove - 1);\r
2847                     if (started == STARTED_MOVES) next_out = i;\r
2848                     started = STARTED_NONE;\r
2849                     ics_getting_history = H_FALSE;\r
2850                     break;\r
2851 \r
2852                   case STARTED_OBSERVE:\r
2853                     started = STARTED_NONE;\r
2854                     SendToICS(ics_prefix);\r
2855                     SendToICS("refresh\n");\r
2856                     break;\r
2857 \r
2858                   default:\r
2859                     break;\r
2860                 }\r
2861                 if(bookHit) { // [HGM] book: simulate book reply\r
2862                     static char bookMove[MSG_SIZ]; // a bit generous?\r
2863 \r
2864                     programStats.nodes = programStats.depth = programStats.time = \r
2865                     programStats.score = programStats.got_only_move = 0;\r
2866                     sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
2867 \r
2868                     strcpy(bookMove, "move ");\r
2869                     strcat(bookMove, bookHit);\r
2870                     HandleMachineMove(bookMove, &first);\r
2871                 }\r
2872                 continue;\r
2873             }\r
2874             \r
2875             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2876                  started == STARTED_HOLDINGS ||\r
2877                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2878                 /* Accumulate characters in move list or board */\r
2879                 parse[parse_pos++] = buf[i];\r
2880             }\r
2881             \r
2882             /* Start of game messages.  Mostly we detect start of game\r
2883                when the first board image arrives.  On some versions\r
2884                of the ICS, though, we need to do a "refresh" after starting\r
2885                to observe in order to get the current board right away. */\r
2886             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2887                 started = STARTED_OBSERVE;\r
2888                 continue;\r
2889             }\r
2890 \r
2891             /* Handle auto-observe */\r
2892             if (appData.autoObserve &&\r
2893                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2894                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2895                 char *player;\r
2896                 /* Choose the player that was highlighted, if any. */\r
2897                 if (star_match[0][0] == '\033' ||\r
2898                     star_match[1][0] != '\033') {\r
2899                     player = star_match[0];\r
2900                 } else {\r
2901                     player = star_match[2];\r
2902                 }\r
2903                 sprintf(str, "%sobserve %s\n",\r
2904                         ics_prefix, StripHighlightAndTitle(player));\r
2905                 SendToICS(str);\r
2906 \r
2907                 /* Save ratings from notify string */\r
2908                 strcpy(player1Name, star_match[0]);\r
2909                 player1Rating = string_to_rating(star_match[1]);\r
2910                 strcpy(player2Name, star_match[2]);\r
2911                 player2Rating = string_to_rating(star_match[3]);\r
2912 \r
2913                 if (appData.debugMode)\r
2914                   fprintf(debugFP, \r
2915                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2916                           player1Name, player1Rating,\r
2917                           player2Name, player2Rating);\r
2918 \r
2919                 continue;\r
2920             }\r
2921 \r
2922             /* Deal with automatic examine mode after a game,\r
2923                and with IcsObserving -> IcsExamining transition */\r
2924             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2925                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2926 \r
2927                 int gamenum = atoi(star_match[0]);\r
2928                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2929                     gamenum == ics_gamenum) {\r
2930                     /* We were already playing or observing this game;\r
2931                        no need to refetch history */\r
2932                     gameMode = IcsExamining;\r
2933                     if (pausing) {\r
2934                         pauseExamForwardMostMove = forwardMostMove;\r
2935                     } else if (currentMove < forwardMostMove) {\r
2936                         ForwardInner(forwardMostMove);\r
2937                     }\r
2938                 } else {\r
2939                     /* I don't think this case really can happen */\r
2940                     SendToICS(ics_prefix);\r
2941                     SendToICS("refresh\n");\r
2942                 }\r
2943                 continue;\r
2944             }    \r
2945             \r
2946             /* Error messages */\r
2947             if (ics_user_moved) {\r
2948                 if (looking_at(buf, &i, "Illegal move") ||\r
2949                     looking_at(buf, &i, "Not a legal move") ||\r
2950                     looking_at(buf, &i, "Your king is in check") ||\r
2951                     looking_at(buf, &i, "It isn't your turn") ||\r
2952                     looking_at(buf, &i, "It is not your move")) {\r
2953                     /* Illegal move */\r
2954                     ics_user_moved = 0;\r
2955                     if (forwardMostMove > backwardMostMove) {\r
2956                         currentMove = --forwardMostMove;\r
2957                         DisplayMove(currentMove - 1); /* before DMError */\r
2958                         DisplayMoveError(_("Illegal move (rejected by ICS)"));\r
2959                         DrawPosition(FALSE, boards[currentMove]);\r
2960                         SwitchClocks();\r
2961                         DisplayBothClocks();\r
2962                     }\r
2963                     continue;\r
2964                 }\r
2965             }\r
2966 \r
2967             if (looking_at(buf, &i, "still have time") ||\r
2968                 looking_at(buf, &i, "not out of time") ||\r
2969                 looking_at(buf, &i, "either player is out of time") ||\r
2970                 looking_at(buf, &i, "has timeseal; checking")) {\r
2971                 /* We must have called his flag a little too soon */\r
2972                 whiteFlag = blackFlag = FALSE;\r
2973                 continue;\r
2974             }\r
2975 \r
2976             if (looking_at(buf, &i, "added * seconds to") ||\r
2977                 looking_at(buf, &i, "seconds were added to")) {\r
2978                 /* Update the clocks */\r
2979                 SendToICS(ics_prefix);\r
2980                 SendToICS("refresh\n");\r
2981                 continue;\r
2982             }\r
2983 \r
2984             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2985                 ics_clock_paused = TRUE;\r
2986                 StopClocks();\r
2987                 continue;\r
2988             }\r
2989 \r
2990             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2991                 ics_clock_paused = FALSE;\r
2992                 StartClocks();\r
2993                 continue;\r
2994             }\r
2995 \r
2996             /* Grab player ratings from the Creating: message.\r
2997                Note we have to check for the special case when\r
2998                the ICS inserts things like [white] or [black]. */\r
2999             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
3000                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
3001                 /* star_matches:\r
3002                    0    player 1 name (not necessarily white)\r
3003                    1    player 1 rating\r
3004                    2    empty, white, or black (IGNORED)\r
3005                    3    player 2 name (not necessarily black)\r
3006                    4    player 2 rating\r
3007                    \r
3008                    The names/ratings are sorted out when the game\r
3009                    actually starts (below).\r
3010                 */\r
3011                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
3012                 player1Rating = string_to_rating(star_match[1]);\r
3013                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
3014                 player2Rating = string_to_rating(star_match[4]);\r
3015 \r
3016                 if (appData.debugMode)\r
3017                   fprintf(debugFP, \r
3018                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
3019                           player1Name, player1Rating,\r
3020                           player2Name, player2Rating);\r
3021 \r
3022                 continue;\r
3023             }\r
3024             \r
3025             /* Improved generic start/end-of-game messages */\r
3026             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
3027                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
3028                 /* If tkind == 0: */\r
3029                 /* star_match[0] is the game number */\r
3030                 /*           [1] is the white player's name */\r
3031                 /*           [2] is the black player's name */\r
3032                 /* For end-of-game: */\r
3033                 /*           [3] is the reason for the game end */\r
3034                 /*           [4] is a PGN end game-token, preceded by " " */\r
3035                 /* For start-of-game: */\r
3036                 /*           [3] begins with "Creating" or "Continuing" */\r
3037                 /*           [4] is " *" or empty (don't care). */\r
3038                 int gamenum = atoi(star_match[0]);\r
3039                 char *whitename, *blackname, *why, *endtoken;\r
3040                 ChessMove endtype = (ChessMove) 0;\r
3041 \r
3042                 if (tkind == 0) {\r
3043                   whitename = star_match[1];\r
3044                   blackname = star_match[2];\r
3045                   why = star_match[3];\r
3046                   endtoken = star_match[4];\r
3047                 } else {\r
3048                   whitename = star_match[1];\r
3049                   blackname = star_match[3];\r
3050                   why = star_match[5];\r
3051                   endtoken = star_match[6];\r
3052                 }\r
3053 \r
3054                 /* Game start messages */\r
3055                 if (strncmp(why, "Creating ", 9) == 0 ||\r
3056                     strncmp(why, "Continuing ", 11) == 0) {\r
3057                     gs_gamenum = gamenum;\r
3058                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
3059 #if ZIPPY\r
3060                     if (appData.zippyPlay) {\r
3061                         ZippyGameStart(whitename, blackname);\r
3062                     }\r
3063 #endif /*ZIPPY*/\r
3064                     continue;\r
3065                 }\r
3066 \r
3067                 /* Game end messages */\r
3068                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
3069                     ics_gamenum != gamenum) {\r
3070                     continue;\r
3071                 }\r
3072                 while (endtoken[0] == ' ') endtoken++;\r
3073                 switch (endtoken[0]) {\r
3074                   case '*':\r
3075                   default:\r
3076                     endtype = GameUnfinished;\r
3077                     break;\r
3078                   case '0':\r
3079                     endtype = BlackWins;\r
3080                     break;\r
3081                   case '1':\r
3082                     if (endtoken[1] == '/')\r
3083                       endtype = GameIsDrawn;\r
3084                     else\r
3085                       endtype = WhiteWins;\r
3086                     break;\r
3087                 }\r
3088                 GameEnds(endtype, why, GE_ICS);\r
3089 #if ZIPPY\r
3090                 if (appData.zippyPlay && first.initDone) {\r
3091                     ZippyGameEnd(endtype, why);\r
3092                     if (first.pr == NULL) {\r
3093                       /* Start the next process early so that we'll\r
3094                          be ready for the next challenge */\r
3095                       StartChessProgram(&first);\r
3096                     }\r
3097                     /* Send "new" early, in case this command takes\r
3098                        a long time to finish, so that we'll be ready\r
3099                        for the next challenge. */\r
3100                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'\r
3101                     Reset(TRUE, TRUE);\r
3102                 }\r
3103 #endif /*ZIPPY*/\r
3104                 continue;\r
3105             }\r
3106 \r
3107             if (looking_at(buf, &i, "Removing game * from observation") ||\r
3108                 looking_at(buf, &i, "no longer observing game *") ||\r
3109                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
3110                 if (gameMode == IcsObserving &&\r
3111                     atoi(star_match[0]) == ics_gamenum)\r
3112                   {\r
3113                       /* icsEngineAnalyze */\r
3114                       if (appData.icsEngineAnalyze) {\r
3115                             ExitAnalyzeMode();\r
3116                             ModeHighlight();\r
3117                       }\r
3118                       StopClocks();\r
3119                       gameMode = IcsIdle;\r
3120                       ics_gamenum = -1;\r
3121                       ics_user_moved = FALSE;\r
3122                   }\r
3123                 continue;\r
3124             }\r
3125 \r
3126             if (looking_at(buf, &i, "no longer examining game *")) {\r
3127                 if (gameMode == IcsExamining &&\r
3128                     atoi(star_match[0]) == ics_gamenum)\r
3129                   {\r
3130                       gameMode = IcsIdle;\r
3131                       ics_gamenum = -1;\r
3132                       ics_user_moved = FALSE;\r
3133                   }\r
3134                 continue;\r
3135             }\r
3136 \r
3137             /* Advance leftover_start past any newlines we find,\r
3138                so only partial lines can get reparsed */\r
3139             if (looking_at(buf, &i, "\n")) {\r
3140                 prevColor = curColor;\r
3141                 if (curColor != ColorNormal) {\r
3142                     if (oldi > next_out) {\r
3143                         SendToPlayer(&buf[next_out], oldi - next_out);\r
3144                         next_out = oldi;\r
3145                     }\r
3146                     Colorize(ColorNormal, FALSE);\r
3147                     curColor = ColorNormal;\r
3148                 }\r
3149                 if (started == STARTED_BOARD) {\r
3150                     started = STARTED_NONE;\r
3151                     parse[parse_pos] = NULLCHAR;\r
3152                     ParseBoard12(parse);\r
3153                     ics_user_moved = 0;\r
3154 \r
3155                     /* Send premove here */\r
3156                     if (appData.premove) {\r
3157                       char str[MSG_SIZ];\r
3158                       if (currentMove == 0 &&\r
3159                           gameMode == IcsPlayingWhite &&\r
3160                           appData.premoveWhite) {\r
3161                         sprintf(str, "%s%s\n", ics_prefix,\r
3162                                 appData.premoveWhiteText);\r
3163                         if (appData.debugMode)\r
3164                           fprintf(debugFP, "Sending premove:\n");\r
3165                         SendToICS(str);\r
3166                       } else if (currentMove == 1 &&\r
3167                                  gameMode == IcsPlayingBlack &&\r
3168                                  appData.premoveBlack) {\r
3169                         sprintf(str, "%s%s\n", ics_prefix,\r
3170                                 appData.premoveBlackText);\r
3171                         if (appData.debugMode)\r
3172                           fprintf(debugFP, "Sending premove:\n");\r
3173                         SendToICS(str);\r
3174                       } else if (gotPremove) {\r
3175                         gotPremove = 0;\r
3176                         ClearPremoveHighlights();\r
3177                         if (appData.debugMode)\r
3178                           fprintf(debugFP, "Sending premove:\n");\r
3179                           UserMoveEvent(premoveFromX, premoveFromY, \r
3180                                         premoveToX, premoveToY, \r
3181                                         premovePromoChar);\r
3182                       }\r
3183                     }\r
3184 \r
3185                     /* Usually suppress following prompt */\r
3186                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
3187                         if (looking_at(buf, &i, "*% ")) {\r
3188                             savingComment = FALSE;\r
3189                         }\r
3190                     }\r
3191                     next_out = i;\r
3192                 } else if (started == STARTED_HOLDINGS) {\r
3193                     int gamenum;\r
3194                     char new_piece[MSG_SIZ];\r
3195                     started = STARTED_NONE;\r
3196                     parse[parse_pos] = NULLCHAR;\r
3197                     if (appData.debugMode)\r
3198                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
3199                                                         parse, currentMove);\r
3200                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
3201                         gamenum == ics_gamenum) {\r
3202                         if (gameInfo.variant == VariantNormal) {\r
3203                           /* [HGM] We seem to switch variant during a game!\r
3204                            * Presumably no holdings were displayed, so we have\r
3205                            * to move the position two files to the right to\r
3206                            * create room for them!\r
3207                            */\r
3208                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
3209                           /* Get a move list just to see the header, which\r
3210                              will tell us whether this is really bug or zh */\r
3211                           if (ics_getting_history == H_FALSE) {\r
3212                             ics_getting_history = H_REQUESTED;\r
3213                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3214                             SendToICS(str);\r
3215                           }\r
3216                         }\r
3217                         new_piece[0] = NULLCHAR;\r
3218                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
3219                                &gamenum, white_holding, black_holding,\r
3220                                new_piece);\r
3221                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
3222                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
3223                         /* [HGM] copy holdings to board holdings area */\r
3224                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
3225                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
3226 #if ZIPPY\r
3227                         if (appData.zippyPlay && first.initDone) {\r
3228                             ZippyHoldings(white_holding, black_holding,\r
3229                                           new_piece);\r
3230                         }\r
3231 #endif /*ZIPPY*/\r
3232                         if (tinyLayout || smallLayout) {\r
3233                             char wh[16], bh[16];\r
3234                             PackHolding(wh, white_holding);\r
3235                             PackHolding(bh, black_holding);\r
3236                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
3237                                     gameInfo.white, gameInfo.black);\r
3238                         } else {\r
3239                             sprintf(str, "%s [%s] vs. %s [%s]",\r
3240                                     gameInfo.white, white_holding,\r
3241                                     gameInfo.black, black_holding);\r
3242                         }\r
3243 \r
3244                         DrawPosition(FALSE, boards[currentMove]);\r
3245                         DisplayTitle(str);\r
3246                     }\r
3247                     /* Suppress following prompt */\r
3248                     if (looking_at(buf, &i, "*% ")) {\r
3249                         savingComment = FALSE;\r
3250                     }\r
3251                     next_out = i;\r
3252                 }\r
3253                 continue;\r
3254             }\r
3255 \r
3256             i++;                /* skip unparsed character and loop back */\r
3257         }\r
3258         \r
3259         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window\r
3260             started != STARTED_HOLDINGS && i > next_out) {\r
3261             SendToPlayer(&buf[next_out], i - next_out);\r
3262             next_out = i;\r
3263         }\r
3264         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above\r
3265         \r
3266         leftover_len = buf_len - leftover_start;\r
3267         /* if buffer ends with something we couldn't parse,\r
3268            reparse it after appending the next read */\r
3269         \r
3270     } else if (count == 0) {\r
3271         RemoveInputSource(isr);\r
3272         DisplayFatalError(_("Connection closed by ICS"), 0, 0);\r
3273     } else {\r
3274         DisplayFatalError(_("Error reading from ICS"), error, 1);\r
3275     }\r
3276 }\r
3277 \r
3278 \r
3279 /* Board style 12 looks like this:\r
3280    \r
3281    <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
3282    \r
3283  * The "<12> " is stripped before it gets to this routine.  The two\r
3284  * trailing 0's (flip state and clock ticking) are later addition, and\r
3285  * some chess servers may not have them, or may have only the first.\r
3286  * Additional trailing fields may be added in the future.  \r
3287  */\r
3288 \r
3289 #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
3290 \r
3291 #define RELATION_OBSERVING_PLAYED    0\r
3292 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
3293 #define RELATION_PLAYING_MYMOVE      1\r
3294 #define RELATION_PLAYING_NOTMYMOVE  -1\r
3295 #define RELATION_EXAMINING           2\r
3296 #define RELATION_ISOLATED_BOARD     -3\r
3297 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
3298 \r
3299 void\r
3300 ParseBoard12(string)\r
3301      char *string;\r
3302\r
3303     GameMode newGameMode;\r
3304     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;\r
3305     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;\r
3306     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
3307     char to_play, board_chars[200];\r
3308     char move_str[500], str[500], elapsed_time[500];\r
3309     char black[32], white[32];\r
3310     Board board;\r
3311     int prevMove = currentMove;\r
3312     int ticking = 2;\r
3313     ChessMove moveType;\r
3314     int fromX, fromY, toX, toY;\r
3315     char promoChar;\r
3316     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */\r
3317     char *bookHit = NULL; // [HGM] book\r
3318 \r
3319     fromX = fromY = toX = toY = -1;\r
3320     \r
3321     newGame = FALSE;\r
3322 \r
3323     if (appData.debugMode)\r
3324       fprintf(debugFP, _("Parsing board: %s\n"), string);\r
3325 \r
3326     move_str[0] = NULLCHAR;\r
3327     elapsed_time[0] = NULLCHAR;\r
3328     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */\r
3329         int  i = 0, j;\r
3330         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {\r
3331             if(string[i] == ' ') { ranks++; files = 0; }\r
3332             else files++;\r
3333             i++;\r
3334         }\r
3335         for(j = 0; j <i; j++) board_chars[j] = string[j];\r
3336         board_chars[i] = '\0';\r
3337         string += i + 1;\r
3338     }\r
3339     n = sscanf(string, PATTERN, &to_play, &double_push,\r
3340                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
3341                &gamenum, white, black, &relation, &basetime, &increment,\r
3342                &white_stren, &black_stren, &white_time, &black_time,\r
3343                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
3344                &ticking);\r
3345 \r
3346     if (n < 21) {\r
3347         sprintf(str, _("Failed to parse board string:\n\"%s\""), string);\r
3348         DisplayError(str, 0);\r
3349         return;\r
3350     }\r
3351 \r
3352     /* Convert the move number to internal form */\r
3353     moveNum = (moveNum - 1) * 2;\r
3354     if (to_play == 'B') moveNum++;\r
3355     if (moveNum >= MAX_MOVES) {\r
3356       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
3357                         0, 1);\r
3358       return;\r
3359     }\r
3360     \r
3361     switch (relation) {\r
3362       case RELATION_OBSERVING_PLAYED:\r
3363       case RELATION_OBSERVING_STATIC:\r
3364         if (gamenum == -1) {\r
3365             /* Old ICC buglet */\r
3366             relation = RELATION_OBSERVING_STATIC;\r
3367         }\r
3368         newGameMode = IcsObserving;\r
3369         break;\r
3370       case RELATION_PLAYING_MYMOVE:\r
3371       case RELATION_PLAYING_NOTMYMOVE:\r
3372         newGameMode =\r
3373           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3374             IcsPlayingWhite : IcsPlayingBlack;\r
3375         break;\r
3376       case RELATION_EXAMINING:\r
3377         newGameMode = IcsExamining;\r
3378         break;\r
3379       case RELATION_ISOLATED_BOARD:\r
3380       default:\r
3381         /* Just display this board.  If user was doing something else,\r
3382            we will forget about it until the next board comes. */ \r
3383         newGameMode = IcsIdle;\r
3384         break;\r
3385       case RELATION_STARTING_POSITION:\r
3386         newGameMode = gameMode;\r
3387         break;\r
3388     }\r
3389     \r
3390     /* Modify behavior for initial board display on move listing\r
3391        of wild games.\r
3392        */\r
3393     switch (ics_getting_history) {\r
3394       case H_FALSE:\r
3395       case H_REQUESTED:\r
3396         break;\r
3397       case H_GOT_REQ_HEADER:\r
3398       case H_GOT_UNREQ_HEADER:\r
3399         /* This is the initial position of the current game */\r
3400         gamenum = ics_gamenum;\r
3401         moveNum = 0;            /* old ICS bug workaround */\r
3402         if (to_play == 'B') {\r
3403           startedFromSetupPosition = TRUE;\r
3404           blackPlaysFirst = TRUE;\r
3405           moveNum = 1;\r
3406           if (forwardMostMove == 0) forwardMostMove = 1;\r
3407           if (backwardMostMove == 0) backwardMostMove = 1;\r
3408           if (currentMove == 0) currentMove = 1;\r
3409         }\r
3410         newGameMode = gameMode;\r
3411         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3412         break;\r
3413       case H_GOT_UNWANTED_HEADER:\r
3414         /* This is an initial board that we don't want */\r
3415         return;\r
3416       case H_GETTING_MOVES:\r
3417         /* Should not happen */\r
3418         DisplayError(_("Error gathering move list: extra board"), 0);\r
3419         ics_getting_history = H_FALSE;\r
3420         return;\r
3421     }\r
3422     \r
3423     /* Take action if this is the first board of a new game, or of a\r
3424        different game than is currently being displayed.  */\r
3425     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3426         relation == RELATION_ISOLATED_BOARD) {\r
3427         \r
3428         /* Forget the old game and get the history (if any) of the new one */\r
3429         if (gameMode != BeginningOfGame) {\r
3430           Reset(FALSE, TRUE);\r
3431         }\r
3432         newGame = TRUE;\r
3433         if (appData.autoRaiseBoard) BoardToTop();\r
3434         prevMove = -3;\r
3435         if (gamenum == -1) {\r
3436             newGameMode = IcsIdle;\r
3437         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3438                    appData.getMoveList) {\r
3439             /* Need to get game history */\r
3440             ics_getting_history = H_REQUESTED;\r
3441             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3442             SendToICS(str);\r
3443         }\r
3444         \r
3445         /* Initially flip the board to have black on the bottom if playing\r
3446            black or if the ICS flip flag is set, but let the user change\r
3447            it with the Flip View button. */\r
3448         flipView = appData.autoFlipView ? \r
3449           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3450           appData.flipView;\r
3451         \r
3452         /* Done with values from previous mode; copy in new ones */\r
3453         gameMode = newGameMode;\r
3454         ModeHighlight();\r
3455         ics_gamenum = gamenum;\r
3456         if (gamenum == gs_gamenum) {\r
3457             int klen = strlen(gs_kind);\r
3458             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3459             sprintf(str, "ICS %s", gs_kind);\r
3460             gameInfo.event = StrSave(str);\r
3461         } else {\r
3462             gameInfo.event = StrSave("ICS game");\r
3463         }\r
3464         gameInfo.site = StrSave(appData.icsHost);\r
3465         gameInfo.date = PGNDate();\r
3466         gameInfo.round = StrSave("-");\r
3467         gameInfo.white = StrSave(white);\r
3468         gameInfo.black = StrSave(black);\r
3469         timeControl = basetime * 60 * 1000;\r
3470         timeControl_2 = 0;\r
3471         timeIncrement = increment * 1000;\r
3472         movesPerSession = 0;\r
3473         gameInfo.timeControl = TimeControlTagValue();\r
3474         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3475   if (appData.debugMode) {\r
3476     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3477     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3478     setbuf(debugFP, NULL);\r
3479   }\r
3480 \r
3481         gameInfo.outOfBook = NULL;\r
3482         \r
3483         /* Do we have the ratings? */\r
3484         if (strcmp(player1Name, white) == 0 &&\r
3485             strcmp(player2Name, black) == 0) {\r
3486             if (appData.debugMode)\r
3487               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3488                       player1Rating, player2Rating);\r
3489             gameInfo.whiteRating = player1Rating;\r
3490             gameInfo.blackRating = player2Rating;\r
3491         } else if (strcmp(player2Name, white) == 0 &&\r
3492                    strcmp(player1Name, black) == 0) {\r
3493             if (appData.debugMode)\r
3494               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3495                       player2Rating, player1Rating);\r
3496             gameInfo.whiteRating = player2Rating;\r
3497             gameInfo.blackRating = player1Rating;\r
3498         }\r
3499         player1Name[0] = player2Name[0] = NULLCHAR;\r
3500 \r
3501         /* Silence shouts if requested */\r
3502         if (appData.quietPlay &&\r
3503             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3504             SendToICS(ics_prefix);\r
3505             SendToICS("set shout 0\n");\r
3506         }\r
3507     }\r
3508     \r
3509     /* Deal with midgame name changes */\r
3510     if (!newGame) {\r
3511         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3512             if (gameInfo.white) free(gameInfo.white);\r
3513             gameInfo.white = StrSave(white);\r
3514         }\r
3515         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3516             if (gameInfo.black) free(gameInfo.black);\r
3517             gameInfo.black = StrSave(black);\r
3518         }\r
3519     }\r
3520     \r
3521     /* Throw away game result if anything actually changes in examine mode */\r
3522     if (gameMode == IcsExamining && !newGame) {\r
3523         gameInfo.result = GameUnfinished;\r
3524         if (gameInfo.resultDetails != NULL) {\r
3525             free(gameInfo.resultDetails);\r
3526             gameInfo.resultDetails = NULL;\r
3527         }\r
3528     }\r
3529     \r
3530     /* In pausing && IcsExamining mode, we ignore boards coming\r
3531        in if they are in a different variation than we are. */\r
3532     if (pauseExamInvalid) return;\r
3533     if (pausing && gameMode == IcsExamining) {\r
3534         if (moveNum <= pauseExamForwardMostMove) {\r
3535             pauseExamInvalid = TRUE;\r
3536             forwardMostMove = pauseExamForwardMostMove;\r
3537             return;\r
3538         }\r
3539     }\r
3540     \r
3541   if (appData.debugMode) {\r
3542     fprintf(debugFP, "load %dx%d board\n", files, ranks);\r
3543   }\r
3544     /* Parse the board */\r
3545     for (k = 0; k < ranks; k++) {\r
3546       for (j = 0; j < files; j++)\r
3547         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);\r
3548       if(gameInfo.holdingsWidth > 1) {\r
3549            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3550            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3551       }\r
3552     }\r
3553     CopyBoard(boards[moveNum], board);\r
3554     if (moveNum == 0) {\r
3555         startedFromSetupPosition =\r
3556           !CompareBoards(board, initialPosition);\r
3557         if(startedFromSetupPosition)\r
3558             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */\r
3559     }\r
3560 \r
3561     /* [HGM] Set castling rights. Take the outermost Rooks,\r
3562        to make it also work for FRC opening positions. Note that board12\r
3563        is really defective for later FRC positions, as it has no way to\r
3564        indicate which Rook can castle if they are on the same side of King.\r
3565        For the initial position we grant rights to the outermost Rooks,\r
3566        and remember thos rights, and we then copy them on positions\r
3567        later in an FRC game. This means WB might not recognize castlings with\r
3568        Rooks that have moved back to their original position as illegal,\r
3569        but in ICS mode that is not its job anyway.\r
3570     */\r
3571     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)\r
3572     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;\r
3573 \r
3574         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3575             if(board[0][i] == WhiteRook) j = i;\r
3576         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3577         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3578             if(board[0][i] == WhiteRook) j = i;\r
3579         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3580         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3581             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3582         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3583         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3584             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3585         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3586 \r
3587         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }\r
3588         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3589             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;\r
3590         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3591             if(board[BOARD_HEIGHT-1][k] == bKing)\r
3592                 initialRights[5] = castlingRights[moveNum][5] = k;\r
3593     } else { int r;\r
3594         r = castlingRights[moveNum][0] = initialRights[0];\r
3595         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;\r
3596         r = castlingRights[moveNum][1] = initialRights[1];\r
3597         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;\r
3598         r = castlingRights[moveNum][3] = initialRights[3];\r
3599         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;\r
3600         r = castlingRights[moveNum][4] = initialRights[4];\r
3601         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;\r
3602         /* wildcastle kludge: always assume King has rights */\r
3603         r = castlingRights[moveNum][2] = initialRights[2];\r
3604         r = castlingRights[moveNum][5] = initialRights[5];\r
3605     }\r
3606     /* [HGM] e.p. rights. Assume that ICS sends file number here? */\r
3607     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;\r
3608 \r
3609     \r
3610     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3611         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3612         /* This was an initial position from a move list, not\r
3613            the current position */\r
3614         return;\r
3615     }\r
3616     \r
3617     /* Update currentMove and known move number limits */\r
3618     newMove = newGame || moveNum > forwardMostMove;\r
3619 \r
3620     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */\r
3621     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {\r
3622         takeback = forwardMostMove - moveNum;\r
3623         for (i = 0; i < takeback; i++) {\r
3624              if (appData.debugMode) fprintf(debugFP, "take back move\n");\r
3625              SendToProgram("undo\n", &first);\r
3626         }\r
3627     }\r
3628 \r
3629     if (newGame) {\r
3630         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3631         if (gameMode == IcsExamining && moveNum == 0) {\r
3632           /* Workaround for ICS limitation: we are not told the wild\r
3633              type when starting to examine a game.  But if we ask for\r
3634              the move list, the move list header will tell us */\r
3635             ics_getting_history = H_REQUESTED;\r
3636             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3637             SendToICS(str);\r
3638         }\r
3639     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3640                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3641         forwardMostMove = moveNum;\r
3642         if (!pausing || currentMove > forwardMostMove)\r
3643           currentMove = forwardMostMove;\r
3644     } else {\r
3645         /* New part of history that is not contiguous with old part */ \r
3646         if (pausing && gameMode == IcsExamining) {\r
3647             pauseExamInvalid = TRUE;\r
3648             forwardMostMove = pauseExamForwardMostMove;\r
3649             return;\r
3650         }\r
3651         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3652         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3653             ics_getting_history = H_REQUESTED;\r
3654             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3655             SendToICS(str);\r
3656         }\r
3657     }\r
3658     \r
3659     /* Update the clocks */\r
3660     if (strchr(elapsed_time, '.')) {\r
3661       /* Time is in ms */\r
3662       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3663       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3664     } else {\r
3665       /* Time is in seconds */\r
3666       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3667       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3668     }\r
3669       \r
3670 \r
3671 #if ZIPPY\r
3672     if (appData.zippyPlay && newGame &&\r
3673         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3674         gameMode != IcsExamining)\r
3675       ZippyFirstBoard(moveNum, basetime, increment);\r
3676 #endif\r
3677     \r
3678     /* Put the move on the move list, first converting\r
3679        to canonical algebraic form. */\r
3680     if (moveNum > 0) {\r
3681   if (appData.debugMode) {\r
3682     if (appData.debugMode) { int f = forwardMostMove;\r
3683         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,\r
3684                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
3685     }\r
3686     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3687     fprintf(debugFP, "moveNum = %d\n", moveNum);\r
3688     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3689     setbuf(debugFP, NULL);\r
3690   }\r
3691         if (moveNum <= backwardMostMove) {\r
3692             /* We don't know what the board looked like before\r
3693                this move.  Punt. */\r
3694             strcpy(parseList[moveNum - 1], move_str);\r
3695             strcat(parseList[moveNum - 1], " ");\r
3696             strcat(parseList[moveNum - 1], elapsed_time);\r
3697             moveList[moveNum - 1][0] = NULLCHAR;\r
3698         } else if (strcmp(move_str, "none") == 0) {\r
3699             // [HGM] long SAN: swapped order; test for 'none' before parsing move\r
3700             /* Again, we don't know what the board looked like;\r
3701                this is really the start of the game. */\r
3702             parseList[moveNum - 1][0] = NULLCHAR;\r
3703             moveList[moveNum - 1][0] = NULLCHAR;\r
3704             backwardMostMove = moveNum;\r
3705             startedFromSetupPosition = TRUE;\r
3706             fromX = fromY = toX = toY = -1;\r
3707         } else {\r
3708           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. \r
3709           //                 So we parse the long-algebraic move string in stead of the SAN move\r
3710           int valid; char buf[MSG_SIZ], *prom;\r
3711 \r
3712           // str looks something like "Q/a1-a2"; kill the slash\r
3713           if(str[1] == '/') \r
3714                 sprintf(buf, "%c%s", str[0], str+2);\r
3715           else  strcpy(buf, str); // might be castling\r
3716           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) \r
3717                 strcat(buf, prom); // long move lacks promo specification!\r
3718           if(!appData.testLegality) {\r
3719                 if(appData.debugMode) \r
3720                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);\r
3721                 strcpy(move_str, buf);\r
3722           }\r
3723           valid = ParseOneMove(move_str, moveNum - 1, &moveType,\r
3724                                 &fromX, &fromY, &toX, &toY, &promoChar)\r
3725                || ParseOneMove(buf, moveNum - 1, &moveType,\r
3726                                 &fromX, &fromY, &toX, &toY, &promoChar);\r
3727           // end of long SAN patch\r
3728           if (valid) {\r
3729             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3730                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3731                                      fromY, fromX, toY, toX, promoChar,\r
3732                                      parseList[moveNum-1]);\r
3733             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3734                              castlingRights[moveNum]) ) {\r
3735               case MT_NONE:\r
3736               case MT_STALEMATE:\r
3737               default:\r
3738                 break;\r
3739               case MT_CHECK:\r
3740                 if(gameInfo.variant != VariantShogi)\r
3741                     strcat(parseList[moveNum - 1], "+");\r
3742                 break;\r
3743               case MT_CHECKMATE:\r
3744                 strcat(parseList[moveNum - 1], "#");\r
3745                 break;\r
3746             }\r
3747             strcat(parseList[moveNum - 1], " ");\r
3748             strcat(parseList[moveNum - 1], elapsed_time);\r
3749             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3750             strcpy(moveList[moveNum - 1], currentMoveString);\r
3751             strcat(moveList[moveNum - 1], "\n");\r
3752           } else {\r
3753             /* Move from ICS was illegal!?  Punt. */\r
3754   if (appData.debugMode) {\r
3755     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3756     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3757   }\r
3758 #if 0\r
3759             if (appData.testLegality && appData.debugMode) {\r
3760                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3761                 DisplayError(str, 0);\r
3762             }\r
3763 #endif\r
3764             strcpy(parseList[moveNum - 1], move_str);\r
3765             strcat(parseList[moveNum - 1], " ");\r
3766             strcat(parseList[moveNum - 1], elapsed_time);\r
3767             moveList[moveNum - 1][0] = NULLCHAR;\r
3768             fromX = fromY = toX = toY = -1;\r
3769           }\r
3770         }\r
3771   if (appData.debugMode) {\r
3772     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3773     setbuf(debugFP, NULL);\r
3774   }\r
3775 \r
3776 #if ZIPPY\r
3777         /* Send move to chess program (BEFORE animating it). */\r
3778         if (appData.zippyPlay && !newGame && newMove && \r
3779            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3780 \r
3781             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3782                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3783                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3784                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),\r
3785                             move_str);\r
3786                     DisplayError(str, 0);\r
3787                 } else {\r
3788                     if (first.sendTime) {\r
3789                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3790                     }\r
3791                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book\r
3792                     if (firstMove && !bookHit) {\r
3793                         firstMove = FALSE;\r
3794                         if (first.useColors) {\r
3795                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3796                                         "white\ngo\n" :\r
3797                                         "black\ngo\n", &first);\r
3798                         } else {\r
3799                           SendToProgram("go\n", &first);\r
3800                         }\r
3801                         first.maybeThinking = TRUE;\r
3802                     }\r
3803                 }\r
3804             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3805               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3806                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);\r
3807                 DisplayError(str, 0);\r
3808               } else {\r
3809                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!\r
3810                 SendMoveToProgram(moveNum - 1, &first);\r
3811               }\r
3812             }\r
3813         }\r
3814 #endif\r
3815     }\r
3816 \r
3817     if (moveNum > 0 && !gotPremove) {\r
3818         /* If move comes from a remote source, animate it.  If it\r
3819            isn't remote, it will have already been animated. */\r
3820         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3821             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3822         }\r
3823         if (!pausing && appData.highlightLastMove) {\r
3824             SetHighlights(fromX, fromY, toX, toY);\r
3825         }\r
3826     }\r
3827     \r
3828     /* Start the clocks */\r
3829     whiteFlag = blackFlag = FALSE;\r
3830     appData.clockMode = !(basetime == 0 && increment == 0);\r
3831     if (ticking == 0) {\r
3832       ics_clock_paused = TRUE;\r
3833       StopClocks();\r
3834     } else if (ticking == 1) {\r
3835       ics_clock_paused = FALSE;\r
3836     }\r
3837     if (gameMode == IcsIdle ||\r
3838         relation == RELATION_OBSERVING_STATIC ||\r
3839         relation == RELATION_EXAMINING ||\r
3840         ics_clock_paused)\r
3841       DisplayBothClocks();\r
3842     else\r
3843       StartClocks();\r
3844     \r
3845     /* Display opponents and material strengths */\r
3846     if (gameInfo.variant != VariantBughouse &&\r
3847         gameInfo.variant != VariantCrazyhouse) {\r
3848         if (tinyLayout || smallLayout) {\r
3849             if(gameInfo.variant == VariantNormal)\r
3850                 sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3851                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3852                     basetime, increment);\r
3853             else\r
3854                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", \r
3855                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3856                     basetime, increment, (int) gameInfo.variant);\r
3857         } else {\r
3858             if(gameInfo.variant == VariantNormal)\r
3859                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3860                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3861                     basetime, increment);\r
3862             else\r
3863                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", \r
3864                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3865                     basetime, increment, VariantName(gameInfo.variant));\r
3866         }\r
3867         DisplayTitle(str);\r
3868   if (appData.debugMode) {\r
3869     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);\r
3870   }\r
3871     }\r
3872 \r
3873    \r
3874     /* Display the board */\r
3875     if (!pausing) {\r
3876       \r
3877       if (appData.premove)\r
3878           if (!gotPremove || \r
3879              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3880              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3881               ClearPremoveHighlights();\r
3882 \r
3883       DrawPosition(FALSE, boards[currentMove]);\r
3884       DisplayMove(moveNum - 1);\r
3885       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3886         RingBell();\r
3887     }\r
3888 \r
3889     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3890 #if ZIPPY\r
3891     if(bookHit) { // [HGM] book: simulate book reply\r
3892         static char bookMove[MSG_SIZ]; // a bit generous?\r
3893 \r
3894         programStats.nodes = programStats.depth = programStats.time = \r
3895         programStats.score = programStats.got_only_move = 0;\r
3896         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
3897 \r
3898         strcpy(bookMove, "move ");\r
3899         strcat(bookMove, bookHit);\r
3900         HandleMachineMove(bookMove, &first);\r
3901     }\r
3902 #endif\r
3903 }\r
3904 \r
3905 void\r
3906 GetMoveListEvent()\r
3907 {\r
3908     char buf[MSG_SIZ];\r
3909     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3910         ics_getting_history = H_REQUESTED;\r
3911         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3912         SendToICS(buf);\r
3913     }\r
3914 }\r
3915 \r
3916 void\r
3917 AnalysisPeriodicEvent(force)\r
3918      int force;\r
3919 {\r
3920     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3921          && !force) || !appData.periodicUpdates)\r
3922       return;\r
3923 \r
3924     /* Send . command to Crafty to collect stats */\r
3925     SendToProgram(".\n", &first);\r
3926 \r
3927     /* Don't send another until we get a response (this makes\r
3928        us stop sending to old Crafty's which don't understand\r
3929        the "." command (sending illegal cmds resets node count & time,\r
3930        which looks bad)) */\r
3931     programStats.ok_to_send = 0;\r
3932 }\r
3933 \r
3934 void\r
3935 SendMoveToProgram(moveNum, cps)\r
3936      int moveNum;\r
3937      ChessProgramState *cps;\r
3938 {\r
3939     char buf[MSG_SIZ];\r
3940 \r
3941     if (cps->useUsermove) {\r
3942       SendToProgram("usermove ", cps);\r
3943     }\r
3944     if (cps->useSAN) {\r
3945       char *space;\r
3946       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3947         int len = space - parseList[moveNum];\r
3948         memcpy(buf, parseList[moveNum], len);\r
3949         buf[len++] = '\n';\r
3950         buf[len] = NULLCHAR;\r
3951       } else {\r
3952         sprintf(buf, "%s\n", parseList[moveNum]);\r
3953       }\r
3954       SendToProgram(buf, cps);\r
3955     } else {\r
3956       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */\r
3957         AlphaRank(moveList[moveNum], 4);\r
3958         SendToProgram(moveList[moveNum], cps);\r
3959         AlphaRank(moveList[moveNum], 4); // and back\r
3960       } else\r
3961       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3962        * the engine. It would be nice to have a better way to identify castle \r
3963        * moves here. */\r
3964       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)\r
3965                                                                          && cps->useOOCastle) {\r
3966         int fromX = moveList[moveNum][0] - AAA; \r
3967         int fromY = moveList[moveNum][1] - ONE;\r
3968         int toX = moveList[moveNum][2] - AAA; \r
3969         int toY = moveList[moveNum][3] - ONE;\r
3970         if((boards[moveNum][fromY][fromX] == WhiteKing \r
3971             && boards[moveNum][toY][toX] == WhiteRook)\r
3972            || (boards[moveNum][fromY][fromX] == BlackKing \r
3973                && boards[moveNum][toY][toX] == BlackRook)) {\r
3974           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3975           else SendToProgram("O-O-O\n", cps);\r
3976         }\r
3977         else SendToProgram(moveList[moveNum], cps);\r
3978       }\r
3979       else SendToProgram(moveList[moveNum], cps);\r
3980       /* End of additions by Tord */\r
3981     }\r
3982 \r
3983     /* [HGM] setting up the opening has brought engine in force mode! */\r
3984     /*       Send 'go' if we are in a mode where machine should play. */\r
3985     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
3986         (gameMode == TwoMachinesPlay   ||\r
3987 #ifdef ZIPPY\r
3988          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||\r
3989 #endif\r
3990          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
3991         SendToProgram("go\n", cps);\r
3992   if (appData.debugMode) {\r
3993     fprintf(debugFP, "(extra)\n");\r
3994   }\r
3995     }\r
3996     setboardSpoiledMachineBlack = 0;\r
3997 }\r
3998 \r
3999 void\r
4000 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
4001      ChessMove moveType;\r
4002      int fromX, fromY, toX, toY;\r
4003 {\r
4004     char user_move[MSG_SIZ];\r
4005 \r
4006     switch (moveType) {\r
4007       default:\r
4008         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),\r
4009                 (int)moveType, fromX, fromY, toX, toY);\r
4010         DisplayError(user_move + strlen("say "), 0);\r
4011         break;\r
4012       case WhiteKingSideCastle:\r
4013       case BlackKingSideCastle:\r
4014       case WhiteQueenSideCastleWild:\r
4015       case BlackQueenSideCastleWild:\r
4016       /* PUSH Fabien */\r
4017       case WhiteHSideCastleFR:\r
4018       case BlackHSideCastleFR:\r
4019       /* POP Fabien */\r
4020         sprintf(user_move, "o-o\n");\r
4021         break;\r
4022       case WhiteQueenSideCastle:\r
4023       case BlackQueenSideCastle:\r
4024       case WhiteKingSideCastleWild:\r
4025       case BlackKingSideCastleWild:\r
4026       /* PUSH Fabien */\r
4027       case WhiteASideCastleFR:\r
4028       case BlackASideCastleFR:\r
4029       /* POP Fabien */\r
4030         sprintf(user_move, "o-o-o\n");\r
4031         break;\r
4032       case WhitePromotionQueen:\r
4033       case BlackPromotionQueen:\r
4034       case WhitePromotionRook:\r
4035       case BlackPromotionRook:\r
4036       case WhitePromotionBishop:\r
4037       case BlackPromotionBishop:\r
4038       case WhitePromotionKnight:\r
4039       case BlackPromotionKnight:\r
4040       case WhitePromotionKing:\r
4041       case BlackPromotionKing:\r
4042       case WhitePromotionChancellor:\r
4043       case BlackPromotionChancellor:\r
4044       case WhitePromotionArchbishop:\r
4045       case BlackPromotionArchbishop:\r
4046         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)\r
4047             sprintf(user_move, "%c%c%c%c=%c\n",\r
4048                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4049                 PieceToChar(WhiteFerz));\r
4050         else if(gameInfo.variant == VariantGreat)\r
4051             sprintf(user_move, "%c%c%c%c=%c\n",\r
4052                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4053                 PieceToChar(WhiteMan));\r
4054         else\r
4055             sprintf(user_move, "%c%c%c%c=%c\n",\r
4056                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4057                 PieceToChar(PromoPiece(moveType)));\r
4058         break;\r
4059       case WhiteDrop:\r
4060       case BlackDrop:\r
4061         sprintf(user_move, "%c@%c%c\n",\r
4062                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
4063                 AAA + toX, ONE + toY);\r
4064         break;\r
4065       case NormalMove:\r
4066       case WhiteCapturesEnPassant:\r
4067       case BlackCapturesEnPassant:\r
4068       case IllegalMove:  /* could be a variant we don't quite understand */\r
4069         sprintf(user_move, "%c%c%c%c\n",\r
4070                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
4071         break;\r
4072     }\r
4073     SendToICS(user_move);\r
4074 }\r
4075 \r
4076 void\r
4077 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
4078      int rf, ff, rt, ft;\r
4079      char promoChar;\r
4080      char move[7];\r
4081 {\r
4082     if (rf == DROP_RANK) {\r
4083         sprintf(move, "%c@%c%c\n",\r
4084                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
4085     } else {\r
4086         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
4087             sprintf(move, "%c%c%c%c\n",\r
4088                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
4089         } else {\r
4090             sprintf(move, "%c%c%c%c%c\n",\r
4091                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
4092         }\r
4093     }\r
4094 }\r
4095 \r
4096 void\r
4097 ProcessICSInitScript(f)\r
4098      FILE *f;\r
4099 {\r
4100     char buf[MSG_SIZ];\r
4101 \r
4102     while (fgets(buf, MSG_SIZ, f)) {\r
4103         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
4104     }\r
4105 \r
4106     fclose(f);\r
4107 }\r
4108 \r
4109 \r
4110 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
4111 void\r
4112 AlphaRank(char *move, int n)\r
4113 {\r
4114 //    char *p = move, c; int x, y;\r
4115 \r
4116     if (appData.debugMode) {\r
4117         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
4118     }\r
4119 \r
4120     if(move[1]=='*' && \r
4121        move[2]>='0' && move[2]<='9' &&\r
4122        move[3]>='a' && move[3]<='x'    ) {\r
4123         move[1] = '@';\r
4124         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4125         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4126     } else\r
4127     if(move[0]>='0' && move[0]<='9' &&\r
4128        move[1]>='a' && move[1]<='x' &&\r
4129        move[2]>='0' && move[2]<='9' &&\r
4130        move[3]>='a' && move[3]<='x'    ) {\r
4131         /* input move, Shogi -> normal */\r
4132         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;\r
4133         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\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[1]=='@' &&\r
4138        move[3]>='0' && move[3]<='9' &&\r
4139        move[2]>='a' && move[2]<='x'    ) {\r
4140         move[1] = '*';\r
4141         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4142         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4143     } else\r
4144     if(\r
4145        move[0]>='a' && move[0]<='x' &&\r
4146        move[3]>='0' && move[3]<='9' &&\r
4147        move[2]>='a' && move[2]<='x'    ) {\r
4148          /* output move, normal -> Shogi */\r
4149         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
4150         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
4151         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4152         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4153         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
4154     }\r
4155     if (appData.debugMode) {\r
4156         fprintf(debugFP, "   out = '%s'\n", move);\r
4157     }\r
4158 }\r
4159 \r
4160 /* Parser for moves from gnuchess, ICS, or user typein box */\r
4161 Boolean\r
4162 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
4163      char *move;\r
4164      int moveNum;\r
4165      ChessMove *moveType;\r
4166      int *fromX, *fromY, *toX, *toY;\r
4167      char *promoChar;\r
4168 {       \r
4169     if (appData.debugMode) {\r
4170         fprintf(debugFP, "move to parse: %s\n", move);\r
4171     }\r
4172     *moveType = yylexstr(moveNum, move);\r
4173 \r
4174     switch (*moveType) {\r
4175       case WhitePromotionChancellor:\r
4176       case BlackPromotionChancellor:\r
4177       case WhitePromotionArchbishop:\r
4178       case BlackPromotionArchbishop:\r
4179       case WhitePromotionQueen:\r
4180       case BlackPromotionQueen:\r
4181       case WhitePromotionRook:\r
4182       case BlackPromotionRook:\r
4183       case WhitePromotionBishop:\r
4184       case BlackPromotionBishop:\r
4185       case WhitePromotionKnight:\r
4186       case BlackPromotionKnight:\r
4187       case WhitePromotionKing:\r
4188       case BlackPromotionKing:\r
4189       case NormalMove:\r
4190       case WhiteCapturesEnPassant:\r
4191       case BlackCapturesEnPassant:\r
4192       case WhiteKingSideCastle:\r
4193       case WhiteQueenSideCastle:\r
4194       case BlackKingSideCastle:\r
4195       case BlackQueenSideCastle:\r
4196       case WhiteKingSideCastleWild:\r
4197       case WhiteQueenSideCastleWild:\r
4198       case BlackKingSideCastleWild:\r
4199       case BlackQueenSideCastleWild:\r
4200       /* Code added by Tord: */\r
4201       case WhiteHSideCastleFR:\r
4202       case WhiteASideCastleFR:\r
4203       case BlackHSideCastleFR:\r
4204       case BlackASideCastleFR:\r
4205       /* End of code added by Tord */\r
4206       case IllegalMove:         /* bug or odd chess variant */\r
4207         *fromX = currentMoveString[0] - AAA;\r
4208         *fromY = currentMoveString[1] - ONE;\r
4209         *toX = currentMoveString[2] - AAA;\r
4210         *toY = currentMoveString[3] - ONE;\r
4211         *promoChar = currentMoveString[4];\r
4212         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
4213             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
4214     if (appData.debugMode) {\r
4215         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
4216     }\r
4217             *fromX = *fromY = *toX = *toY = 0;\r
4218             return FALSE;\r
4219         }\r
4220         if (appData.testLegality) {\r
4221           return (*moveType != IllegalMove);\r
4222         } else {\r
4223           return !(fromX == fromY && toX == toY);\r
4224         }\r
4225 \r
4226       case WhiteDrop:\r
4227       case BlackDrop:\r
4228         *fromX = *moveType == WhiteDrop ?\r
4229           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
4230           (int) CharToPiece(ToLower(currentMoveString[0]));\r
4231         *fromY = DROP_RANK;\r
4232         *toX = currentMoveString[2] - AAA;\r
4233         *toY = currentMoveString[3] - ONE;\r
4234         *promoChar = NULLCHAR;\r
4235         return TRUE;\r
4236 \r
4237       case AmbiguousMove:\r
4238       case ImpossibleMove:\r
4239       case (ChessMove) 0:       /* end of file */\r
4240       case ElapsedTime:\r
4241       case Comment:\r
4242       case PGNTag:\r
4243       case NAG:\r
4244       case WhiteWins:\r
4245       case BlackWins:\r
4246       case GameIsDrawn:\r
4247       default:\r
4248     if (appData.debugMode) {\r
4249         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
4250     }\r
4251         /* bug? */\r
4252         *fromX = *fromY = *toX = *toY = 0;\r
4253         *promoChar = NULLCHAR;\r
4254         return FALSE;\r
4255     }\r
4256 }\r
4257 \r
4258 #if 0\r
4259 /* [AS] FRC game initialization */\r
4260 static int FindEmptySquare( Board board, int n )\r
4261 {\r
4262     int i = 0;\r
4263 \r
4264     while( 1 ) {\r
4265         while( board[0][i] != EmptySquare ) i++;\r
4266         if( n == 0 )\r
4267             break;\r
4268         n--;\r
4269         i++;\r
4270     }\r
4271 \r
4272     return i;\r
4273 }\r
4274 \r
4275 static void ShuffleFRC( Board board )\r
4276 {\r
4277     int i;\r
4278 \r
4279     srand( time(0) );\r
4280     \r
4281     for( i=0; i<8; i++ ) {\r
4282         board[0][i] = EmptySquare;\r
4283     }\r
4284 \r
4285     board[0][(rand() % 4)*2  ] = WhiteBishop; /* On dark square */\r
4286     board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */\r
4287     board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;\r
4288     board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;\r
4289     board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;\r
4290     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4291     initialRights[1]  = initialRights[4]  =\r
4292     castlingRights[0][1] = castlingRights[0][4] = i;\r
4293     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4294     initialRights[2]  = initialRights[5]  =\r
4295     castlingRights[0][2] = castlingRights[0][5] = i;\r
4296     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4297     initialRights[0]  = initialRights[3]  =\r
4298     castlingRights[0][0] = castlingRights[0][3] = i;\r
4299 \r
4300     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4301         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4302     }\r
4303 }\r
4304 \r
4305 static unsigned char FRC_KnightTable[10] = {\r
4306     0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33\r
4307 };\r
4308 \r
4309 static void SetupFRC( Board board, int pos_index )\r
4310 {\r
4311     int i;\r
4312     unsigned char knights;\r
4313 \r
4314     /* Bring the position index into a safe range (just in case...) */\r
4315     if( pos_index < 0 ) pos_index = 0;\r
4316 \r
4317     pos_index %= 960;\r
4318 \r
4319     /* Clear the board */\r
4320     for( i=0; i<8; i++ ) {\r
4321         board[0][i] = EmptySquare;\r
4322     }\r
4323 \r
4324     /* Place bishops and queen */\r
4325     board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */\r
4326     pos_index /= 4;\r
4327     \r
4328     board[0][ (pos_index % 4)*2     ] = WhiteBishop; /* On dark square */\r
4329     pos_index /= 4;\r
4330 \r
4331     board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;\r
4332     pos_index /= 6;\r
4333 \r
4334     /* Place knigths */\r
4335     knights = FRC_KnightTable[ pos_index ];\r
4336 \r
4337     board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;\r
4338     board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;\r
4339 \r
4340     /* Place rooks and king */\r
4341     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4342     initialRights[1]  = initialRights[4]  =\r
4343     castlingRights[0][1] = castlingRights[0][4] = i;\r
4344     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4345     initialRights[2]  = initialRights[5]  =\r
4346     castlingRights[0][2] = castlingRights[0][5] = i;\r
4347     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4348     initialRights[0]  = initialRights[3]  =\r
4349     castlingRights[0][0] = castlingRights[0][3] = i;\r
4350 \r
4351     /* Mirror piece placement for black */\r
4352     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4353         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4354     }\r
4355 }\r
4356 #else\r
4357 // [HGM] shuffle: a more general way to suffle opening setups, applicable to arbitrry variants.\r
4358 // All positions will have equal probability, but the current method will not provide a unique\r
4359 // numbering scheme for arrays that contain 3 or more pieces of the same kind.\r
4360 #define DARK 1\r
4361 #define LITE 2\r
4362 #define ANY 3\r
4363 \r
4364 int squaresLeft[4];\r
4365 int piecesLeft[(int)BlackPawn];\r
4366 u64 seed, nrOfShuffles;\r
4367 \r
4368 void GetPositionNumber()\r
4369 {       // sets global variable seed\r
4370         int i;\r
4371 \r
4372         seed = appData.defaultFrcPosition;\r
4373         if(seed < 0) { // randomize based on time for negative FRC position numbers\r
4374                 srandom(time(0)); \r
4375                 for(i=0; i<50; i++) seed += random();\r
4376                 seed = random() ^ random() >> 8 ^ random() << 8;\r
4377                 if(seed<0) seed = -seed;\r
4378         }\r
4379 }\r
4380 \r
4381 int put(Board board, int pieceType, int rank, int n, int shade)\r
4382 // put the piece on the (n-1)-th empty squares of the given shade\r
4383 {\r
4384         int i;\r
4385 \r
4386         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
4387                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {\r
4388                         board[rank][i] = (ChessSquare) pieceType;\r
4389                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;\r
4390                         squaresLeft[ANY]--;\r
4391                         piecesLeft[pieceType]--; \r
4392                         return i;\r
4393                 }\r
4394         }\r
4395         return -1;\r
4396 }\r
4397 \r
4398 \r
4399 void AddOnePiece(Board board, int pieceType, int rank, int shade)\r
4400 // calculate where the next piece goes, (any empty square), and put it there\r
4401 {\r
4402         int i;\r
4403 \r
4404         i = seed % squaresLeft[shade];\r
4405         nrOfShuffles *= squaresLeft[shade];\r
4406         seed /= squaresLeft[shade];\r
4407         put(board, pieceType, rank, i, shade);\r
4408 }\r
4409 \r
4410 void AddTwoPieces(Board board, int pieceType, int rank)\r
4411 // calculate where the next 2 identical pieces go, (any empty square), and put it there\r
4412 {\r
4413         int i, n=squaresLeft[ANY], j=n-1, k;\r
4414 \r
4415         k = n*(n-1)/2; // nr of possibilities, not counting permutations\r
4416         i = seed % k;  // pick one\r
4417         nrOfShuffles *= k;\r
4418         seed /= k;\r
4419         while(i >= j) i -= j--;\r
4420         j = n - 1 - j; i += j;\r
4421         put(board, pieceType, rank, j, ANY);\r
4422         put(board, pieceType, rank, i, ANY);\r
4423 }\r
4424 \r
4425 void SetUpShuffle(Board board, int number)\r
4426 {\r
4427         int i, p, first=1;\r
4428 \r
4429         GetPositionNumber(); nrOfShuffles = 1;\r
4430 \r
4431         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;\r
4432         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;\r
4433         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];\r
4434 \r
4435         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;\r
4436 \r
4437         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board\r
4438             p = (int) board[0][i];\r
4439             if(p < (int) BlackPawn) piecesLeft[p] ++;\r
4440             board[0][i] = EmptySquare;\r
4441         }\r
4442 \r
4443         if(PosFlags(0) & F_ALL_CASTLE_OK) {\r
4444             // shuffles restricted to allow normal castling put KRR first\r
4445             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle\r
4446                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4447             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles\r
4448                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4449             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling\r
4450                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);\r
4451             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling\r
4452                 put(board, WhiteRook, 0, 0, ANY);\r
4453             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle\r
4454         }\r
4455 \r
4456         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)\r
4457             // only for even boards make effort to put pairs of colorbound pieces on opposite colors\r
4458             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {\r
4459                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;\r
4460                 while(piecesLeft[p] >= 2) {\r
4461                     AddOnePiece(board, p, 0, LITE);\r
4462                     AddOnePiece(board, p, 0, DARK);\r
4463                 }\r
4464                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)\r
4465             }\r
4466 \r
4467         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {\r
4468             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere\r
4469             // but we leave King and Rooks for last, to possibly obey FRC restriction\r
4470             if(p == (int)WhiteRook) continue;\r
4471             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations\r
4472             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece\r
4473         }\r
4474 \r
4475         // now everything is placed, except perhaps King (Unicorn) and Rooks\r
4476 \r
4477         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {\r
4478             // Last King gets castling rights\r
4479             while(piecesLeft[(int)WhiteUnicorn]) {\r
4480                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4481                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4482             }\r
4483 \r
4484             while(piecesLeft[(int)WhiteKing]) {\r
4485                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4486                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4487             }\r
4488 \r
4489 \r
4490         } else {\r
4491             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);\r
4492             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);\r
4493         }\r
4494 \r
4495         // Only Rooks can be left; simply place them all\r
4496         while(piecesLeft[(int)WhiteRook]) {\r
4497                 i = put(board, WhiteRook, 0, 0, ANY);\r
4498                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights\r
4499                         if(first) {\r
4500                                 first=0;\r
4501                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;\r
4502                         }\r
4503                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;\r
4504                 }\r
4505         }\r
4506         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white\r
4507             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;\r
4508         }\r
4509 \r
4510         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize\r
4511 }\r
4512 \r
4513 #endif\r
4514 \r
4515 int SetCharTable( char *table, const char * map )\r
4516 /* [HGM] moved here from winboard.c because of its general usefulness */\r
4517 /*       Basically a safe strcpy that uses the last character as King */\r
4518 {\r
4519     int result = FALSE; int NrPieces;\r
4520 \r
4521     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
4522                     && NrPieces >= 12 && !(NrPieces&1)) {\r
4523         int i; /* [HGM] Accept even length from 12 to 34 */\r
4524 \r
4525         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
4526         for( i=0; i<NrPieces/2-1; i++ ) {\r
4527             table[i] = map[i];\r
4528             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
4529         }\r
4530         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
4531         table[(int) BlackKing]  = map[NrPieces-1];\r
4532 \r
4533         result = TRUE;\r
4534     }\r
4535 \r
4536     return result;\r
4537 }\r
4538 \r
4539 void Prelude(Board board)\r
4540 {       // [HGM] superchess: random selection of exo-pieces\r
4541         int i, j, k; ChessSquare p; \r
4542         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };\r
4543 \r
4544         GetPositionNumber(); // use FRC position number\r
4545 \r
4546         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table\r
4547             SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4548             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) \r
4549                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;\r
4550         }\r
4551 \r
4552         j = seed%4;                 seed /= 4; \r
4553         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4554         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4555         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4556         j = seed%3 + (seed%3 >= j); seed /= 3; \r
4557         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4558         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4559         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4560         j = seed%3;                 seed /= 3; \r
4561         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4562         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4563         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4564         j = seed%2 + (seed%2 >= j); seed /= 2; \r
4565         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4566         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4567         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4568         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);\r
4569         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);\r
4570         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);\r
4571         put(board, exoPieces[0],    0, 0, ANY);\r
4572         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];\r
4573 }\r
4574 \r
4575 void\r
4576 InitPosition(redraw)\r
4577      int redraw;\r
4578 {\r
4579     ChessSquare (* pieces)[BOARD_SIZE];\r
4580     int i, j, pawnRow, overrule,\r
4581     oldx = gameInfo.boardWidth,\r
4582     oldy = gameInfo.boardHeight,\r
4583     oldh = gameInfo.holdingsWidth,\r
4584     oldv = gameInfo.variant;\r
4585 \r
4586     currentMove = forwardMostMove = backwardMostMove = 0;\r
4587     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request\r
4588 \r
4589     /* [AS] Initialize pv info list [HGM] and game status */\r
4590     {\r
4591         for( i=0; i<MAX_MOVES; i++ ) {\r
4592             pvInfoList[i].depth = 0;\r
4593             epStatus[i]=EP_NONE;\r
4594             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
4595         }\r
4596 \r
4597         initialRulePlies = 0; /* 50-move counter start */\r
4598 \r
4599         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4600         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4601     }\r
4602 \r
4603     \r
4604     /* [HGM] logic here is completely changed. In stead of full positions */\r
4605     /* the initialized data only consist of the two backranks. The switch */\r
4606     /* selects which one we will use, which is than copied to the Board   */\r
4607     /* initialPosition, which for the rest is initialized by Pawns and    */\r
4608     /* empty squares. This initial position is then copied to boards[0],  */\r
4609     /* possibly after shuffling, so that it remains available.            */\r
4610 \r
4611     gameInfo.holdingsWidth = 0; /* default board sizes */\r
4612     gameInfo.boardWidth    = 8;\r
4613     gameInfo.boardHeight   = 8;\r
4614     gameInfo.holdingsSize  = 0;\r
4615     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
4616     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
4617     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
4618 \r
4619     switch (gameInfo.variant) {\r
4620     case VariantFischeRandom:\r
4621       shuffleOpenings = TRUE;\r
4622     default:\r
4623       pieces = FIDEArray;\r
4624       break;\r
4625     case VariantShatranj:\r
4626       pieces = ShatranjArray;\r
4627       nrCastlingRights = 0;\r
4628       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
4629       break;\r
4630     case VariantTwoKings:\r
4631       pieces = twoKingsArray;\r
4632       break;\r
4633     case VariantCapaRandom:\r
4634       shuffleOpenings = TRUE;\r
4635     case VariantCapablanca:\r
4636       pieces = CapablancaArray;\r
4637       gameInfo.boardWidth = 10;\r
4638       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4639       break;\r
4640     case VariantGothic:\r
4641       pieces = GothicArray;\r
4642       gameInfo.boardWidth = 10;\r
4643       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4644       break;\r
4645     case VariantJanus:\r
4646       pieces = JanusArray;\r
4647       gameInfo.boardWidth = 10;\r
4648       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
4649       nrCastlingRights = 6;\r
4650         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4651         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4652         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;\r
4653         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4654         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4655         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;\r
4656       break;\r
4657     case VariantFalcon:\r
4658       pieces = FalconArray;\r
4659       gameInfo.boardWidth = 10;\r
4660       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4661       break;\r
4662     case VariantXiangqi:\r
4663       pieces = XiangqiArray;\r
4664       gameInfo.boardWidth  = 9;\r
4665       gameInfo.boardHeight = 10;\r
4666       nrCastlingRights = 0;\r
4667       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4668       break;\r
4669     case VariantShogi:\r
4670       pieces = ShogiArray;\r
4671       gameInfo.boardWidth  = 9;\r
4672       gameInfo.boardHeight = 9;\r
4673       gameInfo.holdingsSize = 7;\r
4674       nrCastlingRights = 0;\r
4675       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4676       break;\r
4677     case VariantCourier:\r
4678       pieces = CourierArray;\r
4679       gameInfo.boardWidth  = 12;\r
4680       nrCastlingRights = 0;\r
4681       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4682       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4683       break;\r
4684     case VariantKnightmate:\r
4685       pieces = KnightmateArray;\r
4686       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4687       break;\r
4688     case VariantFairy:\r
4689       pieces = fairyArray;\r
4690       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4691       break;\r
4692     case VariantGreat:\r
4693       pieces = GreatArray;\r
4694       gameInfo.boardWidth = 10;\r
4695       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");\r
4696       gameInfo.holdingsSize = 8;\r
4697       break;\r
4698     case VariantSuper:\r
4699       pieces = FIDEArray;\r
4700       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
4701       gameInfo.holdingsSize = 8;\r
4702       startedFromSetupPosition = TRUE;\r
4703       break;\r
4704     case VariantCrazyhouse:\r
4705     case VariantBughouse:\r
4706       pieces = FIDEArray;\r
4707       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4708       gameInfo.holdingsSize = 5;\r
4709       break;\r
4710     case VariantWildCastle:\r
4711       pieces = FIDEArray;\r
4712       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4713       shuffleOpenings = 1;\r
4714       break;\r
4715     case VariantNoCastle:\r
4716       pieces = FIDEArray;\r
4717       nrCastlingRights = 0;\r
4718       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4719       /* !!?unconstrained back-rank shuffle */\r
4720       shuffleOpenings = 1;\r
4721       break;\r
4722     }\r
4723 \r
4724     overrule = 0;\r
4725     if(appData.NrFiles >= 0) {\r
4726         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4727         gameInfo.boardWidth = appData.NrFiles;\r
4728     }\r
4729     if(appData.NrRanks >= 0) {\r
4730         gameInfo.boardHeight = appData.NrRanks;\r
4731     }\r
4732     if(appData.holdingsSize >= 0) {\r
4733         i = appData.holdingsSize;\r
4734         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4735         gameInfo.holdingsSize = i;\r
4736     }\r
4737     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4738     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4739         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);\r
4740 \r
4741     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4742     if(pawnRow < 1) pawnRow = 1;\r
4743 \r
4744     /* User pieceToChar list overrules defaults */\r
4745     if(appData.pieceToCharTable != NULL)\r
4746         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4747 \r
4748     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4749 \r
4750         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4751             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4752         for( i=0; i<BOARD_HEIGHT; i++ )\r
4753             initialPosition[i][j] = s;\r
4754 \r
4755         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4756         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4757         initialPosition[pawnRow][j] = WhitePawn;\r
4758         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4759         if(gameInfo.variant == VariantXiangqi) {\r
4760             if(j&1) {\r
4761                 initialPosition[pawnRow][j] = \r
4762                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4763                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4764                    initialPosition[2][j] = WhiteCannon;\r
4765                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4766                 }\r
4767             }\r
4768         }\r
4769         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4770     }\r
4771     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4772 \r
4773             j=BOARD_LEFT+1;\r
4774             initialPosition[1][j] = WhiteBishop;\r
4775             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4776             j=BOARD_RGHT-2;\r
4777             initialPosition[1][j] = WhiteRook;\r
4778             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4779     }\r
4780 \r
4781     if( nrCastlingRights == -1) {\r
4782         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4783         /*       This sets default castling rights from none to normal corners   */\r
4784         /* Variants with other castling rights must set them themselves above    */\r
4785         nrCastlingRights = 6;\r
4786        \r
4787         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4788         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4789         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4790         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4791         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4792         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4793      }\r
4794 \r
4795      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
4796      if(gameInfo.variant == VariantGreat) { // promotion commoners\r
4797         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;\r
4798         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;\r
4799         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;\r
4800         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;\r
4801      }\r
4802 #if 0\r
4803     if(gameInfo.variant == VariantFischeRandom) {\r
4804       if( appData.defaultFrcPosition < 0 ) {\r
4805         ShuffleFRC( initialPosition );\r
4806       }\r
4807       else {\r
4808         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4809       }\r
4810       startedFromSetupPosition = TRUE;\r
4811     } else \r
4812 #else\r
4813   if (appData.debugMode) {\r
4814     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
4815   }\r
4816     if(shuffleOpenings) {\r
4817         SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
4818         startedFromSetupPosition = TRUE;\r
4819     }\r
4820 #endif\r
4821     if(startedFromPositionFile) {\r
4822       /* [HGM] loadPos: use PositionFile for every new game */\r
4823       CopyBoard(initialPosition, filePosition);\r
4824       for(i=0; i<nrCastlingRights; i++)\r
4825           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4826       startedFromSetupPosition = TRUE;\r
4827     }\r
4828 \r
4829     CopyBoard(boards[0], initialPosition);\r
4830 \r
4831     if(oldx != gameInfo.boardWidth ||\r
4832        oldy != gameInfo.boardHeight ||\r
4833        oldh != gameInfo.holdingsWidth\r
4834 #ifdef GOTHIC\r
4835        || oldv == VariantGothic ||        // For licensing popups\r
4836        gameInfo.variant == VariantGothic\r
4837 #endif\r
4838 #ifdef FALCON\r
4839        || oldv == VariantFalcon ||\r
4840        gameInfo.variant == VariantFalcon\r
4841 #endif\r
4842                                          )\r
4843             InitDrawingSizes(-2 ,0);\r
4844 \r
4845     if (redraw)\r
4846       DrawPosition(TRUE, boards[currentMove]);\r
4847 }\r
4848 \r
4849 void\r
4850 SendBoard(cps, moveNum)\r
4851      ChessProgramState *cps;\r
4852      int moveNum;\r
4853 {\r
4854     char message[MSG_SIZ];\r
4855     \r
4856     if (cps->useSetboard) {\r
4857       char* fen = PositionToFEN(moveNum, cps->useFEN960);\r
4858       sprintf(message, "setboard %s\n", fen);\r
4859       SendToProgram(message, cps);\r
4860       free(fen);\r
4861 \r
4862     } else {\r
4863       ChessSquare *bp;\r
4864       int i, j;\r
4865       /* Kludge to set black to move, avoiding the troublesome and now\r
4866        * deprecated "black" command.\r
4867        */\r
4868       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4869 \r
4870       SendToProgram("edit\n", cps);\r
4871       SendToProgram("#\n", cps);\r
4872       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4873         bp = &boards[moveNum][i][BOARD_LEFT];\r
4874         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4875           if ((int) *bp < (int) BlackPawn) {\r
4876             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4877                     AAA + j, ONE + i);\r
4878             if(message[0] == '+' || message[0] == '~') {\r
4879                 sprintf(message, "%c%c%c+\n",\r
4880                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4881                         AAA + j, ONE + i);\r
4882             }\r
4883             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4884                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4885                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4886             }\r
4887             SendToProgram(message, cps);\r
4888           }\r
4889         }\r
4890       }\r
4891     \r
4892       SendToProgram("c\n", cps);\r
4893       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4894         bp = &boards[moveNum][i][BOARD_LEFT];\r
4895         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4896           if (((int) *bp != (int) EmptySquare)\r
4897               && ((int) *bp >= (int) BlackPawn)) {\r
4898             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4899                     AAA + j, ONE + i);\r
4900             if(message[0] == '+' || message[0] == '~') {\r
4901                 sprintf(message, "%c%c%c+\n",\r
4902                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4903                         AAA + j, ONE + i);\r
4904             }\r
4905             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4906                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4907                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4908             }\r
4909             SendToProgram(message, cps);\r
4910           }\r
4911         }\r
4912       }\r
4913     \r
4914       SendToProgram(".\n", cps);\r
4915     }\r
4916     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4917 }\r
4918 \r
4919 int\r
4920 IsPromotion(fromX, fromY, toX, toY)\r
4921      int fromX, fromY, toX, toY;\r
4922 {\r
4923     /* [HGM] add Shogi promotions */\r
4924     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4925     ChessSquare piece;\r
4926 \r
4927     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4928       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4929    /* [HGM] Note to self: line above also weeds out drops */\r
4930     piece = boards[currentMove][fromY][fromX];\r
4931     if(gameInfo.variant == VariantShogi) {\r
4932         promotionZoneSize = 3;\r
4933         highestPromotingPiece = (int)WhiteKing;\r
4934         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4935            and if in normal chess we then allow promotion to King, why not\r
4936            allow promotion of other piece in Shogi?                         */\r
4937     }\r
4938     if((int)piece >= BlackPawn) {\r
4939         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4940              return FALSE;\r
4941         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4942     } else {\r
4943         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4944            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4945     }\r
4946     return ( (int)piece <= highestPromotingPiece );\r
4947 }\r
4948 \r
4949 int\r
4950 InPalace(row, column)\r
4951      int row, column;\r
4952 {   /* [HGM] for Xiangqi */\r
4953     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4954          column < (BOARD_WIDTH + 4)/2 &&\r
4955          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4956     return FALSE;\r
4957 }\r
4958 \r
4959 int\r
4960 PieceForSquare (x, y)\r
4961      int x;\r
4962      int y;\r
4963 {\r
4964   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
4965      return -1;\r
4966   else\r
4967      return boards[currentMove][y][x];\r
4968 }\r
4969 \r
4970 int\r
4971 OKToStartUserMove(x, y)\r
4972      int x, y;\r
4973 {\r
4974     ChessSquare from_piece;\r
4975     int white_piece;\r
4976 \r
4977     if (matchMode) return FALSE;\r
4978     if (gameMode == EditPosition) return TRUE;\r
4979 \r
4980     if (x >= 0 && y >= 0)\r
4981       from_piece = boards[currentMove][y][x];\r
4982     else\r
4983       from_piece = EmptySquare;\r
4984 \r
4985     if (from_piece == EmptySquare) return FALSE;\r
4986 \r
4987     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4988       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4989 \r
4990     switch (gameMode) {\r
4991       case PlayFromGameFile:\r
4992       case AnalyzeFile:\r
4993       case TwoMachinesPlay:\r
4994       case EndOfGame:\r
4995         return FALSE;\r
4996 \r
4997       case IcsObserving:\r
4998       case IcsIdle:\r
4999         return FALSE;\r
5000 \r
5001       case MachinePlaysWhite:\r
5002       case IcsPlayingBlack:\r
5003         if (appData.zippyPlay) return FALSE;\r
5004         if (white_piece) {\r
5005             DisplayMoveError(_("You are playing Black"));\r
5006             return FALSE;\r
5007         }\r
5008         break;\r
5009 \r
5010       case MachinePlaysBlack:\r
5011       case IcsPlayingWhite:\r
5012         if (appData.zippyPlay) return FALSE;\r
5013         if (!white_piece) {\r
5014             DisplayMoveError(_("You are playing White"));\r
5015             return FALSE;\r
5016         }\r
5017         break;\r
5018 \r
5019       case EditGame:\r
5020         if (!white_piece && WhiteOnMove(currentMove)) {\r
5021             DisplayMoveError(_("It is White's turn"));\r
5022             return FALSE;\r
5023         }           \r
5024         if (white_piece && !WhiteOnMove(currentMove)) {\r
5025             DisplayMoveError(_("It is Black's turn"));\r
5026             return FALSE;\r
5027         }           \r
5028         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
5029             /* Editing correspondence game history */\r
5030             /* Could disallow this or prompt for confirmation */\r
5031             cmailOldMove = -1;\r
5032         }\r
5033         if (currentMove < forwardMostMove) {\r
5034             /* Discarding moves */\r
5035             /* Could prompt for confirmation here,\r
5036                but I don't think that's such a good idea */\r
5037             forwardMostMove = currentMove;\r
5038         }\r
5039         break;\r
5040 \r
5041       case BeginningOfGame:\r
5042         if (appData.icsActive) return FALSE;\r
5043         if (!appData.noChessProgram) {\r
5044             if (!white_piece) {\r
5045                 DisplayMoveError(_("You are playing White"));\r
5046                 return FALSE;\r
5047             }\r
5048         }\r
5049         break;\r
5050         \r
5051       case Training:\r
5052         if (!white_piece && WhiteOnMove(currentMove)) {\r
5053             DisplayMoveError(_("It is White's turn"));\r
5054             return FALSE;\r
5055         }           \r
5056         if (white_piece && !WhiteOnMove(currentMove)) {\r
5057             DisplayMoveError(_("It is Black's turn"));\r
5058             return FALSE;\r
5059         }           \r
5060         break;\r
5061 \r
5062       default:\r
5063       case IcsExamining:\r
5064         break;\r
5065     }\r
5066     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
5067         && gameMode != AnalyzeFile && gameMode != Training) {\r
5068         DisplayMoveError(_("Displayed position is not current"));\r
5069         return FALSE;\r
5070     }\r
5071     return TRUE;\r
5072 }\r
5073 \r
5074 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
5075 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
5076 int lastLoadGameUseList = FALSE;\r
5077 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
5078 ChessMove lastLoadGameStart = (ChessMove) 0;\r
5079 \r
5080 \r
5081 ChessMove\r
5082 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
5083      int fromX, fromY, toX, toY;\r
5084      int promoChar;\r
5085 {\r
5086     ChessMove moveType;\r
5087     ChessSquare pdown, pup;\r
5088 \r
5089     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
5090     if ((fromX == toX) && (fromY == toY)) {\r
5091         return ImpossibleMove;\r
5092     }\r
5093 \r
5094     /* [HGM] suppress all moves into holdings area and guard band */\r
5095     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
5096             return ImpossibleMove;\r
5097 \r
5098     /* [HGM] <sameColor> moved to here from winboard.c */\r
5099     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
5100     pdown = boards[currentMove][fromY][fromX];\r
5101     pup = boards[currentMove][toY][toX];\r
5102     if (    gameMode != EditPosition &&\r
5103             (WhitePawn <= pdown && pdown < BlackPawn &&\r
5104              WhitePawn <= pup && pup < BlackPawn  ||\r
5105              BlackPawn <= pdown && pdown < EmptySquare &&\r
5106              BlackPawn <= pup && pup < EmptySquare \r
5107             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
5108                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
5109                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
5110         )           )\r
5111          return ImpossibleMove;\r
5112 \r
5113     /* Check if the user is playing in turn.  This is complicated because we\r
5114        let the user "pick up" a piece before it is his turn.  So the piece he\r
5115        tried to pick up may have been captured by the time he puts it down!\r
5116        Therefore we use the color the user is supposed to be playing in this\r
5117        test, not the color of the piece that is currently on the starting\r
5118        square---except in EditGame mode, where the user is playing both\r
5119        sides; fortunately there the capture race can't happen.  (It can\r
5120        now happen in IcsExamining mode, but that's just too bad.  The user\r
5121        will get a somewhat confusing message in that case.)\r
5122        */\r
5123 \r
5124     switch (gameMode) {\r
5125       case PlayFromGameFile:\r
5126       case AnalyzeFile:\r
5127       case TwoMachinesPlay:\r
5128       case EndOfGame:\r
5129       case IcsObserving:\r
5130       case IcsIdle:\r
5131         /* We switched into a game mode where moves are not accepted,\r
5132            perhaps while the mouse button was down. */\r
5133         return ImpossibleMove;\r
5134 \r
5135       case MachinePlaysWhite:\r
5136         /* User is moving for Black */\r
5137         if (WhiteOnMove(currentMove)) {\r
5138             DisplayMoveError(_("It is White's turn"));\r
5139             return ImpossibleMove;\r
5140         }\r
5141         break;\r
5142 \r
5143       case MachinePlaysBlack:\r
5144         /* User is moving for White */\r
5145         if (!WhiteOnMove(currentMove)) {\r
5146             DisplayMoveError(_("It is Black's turn"));\r
5147             return ImpossibleMove;\r
5148         }\r
5149         break;\r
5150 \r
5151       case EditGame:\r
5152       case IcsExamining:\r
5153       case BeginningOfGame:\r
5154       case AnalyzeMode:\r
5155       case Training:\r
5156         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
5157             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
5158             /* User is moving for Black */\r
5159             if (WhiteOnMove(currentMove)) {\r
5160                 DisplayMoveError(_("It is White's turn"));\r
5161                 return ImpossibleMove;\r
5162             }\r
5163         } else {\r
5164             /* User is moving for White */\r
5165             if (!WhiteOnMove(currentMove)) {\r
5166                 DisplayMoveError(_("It is Black's turn"));\r
5167                 return ImpossibleMove;\r
5168             }\r
5169         }\r
5170         break;\r
5171 \r
5172       case IcsPlayingBlack:\r
5173         /* User is moving for Black */\r
5174         if (WhiteOnMove(currentMove)) {\r
5175             if (!appData.premove) {\r
5176                 DisplayMoveError(_("It is White's turn"));\r
5177             } else if (toX >= 0 && toY >= 0) {\r
5178                 premoveToX = toX;\r
5179                 premoveToY = toY;\r
5180                 premoveFromX = fromX;\r
5181                 premoveFromY = fromY;\r
5182                 premovePromoChar = promoChar;\r
5183                 gotPremove = 1;\r
5184                 if (appData.debugMode) \r
5185                     fprintf(debugFP, "Got premove: fromX %d,"\r
5186                             "fromY %d, toX %d, toY %d\n",\r
5187                             fromX, fromY, toX, toY);\r
5188             }\r
5189             return ImpossibleMove;\r
5190         }\r
5191         break;\r
5192 \r
5193       case IcsPlayingWhite:\r
5194         /* User is moving for White */\r
5195         if (!WhiteOnMove(currentMove)) {\r
5196             if (!appData.premove) {\r
5197                 DisplayMoveError(_("It is Black's turn"));\r
5198             } else if (toX >= 0 && toY >= 0) {\r
5199                 premoveToX = toX;\r
5200                 premoveToY = toY;\r
5201                 premoveFromX = fromX;\r
5202                 premoveFromY = fromY;\r
5203                 premovePromoChar = promoChar;\r
5204                 gotPremove = 1;\r
5205                 if (appData.debugMode) \r
5206                     fprintf(debugFP, "Got premove: fromX %d,"\r
5207                             "fromY %d, toX %d, toY %d\n",\r
5208                             fromX, fromY, toX, toY);\r
5209             }\r
5210             return ImpossibleMove;\r
5211         }\r
5212         break;\r
5213 \r
5214       default:\r
5215         break;\r
5216 \r
5217       case EditPosition:\r
5218         /* EditPosition, empty square, or different color piece;\r
5219            click-click move is possible */\r
5220         if (toX == -2 || toY == -2) {\r
5221             boards[0][fromY][fromX] = EmptySquare;\r
5222             return AmbiguousMove;\r
5223         } else if (toX >= 0 && toY >= 0) {\r
5224             boards[0][toY][toX] = boards[0][fromY][fromX];\r
5225             boards[0][fromY][fromX] = EmptySquare;\r
5226             return AmbiguousMove;\r
5227         }\r
5228         return ImpossibleMove;\r
5229     }\r
5230 \r
5231     /* [HGM] If move started in holdings, it means a drop */\r
5232     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
5233          if( pup != EmptySquare ) return ImpossibleMove;\r
5234          if(appData.testLegality) {\r
5235              /* it would be more logical if LegalityTest() also figured out\r
5236               * which drops are legal. For now we forbid pawns on back rank.\r
5237               * Shogi is on its own here...\r
5238               */\r
5239              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
5240                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
5241                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
5242          }\r
5243          return WhiteDrop; /* Not needed to specify white or black yet */\r
5244     }\r
5245 \r
5246     userOfferedDraw = FALSE;\r
5247         \r
5248     /* [HGM] always test for legality, to get promotion info */\r
5249     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
5250                           epStatus[currentMove], castlingRights[currentMove],\r
5251                                          fromY, fromX, toY, toX, promoChar);\r
5252 \r
5253     /* [HGM] but possibly ignore an IllegalMove result */\r
5254     if (appData.testLegality) {\r
5255         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
5256             DisplayMoveError(_("Illegal move"));\r
5257             return ImpossibleMove;\r
5258         }\r
5259     }\r
5260 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);\r
5261     return moveType;\r
5262     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
5263        function is made into one that returns an OK move type if FinishMove\r
5264        should be called. This to give the calling driver routine the\r
5265        opportunity to finish the userMove input with a promotion popup,\r
5266        without bothering the user with this for invalid or illegal moves */\r
5267 \r
5268 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
5269 }\r
5270 \r
5271 /* Common tail of UserMoveEvent and DropMenuEvent */\r
5272 int\r
5273 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
5274      ChessMove moveType;\r
5275      int fromX, fromY, toX, toY;\r
5276      /*char*/int promoChar;\r
5277 {\r
5278     char *bookHit = 0;\r
5279 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);\r
5280     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { \r
5281         // [HGM] superchess: suppress promotions to non-available piece\r
5282         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5283         if(WhiteOnMove(currentMove)) {\r
5284             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;\r
5285         } else {\r
5286             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;\r
5287         }\r
5288     }\r
5289 \r
5290     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion\r
5291        move type in caller when we know the move is a legal promotion */\r
5292     if(moveType == NormalMove && promoChar)\r
5293         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
5294 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);\r
5295     /* [HGM] convert drag-and-drop piece drops to standard form */\r
5296     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
5297          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
5298          fromX = boards[currentMove][fromY][fromX];\r
5299          fromY = DROP_RANK;\r
5300     }\r
5301 \r
5302     /* [HGM] <popupFix> The following if has been moved here from\r
5303        UserMoveEvent(). Because it seemed to belon here (why not allow\r
5304        piece drops in training games?), and because it can only be\r
5305        performed after it is known to what we promote. */\r
5306     if (gameMode == Training) {\r
5307       /* compare the move played on the board to the next move in the\r
5308        * game. If they match, display the move and the opponent's response. \r
5309        * If they don't match, display an error message.\r
5310        */\r
5311       int saveAnimate;\r
5312       Board testBoard;\r
5313       CopyBoard(testBoard, boards[currentMove]);\r
5314       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
5315 \r
5316       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
5317         ForwardInner(currentMove+1);\r
5318 \r
5319         /* Autoplay the opponent's response.\r
5320          * if appData.animate was TRUE when Training mode was entered,\r
5321          * the response will be animated.\r
5322          */\r
5323         saveAnimate = appData.animate;\r
5324         appData.animate = animateTraining;\r
5325         ForwardInner(currentMove+1);\r
5326         appData.animate = saveAnimate;\r
5327 \r
5328         /* check for the end of the game */\r
5329         if (currentMove >= forwardMostMove) {\r
5330           gameMode = PlayFromGameFile;\r
5331           ModeHighlight();\r
5332           SetTrainingModeOff();\r
5333           DisplayInformation(_("End of game"));\r
5334         }\r
5335       } else {\r
5336         DisplayError(_("Incorrect move"), 0);\r
5337       }\r
5338       return 1;\r
5339     }\r
5340 \r
5341   /* Ok, now we know that the move is good, so we can kill\r
5342      the previous line in Analysis Mode */\r
5343   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
5344     forwardMostMove = currentMove;\r
5345   }\r
5346 \r
5347   /* If we need the chess program but it's dead, restart it */\r
5348   ResurrectChessProgram();\r
5349 \r
5350   /* A user move restarts a paused game*/\r
5351   if (pausing)\r
5352     PauseEvent();\r
5353 \r
5354   thinkOutput[0] = NULLCHAR;\r
5355 \r
5356   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
5357 \r
5358     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) \r
5359                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
5360         // [HGM] superchess: take promotion piece out of holdings\r
5361         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5362         if(WhiteOnMove(forwardMostMove-1)) {\r
5363             if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])\r
5364                 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;\r
5365         } else {\r
5366             if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])\r
5367                 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;\r
5368         }\r
5369     }\r
5370 \r
5371   if (gameMode == BeginningOfGame) {\r
5372     if (appData.noChessProgram) {\r
5373       gameMode = EditGame;\r
5374       SetGameInfo();\r
5375     } else {\r
5376       char buf[MSG_SIZ];\r
5377       gameMode = MachinePlaysBlack;\r
5378       StartClocks();\r
5379       SetGameInfo();\r
5380       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
5381       DisplayTitle(buf);\r
5382       if (first.sendName) {\r
5383         sprintf(buf, "name %s\n", gameInfo.white);\r
5384         SendToProgram(buf, &first);\r
5385       }\r
5386       StartClocks();\r
5387     }\r
5388     ModeHighlight();\r
5389   }\r
5390 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);\r
5391   /* Relay move to ICS or chess engine */\r
5392   if (appData.icsActive) {\r
5393     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
5394         gameMode == IcsExamining) {\r
5395       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5396       ics_user_moved = 1;\r
5397     }\r
5398   } else {\r
5399     if (first.sendTime && (gameMode == BeginningOfGame ||\r
5400                            gameMode == MachinePlaysWhite ||\r
5401                            gameMode == MachinePlaysBlack)) {\r
5402       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
5403     }\r
5404     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
5405          // [HGM] book: if program might be playing, let it use book\r
5406         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);\r
5407         first.maybeThinking = TRUE;\r
5408     } else SendMoveToProgram(forwardMostMove-1, &first);\r
5409     if (currentMove == cmailOldMove + 1) {\r
5410       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
5411     }\r
5412   }\r
5413 \r
5414   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5415 \r
5416   switch (gameMode) {\r
5417   case EditGame:\r
5418     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
5419                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
5420     case MT_NONE:\r
5421     case MT_CHECK:\r
5422       break;\r
5423     case MT_CHECKMATE:\r
5424       if (WhiteOnMove(currentMove)) {\r
5425         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
5426       } else {\r
5427         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
5428       }\r
5429       break;\r
5430     case MT_STALEMATE:\r
5431       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
5432       break;\r
5433     }\r
5434     break;\r
5435     \r
5436   case MachinePlaysBlack:\r
5437   case MachinePlaysWhite:\r
5438     /* disable certain menu options while machine is thinking */\r
5439     SetMachineThinkingEnables();\r
5440     break;\r
5441 \r
5442   default:\r
5443     break;\r
5444   }\r
5445 \r
5446   if(bookHit) { // [HGM] book: simulate book reply\r
5447         static char bookMove[MSG_SIZ]; // a bit generous?\r
5448 \r
5449         programStats.nodes = programStats.depth = programStats.time = \r
5450         programStats.score = programStats.got_only_move = 0;\r
5451         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
5452 \r
5453         strcpy(bookMove, "move ");\r
5454         strcat(bookMove, bookHit);\r
5455         HandleMachineMove(bookMove, &first);\r
5456   }\r
5457   return 1;\r
5458 }\r
5459 \r
5460 void\r
5461 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
5462      int fromX, fromY, toX, toY;\r
5463      int promoChar;\r
5464 {\r
5465     /* [HGM] This routine was added to allow calling of its two logical\r
5466        parts from other modules in the old way. Before, UserMoveEvent()\r
5467        automatically called FinishMove() if the move was OK, and returned\r
5468        otherwise. I separated the two, in order to make it possible to\r
5469        slip a promotion popup in between. But that it always needs two\r
5470        calls, to the first part, (now called UserMoveTest() ), and to\r
5471        FinishMove if the first part succeeded. Calls that do not need\r
5472        to do anything in between, can call this routine the old way. \r
5473     */\r
5474     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
5475 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);\r
5476     if(moveType != ImpossibleMove)\r
5477         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
5478 }\r
5479 \r
5480 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
5481 {\r
5482 //    char * hint = lastHint;\r
5483     FrontEndProgramStats stats;\r
5484 \r
5485     stats.which = cps == &first ? 0 : 1;\r
5486     stats.depth = cpstats->depth;\r
5487     stats.nodes = cpstats->nodes;\r
5488     stats.score = cpstats->score;\r
5489     stats.time = cpstats->time;\r
5490     stats.pv = cpstats->movelist;\r
5491     stats.hint = lastHint;\r
5492     stats.an_move_index = 0;\r
5493     stats.an_move_count = 0;\r
5494 \r
5495     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
5496         stats.hint = cpstats->move_name;\r
5497         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
5498         stats.an_move_count = cpstats->nr_moves;\r
5499     }\r
5500 \r
5501     SetProgramStats( &stats );\r
5502 }\r
5503 \r
5504 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)\r
5505 {   // [HGM] book: this routine intercepts moves to simulate book replies\r
5506     char *bookHit = NULL;\r
5507 \r
5508     //first determine if the incoming move brings opponent into his book\r
5509     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
5510         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
5511     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");\r
5512     if(bookHit != NULL && !cps->bookSuspend) {\r
5513         // make sure opponent is not going to reply after receiving move to book position\r
5514         SendToProgram("force\n", cps);\r
5515         cps->bookSuspend = TRUE; // flag indicating it has to be restarted\r
5516     }\r
5517     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move\r
5518     // now arrange restart after book miss\r
5519     if(bookHit) {\r
5520         // after a book hit we never send 'go', and the code after the call to this routine\r
5521         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').\r
5522         char buf[MSG_SIZ];\r
5523         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(\r
5524         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it\r
5525         SendToProgram(buf, cps);\r
5526         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'\r
5527     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine\r
5528         SendToProgram("go\n", cps);\r
5529         cps->bookSuspend = FALSE; // after a 'go' we are never suspended\r
5530     } else { // 'go' might be sent based on 'firstMove' after this routine returns\r
5531         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return\r
5532             SendToProgram("go\n", cps); \r
5533         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss\r
5534     }\r
5535     return bookHit; // notify caller of hit, so it can take action to send move to opponent\r
5536 }\r
5537 \r
5538 char *savedMessage;\r
5539 ChessProgramState *savedState;\r
5540 void DeferredBookMove(void)\r
5541 {\r
5542         if(savedState->lastPing != savedState->lastPong)\r
5543                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
5544         else\r
5545         HandleMachineMove(savedMessage, savedState);\r
5546 }\r
5547 \r
5548 void\r
5549 HandleMachineMove(message, cps)\r
5550      char *message;\r
5551      ChessProgramState *cps;\r
5552 {\r
5553     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
5554     char realname[MSG_SIZ];\r
5555     int fromX, fromY, toX, toY;\r
5556     ChessMove moveType;\r
5557     char promoChar;\r
5558     char *p;\r
5559     int machineWhite;\r
5560     char *bookHit;\r
5561 \r
5562 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit\r
5563     /*\r
5564      * Kludge to ignore BEL characters\r
5565      */\r
5566     while (*message == '\007') message++;\r
5567 \r
5568     /*\r
5569      * [HGM] engine debug message: ignore lines starting with '#' character\r
5570      */\r
5571     if(cps->debug && *message == '#') return;\r
5572 \r
5573     /*\r
5574      * Look for book output\r
5575      */\r
5576     if (cps == &first && bookRequested) {\r
5577         if (message[0] == '\t' || message[0] == ' ') {\r
5578             /* Part of the book output is here; append it */\r
5579             strcat(bookOutput, message);\r
5580             strcat(bookOutput, "  \n");\r
5581             return;\r
5582         } else if (bookOutput[0] != NULLCHAR) {\r
5583             /* All of book output has arrived; display it */\r
5584             char *p = bookOutput;\r
5585             while (*p != NULLCHAR) {\r
5586                 if (*p == '\t') *p = ' ';\r
5587                 p++;\r
5588             }\r
5589             DisplayInformation(bookOutput);\r
5590             bookRequested = FALSE;\r
5591             /* Fall through to parse the current output */\r
5592         }\r
5593     }\r
5594 \r
5595     /*\r
5596      * Look for machine move.\r
5597      */\r
5598     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
5599         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
5600     {\r
5601         /* This method is only useful on engines that support ping */\r
5602         if (cps->lastPing != cps->lastPong) {\r
5603           if (gameMode == BeginningOfGame) {\r
5604             /* Extra move from before last new; ignore */\r
5605             if (appData.debugMode) {\r
5606                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5607             }\r
5608           } else {\r
5609             if (appData.debugMode) {\r
5610                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5611                         cps->which, gameMode);\r
5612             }\r
5613 \r
5614             SendToProgram("undo\n", cps);\r
5615           }\r
5616           return;\r
5617         }\r
5618 \r
5619         switch (gameMode) {\r
5620           case BeginningOfGame:\r
5621             /* Extra move from before last reset; ignore */\r
5622             if (appData.debugMode) {\r
5623                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5624             }\r
5625             return;\r
5626 \r
5627           case EndOfGame:\r
5628           case IcsIdle:\r
5629           default:\r
5630             /* Extra move after we tried to stop.  The mode test is\r
5631                not a reliable way of detecting this problem, but it's\r
5632                the best we can do on engines that don't support ping.\r
5633             */\r
5634             if (appData.debugMode) {\r
5635                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5636                         cps->which, gameMode);\r
5637             }\r
5638             SendToProgram("undo\n", cps);\r
5639             return;\r
5640 \r
5641           case MachinePlaysWhite:\r
5642           case IcsPlayingWhite:\r
5643             machineWhite = TRUE;\r
5644             break;\r
5645 \r
5646           case MachinePlaysBlack:\r
5647           case IcsPlayingBlack:\r
5648             machineWhite = FALSE;\r
5649             break;\r
5650 \r
5651           case TwoMachinesPlay:\r
5652             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5653             break;\r
5654         }\r
5655         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5656             if (appData.debugMode) {\r
5657                 fprintf(debugFP,\r
5658                         "Ignoring move out of turn by %s, gameMode %d"\r
5659                         ", forwardMost %d\n",\r
5660                         cps->which, gameMode, forwardMostMove);\r
5661             }\r
5662             return;\r
5663         }\r
5664 \r
5665     if (appData.debugMode) { int f = forwardMostMove;\r
5666         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5667                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5668     }\r
5669         if(cps->alphaRank) AlphaRank(machineMove, 4);\r
5670         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5671                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5672             /* Machine move could not be parsed; ignore it. */\r
5673             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),\r
5674                     machineMove, cps->which);\r
5675             DisplayError(buf1, 0);\r
5676             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",\r
5677                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);\r
5678             if (gameMode == TwoMachinesPlay) {\r
5679               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5680                        buf1, GE_XBOARD);\r
5681             }\r
5682             return;\r
5683         }\r
5684 \r
5685         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5686         /* So we have to redo legality test with true e.p. status here,  */\r
5687         /* to make sure an illegal e.p. capture does not slip through,   */\r
5688         /* to cause a forfeit on a justified illegal-move complaint      */\r
5689         /* of the opponent.                                              */\r
5690         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5691             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5692                                                               ) {\r
5693            ChessMove moveType;\r
5694            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5695                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5696                              fromY, fromX, toY, toX, promoChar);\r
5697             if (appData.debugMode) {\r
5698                 int i;\r
5699                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5700                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5701                 fprintf(debugFP, "castling rights\n");\r
5702             }\r
5703             if(moveType == IllegalMove) {\r
5704                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5705                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5706                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5707                            buf1, GE_XBOARD);\r
5708            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
5709            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5710               when they shouldn't (like TSCP-Gothic) */\r
5711            switch(moveType) {\r
5712              case WhiteASideCastleFR:\r
5713              case BlackASideCastleFR:\r
5714                toX+=2;\r
5715                currentMoveString[2]++;\r
5716                break;\r
5717              case WhiteHSideCastleFR:\r
5718              case BlackHSideCastleFR:\r
5719                toX--;\r
5720                currentMoveString[2]--;\r
5721                break;\r
5722              default: ; // nothing to do, but suppresses warning of pedantic compilers\r
5723            }\r
5724         }\r
5725         hintRequested = FALSE;\r
5726         lastHint[0] = NULLCHAR;\r
5727         bookRequested = FALSE;\r
5728         /* Program may be pondering now */\r
5729         cps->maybeThinking = TRUE;\r
5730         if (cps->sendTime == 2) cps->sendTime = 1;\r
5731         if (cps->offeredDraw) cps->offeredDraw--;\r
5732 \r
5733 #if ZIPPY\r
5734         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5735             first.initDone) {\r
5736           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5737           ics_user_moved = 1;\r
5738           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
5739                 char buf[3*MSG_SIZ];\r
5740 \r
5741                 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",\r
5742                         programStats.depth,\r
5743                         programStats.score / 100.,\r
5744                         programStats.time / 100.,\r
5745                         u64ToDouble(programStats.nodes),\r
5746                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),\r
5747                         programStats.movelist);\r
5748                 SendToICS(buf);\r
5749           }\r
5750         }\r
5751 #endif\r
5752         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5753         strcpy(machineMove, currentMoveString);\r
5754         strcat(machineMove, "\n");\r
5755         strcpy(moveList[forwardMostMove], machineMove);\r
5756 \r
5757         /* [AS] Save move info and clear stats for next move */\r
5758         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5759         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5760         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5761         ClearProgramStats();\r
5762         thinkOutput[0] = NULLCHAR;\r
5763         hiddenThinkOutputState = 0;\r
5764 \r
5765         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5766 \r
5767         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5768         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5769             int count = 0;\r
5770 \r
5771             while( count < adjudicateLossPlies ) {\r
5772                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5773 \r
5774                 if( count & 1 ) {\r
5775                     score = -score; /* Flip score for winning side */\r
5776                 }\r
5777 \r
5778                 if( score > adjudicateLossThreshold ) {\r
5779                     break;\r
5780                 }\r
5781 \r
5782                 count++;\r
5783             }\r
5784 \r
5785             if( count >= adjudicateLossPlies ) {\r
5786                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5787 \r
5788                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5789                     "Xboard adjudication", \r
5790                     GE_XBOARD );\r
5791 \r
5792                 return;\r
5793             }\r
5794         }\r
5795 \r
5796         if( gameMode == TwoMachinesPlay ) {\r
5797           // [HGM] some adjudications useful with buggy engines\r
5798             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
5799           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {\r
5800 \r
5801             if(appData.testLegality)\r
5802             // don't wait for engine to announce game end if we can judge ourselves\r
5803             switch (MateTest(boards[forwardMostMove],\r
5804                                  PosFlags(forwardMostMove), epFile,\r
5805                                        castlingRights[forwardMostMove]) ) {\r
5806               case MT_NONE:\r
5807               case MT_CHECK:\r
5808               default:\r
5809                 break;\r
5810               case MT_STALEMATE:\r
5811                 epStatus[forwardMostMove] = EP_STALEMATE;\r
5812                 if(appData.checkMates) {\r
5813                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5814                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5815                     if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantSuicide\r
5816                                                          || gameInfo.variant == VariantGiveaway) // [HGM] losers:\r
5817                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, // stalemated side wins!\r
5818                                 "Xboard adjudication: Stalemate", GE_XBOARD );\r
5819                     else\r
5820                         GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate", GE_XBOARD );\r
5821                     return;\r
5822                 }\r
5823                 break;\r
5824               case MT_CHECKMATE:\r
5825                 epStatus[forwardMostMove] = EP_CHECKMATE;\r
5826                 if(appData.checkMates) {\r
5827                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5828                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5829                     GameEnds( WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers) // [HGM] losers:\r
5830                              ? BlackWins : WhiteWins,            // reverse the result ( A!=1 is !A for a boolean)\r
5831                              "Xboard adjudication: Checkmate", GE_XBOARD );\r
5832                     return;\r
5833                 }\r
5834                 break;\r
5835             }\r
5836 \r
5837             if( appData.testLegality )\r
5838             {   /* [HGM] Some more adjudications for obstinate engines */\r
5839                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5840                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,\r
5841                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;\r
5842                 static int moveCount = 6;\r
5843 \r
5844                 /* First absolutely insufficient mating material. Count what is on board. */\r
5845                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5846                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5847                     int m=i;\r
5848 \r
5849                     switch((int) p)\r
5850                     {   /* count B,N,R and other of each side */\r
5851                         case WhiteKing:\r
5852                         case BlackKing:\r
5853                              NrK++; break; // [HGM] atomic: count Kings\r
5854                         case WhiteKnight:\r
5855                              NrWN++; break;\r
5856                         case WhiteBishop:\r
5857                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5858                              bishopsColor |= 1 << ((i^j)&1);\r
5859                              NrWB++; break;\r
5860                         case BlackKnight:\r
5861                              NrBN++; break;\r
5862                         case BlackBishop:\r
5863                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5864                              bishopsColor |= 1 << ((i^j)&1);\r
5865                              NrBB++; break;\r
5866                         case WhiteRook:\r
5867                              NrWR++; break;\r
5868                         case BlackRook:\r
5869                              NrBR++; break;\r
5870                         case WhiteQueen:\r
5871                              NrWQ++; break;\r
5872                         case BlackQueen:\r
5873                              NrBQ++; break;\r
5874                         case EmptySquare: \r
5875                              break;\r
5876                         case BlackPawn:\r
5877                              m = 7-i;\r
5878                         case WhitePawn:\r
5879                              PawnAdvance += m; NrPawns++;\r
5880                     }\r
5881                     NrPieces += (p != EmptySquare);\r
5882                     NrW += ((int)p < (int)BlackPawn);\r
5883                     if(gameInfo.variant == VariantXiangqi && \r
5884                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {\r
5885                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces\r
5886                         NrW -= ((int)p < (int)BlackPawn);\r
5887                     }\r
5888                 }\r
5889 \r
5890                 if(gameInfo.variant == VariantAtomic && NrK < 2) {\r
5891                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal\r
5892                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated\r
5893                      if(appData.checkMates) {\r
5894                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move\r
5895                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5896                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5897                                                         "Xboard adjudication: King destroyed", GE_XBOARD );\r
5898                          return;\r
5899                      }\r
5900                 }\r
5901 \r
5902                 /* Bare King in Shatranj (loses) or Losers (wins) */\r
5903                 if( NrW == 1 || NrPieces - NrW == 1) {\r
5904                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)\r
5905                      epStatus[forwardMostMove] = EP_STALEMATE; // kludge to make position claimable as win\r
5906                      if(appData.checkMates) {\r
5907                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5908                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5909                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5910                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5911                          return;\r
5912                      }\r
5913                   } else\r
5914                   if( gameInfo.variant == VariantShatranj && --bare < 0)\r
5915                   {    /* bare King */\r
5916                         epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as win for stm\r
5917                         if(appData.checkMates) {\r
5918                             /* but only adjudicate if adjudication enabled */\r
5919                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move\r
5920                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5921                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
5922                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5923                             return;\r
5924                         }\r
5925                   }\r
5926                 } else bare = 1;\r
5927 \r
5928 \r
5929                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && \r
5930                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible\r
5931                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||\r
5932                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color\r
5933                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5934 \r
5935                      /* always flag draws, for judging claims */\r
5936                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5937 \r
5938                      if(appData.materialDraws) {\r
5939                          /* but only adjudicate them if adjudication enabled */\r
5940                          SendToProgram("force\n", cps->other); // suppress reply\r
5941                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */\r
5942                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5943                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5944                          return;\r
5945                      }\r
5946                 }\r
5947 \r
5948                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5949                 if(NrPieces == 4 && \r
5950                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5951                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5952                    || NrWN==2 || NrBN==2     /* KNNK */\r
5953                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5954                   ) ) {\r
5955                      if(--moveCount < 0 && appData.trivialDraws)\r
5956                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5957                           SendToProgram("force\n", cps->other); // suppress reply\r
5958                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5959                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5960                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5961                           return;\r
5962                      }\r
5963                 } else moveCount = 6;\r
5964             }\r
5965           }\r
5966 #if 1\r
5967     if (appData.debugMode) { int i;\r
5968       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5969               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5970               appData.drawRepeats);\r
5971       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5972            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5973 \r
5974     }\r
5975 #endif\r
5976                 /* Check for rep-draws */\r
5977                 count = 0;\r
5978                 for(k = forwardMostMove-2;\r
5979                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5980                         epStatus[k] < EP_UNKNOWN &&\r
5981                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5982                     k-=2)\r
5983                 {   int rights=0;\r
5984 #if 0\r
5985     if (appData.debugMode) {\r
5986       fprintf(debugFP, " loop\n");\r
5987     }\r
5988 #endif\r
5989                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5990 #if 0\r
5991     if (appData.debugMode) {\r
5992       fprintf(debugFP, "match\n");\r
5993     }\r
5994 #endif\r
5995                         /* compare castling rights */\r
5996                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5997                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5998                                 rights++; /* King lost rights, while rook still had them */\r
5999                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
6000                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
6001                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
6002                                    rights++; /* but at least one rook lost them */\r
6003                         }\r
6004                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
6005                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
6006                                 rights++; \r
6007                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
6008                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
6009                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
6010                                    rights++;\r
6011                         }\r
6012 #if 0\r
6013     if (appData.debugMode) {\r
6014       for(i=0; i<nrCastlingRights; i++)\r
6015       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
6016     }\r
6017 \r
6018     if (appData.debugMode) {\r
6019       fprintf(debugFP, " %d %d\n", rights, k);\r
6020     }\r
6021 #endif\r
6022                         if( rights == 0 && ++count > appData.drawRepeats-2\r
6023                             && appData.drawRepeats > 1) {\r
6024                              /* adjudicate after user-specified nr of repeats */\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                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { \r
6029                                 // [HGM] xiangqi: check for forbidden perpetuals\r
6030                                 int m, ourPerpetual = 1, hisPerpetual = 1;\r
6031                                 for(m=forwardMostMove; m>k; m-=2) {\r
6032                                     if(MateTest(boards[m], PosFlags(m), \r
6033                                                         EP_NONE, castlingRights[m]) != MT_CHECK)\r
6034                                         ourPerpetual = 0; // the current mover did not always check\r
6035                                     if(MateTest(boards[m-1], PosFlags(m-1), \r
6036                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)\r
6037                                         hisPerpetual = 0; // the opponent did not always check\r
6038                                 }\r
6039                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",\r
6040                                                                         ourPerpetual, hisPerpetual);\r
6041                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
6042                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
6043                                            "Xboard adjudication: perpetual checking", GE_XBOARD );\r
6044                                     return;\r
6045                                 }\r
6046                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet\r
6047                                     break; // (or we would have caught him before). Abort repetition-checking loop.\r
6048                                 // Now check for perpetual chases\r
6049                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase\r
6050                                     hisPerpetual = PerpetualChase(k, forwardMostMove);\r
6051                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);\r
6052                                     if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
6053                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
6054                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );\r
6055                                         return;\r
6056                                     }\r
6057                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet\r
6058                                         break; // Abort repetition-checking loop.\r
6059                                 }\r
6060                                 // if neither of us is checking or chasing all the time, or both are, it is draw\r
6061                              }\r
6062                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
6063                              return;\r
6064                         }\r
6065                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
6066                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
6067                     }\r
6068                 }\r
6069 \r
6070                 /* Now we test for 50-move draws. Determine ply count */\r
6071                 count = forwardMostMove;\r
6072                 /* look for last irreversble move */\r
6073                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
6074                     count--;\r
6075                 /* if we hit starting position, add initial plies */\r
6076                 if( count == backwardMostMove )\r
6077                     count -= initialRulePlies;\r
6078                 count = forwardMostMove - count; \r
6079                 if( count >= 100)\r
6080                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
6081                          /* this is used to judge if draw claims are legal */\r
6082                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
6083                          SendToProgram("force\n", cps->other); // suppress reply\r
6084                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6085                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6086                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
6087                          return;\r
6088                 }\r
6089 \r
6090                 /* if draw offer is pending, treat it as a draw claim\r
6091                  * when draw condition present, to allow engines a way to\r
6092                  * claim draws before making their move to avoid a race\r
6093                  * condition occurring after their move\r
6094                  */\r
6095                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
6096                          char *p = NULL;\r
6097                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
6098                              p = "Draw claim: 50-move rule";\r
6099                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
6100                              p = "Draw claim: 3-fold repetition";\r
6101                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
6102                              p = "Draw claim: insufficient mating material";\r
6103                          if( p != NULL ) {\r
6104                              SendToProgram("force\n", cps->other); // suppress reply\r
6105                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6106                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
6107                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6108                              return;\r
6109                          }\r
6110                 }\r
6111 \r
6112 \r
6113                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
6114                     SendToProgram("force\n", cps->other); // suppress reply\r
6115                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6116                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6117 \r
6118                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
6119 \r
6120                     return;\r
6121                 }\r
6122         }\r
6123 \r
6124         bookHit = NULL;\r
6125         if (gameMode == TwoMachinesPlay) {\r
6126             /* [HGM] relaying draw offers moved to after reception of move */\r
6127             /* and interpreting offer as claim if it brings draw condition */\r
6128             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
6129                 SendToProgram("draw\n", cps->other);\r
6130             }\r
6131             if (cps->other->sendTime) {\r
6132                 SendTimeRemaining(cps->other,\r
6133                                   cps->other->twoMachinesColor[0] == 'w');\r
6134             }\r
6135             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);\r
6136             if (firstMove && !bookHit) {\r
6137                 firstMove = FALSE;\r
6138                 if (cps->other->useColors) {\r
6139                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
6140                 }\r
6141                 SendToProgram("go\n", cps->other);\r
6142             }\r
6143             cps->other->maybeThinking = TRUE;\r
6144         }\r
6145 \r
6146         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6147         \r
6148         if (!pausing && appData.ringBellAfterMoves) {\r
6149             RingBell();\r
6150         }\r
6151 \r
6152         /* \r
6153          * Reenable menu items that were disabled while\r
6154          * machine was thinking\r
6155          */\r
6156         if (gameMode != TwoMachinesPlay)\r
6157             SetUserThinkingEnables();\r
6158 \r
6159         // [HGM] book: after book hit opponent has received move and is now in force mode\r
6160         // force the book reply into it, and then fake that it outputted this move by jumping\r
6161         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move\r
6162         if(bookHit) {\r
6163                 static char bookMove[MSG_SIZ]; // a bit generous?\r
6164 \r
6165                 strcpy(bookMove, "move ");\r
6166                 strcat(bookMove, bookHit);\r
6167                 message = bookMove;\r
6168                 cps = cps->other;\r
6169                 programStats.nodes = programStats.depth = programStats.time = \r
6170                 programStats.score = programStats.got_only_move = 0;\r
6171                 sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
6172 \r
6173                 if(cps->lastPing != cps->lastPong) {\r
6174                     savedMessage = message; // args for deferred call\r
6175                     savedState = cps;\r
6176                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
6177                     return;\r
6178                 }\r
6179                 goto FakeBookMove;\r
6180         }\r
6181 \r
6182         return;\r
6183     }\r
6184 \r
6185     /* Set special modes for chess engines.  Later something general\r
6186      *  could be added here; for now there is just one kludge feature,\r
6187      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
6188      *  when "xboard" is given as an interactive command.\r
6189      */\r
6190     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
6191         cps->useSigint = FALSE;\r
6192         cps->useSigterm = FALSE;\r
6193     }\r
6194 \r
6195     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
6196      * want this, I was asked to put it in, and obliged.\r
6197      */\r
6198     if (!strncmp(message, "setboard ", 9)) {\r
6199         Board initial_position; int i;\r
6200 \r
6201         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
6202 \r
6203         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
6204             DisplayError(_("Bad FEN received from engine"), 0);\r
6205             return ;\r
6206         } else {\r
6207            Reset(FALSE, FALSE);\r
6208            CopyBoard(boards[0], initial_position);\r
6209            initialRulePlies = FENrulePlies;\r
6210            epStatus[0] = FENepStatus;\r
6211            for( i=0; i<nrCastlingRights; i++ )\r
6212                 castlingRights[0][i] = FENcastlingRights[i];\r
6213            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
6214            else gameMode = MachinePlaysBlack;                 \r
6215            DrawPosition(FALSE, boards[currentMove]);\r
6216         }\r
6217         return;\r
6218     }\r
6219 \r
6220     /*\r
6221      * Look for communication commands\r
6222      */\r
6223     if (!strncmp(message, "telluser ", 9)) {\r
6224         DisplayNote(message + 9);\r
6225         return;\r
6226     }\r
6227     if (!strncmp(message, "tellusererror ", 14)) {\r
6228         DisplayError(message + 14, 0);\r
6229         return;\r
6230     }\r
6231     if (!strncmp(message, "tellopponent ", 13)) {\r
6232       if (appData.icsActive) {\r
6233         if (loggedOn) {\r
6234           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
6235           SendToICS(buf1);\r
6236         }\r
6237       } else {\r
6238         DisplayNote(message + 13);\r
6239       }\r
6240       return;\r
6241     }\r
6242     if (!strncmp(message, "tellothers ", 11)) {\r
6243       if (appData.icsActive) {\r
6244         if (loggedOn) {\r
6245           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
6246           SendToICS(buf1);\r
6247         }\r
6248       }\r
6249       return;\r
6250     }\r
6251     if (!strncmp(message, "tellall ", 8)) {\r
6252       if (appData.icsActive) {\r
6253         if (loggedOn) {\r
6254           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
6255           SendToICS(buf1);\r
6256         }\r
6257       } else {\r
6258         DisplayNote(message + 8);\r
6259       }\r
6260       return;\r
6261     }\r
6262     if (strncmp(message, "warning", 7) == 0) {\r
6263         /* Undocumented feature, use tellusererror in new code */\r
6264         DisplayError(message, 0);\r
6265         return;\r
6266     }\r
6267     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
6268         strcpy(realname, cps->tidy);\r
6269         strcat(realname, " query");\r
6270         AskQuestion(realname, buf2, buf1, cps->pr);\r
6271         return;\r
6272     }\r
6273     /* Commands from the engine directly to ICS.  We don't allow these to be \r
6274      *  sent until we are logged on. Crafty kibitzes have been known to \r
6275      *  interfere with the login process.\r
6276      */\r
6277     if (loggedOn) {\r
6278         if (!strncmp(message, "tellics ", 8)) {\r
6279             SendToICS(message + 8);\r
6280             SendToICS("\n");\r
6281             return;\r
6282         }\r
6283         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
6284             SendToICS(ics_prefix);\r
6285             SendToICS(message + 15);\r
6286             SendToICS("\n");\r
6287             return;\r
6288         }\r
6289         /* The following are for backward compatibility only */\r
6290         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
6291             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
6292             SendToICS(ics_prefix);\r
6293             SendToICS(message);\r
6294             SendToICS("\n");\r
6295             return;\r
6296         }\r
6297     }\r
6298     if (strncmp(message, "feature ", 8) == 0) {\r
6299       ParseFeatures(message+8, cps);\r
6300     }\r
6301     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
6302         return;\r
6303     }\r
6304     /*\r
6305      * If the move is illegal, cancel it and redraw the board.\r
6306      * Also deal with other error cases.  Matching is rather loose\r
6307      * here to accommodate engines written before the spec.\r
6308      */\r
6309     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
6310         strncmp(message, "Error", 5) == 0) {\r
6311         if (StrStr(message, "name") || \r
6312             StrStr(message, "rating") || StrStr(message, "?") ||\r
6313             StrStr(message, "result") || StrStr(message, "board") ||\r
6314             StrStr(message, "bk") || StrStr(message, "computer") ||\r
6315             StrStr(message, "variant") || StrStr(message, "hint") ||\r
6316             StrStr(message, "random") || StrStr(message, "depth") ||\r
6317             StrStr(message, "accepted")) {\r
6318             return;\r
6319         }\r
6320         if (StrStr(message, "protover")) {\r
6321           /* Program is responding to input, so it's apparently done\r
6322              initializing, and this error message indicates it is\r
6323              protocol version 1.  So we don't need to wait any longer\r
6324              for it to initialize and send feature commands. */\r
6325           FeatureDone(cps, 1);\r
6326           cps->protocolVersion = 1;\r
6327           return;\r
6328         }\r
6329         cps->maybeThinking = FALSE;\r
6330 \r
6331         if (StrStr(message, "draw")) {\r
6332             /* Program doesn't have "draw" command */\r
6333             cps->sendDrawOffers = 0;\r
6334             return;\r
6335         }\r
6336         if (cps->sendTime != 1 &&\r
6337             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
6338           /* Program apparently doesn't have "time" or "otim" command */\r
6339           cps->sendTime = 0;\r
6340           return;\r
6341         }\r
6342         if (StrStr(message, "analyze")) {\r
6343             cps->analysisSupport = FALSE;\r
6344             cps->analyzing = FALSE;\r
6345             Reset(FALSE, TRUE);\r
6346             sprintf(buf2, _("%s does not support analysis"), cps->tidy);\r
6347             DisplayError(buf2, 0);\r
6348             return;\r
6349         }\r
6350         if (StrStr(message, "(no matching move)st")) {\r
6351           /* Special kludge for GNU Chess 4 only */\r
6352           cps->stKludge = TRUE;\r
6353           SendTimeControl(cps, movesPerSession, timeControl,\r
6354                           timeIncrement, appData.searchDepth,\r
6355                           searchTime);\r
6356           return;\r
6357         }\r
6358         if (StrStr(message, "(no matching move)sd")) {\r
6359           /* Special kludge for GNU Chess 4 only */\r
6360           cps->sdKludge = TRUE;\r
6361           SendTimeControl(cps, movesPerSession, timeControl,\r
6362                           timeIncrement, appData.searchDepth,\r
6363                           searchTime);\r
6364           return;\r
6365         }\r
6366         if (!StrStr(message, "llegal")) {\r
6367             return;\r
6368         }\r
6369         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6370             gameMode == IcsIdle) return;\r
6371         if (forwardMostMove <= backwardMostMove) return;\r
6372 #if 0\r
6373         /* Following removed: it caused a bug where a real illegal move\r
6374            message in analyze mored would be ignored. */\r
6375         if (cps == &first && programStats.ok_to_send == 0) {\r
6376             /* Bogus message from Crafty responding to "."  This filtering\r
6377                can miss some of the bad messages, but fortunately the bug \r
6378                is fixed in current Crafty versions, so it doesn't matter. */\r
6379             return;\r
6380         }\r
6381 #endif\r
6382         if (pausing) PauseEvent();\r
6383         if (gameMode == PlayFromGameFile) {\r
6384             /* Stop reading this game file */\r
6385             gameMode = EditGame;\r
6386             ModeHighlight();\r
6387         }\r
6388         currentMove = --forwardMostMove;\r
6389         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
6390         SwitchClocks();\r
6391         DisplayBothClocks();\r
6392         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),\r
6393                 parseList[currentMove], cps->which);\r
6394         DisplayMoveError(buf1);\r
6395         DrawPosition(FALSE, boards[currentMove]);\r
6396 \r
6397         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
6398         /* only passes fully legal moves                            */\r
6399         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
6400             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
6401                                 "False illegal-move claim", GE_XBOARD );\r
6402         }\r
6403         return;\r
6404     }\r
6405     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
6406         /* Program has a broken "time" command that\r
6407            outputs a string not ending in newline.\r
6408            Don't use it. */\r
6409         cps->sendTime = 0;\r
6410     }\r
6411     \r
6412     /*\r
6413      * If chess program startup fails, exit with an error message.\r
6414      * Attempts to recover here are futile.\r
6415      */\r
6416     if ((StrStr(message, "unknown host") != NULL)\r
6417         || (StrStr(message, "No remote directory") != NULL)\r
6418         || (StrStr(message, "not found") != NULL)\r
6419         || (StrStr(message, "No such file") != NULL)\r
6420         || (StrStr(message, "can't alloc") != NULL)\r
6421         || (StrStr(message, "Permission denied") != NULL)) {\r
6422 \r
6423         cps->maybeThinking = FALSE;\r
6424         sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),\r
6425                 cps->which, cps->program, cps->host, message);\r
6426         RemoveInputSource(cps->isr);\r
6427         DisplayFatalError(buf1, 0, 1);\r
6428         return;\r
6429     }\r
6430     \r
6431     /* \r
6432      * Look for hint output\r
6433      */\r
6434     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
6435         if (cps == &first && hintRequested) {\r
6436             hintRequested = FALSE;\r
6437             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
6438                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
6439                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
6440                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
6441                                     fromY, fromX, toY, toX, promoChar, buf1);\r
6442                 sprintf(buf2, _("Hint: %s"), buf1);\r
6443                 DisplayInformation(buf2);\r
6444             } else {\r
6445                 /* Hint move could not be parsed!? */\r
6446                 sprintf(buf2,\r
6447                         _("Illegal hint move \"%s\"\nfrom %s chess program"),\r
6448                         buf1, cps->which);\r
6449                 DisplayError(buf2, 0);\r
6450             }\r
6451         } else {\r
6452             strcpy(lastHint, buf1);\r
6453         }\r
6454         return;\r
6455     }\r
6456 \r
6457     /*\r
6458      * Ignore other messages if game is not in progress\r
6459      */\r
6460     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6461         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
6462 \r
6463     /*\r
6464      * look for win, lose, draw, or draw offer\r
6465      */\r
6466     if (strncmp(message, "1-0", 3) == 0) {\r
6467         char *p, *q, *r = "";\r
6468         p = strchr(message, '{');\r
6469         if (p) {\r
6470             q = strchr(p, '}');\r
6471             if (q) {\r
6472                 *q = NULLCHAR;\r
6473                 r = p + 1;\r
6474             }\r
6475         }\r
6476         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
6477         return;\r
6478     } else if (strncmp(message, "0-1", 3) == 0) {\r
6479         char *p, *q, *r = "";\r
6480         p = strchr(message, '{');\r
6481         if (p) {\r
6482             q = strchr(p, '}');\r
6483             if (q) {\r
6484                 *q = NULLCHAR;\r
6485                 r = p + 1;\r
6486             }\r
6487         }\r
6488         /* Kludge for Arasan 4.1 bug */\r
6489         if (strcmp(r, "Black resigns") == 0) {\r
6490             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
6491             return;\r
6492         }\r
6493         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
6494         return;\r
6495     } else if (strncmp(message, "1/2", 3) == 0) {\r
6496         char *p, *q, *r = "";\r
6497         p = strchr(message, '{');\r
6498         if (p) {\r
6499             q = strchr(p, '}');\r
6500             if (q) {\r
6501                 *q = NULLCHAR;\r
6502                 r = p + 1;\r
6503             }\r
6504         }\r
6505             \r
6506         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
6507         return;\r
6508 \r
6509     } else if (strncmp(message, "White resign", 12) == 0) {\r
6510         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6511         return;\r
6512     } else if (strncmp(message, "Black resign", 12) == 0) {\r
6513         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6514         return;\r
6515     } else if (strncmp(message, "White matches", 13) == 0 ||\r
6516                strncmp(message, "Black matches", 13) == 0   ) {\r
6517         /* [HGM] ignore GNUShogi noises */\r
6518         return;\r
6519     } else if (strncmp(message, "White", 5) == 0 &&\r
6520                message[5] != '(' &&\r
6521                StrStr(message, "Black") == NULL) {\r
6522         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6523         return;\r
6524     } else if (strncmp(message, "Black", 5) == 0 &&\r
6525                message[5] != '(') {\r
6526         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6527         return;\r
6528     } else if (strcmp(message, "resign") == 0 ||\r
6529                strcmp(message, "computer resigns") == 0) {\r
6530         switch (gameMode) {\r
6531           case MachinePlaysBlack:\r
6532           case IcsPlayingBlack:\r
6533             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
6534             break;\r
6535           case MachinePlaysWhite:\r
6536           case IcsPlayingWhite:\r
6537             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
6538             break;\r
6539           case TwoMachinesPlay:\r
6540             if (cps->twoMachinesColor[0] == 'w')\r
6541               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6542             else\r
6543               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6544             break;\r
6545           default:\r
6546             /* can't happen */\r
6547             break;\r
6548         }\r
6549         return;\r
6550     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
6551         switch (gameMode) {\r
6552           case MachinePlaysBlack:\r
6553           case IcsPlayingBlack:\r
6554             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6555             break;\r
6556           case MachinePlaysWhite:\r
6557           case IcsPlayingWhite:\r
6558             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
6559             break;\r
6560           case TwoMachinesPlay:\r
6561             if (cps->twoMachinesColor[0] == 'w')\r
6562               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6563             else\r
6564               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6565             break;\r
6566           default:\r
6567             /* can't happen */\r
6568             break;\r
6569         }\r
6570         return;\r
6571     } else if (strncmp(message, "computer mates", 14) == 0) {\r
6572         switch (gameMode) {\r
6573           case MachinePlaysBlack:\r
6574           case IcsPlayingBlack:\r
6575             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
6576             break;\r
6577           case MachinePlaysWhite:\r
6578           case IcsPlayingWhite:\r
6579             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6580             break;\r
6581           case TwoMachinesPlay:\r
6582             if (cps->twoMachinesColor[0] == 'w')\r
6583               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6584             else\r
6585               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6586             break;\r
6587           default:\r
6588             /* can't happen */\r
6589             break;\r
6590         }\r
6591         return;\r
6592     } else if (strncmp(message, "checkmate", 9) == 0) {\r
6593         if (WhiteOnMove(forwardMostMove)) {\r
6594             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6595         } else {\r
6596             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6597         }\r
6598         return;\r
6599     } else if (strstr(message, "Draw") != NULL ||\r
6600                strstr(message, "game is a draw") != NULL) {\r
6601         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
6602         return;\r
6603     } else if (strstr(message, "offer") != NULL &&\r
6604                strstr(message, "draw") != NULL) {\r
6605 #if ZIPPY\r
6606         if (appData.zippyPlay && first.initDone) {\r
6607             /* Relay offer to ICS */\r
6608             SendToICS(ics_prefix);\r
6609             SendToICS("draw\n");\r
6610         }\r
6611 #endif\r
6612         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
6613         if (gameMode == TwoMachinesPlay) {\r
6614             if (cps->other->offeredDraw) {\r
6615                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6616             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
6617             /* until after we also have move, to see if it is really claim */\r
6618             }\r
6619 #if 0\r
6620               else {\r
6621                 if (cps->other->sendDrawOffers) {\r
6622                     SendToProgram("draw\n", cps->other);\r
6623                 }\r
6624             }\r
6625 #endif\r
6626         } else if (gameMode == MachinePlaysWhite ||\r
6627                    gameMode == MachinePlaysBlack) {\r
6628           if (userOfferedDraw) {\r
6629             DisplayInformation(_("Machine accepts your draw offer"));\r
6630             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6631           } else {\r
6632             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));\r
6633           }\r
6634         }\r
6635     }\r
6636 \r
6637     \r
6638     /*\r
6639      * Look for thinking output\r
6640      */\r
6641     if ( appData.showThinking // [HGM] thinking: test all options that cause this output\r
6642           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
6643                                 ) {\r
6644         int plylev, mvleft, mvtot, curscore, time;\r
6645         char mvname[MOVE_LEN];\r
6646         u64 nodes; // [DM]\r
6647         char plyext;\r
6648         int ignore = FALSE;\r
6649         int prefixHint = FALSE;\r
6650         mvname[0] = NULLCHAR;\r
6651 \r
6652         switch (gameMode) {\r
6653           case MachinePlaysBlack:\r
6654           case IcsPlayingBlack:\r
6655             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6656             break;\r
6657           case MachinePlaysWhite:\r
6658           case IcsPlayingWhite:\r
6659             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6660             break;\r
6661           case AnalyzeMode:\r
6662           case AnalyzeFile:\r
6663             break;\r
6664           case IcsObserving: /* [DM] icsEngineAnalyze */\r
6665             if (!appData.icsEngineAnalyze) ignore = TRUE;\r
6666             break;\r
6667           case TwoMachinesPlay:\r
6668             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
6669                 ignore = TRUE;\r
6670             }\r
6671             break;\r
6672           default:\r
6673             ignore = TRUE;\r
6674             break;\r
6675         }\r
6676 \r
6677         if (!ignore) {\r
6678             buf1[0] = NULLCHAR;\r
6679             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6680                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
6681 \r
6682                 if (plyext != ' ' && plyext != '\t') {\r
6683                     time *= 100;\r
6684                 }\r
6685 \r
6686                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6687                 if( cps->scoreIsAbsolute && \r
6688                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
6689                 {\r
6690                     curscore = -curscore;\r
6691                 }\r
6692 \r
6693 \r
6694                 programStats.depth = plylev;\r
6695                 programStats.nodes = nodes;\r
6696                 programStats.time = time;\r
6697                 programStats.score = curscore;\r
6698                 programStats.got_only_move = 0;\r
6699 \r
6700                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
6701                         int ticklen;\r
6702 \r
6703                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time\r
6704                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time\r
6705                         if(WhiteOnMove(forwardMostMove)) \r
6706                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
6707                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
6708                 }\r
6709 \r
6710                 /* Buffer overflow protection */\r
6711                 if (buf1[0] != NULLCHAR) {\r
6712                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
6713                         && appData.debugMode) {\r
6714                         fprintf(debugFP,\r
6715                                 "PV is too long; using the first %d bytes.\n",\r
6716                                 sizeof(programStats.movelist) - 1);\r
6717                     }\r
6718 \r
6719                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
6720                 } else {\r
6721                     sprintf(programStats.movelist, " no PV\n");\r
6722                 }\r
6723 \r
6724                 if (programStats.seen_stat) {\r
6725                     programStats.ok_to_send = 1;\r
6726                 }\r
6727 \r
6728                 if (strchr(programStats.movelist, '(') != NULL) {\r
6729                     programStats.line_is_book = 1;\r
6730                     programStats.nr_moves = 0;\r
6731                     programStats.moves_left = 0;\r
6732                 } else {\r
6733                     programStats.line_is_book = 0;\r
6734                 }\r
6735 \r
6736                 SendProgramStatsToFrontend( cps, &programStats );\r
6737 \r
6738                 /* \r
6739                     [AS] Protect the thinkOutput buffer from overflow... this\r
6740                     is only useful if buf1 hasn't overflowed first!\r
6741                 */\r
6742                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
6743                         plylev, \r
6744                         (gameMode == TwoMachinesPlay ?\r
6745                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
6746                         ((double) curscore) / 100.0,\r
6747                         prefixHint ? lastHint : "",\r
6748                         prefixHint ? " " : "" );\r
6749 \r
6750                 if( buf1[0] != NULLCHAR ) {\r
6751                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
6752 \r
6753                     if( strlen(buf1) > max_len ) {\r
6754                         if( appData.debugMode) {\r
6755                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
6756                         }\r
6757                         buf1[max_len+1] = '\0';\r
6758                     }\r
6759 \r
6760                     strcat( thinkOutput, buf1 );\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             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6771                 /* crafty (9.25+) says "(only move) <move>"\r
6772                  * if there is only 1 legal move\r
6773                  */\r
6774                 sscanf(p, "(only move) %s", buf1);\r
6775                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6776                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6777                 programStats.depth = 1;\r
6778                 programStats.nr_moves = 1;\r
6779                 programStats.moves_left = 1;\r
6780                 programStats.nodes = 1;\r
6781                 programStats.time = 1;\r
6782                 programStats.got_only_move = 1;\r
6783 \r
6784                 /* Not really, but we also use this member to\r
6785                    mean "line isn't going to change" (Crafty\r
6786                    isn't searching, so stats won't change) */\r
6787                 programStats.line_is_book = 1;\r
6788 \r
6789                 SendProgramStatsToFrontend( cps, &programStats );\r
6790                 \r
6791                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || \r
6792                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6793                     DisplayMove(currentMove - 1);\r
6794                     DisplayAnalysis();\r
6795                 }\r
6796                 return;\r
6797             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",\r
6798                               &time, &nodes, &plylev, &mvleft,\r
6799                               &mvtot, mvname) >= 5) {\r
6800                 /* The stat01: line is from Crafty (9.29+) in response\r
6801                    to the "." command */\r
6802                 programStats.seen_stat = 1;\r
6803                 cps->maybeThinking = TRUE;\r
6804 \r
6805                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6806                   return;\r
6807 \r
6808                 programStats.depth = plylev;\r
6809                 programStats.time = time;\r
6810                 programStats.nodes = nodes;\r
6811                 programStats.moves_left = mvleft;\r
6812                 programStats.nr_moves = mvtot;\r
6813                 strcpy(programStats.move_name, mvname);\r
6814                 programStats.ok_to_send = 1;\r
6815                 programStats.movelist[0] = '\0';\r
6816 \r
6817                 SendProgramStatsToFrontend( cps, &programStats );\r
6818 \r
6819                 DisplayAnalysis();\r
6820                 return;\r
6821 \r
6822             } else if (strncmp(message,"++",2) == 0) {\r
6823                 /* Crafty 9.29+ outputs this */\r
6824                 programStats.got_fail = 2;\r
6825                 return;\r
6826 \r
6827             } else if (strncmp(message,"--",2) == 0) {\r
6828                 /* Crafty 9.29+ outputs this */\r
6829                 programStats.got_fail = 1;\r
6830                 return;\r
6831 \r
6832             } else if (thinkOutput[0] != NULLCHAR &&\r
6833                        strncmp(message, "    ", 4) == 0) {\r
6834                 unsigned message_len;\r
6835 \r
6836                 p = message;\r
6837                 while (*p && *p == ' ') p++;\r
6838 \r
6839                 message_len = strlen( p );\r
6840 \r
6841                 /* [AS] Avoid buffer overflow */\r
6842                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6843                     strcat(thinkOutput, " ");\r
6844                     strcat(thinkOutput, p);\r
6845                 }\r
6846 \r
6847                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6848                     strcat(programStats.movelist, " ");\r
6849                     strcat(programStats.movelist, p);\r
6850                 }\r
6851 \r
6852                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||\r
6853                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6854                     DisplayMove(currentMove - 1);\r
6855                     DisplayAnalysis();\r
6856                 }\r
6857                 return;\r
6858             }\r
6859         }\r
6860         else {\r
6861             buf1[0] = NULLCHAR;\r
6862 \r
6863             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6864                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6865             {\r
6866                 ChessProgramStats cpstats;\r
6867 \r
6868                 if (plyext != ' ' && plyext != '\t') {\r
6869                     time *= 100;\r
6870                 }\r
6871 \r
6872                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6873                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6874                     curscore = -curscore;\r
6875                 }\r
6876 \r
6877                 cpstats.depth = plylev;\r
6878                 cpstats.nodes = nodes;\r
6879                 cpstats.time = time;\r
6880                 cpstats.score = curscore;\r
6881                 cpstats.got_only_move = 0;\r
6882                 cpstats.movelist[0] = '\0';\r
6883 \r
6884                 if (buf1[0] != NULLCHAR) {\r
6885                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6886                 }\r
6887 \r
6888                 cpstats.ok_to_send = 0;\r
6889                 cpstats.line_is_book = 0;\r
6890                 cpstats.nr_moves = 0;\r
6891                 cpstats.moves_left = 0;\r
6892 \r
6893                 SendProgramStatsToFrontend( cps, &cpstats );\r
6894             }\r
6895         }\r
6896     }\r
6897 }\r
6898 \r
6899 \r
6900 /* Parse a game score from the character string "game", and\r
6901    record it as the history of the current game.  The game\r
6902    score is NOT assumed to start from the standard position. \r
6903    The display is not updated in any way.\r
6904    */\r
6905 void\r
6906 ParseGameHistory(game)\r
6907      char *game;\r
6908 {\r
6909     ChessMove moveType;\r
6910     int fromX, fromY, toX, toY, boardIndex;\r
6911     char promoChar;\r
6912     char *p, *q;\r
6913     char buf[MSG_SIZ];\r
6914 \r
6915     if (appData.debugMode)\r
6916       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6917 \r
6918     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6919     gameInfo.site = StrSave(appData.icsHost);\r
6920     gameInfo.date = PGNDate();\r
6921     gameInfo.round = StrSave("-");\r
6922 \r
6923     /* Parse out names of players */\r
6924     while (*game == ' ') game++;\r
6925     p = buf;\r
6926     while (*game != ' ') *p++ = *game++;\r
6927     *p = NULLCHAR;\r
6928     gameInfo.white = StrSave(buf);\r
6929     while (*game == ' ') game++;\r
6930     p = buf;\r
6931     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6932     *p = NULLCHAR;\r
6933     gameInfo.black = StrSave(buf);\r
6934 \r
6935     /* Parse moves */\r
6936     boardIndex = blackPlaysFirst ? 1 : 0;\r
6937     yynewstr(game);\r
6938     for (;;) {\r
6939         yyboardindex = boardIndex;\r
6940         moveType = (ChessMove) yylex();\r
6941         switch (moveType) {\r
6942           case IllegalMove:             /* maybe suicide chess, etc. */\r
6943   if (appData.debugMode) {\r
6944     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6945     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6946     setbuf(debugFP, NULL);\r
6947   }\r
6948           case WhitePromotionChancellor:\r
6949           case BlackPromotionChancellor:\r
6950           case WhitePromotionArchbishop:\r
6951           case BlackPromotionArchbishop:\r
6952           case WhitePromotionQueen:\r
6953           case BlackPromotionQueen:\r
6954           case WhitePromotionRook:\r
6955           case BlackPromotionRook:\r
6956           case WhitePromotionBishop:\r
6957           case BlackPromotionBishop:\r
6958           case WhitePromotionKnight:\r
6959           case BlackPromotionKnight:\r
6960           case WhitePromotionKing:\r
6961           case BlackPromotionKing:\r
6962           case NormalMove:\r
6963           case WhiteCapturesEnPassant:\r
6964           case BlackCapturesEnPassant:\r
6965           case WhiteKingSideCastle:\r
6966           case WhiteQueenSideCastle:\r
6967           case BlackKingSideCastle:\r
6968           case BlackQueenSideCastle:\r
6969           case WhiteKingSideCastleWild:\r
6970           case WhiteQueenSideCastleWild:\r
6971           case BlackKingSideCastleWild:\r
6972           case BlackQueenSideCastleWild:\r
6973           /* PUSH Fabien */\r
6974           case WhiteHSideCastleFR:\r
6975           case WhiteASideCastleFR:\r
6976           case BlackHSideCastleFR:\r
6977           case BlackASideCastleFR:\r
6978           /* POP Fabien */\r
6979             fromX = currentMoveString[0] - AAA;\r
6980             fromY = currentMoveString[1] - ONE;\r
6981             toX = currentMoveString[2] - AAA;\r
6982             toY = currentMoveString[3] - ONE;\r
6983             promoChar = currentMoveString[4];\r
6984             break;\r
6985           case WhiteDrop:\r
6986           case BlackDrop:\r
6987             fromX = moveType == WhiteDrop ?\r
6988               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6989             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6990             fromY = DROP_RANK;\r
6991             toX = currentMoveString[2] - AAA;\r
6992             toY = currentMoveString[3] - ONE;\r
6993             promoChar = NULLCHAR;\r
6994             break;\r
6995           case AmbiguousMove:\r
6996             /* bug? */\r
6997             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);\r
6998   if (appData.debugMode) {\r
6999     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
7000     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
7001     setbuf(debugFP, NULL);\r
7002   }\r
7003             DisplayError(buf, 0);\r
7004             return;\r
7005           case ImpossibleMove:\r
7006             /* bug? */\r
7007             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);\r
7008   if (appData.debugMode) {\r
7009     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
7010     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
7011     setbuf(debugFP, NULL);\r
7012   }\r
7013             DisplayError(buf, 0);\r
7014             return;\r
7015           case (ChessMove) 0:   /* end of file */\r
7016             if (boardIndex < backwardMostMove) {\r
7017                 /* Oops, gap.  How did that happen? */\r
7018                 DisplayError(_("Gap in move list"), 0);\r
7019                 return;\r
7020             }\r
7021             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
7022             if (boardIndex > forwardMostMove) {\r
7023                 forwardMostMove = boardIndex;\r
7024             }\r
7025             return;\r
7026           case ElapsedTime:\r
7027             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
7028                 strcat(parseList[boardIndex-1], " ");\r
7029                 strcat(parseList[boardIndex-1], yy_text);\r
7030             }\r
7031             continue;\r
7032           case Comment:\r
7033           case PGNTag:\r
7034           case NAG:\r
7035           default:\r
7036             /* ignore */\r
7037             continue;\r
7038           case WhiteWins:\r
7039           case BlackWins:\r
7040           case GameIsDrawn:\r
7041           case GameUnfinished:\r
7042             if (gameMode == IcsExamining) {\r
7043                 if (boardIndex < backwardMostMove) {\r
7044                     /* Oops, gap.  How did that happen? */\r
7045                     return;\r
7046                 }\r
7047                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
7048                 return;\r
7049             }\r
7050             gameInfo.result = moveType;\r
7051             p = strchr(yy_text, '{');\r
7052             if (p == NULL) p = strchr(yy_text, '(');\r
7053             if (p == NULL) {\r
7054                 p = yy_text;\r
7055                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
7056             } else {\r
7057                 q = strchr(p, *p == '{' ? '}' : ')');\r
7058                 if (q != NULL) *q = NULLCHAR;\r
7059                 p++;\r
7060             }\r
7061             gameInfo.resultDetails = StrSave(p);\r
7062             continue;\r
7063         }\r
7064         if (boardIndex >= forwardMostMove &&\r
7065             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
7066             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
7067             return;\r
7068         }\r
7069         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
7070                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
7071                                  parseList[boardIndex]);\r
7072         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
7073         /* currentMoveString is set as a side-effect of yylex */\r
7074         strcpy(moveList[boardIndex], currentMoveString);\r
7075         strcat(moveList[boardIndex], "\n");\r
7076         boardIndex++;\r
7077         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
7078         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
7079                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
7080           case MT_NONE:\r
7081           case MT_STALEMATE:\r
7082           default:\r
7083             break;\r
7084           case MT_CHECK:\r
7085             if(gameInfo.variant != VariantShogi)\r
7086                 strcat(parseList[boardIndex - 1], "+");\r
7087             break;\r
7088           case MT_CHECKMATE:\r
7089             strcat(parseList[boardIndex - 1], "#");\r
7090             break;\r
7091         }\r
7092     }\r
7093 }\r
7094 \r
7095 \r
7096 /* Apply a move to the given board  */\r
7097 void\r
7098 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
7099      int fromX, fromY, toX, toY;\r
7100      int promoChar;\r
7101      Board board;\r
7102 {\r
7103   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
7104 \r
7105     /* [HGM] compute & store e.p. status and castling rights for new position */\r
7106     /* if we are updating a board for which those exist (i.e. in boards[])    */\r
7107     if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)\r
7108     { int i;\r
7109 \r
7110       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
7111       oldEP = epStatus[p-1];\r
7112       epStatus[p] = EP_NONE;\r
7113 \r
7114       if( board[toY][toX] != EmptySquare ) \r
7115            epStatus[p] = EP_CAPTURE;  \r
7116 \r
7117       if( board[fromY][fromX] == WhitePawn ) {\r
7118            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7119                epStatus[p] = EP_PAWN_MOVE;\r
7120            if( toY-fromY==2) {\r
7121                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
7122                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7123                       epStatus[p] = toX | berolina;\r
7124                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
7125                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7126                       epStatus[p] = toX;\r
7127            }\r
7128       } else \r
7129       if( board[fromY][fromX] == BlackPawn ) {\r
7130            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7131                epStatus[p] = EP_PAWN_MOVE; \r
7132            if( toY-fromY== -2) {\r
7133                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
7134                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7135                       epStatus[p] = toX | berolina;\r
7136                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
7137                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7138                       epStatus[p] = toX;\r
7139            }\r
7140        }\r
7141 \r
7142        for(i=0; i<nrCastlingRights; i++) {\r
7143            castlingRights[p][i] = castlingRights[p-1][i];\r
7144            if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||\r
7145               castlingRights[p][i] == toX   && castlingRank[i] == toY   \r
7146              ) castlingRights[p][i] = -1; // revoke for moved or captured piece\r
7147        }\r
7148 \r
7149     }\r
7150 \r
7151   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
7152   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
7153        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
7154          \r
7155   if (fromX == toX && fromY == toY) return;\r
7156 \r
7157   if (fromY == DROP_RANK) {\r
7158         /* must be first */\r
7159         piece = board[toY][toX] = (ChessSquare) fromX;\r
7160   } else {\r
7161      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
7162      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
7163      if(gameInfo.variant == VariantKnightmate)\r
7164          king += (int) WhiteUnicorn - (int) WhiteKing;\r
7165 \r
7166     /* Code added by Tord: */\r
7167     /* FRC castling assumed when king captures friendly rook. */\r
7168     if (board[fromY][fromX] == WhiteKing &&\r
7169              board[toY][toX] == WhiteRook) {\r
7170       board[fromY][fromX] = EmptySquare;\r
7171       board[toY][toX] = EmptySquare;\r
7172       if(toX > fromX) {\r
7173         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
7174       } else {\r
7175         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
7176       }\r
7177     } else if (board[fromY][fromX] == BlackKing &&\r
7178                board[toY][toX] == BlackRook) {\r
7179       board[fromY][fromX] = EmptySquare;\r
7180       board[toY][toX] = EmptySquare;\r
7181       if(toX > fromX) {\r
7182         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
7183       } else {\r
7184         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
7185       }\r
7186     /* End of code added by Tord */\r
7187 \r
7188     } else if (board[fromY][fromX] == king\r
7189         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7190         && toY == fromY && toX > fromX+1) {\r
7191         board[fromY][fromX] = EmptySquare;\r
7192         board[toY][toX] = king;\r
7193         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7194         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7195     } else if (board[fromY][fromX] == king\r
7196         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7197                && toY == fromY && toX < fromX-1) {\r
7198         board[fromY][fromX] = EmptySquare;\r
7199         board[toY][toX] = king;\r
7200         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7201         board[fromY][BOARD_LEFT] = EmptySquare;\r
7202     } else if (board[fromY][fromX] == WhitePawn\r
7203                && toY == BOARD_HEIGHT-1\r
7204                && gameInfo.variant != VariantXiangqi\r
7205                ) {\r
7206         /* white pawn promotion */\r
7207         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
7208         if (board[toY][toX] == EmptySquare) {\r
7209             board[toY][toX] = WhiteQueen;\r
7210         }\r
7211         if(gameInfo.variant==VariantBughouse ||\r
7212            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7213             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7214         board[fromY][fromX] = EmptySquare;\r
7215     } else if ((fromY == BOARD_HEIGHT-4)\r
7216                && (toX != fromX)\r
7217                && gameInfo.variant != VariantXiangqi\r
7218                && gameInfo.variant != VariantBerolina\r
7219                && (board[fromY][fromX] == WhitePawn)\r
7220                && (board[toY][toX] == EmptySquare)) {\r
7221         board[fromY][fromX] = EmptySquare;\r
7222         board[toY][toX] = WhitePawn;\r
7223         captured = board[toY - 1][toX];\r
7224         board[toY - 1][toX] = EmptySquare;\r
7225     } else if ((fromY == BOARD_HEIGHT-4)\r
7226                && (toX == fromX)\r
7227                && gameInfo.variant == VariantBerolina\r
7228                && (board[fromY][fromX] == WhitePawn)\r
7229                && (board[toY][toX] == EmptySquare)) {\r
7230         board[fromY][fromX] = EmptySquare;\r
7231         board[toY][toX] = WhitePawn;\r
7232         if(oldEP & EP_BEROLIN_A) {\r
7233                 captured = board[fromY][fromX-1];\r
7234                 board[fromY][fromX-1] = EmptySquare;\r
7235         }else{  captured = board[fromY][fromX+1];\r
7236                 board[fromY][fromX+1] = EmptySquare;\r
7237         }\r
7238     } else if (board[fromY][fromX] == king\r
7239         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7240                && toY == fromY && toX > fromX+1) {\r
7241         board[fromY][fromX] = EmptySquare;\r
7242         board[toY][toX] = king;\r
7243         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7244         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7245     } else if (board[fromY][fromX] == king\r
7246         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7247                && toY == fromY && toX < fromX-1) {\r
7248         board[fromY][fromX] = EmptySquare;\r
7249         board[toY][toX] = king;\r
7250         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7251         board[fromY][BOARD_LEFT] = EmptySquare;\r
7252     } else if (fromY == 7 && fromX == 3\r
7253                && board[fromY][fromX] == BlackKing\r
7254                && toY == 7 && toX == 5) {\r
7255         board[fromY][fromX] = EmptySquare;\r
7256         board[toY][toX] = BlackKing;\r
7257         board[fromY][7] = EmptySquare;\r
7258         board[toY][4] = BlackRook;\r
7259     } else if (fromY == 7 && fromX == 3\r
7260                && board[fromY][fromX] == BlackKing\r
7261                && toY == 7 && toX == 1) {\r
7262         board[fromY][fromX] = EmptySquare;\r
7263         board[toY][toX] = BlackKing;\r
7264         board[fromY][0] = EmptySquare;\r
7265         board[toY][2] = BlackRook;\r
7266     } else if (board[fromY][fromX] == BlackPawn\r
7267                && toY == 0\r
7268                && gameInfo.variant != VariantXiangqi\r
7269                ) {\r
7270         /* black pawn promotion */\r
7271         board[0][toX] = CharToPiece(ToLower(promoChar));\r
7272         if (board[0][toX] == EmptySquare) {\r
7273             board[0][toX] = BlackQueen;\r
7274         }\r
7275         if(gameInfo.variant==VariantBughouse ||\r
7276            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7277             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7278         board[fromY][fromX] = EmptySquare;\r
7279     } else if ((fromY == 3)\r
7280                && (toX != fromX)\r
7281                && gameInfo.variant != VariantXiangqi\r
7282                && gameInfo.variant != VariantBerolina\r
7283                && (board[fromY][fromX] == BlackPawn)\r
7284                && (board[toY][toX] == EmptySquare)) {\r
7285         board[fromY][fromX] = EmptySquare;\r
7286         board[toY][toX] = BlackPawn;\r
7287         captured = board[toY + 1][toX];\r
7288         board[toY + 1][toX] = EmptySquare;\r
7289     } else if ((fromY == 3)\r
7290                && (toX == fromX)\r
7291                && gameInfo.variant == VariantBerolina\r
7292                && (board[fromY][fromX] == BlackPawn)\r
7293                && (board[toY][toX] == EmptySquare)) {\r
7294         board[fromY][fromX] = EmptySquare;\r
7295         board[toY][toX] = BlackPawn;\r
7296         if(oldEP & EP_BEROLIN_A) {\r
7297                 captured = board[fromY][fromX-1];\r
7298                 board[fromY][fromX-1] = EmptySquare;\r
7299         }else{  captured = board[fromY][fromX+1];\r
7300                 board[fromY][fromX+1] = EmptySquare;\r
7301         }\r
7302     } else {\r
7303         board[toY][toX] = board[fromY][fromX];\r
7304         board[fromY][fromX] = EmptySquare;\r
7305     }\r
7306 \r
7307     /* [HGM] now we promote for Shogi, if needed */\r
7308     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
7309         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7310   }\r
7311 \r
7312     if (gameInfo.holdingsWidth != 0) {\r
7313 \r
7314       /* !!A lot more code needs to be written to support holdings  */\r
7315       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
7316       /* penultimate board files, so they are automaticlly stored   */\r
7317       /* in the game history.                                       */\r
7318       if (fromY == DROP_RANK) {\r
7319         /* Delete from holdings, by decreasing count */\r
7320         /* and erasing image if necessary            */\r
7321         p = (int) fromX;\r
7322         if(p < (int) BlackPawn) { /* white drop */\r
7323              p -= (int)WhitePawn;\r
7324              if(p >= gameInfo.holdingsSize) p = 0;\r
7325              if(--board[p][BOARD_WIDTH-2] == 0)\r
7326                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
7327         } else {                  /* black drop */\r
7328              p -= (int)BlackPawn;\r
7329              if(p >= gameInfo.holdingsSize) p = 0;\r
7330              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
7331                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
7332         }\r
7333       }\r
7334       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
7335           && gameInfo.variant != VariantBughouse        ) {\r
7336         /* [HGM] holdings: Add to holdings, if holdings exist */\r
7337         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { \r
7338                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
7339                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
7340         }\r
7341         p = (int) captured;\r
7342         if (p >= (int) BlackPawn) {\r
7343           p -= (int)BlackPawn;\r
7344           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7345                   /* in Shogi restore piece to its original  first */\r
7346                   captured = (ChessSquare) (DEMOTED captured);\r
7347                   p = DEMOTED p;\r
7348           }\r
7349           p = PieceToNumber((ChessSquare)p);\r
7350           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
7351           board[p][BOARD_WIDTH-2]++;\r
7352           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;\r
7353         } else {\r
7354           p -= (int)WhitePawn;\r
7355           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7356                   captured = (ChessSquare) (DEMOTED captured);\r
7357                   p = DEMOTED p;\r
7358           }\r
7359           p = PieceToNumber((ChessSquare)p);\r
7360           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
7361           board[BOARD_HEIGHT-1-p][1]++;\r
7362           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;\r
7363         }\r
7364       }\r
7365 \r
7366     } else if (gameInfo.variant == VariantAtomic) {\r
7367       if (captured != EmptySquare) {\r
7368         int y, x;\r
7369         for (y = toY-1; y <= toY+1; y++) {\r
7370           for (x = toX-1; x <= toX+1; x++) {\r
7371             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
7372                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
7373               board[y][x] = EmptySquare;\r
7374             }\r
7375           }\r
7376         }\r
7377         board[toY][toX] = EmptySquare;\r
7378       }\r
7379     }\r
7380     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
7381         /* [HGM] Shogi promotions */\r
7382         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7383     }\r
7384 \r
7385 }\r
7386 \r
7387 /* Updates forwardMostMove */\r
7388 void\r
7389 MakeMove(fromX, fromY, toX, toY, promoChar)\r
7390      int fromX, fromY, toX, toY;\r
7391      int promoChar;\r
7392 {\r
7393 //    forwardMostMove++; // [HGM] bare: moved downstream\r
7394 \r
7395     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */\r
7396         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
7397         piece = boards[forwardMostMove][fromY][fromX];\r
7398         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
7399         if(gameInfo.variant == VariantKnightmate)\r
7400             king += (int) WhiteUnicorn - (int) WhiteKing;\r
7401         if(forwardMostMove == 0) {\r
7402             if(blackPlaysFirst) \r
7403                 fprintf(serverMoves, "%s;", second.tidy);\r
7404             fprintf(serverMoves, "%s;", first.tidy);\r
7405             if(!blackPlaysFirst) \r
7406                 fprintf(serverMoves, "%s;", second.tidy);\r
7407         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
7408         lastLoadFlag = loadFlag;\r
7409         // print base move\r
7410         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
7411         // print castling suffix\r
7412         if( toY == fromY && piece == king ) {\r
7413             if(toX-fromX > 1)\r
7414                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
7415             if(fromX-toX >1)\r
7416                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
7417         }\r
7418         // e.p. suffix\r
7419         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||\r
7420              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&\r
7421              boards[forwardMostMove][toY][toX] == EmptySquare\r
7422              && fromX != toX )\r
7423                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
7424         // promotion suffix\r
7425         if(promoChar != NULLCHAR)\r
7426                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
7427         if(!loadFlag) {\r
7428             fprintf(serverMoves, "/%d/%d",\r
7429                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);\r
7430             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;\r
7431             else                      timeLeft = blackTimeRemaining/1000;\r
7432             fprintf(serverMoves, "/%d", timeLeft);\r
7433         }\r
7434         fflush(serverMoves);\r
7435     }\r
7436 \r
7437     if (forwardMostMove+1 >= MAX_MOVES) {\r
7438       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
7439                         0, 1);\r
7440       return;\r
7441     }\r
7442     SwitchClocks();\r
7443     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;\r
7444     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;\r
7445     if (commentList[forwardMostMove+1] != NULL) {\r
7446         free(commentList[forwardMostMove+1]);\r
7447         commentList[forwardMostMove+1] = NULL;\r
7448     }\r
7449     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);\r
7450     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);\r
7451     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board\r
7452     gameInfo.result = GameUnfinished;\r
7453     if (gameInfo.resultDetails != NULL) {\r
7454         free(gameInfo.resultDetails);\r
7455         gameInfo.resultDetails = NULL;\r
7456     }\r
7457     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
7458                               moveList[forwardMostMove - 1]);\r
7459     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
7460                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
7461                              fromY, fromX, toY, toX, promoChar,\r
7462                              parseList[forwardMostMove - 1]);\r
7463     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
7464                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
7465                             castlingRights[forwardMostMove]) ) {\r
7466       case MT_NONE:\r
7467       case MT_STALEMATE:\r
7468       default:\r
7469         break;\r
7470       case MT_CHECK:\r
7471         if(gameInfo.variant != VariantShogi)\r
7472             strcat(parseList[forwardMostMove - 1], "+");\r
7473         break;\r
7474       case MT_CHECKMATE:\r
7475         strcat(parseList[forwardMostMove - 1], "#");\r
7476         break;\r
7477     }\r
7478     if (appData.debugMode) {\r
7479         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
7480     }\r
7481 \r
7482 }\r
7483 \r
7484 /* Updates currentMove if not pausing */\r
7485 void\r
7486 ShowMove(fromX, fromY, toX, toY)\r
7487 {\r
7488     int instant = (gameMode == PlayFromGameFile) ?\r
7489         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
7490     if(appData.noGUI) return;\r
7491     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
7492         if (!instant) {\r
7493             if (forwardMostMove == currentMove + 1) {\r
7494                 AnimateMove(boards[forwardMostMove - 1],\r
7495                             fromX, fromY, toX, toY);\r
7496             }\r
7497             if (appData.highlightLastMove) {\r
7498                 SetHighlights(fromX, fromY, toX, toY);\r
7499             }\r
7500         }\r
7501         currentMove = forwardMostMove;\r
7502     }\r
7503 \r
7504     if (instant) return;\r
7505 \r
7506     DisplayMove(currentMove - 1);\r
7507     DrawPosition(FALSE, boards[currentMove]);\r
7508     DisplayBothClocks();\r
7509     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
7510 }\r
7511 \r
7512 void SendEgtPath(ChessProgramState *cps)\r
7513 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */\r
7514         char buf[MSG_SIZ], name[MSG_SIZ], *p;\r
7515 \r
7516         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;\r
7517 \r
7518         while(*p) {\r
7519             char c, *q = name+1, *r, *s;\r
7520 \r
7521             name[0] = ','; // extract next format name from feature and copy with prefixed ','\r
7522             while(*p && *p != ',') *q++ = *p++;\r
7523             *q++ = ':'; *q = 0;\r
7524             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && \r
7525                 strcmp(name, ",nalimov:") == 0 ) {\r
7526                 // take nalimov path from the menu-changeable option first, if it is defined\r
7527                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);\r
7528                 SendToProgram(buf,cps);     // send egtbpath command for nalimov\r
7529             } else\r
7530             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||\r
7531                 (s = StrStr(appData.egtFormats, name)) != NULL) {\r
7532                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma\r
7533                 s = r = StrStr(s, ":") + 1; // beginning of path info\r
7534                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string\r
7535                 c = *r; *r = 0;             // temporarily null-terminate path info\r
7536                     *--q = 0;               // strip of trailig ':' from name\r
7537                     sprintf(buf, "egtbpath %s %s\n", name+1, s);\r
7538                 *r = c;\r
7539                 SendToProgram(buf,cps);     // send egtbpath command for this format\r
7540             }\r
7541             if(*p == ',') p++; // read away comma to position for next format name\r
7542         }\r
7543 }\r
7544 \r
7545 void\r
7546 InitChessProgram(cps, setup)\r
7547      ChessProgramState *cps;\r
7548      int setup; /* [HGM] needed to setup FRC opening position */\r
7549 {\r
7550     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
7551     if (appData.noChessProgram) return;\r
7552     hintRequested = FALSE;\r
7553     bookRequested = FALSE;\r
7554 \r
7555     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */\r
7556     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */\r
7557     if(cps->memSize) { /* [HGM] memory */\r
7558         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);\r
7559         SendToProgram(buf, cps);\r
7560     }\r
7561     SendEgtPath(cps); /* [HGM] EGT */\r
7562     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */\r
7563         sprintf(buf, "cores %d\n", appData.smpCores);\r
7564         SendToProgram(buf, cps);\r
7565     }\r
7566 \r
7567     SendToProgram(cps->initString, cps);\r
7568     if (gameInfo.variant != VariantNormal &&\r
7569         gameInfo.variant != VariantLoadable\r
7570         /* [HGM] also send variant if board size non-standard */\r
7571         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
7572                                             ) {\r
7573       char *v = VariantName(gameInfo.variant);\r
7574       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
7575         /* [HGM] in protocol 1 we have to assume all variants valid */\r
7576         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);\r
7577         DisplayFatalError(buf, 0, 1);\r
7578         return;\r
7579       }\r
7580 \r
7581       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
7582       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7583       if( gameInfo.variant == VariantXiangqi )\r
7584            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
7585       if( gameInfo.variant == VariantShogi )\r
7586            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
7587       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
7588            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
7589       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
7590                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
7591            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7592       if( gameInfo.variant == VariantCourier )\r
7593            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7594       if( gameInfo.variant == VariantSuper )\r
7595            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7596       if( gameInfo.variant == VariantGreat )\r
7597            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7598 \r
7599       if(overruled) {\r
7600            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
7601                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
7602            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
7603            if(StrStr(cps->variants, b) == NULL) { \r
7604                // specific sized variant not known, check if general sizing allowed\r
7605                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
7606                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
7607                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
7608                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
7609                        DisplayFatalError(buf, 0, 1);\r
7610                        return;\r
7611                    }\r
7612                    /* [HGM] here we really should compare with the maximum supported board size */\r
7613                }\r
7614            }\r
7615       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
7616       sprintf(buf, "variant %s\n", b);\r
7617       SendToProgram(buf, cps);\r
7618     }\r
7619     currentlyInitializedVariant = gameInfo.variant;\r
7620 \r
7621     /* [HGM] send opening position in FRC to first engine */\r
7622     if(setup) {\r
7623           SendToProgram("force\n", cps);\r
7624           SendBoard(cps, 0);\r
7625           /* engine is now in force mode! Set flag to wake it up after first move. */\r
7626           setboardSpoiledMachineBlack = 1;\r
7627     }\r
7628 \r
7629     if (cps->sendICS) {\r
7630       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
7631       SendToProgram(buf, cps);\r
7632     }\r
7633     cps->maybeThinking = FALSE;\r
7634     cps->offeredDraw = 0;\r
7635     if (!appData.icsActive) {\r
7636         SendTimeControl(cps, movesPerSession, timeControl,\r
7637                         timeIncrement, appData.searchDepth,\r
7638                         searchTime);\r
7639     }\r
7640     if (appData.showThinking \r
7641         // [HGM] thinking: four options require thinking output to be sent\r
7642         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
7643                                 ) {\r
7644         SendToProgram("post\n", cps);\r
7645     }\r
7646     SendToProgram("hard\n", cps);\r
7647     if (!appData.ponderNextMove) {\r
7648         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
7649            it without being sure what state we are in first.  "hard"\r
7650            is not a toggle, so that one is OK.\r
7651          */\r
7652         SendToProgram("easy\n", cps);\r
7653     }\r
7654     if (cps->usePing) {\r
7655       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
7656       SendToProgram(buf, cps);\r
7657     }\r
7658     cps->initDone = TRUE;\r
7659 }   \r
7660 \r
7661 \r
7662 void\r
7663 StartChessProgram(cps)\r
7664      ChessProgramState *cps;\r
7665 {\r
7666     char buf[MSG_SIZ];\r
7667     int err;\r
7668 \r
7669     if (appData.noChessProgram) return;\r
7670     cps->initDone = FALSE;\r
7671 \r
7672     if (strcmp(cps->host, "localhost") == 0) {\r
7673         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
7674     } else if (*appData.remoteShell == NULLCHAR) {\r
7675         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
7676     } else {\r
7677         if (*appData.remoteUser == NULLCHAR) {\r
7678             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
7679                     cps->program);\r
7680         } else {\r
7681             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
7682                     cps->host, appData.remoteUser, cps->program);\r
7683         }\r
7684         err = StartChildProcess(buf, "", &cps->pr);\r
7685     }\r
7686     \r
7687     if (err != 0) {\r
7688         sprintf(buf, _("Startup failure on '%s'"), cps->program);\r
7689         DisplayFatalError(buf, err, 1);\r
7690         cps->pr = NoProc;\r
7691         cps->isr = NULL;\r
7692         return;\r
7693     }\r
7694     \r
7695     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
7696     if (cps->protocolVersion > 1) {\r
7697       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
7698       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options\r
7699       cps->comboCnt = 0;  //                and values of combo boxes\r
7700       SendToProgram(buf, cps);\r
7701     } else {\r
7702       SendToProgram("xboard\n", cps);\r
7703     }\r
7704 }\r
7705 \r
7706 \r
7707 void\r
7708 TwoMachinesEventIfReady P((void))\r
7709 {\r
7710   if (first.lastPing != first.lastPong) {\r
7711     DisplayMessage("", _("Waiting for first chess program"));\r
7712     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7713     return;\r
7714   }\r
7715   if (second.lastPing != second.lastPong) {\r
7716     DisplayMessage("", _("Waiting for second chess program"));\r
7717     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7718     return;\r
7719   }\r
7720   ThawUI();\r
7721   TwoMachinesEvent();\r
7722 }\r
7723 \r
7724 void\r
7725 NextMatchGame P((void))\r
7726 {\r
7727     int index; /* [HGM] autoinc: step lod index during match */\r
7728     Reset(FALSE, TRUE);\r
7729     if (*appData.loadGameFile != NULLCHAR) {\r
7730         index = appData.loadGameIndex;\r
7731         if(index < 0) { // [HGM] autoinc\r
7732             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7733             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7734         } \r
7735         LoadGameFromFile(appData.loadGameFile,\r
7736                          index,\r
7737                          appData.loadGameFile, FALSE);\r
7738     } else if (*appData.loadPositionFile != NULLCHAR) {\r
7739         index = appData.loadPositionIndex;\r
7740         if(index < 0) { // [HGM] autoinc\r
7741             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7742             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7743         } \r
7744         LoadPositionFromFile(appData.loadPositionFile,\r
7745                              index,\r
7746                              appData.loadPositionFile);\r
7747     }\r
7748     TwoMachinesEventIfReady();\r
7749 }\r
7750 \r
7751 void UserAdjudicationEvent( int result )\r
7752 {\r
7753     ChessMove gameResult = GameIsDrawn;\r
7754 \r
7755     if( result > 0 ) {\r
7756         gameResult = WhiteWins;\r
7757     }\r
7758     else if( result < 0 ) {\r
7759         gameResult = BlackWins;\r
7760     }\r
7761 \r
7762     if( gameMode == TwoMachinesPlay ) {\r
7763         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
7764     }\r
7765 }\r
7766 \r
7767 \r
7768 void\r
7769 GameEnds(result, resultDetails, whosays)\r
7770      ChessMove result;\r
7771      char *resultDetails;\r
7772      int whosays;\r
7773 {\r
7774     GameMode nextGameMode;\r
7775     int isIcsGame;\r
7776     char buf[MSG_SIZ];\r
7777 \r
7778     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
7779     endingGame = 1;\r
7780 \r
7781     if (appData.debugMode) {\r
7782       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
7783               result, resultDetails ? resultDetails : "(null)", whosays);\r
7784     }\r
7785 \r
7786     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
7787         /* If we are playing on ICS, the server decides when the\r
7788            game is over, but the engine can offer to draw, claim \r
7789            a draw, or resign. \r
7790          */\r
7791 #if ZIPPY\r
7792         if (appData.zippyPlay && first.initDone) {\r
7793             if (result == GameIsDrawn) {\r
7794                 /* In case draw still needs to be claimed */\r
7795                 SendToICS(ics_prefix);\r
7796                 SendToICS("draw\n");\r
7797             } else if (StrCaseStr(resultDetails, "resign")) {\r
7798                 SendToICS(ics_prefix);\r
7799                 SendToICS("resign\n");\r
7800             }\r
7801         }\r
7802 #endif\r
7803         endingGame = 0; /* [HGM] crash */\r
7804         return;\r
7805     }\r
7806 \r
7807     /* If we're loading the game from a file, stop */\r
7808     if (whosays == GE_FILE) {\r
7809       (void) StopLoadGameTimer();\r
7810       gameFileFP = NULL;\r
7811     }\r
7812 \r
7813     /* Cancel draw offers */\r
7814     first.offeredDraw = second.offeredDraw = 0;\r
7815 \r
7816     /* If this is an ICS game, only ICS can really say it's done;\r
7817        if not, anyone can. */\r
7818     isIcsGame = (gameMode == IcsPlayingWhite || \r
7819                  gameMode == IcsPlayingBlack || \r
7820                  gameMode == IcsObserving    || \r
7821                  gameMode == IcsExamining);\r
7822 \r
7823     if (!isIcsGame || whosays == GE_ICS) {\r
7824         /* OK -- not an ICS game, or ICS said it was done */\r
7825         StopClocks();\r
7826         if (!isIcsGame && !appData.noChessProgram) \r
7827           SetUserThinkingEnables();\r
7828     \r
7829         /* [HGM] if a machine claims the game end we verify this claim */\r
7830         if(gameMode == TwoMachinesPlay && appData.testClaims) {\r
7831             if(appData.testLegality && whosays >= GE_ENGINE1 ) {\r
7832                 char claimer;\r
7833                 ChessMove trueResult = (ChessMove) -1;\r
7834 \r
7835                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
7836                                             first.twoMachinesColor[0] :\r
7837                                             second.twoMachinesColor[0] ;\r
7838 \r
7839                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first\r
7840                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {\r
7841                     /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7842                     trueResult = WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers)\r
7843                         ? BlackWins : WhiteWins; // [HGM] losers: reverse the result in VariantLosers!\r
7844                 } else\r
7845                 if(epStatus[forwardMostMove] == EP_STALEMATE) {\r
7846                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE\r
7847                     if(gameInfo.variant == VariantGiveaway || gameInfo.variant == VariantSuicide || \r
7848                        gameInfo.variant == VariantLosers)  // [HGM] losers: in giveaway variants stalemate wins\r
7849                         trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;\r
7850                 }\r
7851 \r
7852                 // now verify win claims, but not in drop games, as we don't understand those yet\r
7853                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper\r
7854                                                  || gameInfo.variant == VariantGreat) &&\r
7855                     (result == WhiteWins && claimer == 'w' ||\r
7856                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win\r
7857                       if (appData.debugMode) {\r
7858                         fprintf(debugFP, "result=%d sp=%d move=%d\n",\r
7859                                 result, epStatus[forwardMostMove], forwardMostMove);\r
7860                       }\r
7861                       if(result != trueResult) {\r
7862                               sprintf(buf, "False win claim: '%s'", resultDetails);\r
7863                               result = claimer == 'w' ? BlackWins : WhiteWins;\r
7864                               resultDetails = buf;\r
7865                       }\r
7866                 } else\r
7867                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
7868                     && (forwardMostMove <= backwardMostMove ||\r
7869                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
7870                         (claimer=='b')==(forwardMostMove&1))\r
7871                                                                                   ) {\r
7872                       /* [HGM] verify: draws that were not flagged are false claims */\r
7873                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
7874                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7875                       resultDetails = buf;\r
7876                 }\r
7877                 /* (Claiming a loss is accepted no questions asked!) */\r
7878             }\r
7879             /* [HGM] bare: don't allow bare King to win */\r
7880             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)\r
7881                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway \r
7882                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...\r
7883                && result != GameIsDrawn)\r
7884             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
7885                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
7886                         int p = (int)boards[forwardMostMove][i][j] - color;\r
7887                         if(p >= 0 && p <= (int)WhiteKing) k++;\r
7888                 }\r
7889                 if (appData.debugMode) {\r
7890                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",\r
7891                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);\r
7892                 }\r
7893                 if(k <= 1) {\r
7894                         result = GameIsDrawn;\r
7895                         sprintf(buf, "%s but bare king", resultDetails);\r
7896                         resultDetails = buf;\r
7897                 }\r
7898             }\r
7899         }\r
7900 \r
7901 \r
7902         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
7903             if(result==WhiteWins) c = '+';\r
7904             if(result==BlackWins) c = '-';\r
7905             if(resultDetails != NULL)\r
7906                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
7907         }\r
7908         if (resultDetails != NULL) {\r
7909             gameInfo.result = result;\r
7910             gameInfo.resultDetails = StrSave(resultDetails);\r
7911 \r
7912             /* display last move only if game was not loaded from file */\r
7913             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7914                 DisplayMove(currentMove - 1);\r
7915     \r
7916             if (forwardMostMove != 0) {\r
7917                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7918                     if (*appData.saveGameFile != NULLCHAR) {\r
7919                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7920                     } else if (appData.autoSaveGames) {\r
7921                         AutoSaveGame();\r
7922                     }\r
7923                     if (*appData.savePositionFile != NULLCHAR) {\r
7924                         SavePositionToFile(appData.savePositionFile);\r
7925                     }\r
7926                 }\r
7927             }\r
7928 \r
7929             /* Tell program how game ended in case it is learning */\r
7930             /* [HGM] Moved this to after saving the PGN, just in case */\r
7931             /* engine died and we got here through time loss. In that */\r
7932             /* case we will get a fatal error writing the pipe, which */\r
7933             /* would otherwise lose us the PGN.                       */\r
7934             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7935             /* output during GameEnds should never be fatal anymore   */\r
7936             if (gameMode == MachinePlaysWhite ||\r
7937                 gameMode == MachinePlaysBlack ||\r
7938                 gameMode == TwoMachinesPlay ||\r
7939                 gameMode == IcsPlayingWhite ||\r
7940                 gameMode == IcsPlayingBlack ||\r
7941                 gameMode == BeginningOfGame) {\r
7942                 char buf[MSG_SIZ];\r
7943                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7944                         resultDetails);\r
7945                 if (first.pr != NoProc) {\r
7946                     SendToProgram(buf, &first);\r
7947                 }\r
7948                 if (second.pr != NoProc &&\r
7949                     gameMode == TwoMachinesPlay) {\r
7950                     SendToProgram(buf, &second);\r
7951                 }\r
7952             }\r
7953         }\r
7954 \r
7955         if (appData.icsActive) {\r
7956             if (appData.quietPlay &&\r
7957                 (gameMode == IcsPlayingWhite ||\r
7958                  gameMode == IcsPlayingBlack)) {\r
7959                 SendToICS(ics_prefix);\r
7960                 SendToICS("set shout 1\n");\r
7961             }\r
7962             nextGameMode = IcsIdle;\r
7963             ics_user_moved = FALSE;\r
7964             /* clean up premove.  It's ugly when the game has ended and the\r
7965              * premove highlights are still on the board.\r
7966              */\r
7967             if (gotPremove) {\r
7968               gotPremove = FALSE;\r
7969               ClearPremoveHighlights();\r
7970               DrawPosition(FALSE, boards[currentMove]);\r
7971             }\r
7972             if (whosays == GE_ICS) {\r
7973                 switch (result) {\r
7974                 case WhiteWins:\r
7975                     if (gameMode == IcsPlayingWhite)\r
7976                         PlayIcsWinSound();\r
7977                     else if(gameMode == IcsPlayingBlack)\r
7978                         PlayIcsLossSound();\r
7979                     break;\r
7980                 case BlackWins:\r
7981                     if (gameMode == IcsPlayingBlack)\r
7982                         PlayIcsWinSound();\r
7983                     else if(gameMode == IcsPlayingWhite)\r
7984                         PlayIcsLossSound();\r
7985                     break;\r
7986                 case GameIsDrawn:\r
7987                     PlayIcsDrawSound();\r
7988                     break;\r
7989                 default:\r
7990                     PlayIcsUnfinishedSound();\r
7991                 }\r
7992             }\r
7993         } else if (gameMode == EditGame ||\r
7994                    gameMode == PlayFromGameFile || \r
7995                    gameMode == AnalyzeMode || \r
7996                    gameMode == AnalyzeFile) {\r
7997             nextGameMode = gameMode;\r
7998         } else {\r
7999             nextGameMode = EndOfGame;\r
8000         }\r
8001         pausing = FALSE;\r
8002         ModeHighlight();\r
8003     } else {\r
8004         nextGameMode = gameMode;\r
8005     }\r
8006 \r
8007     if (appData.noChessProgram) {\r
8008         gameMode = nextGameMode;\r
8009         ModeHighlight();\r
8010         endingGame = 0; /* [HGM] crash */\r
8011         return;\r
8012     }\r
8013 \r
8014     if (first.reuse) {\r
8015         /* Put first chess program into idle state */\r
8016         if (first.pr != NoProc &&\r
8017             (gameMode == MachinePlaysWhite ||\r
8018              gameMode == MachinePlaysBlack ||\r
8019              gameMode == TwoMachinesPlay ||\r
8020              gameMode == IcsPlayingWhite ||\r
8021              gameMode == IcsPlayingBlack ||\r
8022              gameMode == BeginningOfGame)) {\r
8023             SendToProgram("force\n", &first);\r
8024             if (first.usePing) {\r
8025               char buf[MSG_SIZ];\r
8026               sprintf(buf, "ping %d\n", ++first.lastPing);\r
8027               SendToProgram(buf, &first);\r
8028             }\r
8029         }\r
8030     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
8031         /* Kill off first chess program */\r
8032         if (first.isr != NULL)\r
8033           RemoveInputSource(first.isr);\r
8034         first.isr = NULL;\r
8035     \r
8036         if (first.pr != NoProc) {\r
8037             ExitAnalyzeMode();\r
8038             DoSleep( appData.delayBeforeQuit );\r
8039             SendToProgram("quit\n", &first);\r
8040             DoSleep( appData.delayAfterQuit );\r
8041             DestroyChildProcess(first.pr, first.useSigterm);\r
8042         }\r
8043         first.pr = NoProc;\r
8044     }\r
8045     if (second.reuse) {\r
8046         /* Put second chess program into idle state */\r
8047         if (second.pr != NoProc &&\r
8048             gameMode == TwoMachinesPlay) {\r
8049             SendToProgram("force\n", &second);\r
8050             if (second.usePing) {\r
8051               char buf[MSG_SIZ];\r
8052               sprintf(buf, "ping %d\n", ++second.lastPing);\r
8053               SendToProgram(buf, &second);\r
8054             }\r
8055         }\r
8056     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
8057         /* Kill off second chess program */\r
8058         if (second.isr != NULL)\r
8059           RemoveInputSource(second.isr);\r
8060         second.isr = NULL;\r
8061     \r
8062         if (second.pr != NoProc) {\r
8063             DoSleep( appData.delayBeforeQuit );\r
8064             SendToProgram("quit\n", &second);\r
8065             DoSleep( appData.delayAfterQuit );\r
8066             DestroyChildProcess(second.pr, second.useSigterm);\r
8067         }\r
8068         second.pr = NoProc;\r
8069     }\r
8070 \r
8071     if (matchMode && gameMode == TwoMachinesPlay) {\r
8072         switch (result) {\r
8073         case WhiteWins:\r
8074           if (first.twoMachinesColor[0] == 'w') {\r
8075             first.matchWins++;\r
8076           } else {\r
8077             second.matchWins++;\r
8078           }\r
8079           break;\r
8080         case BlackWins:\r
8081           if (first.twoMachinesColor[0] == 'b') {\r
8082             first.matchWins++;\r
8083           } else {\r
8084             second.matchWins++;\r
8085           }\r
8086           break;\r
8087         default:\r
8088           break;\r
8089         }\r
8090         if (matchGame < appData.matchGames) {\r
8091             char *tmp;\r
8092             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */\r
8093                 tmp = first.twoMachinesColor;\r
8094                 first.twoMachinesColor = second.twoMachinesColor;\r
8095                 second.twoMachinesColor = tmp;\r
8096             }\r
8097             gameMode = nextGameMode;\r
8098             matchGame++;\r
8099             if(appData.matchPause>10000 || appData.matchPause<10)\r
8100                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
8101             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
8102             endingGame = 0; /* [HGM] crash */\r
8103             return;\r
8104         } else {\r
8105             char buf[MSG_SIZ];\r
8106             gameMode = nextGameMode;\r
8107             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),\r
8108                     first.tidy, second.tidy,\r
8109                     first.matchWins, second.matchWins,\r
8110                     appData.matchGames - (first.matchWins + second.matchWins));\r
8111             DisplayFatalError(buf, 0, 0);\r
8112         }\r
8113     }\r
8114     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
8115         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
8116       ExitAnalyzeMode();\r
8117     gameMode = nextGameMode;\r
8118     ModeHighlight();\r
8119     endingGame = 0;  /* [HGM] crash */\r
8120 }\r
8121 \r
8122 /* Assumes program was just initialized (initString sent).\r
8123    Leaves program in force mode. */\r
8124 void\r
8125 FeedMovesToProgram(cps, upto) \r
8126      ChessProgramState *cps;\r
8127      int upto;\r
8128 {\r
8129     int i;\r
8130     \r
8131     if (appData.debugMode)\r
8132       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
8133               startedFromSetupPosition ? "position and " : "",\r
8134               backwardMostMove, upto, cps->which);\r
8135     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
8136         // [HGM] variantswitch: make engine aware of new variant\r
8137         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
8138                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
8139         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
8140         SendToProgram(buf, cps);\r
8141         currentlyInitializedVariant = gameInfo.variant;\r
8142     }\r
8143     SendToProgram("force\n", cps);\r
8144     if (startedFromSetupPosition) {\r
8145         SendBoard(cps, backwardMostMove);\r
8146     if (appData.debugMode) {\r
8147         fprintf(debugFP, "feedMoves\n");\r
8148     }\r
8149     }\r
8150     for (i = backwardMostMove; i < upto; i++) {\r
8151         SendMoveToProgram(i, cps);\r
8152     }\r
8153 }\r
8154 \r
8155 \r
8156 void\r
8157 ResurrectChessProgram()\r
8158 {\r
8159      /* The chess program may have exited.\r
8160         If so, restart it and feed it all the moves made so far. */\r
8161 \r
8162     if (appData.noChessProgram || first.pr != NoProc) return;\r
8163     \r
8164     StartChessProgram(&first);\r
8165     InitChessProgram(&first, FALSE);\r
8166     FeedMovesToProgram(&first, currentMove);\r
8167 \r
8168     if (!first.sendTime) {\r
8169         /* can't tell gnuchess what its clock should read,\r
8170            so we bow to its notion. */\r
8171         ResetClocks();\r
8172         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
8173         timeRemaining[1][currentMove] = blackTimeRemaining;\r
8174     }\r
8175 \r
8176     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||\r
8177                 appData.icsEngineAnalyze) && first.analysisSupport) {\r
8178       SendToProgram("analyze\n", &first);\r
8179       first.analyzing = TRUE;\r
8180     }\r
8181 }\r
8182 \r
8183 /*\r
8184  * Button procedures\r
8185  */\r
8186 void\r
8187 Reset(redraw, init)\r
8188      int redraw, init;\r
8189 {\r
8190     int i;\r
8191 \r
8192     if (appData.debugMode) {\r
8193         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
8194                 redraw, init, gameMode);\r
8195     }\r
8196     pausing = pauseExamInvalid = FALSE;\r
8197     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
8198     firstMove = TRUE;\r
8199     whiteFlag = blackFlag = FALSE;\r
8200     userOfferedDraw = FALSE;\r
8201     hintRequested = bookRequested = FALSE;\r
8202     first.maybeThinking = FALSE;\r
8203     second.maybeThinking = FALSE;\r
8204     first.bookSuspend = FALSE; // [HGM] book\r
8205     second.bookSuspend = FALSE;\r
8206     thinkOutput[0] = NULLCHAR;\r
8207     lastHint[0] = NULLCHAR;\r
8208     ClearGameInfo(&gameInfo);\r
8209     gameInfo.variant = StringToVariant(appData.variant);\r
8210     ics_user_moved = ics_clock_paused = FALSE;\r
8211     ics_getting_history = H_FALSE;\r
8212     ics_gamenum = -1;\r
8213     white_holding[0] = black_holding[0] = NULLCHAR;\r
8214     ClearProgramStats();\r
8215     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode\r
8216     \r
8217     ResetFrontEnd();\r
8218     ClearHighlights();\r
8219     flipView = appData.flipView;\r
8220     ClearPremoveHighlights();\r
8221     gotPremove = FALSE;\r
8222     alarmSounded = FALSE;\r
8223 \r
8224     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8225     if(appData.serverMovesName != NULL) {\r
8226         /* [HGM] prepare to make moves file for broadcasting */\r
8227         clock_t t = clock();\r
8228         if(serverMoves != NULL) fclose(serverMoves);\r
8229         serverMoves = fopen(appData.serverMovesName, "r");\r
8230         if(serverMoves != NULL) {\r
8231             fclose(serverMoves);\r
8232             /* delay 15 sec before overwriting, so all clients can see end */\r
8233             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
8234         }\r
8235         serverMoves = fopen(appData.serverMovesName, "w");\r
8236     }\r
8237 \r
8238     ExitAnalyzeMode();\r
8239     gameMode = BeginningOfGame;\r
8240     ModeHighlight();\r
8241     if(appData.icsActive) gameInfo.variant = VariantNormal;\r
8242     InitPosition(redraw);\r
8243     for (i = 0; i < MAX_MOVES; i++) {\r
8244         if (commentList[i] != NULL) {\r
8245             free(commentList[i]);\r
8246             commentList[i] = NULL;\r
8247         }\r
8248     }\r
8249     ResetClocks();\r
8250     timeRemaining[0][0] = whiteTimeRemaining;\r
8251     timeRemaining[1][0] = blackTimeRemaining;\r
8252     if (first.pr == NULL) {\r
8253         StartChessProgram(&first);\r
8254     }\r
8255     if (init) {\r
8256             InitChessProgram(&first, startedFromSetupPosition);\r
8257     }\r
8258     DisplayTitle("");\r
8259     DisplayMessage("", "");\r
8260     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8261 }\r
8262 \r
8263 void\r
8264 AutoPlayGameLoop()\r
8265 {\r
8266     for (;;) {\r
8267         if (!AutoPlayOneMove())\r
8268           return;\r
8269         if (matchMode || appData.timeDelay == 0)\r
8270           continue;\r
8271         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
8272           return;\r
8273         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
8274         break;\r
8275     }\r
8276 }\r
8277 \r
8278 \r
8279 int\r
8280 AutoPlayOneMove()\r
8281 {\r
8282     int fromX, fromY, toX, toY;\r
8283 \r
8284     if (appData.debugMode) {\r
8285       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
8286     }\r
8287 \r
8288     if (gameMode != PlayFromGameFile)\r
8289       return FALSE;\r
8290 \r
8291     if (currentMove >= forwardMostMove) {\r
8292       gameMode = EditGame;\r
8293       ModeHighlight();\r
8294 \r
8295       /* [AS] Clear current move marker at the end of a game */\r
8296       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
8297 \r
8298       return FALSE;\r
8299     }\r
8300     \r
8301     toX = moveList[currentMove][2] - AAA;\r
8302     toY = moveList[currentMove][3] - ONE;\r
8303 \r
8304     if (moveList[currentMove][1] == '@') {\r
8305         if (appData.highlightLastMove) {\r
8306             SetHighlights(-1, -1, toX, toY);\r
8307         }\r
8308     } else {\r
8309         fromX = moveList[currentMove][0] - AAA;\r
8310         fromY = moveList[currentMove][1] - ONE;\r
8311 \r
8312         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
8313 \r
8314         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
8315 \r
8316         if (appData.highlightLastMove) {\r
8317             SetHighlights(fromX, fromY, toX, toY);\r
8318         }\r
8319     }\r
8320     DisplayMove(currentMove);\r
8321     SendMoveToProgram(currentMove++, &first);\r
8322     DisplayBothClocks();\r
8323     DrawPosition(FALSE, boards[currentMove]);\r
8324     // [HGM] PV info: always display, routine tests if empty\r
8325     DisplayComment(currentMove - 1, commentList[currentMove]);\r
8326     return TRUE;\r
8327 }\r
8328 \r
8329 \r
8330 int\r
8331 LoadGameOneMove(readAhead)\r
8332      ChessMove readAhead;\r
8333 {\r
8334     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
8335     char promoChar = NULLCHAR;\r
8336     ChessMove moveType;\r
8337     char move[MSG_SIZ];\r
8338     char *p, *q;\r
8339     \r
8340     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
8341         gameMode != AnalyzeMode && gameMode != Training) {\r
8342         gameFileFP = NULL;\r
8343         return FALSE;\r
8344     }\r
8345     \r
8346     yyboardindex = forwardMostMove;\r
8347     if (readAhead != (ChessMove)0) {\r
8348       moveType = readAhead;\r
8349     } else {\r
8350       if (gameFileFP == NULL)\r
8351           return FALSE;\r
8352       moveType = (ChessMove) yylex();\r
8353     }\r
8354     \r
8355     done = FALSE;\r
8356     switch (moveType) {\r
8357       case Comment:\r
8358         if (appData.debugMode) \r
8359           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8360         p = yy_text;\r
8361         if (*p == '{' || *p == '[' || *p == '(') {\r
8362             p[strlen(p) - 1] = NULLCHAR;\r
8363             p++;\r
8364         }\r
8365 \r
8366         /* append the comment but don't display it */\r
8367         while (*p == '\n') p++;\r
8368         AppendComment(currentMove, p);\r
8369         return TRUE;\r
8370 \r
8371       case WhiteCapturesEnPassant:\r
8372       case BlackCapturesEnPassant:\r
8373       case WhitePromotionChancellor:\r
8374       case BlackPromotionChancellor:\r
8375       case WhitePromotionArchbishop:\r
8376       case BlackPromotionArchbishop:\r
8377       case WhitePromotionCentaur:\r
8378       case BlackPromotionCentaur:\r
8379       case WhitePromotionQueen:\r
8380       case BlackPromotionQueen:\r
8381       case WhitePromotionRook:\r
8382       case BlackPromotionRook:\r
8383       case WhitePromotionBishop:\r
8384       case BlackPromotionBishop:\r
8385       case WhitePromotionKnight:\r
8386       case BlackPromotionKnight:\r
8387       case WhitePromotionKing:\r
8388       case BlackPromotionKing:\r
8389       case NormalMove:\r
8390       case WhiteKingSideCastle:\r
8391       case WhiteQueenSideCastle:\r
8392       case BlackKingSideCastle:\r
8393       case BlackQueenSideCastle:\r
8394       case WhiteKingSideCastleWild:\r
8395       case WhiteQueenSideCastleWild:\r
8396       case BlackKingSideCastleWild:\r
8397       case BlackQueenSideCastleWild:\r
8398       /* PUSH Fabien */\r
8399       case WhiteHSideCastleFR:\r
8400       case WhiteASideCastleFR:\r
8401       case BlackHSideCastleFR:\r
8402       case BlackASideCastleFR:\r
8403       /* POP Fabien */\r
8404         if (appData.debugMode)\r
8405           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8406         fromX = currentMoveString[0] - AAA;\r
8407         fromY = currentMoveString[1] - ONE;\r
8408         toX = currentMoveString[2] - AAA;\r
8409         toY = currentMoveString[3] - ONE;\r
8410         promoChar = currentMoveString[4];\r
8411         break;\r
8412 \r
8413       case WhiteDrop:\r
8414       case BlackDrop:\r
8415         if (appData.debugMode)\r
8416           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8417         fromX = moveType == WhiteDrop ?\r
8418           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
8419         (int) CharToPiece(ToLower(currentMoveString[0]));\r
8420         fromY = DROP_RANK;\r
8421         toX = currentMoveString[2] - AAA;\r
8422         toY = currentMoveString[3] - ONE;\r
8423         break;\r
8424 \r
8425       case WhiteWins:\r
8426       case BlackWins:\r
8427       case GameIsDrawn:\r
8428       case GameUnfinished:\r
8429         if (appData.debugMode)\r
8430           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
8431         p = strchr(yy_text, '{');\r
8432         if (p == NULL) p = strchr(yy_text, '(');\r
8433         if (p == NULL) {\r
8434             p = yy_text;\r
8435             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
8436         } else {\r
8437             q = strchr(p, *p == '{' ? '}' : ')');\r
8438             if (q != NULL) *q = NULLCHAR;\r
8439             p++;\r
8440         }\r
8441         GameEnds(moveType, p, GE_FILE);\r
8442         done = TRUE;\r
8443         if (cmailMsgLoaded) {\r
8444             ClearHighlights();\r
8445             flipView = WhiteOnMove(currentMove);\r
8446             if (moveType == GameUnfinished) flipView = !flipView;\r
8447             if (appData.debugMode)\r
8448               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
8449         }\r
8450         break;\r
8451 \r
8452       case (ChessMove) 0:       /* end of file */\r
8453         if (appData.debugMode)\r
8454           fprintf(debugFP, "Parser hit end of file\n");\r
8455         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8456                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8457           case MT_NONE:\r
8458           case MT_CHECK:\r
8459             break;\r
8460           case MT_CHECKMATE:\r
8461             if (WhiteOnMove(currentMove)) {\r
8462                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8463             } else {\r
8464                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8465             }\r
8466             break;\r
8467           case MT_STALEMATE:\r
8468             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8469             break;\r
8470         }\r
8471         done = TRUE;\r
8472         break;\r
8473 \r
8474       case MoveNumberOne:\r
8475         if (lastLoadGameStart == GNUChessGame) {\r
8476             /* GNUChessGames have numbers, but they aren't move numbers */\r
8477             if (appData.debugMode)\r
8478               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8479                       yy_text, (int) moveType);\r
8480             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8481         }\r
8482         /* else fall thru */\r
8483 \r
8484       case XBoardGame:\r
8485       case GNUChessGame:\r
8486       case PGNTag:\r
8487         /* Reached start of next game in file */\r
8488         if (appData.debugMode)\r
8489           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
8490         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8491                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8492           case MT_NONE:\r
8493           case MT_CHECK:\r
8494             break;\r
8495           case MT_CHECKMATE:\r
8496             if (WhiteOnMove(currentMove)) {\r
8497                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8498             } else {\r
8499                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8500             }\r
8501             break;\r
8502           case MT_STALEMATE:\r
8503             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8504             break;\r
8505         }\r
8506         done = TRUE;\r
8507         break;\r
8508 \r
8509       case PositionDiagram:     /* should not happen; ignore */\r
8510       case ElapsedTime:         /* ignore */\r
8511       case NAG:                 /* ignore */\r
8512         if (appData.debugMode)\r
8513           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8514                   yy_text, (int) moveType);\r
8515         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8516 \r
8517       case IllegalMove:\r
8518         if (appData.testLegality) {\r
8519             if (appData.debugMode)\r
8520               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
8521             sprintf(move, _("Illegal move: %d.%s%s"),\r
8522                     (forwardMostMove / 2) + 1,\r
8523                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8524             DisplayError(move, 0);\r
8525             done = TRUE;\r
8526         } else {\r
8527             if (appData.debugMode)\r
8528               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
8529                       yy_text, currentMoveString);\r
8530             fromX = currentMoveString[0] - AAA;\r
8531             fromY = currentMoveString[1] - ONE;\r
8532             toX = currentMoveString[2] - AAA;\r
8533             toY = currentMoveString[3] - ONE;\r
8534             promoChar = currentMoveString[4];\r
8535         }\r
8536         break;\r
8537 \r
8538       case AmbiguousMove:\r
8539         if (appData.debugMode)\r
8540           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
8541         sprintf(move, _("Ambiguous move: %d.%s%s"),\r
8542                 (forwardMostMove / 2) + 1,\r
8543                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8544         DisplayError(move, 0);\r
8545         done = TRUE;\r
8546         break;\r
8547 \r
8548       default:\r
8549       case ImpossibleMove:\r
8550         if (appData.debugMode)\r
8551           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);\r
8552         sprintf(move, _("Illegal move: %d.%s%s"),\r
8553                 (forwardMostMove / 2) + 1,\r
8554                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8555         DisplayError(move, 0);\r
8556         done = TRUE;\r
8557         break;\r
8558     }\r
8559 \r
8560     if (done) {\r
8561         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
8562             DrawPosition(FALSE, boards[currentMove]);\r
8563             DisplayBothClocks();\r
8564             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
8565               DisplayComment(currentMove - 1, commentList[currentMove]);\r
8566         }\r
8567         (void) StopLoadGameTimer();\r
8568         gameFileFP = NULL;\r
8569         cmailOldMove = forwardMostMove;\r
8570         return FALSE;\r
8571     } else {\r
8572         /* currentMoveString is set as a side-effect of yylex */\r
8573         strcat(currentMoveString, "\n");\r
8574         strcpy(moveList[forwardMostMove], currentMoveString);\r
8575         \r
8576         thinkOutput[0] = NULLCHAR;\r
8577         MakeMove(fromX, fromY, toX, toY, promoChar);\r
8578         currentMove = forwardMostMove;\r
8579         return TRUE;\r
8580     }\r
8581 }\r
8582 \r
8583 /* Load the nth game from the given file */\r
8584 int\r
8585 LoadGameFromFile(filename, n, title, useList)\r
8586      char *filename;\r
8587      int n;\r
8588      char *title;\r
8589      /*Boolean*/ int useList;\r
8590 {\r
8591     FILE *f;\r
8592     char buf[MSG_SIZ];\r
8593 \r
8594     if (strcmp(filename, "-") == 0) {\r
8595         f = stdin;\r
8596         title = "stdin";\r
8597     } else {\r
8598         f = fopen(filename, "rb");\r
8599         if (f == NULL) {\r
8600             sprintf(buf, _("Can't open \"%s\""), filename);\r
8601             DisplayError(buf, errno);\r
8602             return FALSE;\r
8603         }\r
8604     }\r
8605     if (fseek(f, 0, 0) == -1) {\r
8606         /* f is not seekable; probably a pipe */\r
8607         useList = FALSE;\r
8608     }\r
8609     if (useList && n == 0) {\r
8610         int error = GameListBuild(f);\r
8611         if (error) {\r
8612             DisplayError(_("Cannot build game list"), error);\r
8613         } else if (!ListEmpty(&gameList) &&\r
8614                    ((ListGame *) gameList.tailPred)->number > 1) {\r
8615             GameListPopUp(f, title);\r
8616             return TRUE;\r
8617         }\r
8618         GameListDestroy();\r
8619         n = 1;\r
8620     }\r
8621     if (n == 0) n = 1;\r
8622     return LoadGame(f, n, title, FALSE);\r
8623 }\r
8624 \r
8625 \r
8626 void\r
8627 MakeRegisteredMove()\r
8628 {\r
8629     int fromX, fromY, toX, toY;\r
8630     char promoChar;\r
8631     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8632         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
8633           case CMAIL_MOVE:\r
8634           case CMAIL_DRAW:\r
8635             if (appData.debugMode)\r
8636               fprintf(debugFP, "Restoring %s for game %d\n",\r
8637                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8638     \r
8639             thinkOutput[0] = NULLCHAR;\r
8640             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
8641             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
8642             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
8643             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
8644             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
8645             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
8646             MakeMove(fromX, fromY, toX, toY, promoChar);\r
8647             ShowMove(fromX, fromY, toX, toY);\r
8648               \r
8649             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8650                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8651               case MT_NONE:\r
8652               case MT_CHECK:\r
8653                 break;\r
8654                 \r
8655               case MT_CHECKMATE:\r
8656                 if (WhiteOnMove(currentMove)) {\r
8657                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
8658                 } else {\r
8659                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
8660                 }\r
8661                 break;\r
8662                 \r
8663               case MT_STALEMATE:\r
8664                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
8665                 break;\r
8666             }\r
8667 \r
8668             break;\r
8669             \r
8670           case CMAIL_RESIGN:\r
8671             if (WhiteOnMove(currentMove)) {\r
8672                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
8673             } else {\r
8674                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
8675             }\r
8676             break;\r
8677             \r
8678           case CMAIL_ACCEPT:\r
8679             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
8680             break;\r
8681               \r
8682           default:\r
8683             break;\r
8684         }\r
8685     }\r
8686 \r
8687     return;\r
8688 }\r
8689 \r
8690 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
8691 int\r
8692 CmailLoadGame(f, gameNumber, title, useList)\r
8693      FILE *f;\r
8694      int gameNumber;\r
8695      char *title;\r
8696      int useList;\r
8697 {\r
8698     int retVal;\r
8699 \r
8700     if (gameNumber > nCmailGames) {\r
8701         DisplayError(_("No more games in this message"), 0);\r
8702         return FALSE;\r
8703     }\r
8704     if (f == lastLoadGameFP) {\r
8705         int offset = gameNumber - lastLoadGameNumber;\r
8706         if (offset == 0) {\r
8707             cmailMsg[0] = NULLCHAR;\r
8708             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8709                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8710                 nCmailMovesRegistered--;\r
8711             }\r
8712             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
8713             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
8714                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
8715             }\r
8716         } else {\r
8717             if (! RegisterMove()) return FALSE;\r
8718         }\r
8719     }\r
8720 \r
8721     retVal = LoadGame(f, gameNumber, title, useList);\r
8722 \r
8723     /* Make move registered during previous look at this game, if any */\r
8724     MakeRegisteredMove();\r
8725 \r
8726     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
8727         commentList[currentMove]\r
8728           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
8729         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8730     }\r
8731 \r
8732     return retVal;\r
8733 }\r
8734 \r
8735 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
8736 int\r
8737 ReloadGame(offset)\r
8738      int offset;\r
8739 {\r
8740     int gameNumber = lastLoadGameNumber + offset;\r
8741     if (lastLoadGameFP == NULL) {\r
8742         DisplayError(_("No game has been loaded yet"), 0);\r
8743         return FALSE;\r
8744     }\r
8745     if (gameNumber <= 0) {\r
8746         DisplayError(_("Can't back up any further"), 0);\r
8747         return FALSE;\r
8748     }\r
8749     if (cmailMsgLoaded) {\r
8750         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
8751                              lastLoadGameTitle, lastLoadGameUseList);\r
8752     } else {\r
8753         return LoadGame(lastLoadGameFP, gameNumber,\r
8754                         lastLoadGameTitle, lastLoadGameUseList);\r
8755     }\r
8756 }\r
8757 \r
8758 \r
8759 \r
8760 /* Load the nth game from open file f */\r
8761 int\r
8762 LoadGame(f, gameNumber, title, useList)\r
8763      FILE *f;\r
8764      int gameNumber;\r
8765      char *title;\r
8766      int useList;\r
8767 {\r
8768     ChessMove cm;\r
8769     char buf[MSG_SIZ];\r
8770     int gn = gameNumber;\r
8771     ListGame *lg = NULL;\r
8772     int numPGNTags = 0;\r
8773     int err;\r
8774     GameMode oldGameMode;\r
8775     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
8776 \r
8777     if (appData.debugMode) \r
8778         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
8779 \r
8780     if (gameMode == Training )\r
8781         SetTrainingModeOff();\r
8782 \r
8783     oldGameMode = gameMode;\r
8784     if (gameMode != BeginningOfGame) {\r
8785       Reset(FALSE, TRUE);\r
8786     }\r
8787 \r
8788     gameFileFP = f;\r
8789     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
8790         fclose(lastLoadGameFP);\r
8791     }\r
8792 \r
8793     if (useList) {\r
8794         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
8795         \r
8796         if (lg) {\r
8797             fseek(f, lg->offset, 0);\r
8798             GameListHighlight(gameNumber);\r
8799             gn = 1;\r
8800         }\r
8801         else {\r
8802             DisplayError(_("Game number out of range"), 0);\r
8803             return FALSE;\r
8804         }\r
8805     } else {\r
8806         GameListDestroy();\r
8807         if (fseek(f, 0, 0) == -1) {\r
8808             if (f == lastLoadGameFP ?\r
8809                 gameNumber == lastLoadGameNumber + 1 :\r
8810                 gameNumber == 1) {\r
8811                 gn = 1;\r
8812             } else {\r
8813                 DisplayError(_("Can't seek on game file"), 0);\r
8814                 return FALSE;\r
8815             }\r
8816         }\r
8817     }\r
8818     lastLoadGameFP = f;\r
8819     lastLoadGameNumber = gameNumber;\r
8820     strcpy(lastLoadGameTitle, title);\r
8821     lastLoadGameUseList = useList;\r
8822 \r
8823     yynewfile(f);\r
8824 \r
8825     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
8826         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
8827                 lg->gameInfo.black);\r
8828             DisplayTitle(buf);\r
8829     } else if (*title != NULLCHAR) {\r
8830         if (gameNumber > 1) {\r
8831             sprintf(buf, "%s %d", title, gameNumber);\r
8832             DisplayTitle(buf);\r
8833         } else {\r
8834             DisplayTitle(title);\r
8835         }\r
8836     }\r
8837 \r
8838     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
8839         gameMode = PlayFromGameFile;\r
8840         ModeHighlight();\r
8841     }\r
8842 \r
8843     currentMove = forwardMostMove = backwardMostMove = 0;\r
8844     CopyBoard(boards[0], initialPosition);\r
8845     StopClocks();\r
8846 \r
8847     /*\r
8848      * Skip the first gn-1 games in the file.\r
8849      * Also skip over anything that precedes an identifiable \r
8850      * start of game marker, to avoid being confused by \r
8851      * garbage at the start of the file.  Currently \r
8852      * recognized start of game markers are the move number "1",\r
8853      * the pattern "gnuchess .* game", the pattern\r
8854      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
8855      * A game that starts with one of the latter two patterns\r
8856      * will also have a move number 1, possibly\r
8857      * following a position diagram.\r
8858      * 5-4-02: Let's try being more lenient and allowing a game to\r
8859      * start with an unnumbered move.  Does that break anything?\r
8860      */\r
8861     cm = lastLoadGameStart = (ChessMove) 0;\r
8862     while (gn > 0) {\r
8863         yyboardindex = forwardMostMove;\r
8864         cm = (ChessMove) yylex();\r
8865         switch (cm) {\r
8866           case (ChessMove) 0:\r
8867             if (cmailMsgLoaded) {\r
8868                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
8869             } else {\r
8870                 Reset(TRUE, TRUE);\r
8871                 DisplayError(_("Game not found in file"), 0);\r
8872             }\r
8873             return FALSE;\r
8874 \r
8875           case GNUChessGame:\r
8876           case XBoardGame:\r
8877             gn--;\r
8878             lastLoadGameStart = cm;\r
8879             break;\r
8880             \r
8881           case MoveNumberOne:\r
8882             switch (lastLoadGameStart) {\r
8883               case GNUChessGame:\r
8884               case XBoardGame:\r
8885               case PGNTag:\r
8886                 break;\r
8887               case MoveNumberOne:\r
8888               case (ChessMove) 0:\r
8889                 gn--;           /* count this game */\r
8890                 lastLoadGameStart = cm;\r
8891                 break;\r
8892               default:\r
8893                 /* impossible */\r
8894                 break;\r
8895             }\r
8896             break;\r
8897 \r
8898           case PGNTag:\r
8899             switch (lastLoadGameStart) {\r
8900               case GNUChessGame:\r
8901               case PGNTag:\r
8902               case MoveNumberOne:\r
8903               case (ChessMove) 0:\r
8904                 gn--;           /* count this game */\r
8905                 lastLoadGameStart = cm;\r
8906                 break;\r
8907               case XBoardGame:\r
8908                 lastLoadGameStart = cm; /* game counted already */\r
8909                 break;\r
8910               default:\r
8911                 /* impossible */\r
8912                 break;\r
8913             }\r
8914             if (gn > 0) {\r
8915                 do {\r
8916                     yyboardindex = forwardMostMove;\r
8917                     cm = (ChessMove) yylex();\r
8918                 } while (cm == PGNTag || cm == Comment);\r
8919             }\r
8920             break;\r
8921 \r
8922           case WhiteWins:\r
8923           case BlackWins:\r
8924           case GameIsDrawn:\r
8925             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8926                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8927                     != CMAIL_OLD_RESULT) {\r
8928                     nCmailResults ++ ;\r
8929                     cmailResult[  CMAIL_MAX_GAMES\r
8930                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8931                 }\r
8932             }\r
8933             break;\r
8934 \r
8935           case NormalMove:\r
8936             /* Only a NormalMove can be at the start of a game\r
8937              * without a position diagram. */\r
8938             if (lastLoadGameStart == (ChessMove) 0) {\r
8939               gn--;\r
8940               lastLoadGameStart = MoveNumberOne;\r
8941             }\r
8942             break;\r
8943 \r
8944           default:\r
8945             break;\r
8946         }\r
8947     }\r
8948     \r
8949     if (appData.debugMode)\r
8950       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8951 \r
8952     if (cm == XBoardGame) {\r
8953         /* Skip any header junk before position diagram and/or move 1 */\r
8954         for (;;) {\r
8955             yyboardindex = forwardMostMove;\r
8956             cm = (ChessMove) yylex();\r
8957 \r
8958             if (cm == (ChessMove) 0 ||\r
8959                 cm == GNUChessGame || cm == XBoardGame) {\r
8960                 /* Empty game; pretend end-of-file and handle later */\r
8961                 cm = (ChessMove) 0;\r
8962                 break;\r
8963             }\r
8964 \r
8965             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8966                 cm == PGNTag || cm == Comment)\r
8967               break;\r
8968         }\r
8969     } else if (cm == GNUChessGame) {\r
8970         if (gameInfo.event != NULL) {\r
8971             free(gameInfo.event);\r
8972         }\r
8973         gameInfo.event = StrSave(yy_text);\r
8974     }   \r
8975 \r
8976     startedFromSetupPosition = FALSE;\r
8977     while (cm == PGNTag) {\r
8978         if (appData.debugMode) \r
8979           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8980         err = ParsePGNTag(yy_text, &gameInfo);\r
8981         if (!err) numPGNTags++;\r
8982 \r
8983         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8984         if(gameInfo.variant != oldVariant) {\r
8985             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8986             InitPosition(TRUE);\r
8987             oldVariant = gameInfo.variant;\r
8988             if (appData.debugMode) \r
8989               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8990         }\r
8991 \r
8992 \r
8993         if (gameInfo.fen != NULL) {\r
8994           Board initial_position;\r
8995           startedFromSetupPosition = TRUE;\r
8996           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8997             Reset(TRUE, TRUE);\r
8998             DisplayError(_("Bad FEN position in file"), 0);\r
8999             return FALSE;\r
9000           }\r
9001           CopyBoard(boards[0], initial_position);\r
9002           if (blackPlaysFirst) {\r
9003             currentMove = forwardMostMove = backwardMostMove = 1;\r
9004             CopyBoard(boards[1], initial_position);\r
9005             strcpy(moveList[0], "");\r
9006             strcpy(parseList[0], "");\r
9007             timeRemaining[0][1] = whiteTimeRemaining;\r
9008             timeRemaining[1][1] = blackTimeRemaining;\r
9009             if (commentList[0] != NULL) {\r
9010               commentList[1] = commentList[0];\r
9011               commentList[0] = NULL;\r
9012             }\r
9013           } else {\r
9014             currentMove = forwardMostMove = backwardMostMove = 0;\r
9015           }\r
9016           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */\r
9017           {   int i;\r
9018               initialRulePlies = FENrulePlies;\r
9019               epStatus[forwardMostMove] = FENepStatus;\r
9020               for( i=0; i< nrCastlingRights; i++ )\r
9021                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
9022           }\r
9023           yyboardindex = forwardMostMove;\r
9024           free(gameInfo.fen);\r
9025           gameInfo.fen = NULL;\r
9026         }\r
9027 \r
9028         yyboardindex = forwardMostMove;\r
9029         cm = (ChessMove) yylex();\r
9030 \r
9031         /* Handle comments interspersed among the tags */\r
9032         while (cm == Comment) {\r
9033             char *p;\r
9034             if (appData.debugMode) \r
9035               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
9036             p = yy_text;\r
9037             if (*p == '{' || *p == '[' || *p == '(') {\r
9038                 p[strlen(p) - 1] = NULLCHAR;\r
9039                 p++;\r
9040             }\r
9041             while (*p == '\n') p++;\r
9042             AppendComment(currentMove, p);\r
9043             yyboardindex = forwardMostMove;\r
9044             cm = (ChessMove) yylex();\r
9045         }\r
9046     }\r
9047 \r
9048     /* don't rely on existence of Event tag since if game was\r
9049      * pasted from clipboard the Event tag may not exist\r
9050      */\r
9051     if (numPGNTags > 0){\r
9052         char *tags;\r
9053         if (gameInfo.variant == VariantNormal) {\r
9054           gameInfo.variant = StringToVariant(gameInfo.event);\r
9055         }\r
9056         if (!matchMode) {\r
9057           if( appData.autoDisplayTags ) {\r
9058             tags = PGNTags(&gameInfo);\r
9059             TagsPopUp(tags, CmailMsg());\r
9060             free(tags);\r
9061           }\r
9062         }\r
9063     } else {\r
9064         /* Make something up, but don't display it now */\r
9065         SetGameInfo();\r
9066         TagsPopDown();\r
9067     }\r
9068 \r
9069     if (cm == PositionDiagram) {\r
9070         int i, j;\r
9071         char *p;\r
9072         Board initial_position;\r
9073 \r
9074         if (appData.debugMode)\r
9075           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
9076 \r
9077         if (!startedFromSetupPosition) {\r
9078             p = yy_text;\r
9079             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
9080               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
9081                 switch (*p) {\r
9082                   case '[':\r
9083                   case '-':\r
9084                   case ' ':\r
9085                   case '\t':\r
9086                   case '\n':\r
9087                   case '\r':\r
9088                     break;\r
9089                   default:\r
9090                     initial_position[i][j++] = CharToPiece(*p);\r
9091                     break;\r
9092                 }\r
9093             while (*p == ' ' || *p == '\t' ||\r
9094                    *p == '\n' || *p == '\r') p++;\r
9095         \r
9096             if (strncmp(p, "black", strlen("black"))==0)\r
9097               blackPlaysFirst = TRUE;\r
9098             else\r
9099               blackPlaysFirst = FALSE;\r
9100             startedFromSetupPosition = TRUE;\r
9101         \r
9102             CopyBoard(boards[0], initial_position);\r
9103             if (blackPlaysFirst) {\r
9104                 currentMove = forwardMostMove = backwardMostMove = 1;\r
9105                 CopyBoard(boards[1], initial_position);\r
9106                 strcpy(moveList[0], "");\r
9107                 strcpy(parseList[0], "");\r
9108                 timeRemaining[0][1] = whiteTimeRemaining;\r
9109                 timeRemaining[1][1] = blackTimeRemaining;\r
9110                 if (commentList[0] != NULL) {\r
9111                     commentList[1] = commentList[0];\r
9112                     commentList[0] = NULL;\r
9113                 }\r
9114             } else {\r
9115                 currentMove = forwardMostMove = backwardMostMove = 0;\r
9116             }\r
9117         }\r
9118         yyboardindex = forwardMostMove;\r
9119         cm = (ChessMove) yylex();\r
9120     }\r
9121 \r
9122     if (first.pr == NoProc) {\r
9123         StartChessProgram(&first);\r
9124     }\r
9125     InitChessProgram(&first, FALSE);\r
9126     SendToProgram("force\n", &first);\r
9127     if (startedFromSetupPosition) {\r
9128         SendBoard(&first, forwardMostMove);\r
9129     if (appData.debugMode) {\r
9130         fprintf(debugFP, "Load Game\n");\r
9131     }\r
9132         DisplayBothClocks();\r
9133     }      \r
9134 \r
9135     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
9136     loadFlag = appData.suppressLoadMoves;\r
9137 \r
9138     while (cm == Comment) {\r
9139         char *p;\r
9140         if (appData.debugMode) \r
9141           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
9142         p = yy_text;\r
9143         if (*p == '{' || *p == '[' || *p == '(') {\r
9144             p[strlen(p) - 1] = NULLCHAR;\r
9145             p++;\r
9146         }\r
9147         while (*p == '\n') p++;\r
9148         AppendComment(currentMove, p);\r
9149         yyboardindex = forwardMostMove;\r
9150         cm = (ChessMove) yylex();\r
9151     }\r
9152 \r
9153     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
9154         cm == WhiteWins || cm == BlackWins ||\r
9155         cm == GameIsDrawn || cm == GameUnfinished) {\r
9156         DisplayMessage("", _("No moves in game"));\r
9157         if (cmailMsgLoaded) {\r
9158             if (appData.debugMode)\r
9159               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
9160             ClearHighlights();\r
9161             flipView = FALSE;\r
9162         }\r
9163         DrawPosition(FALSE, boards[currentMove]);\r
9164         DisplayBothClocks();\r
9165         gameMode = EditGame;\r
9166         ModeHighlight();\r
9167         gameFileFP = NULL;\r
9168         cmailOldMove = 0;\r
9169         return TRUE;\r
9170     }\r
9171 \r
9172     // [HGM] PV info: routine tests if comment empty\r
9173     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
9174         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9175     }\r
9176     if (!matchMode && appData.timeDelay != 0) \r
9177       DrawPosition(FALSE, boards[currentMove]);\r
9178 \r
9179     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
9180       programStats.ok_to_send = 1;\r
9181     }\r
9182 \r
9183     /* if the first token after the PGN tags is a move\r
9184      * and not move number 1, retrieve it from the parser \r
9185      */\r
9186     if (cm != MoveNumberOne)\r
9187         LoadGameOneMove(cm);\r
9188 \r
9189     /* load the remaining moves from the file */\r
9190     while (LoadGameOneMove((ChessMove)0)) {\r
9191       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9192       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9193     }\r
9194 \r
9195     /* rewind to the start of the game */\r
9196     currentMove = backwardMostMove;\r
9197 \r
9198     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9199 \r
9200     if (oldGameMode == AnalyzeFile ||\r
9201         oldGameMode == AnalyzeMode) {\r
9202       AnalyzeFileEvent();\r
9203     }\r
9204 \r
9205     if (matchMode || appData.timeDelay == 0) {\r
9206       ToEndEvent();\r
9207       gameMode = EditGame;\r
9208       ModeHighlight();\r
9209     } else if (appData.timeDelay > 0) {\r
9210       AutoPlayGameLoop();\r
9211     }\r
9212 \r
9213     if (appData.debugMode) \r
9214         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
9215 \r
9216     loadFlag = 0; /* [HGM] true game starts */\r
9217     return TRUE;\r
9218 }\r
9219 \r
9220 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
9221 int\r
9222 ReloadPosition(offset)\r
9223      int offset;\r
9224 {\r
9225     int positionNumber = lastLoadPositionNumber + offset;\r
9226     if (lastLoadPositionFP == NULL) {\r
9227         DisplayError(_("No position has been loaded yet"), 0);\r
9228         return FALSE;\r
9229     }\r
9230     if (positionNumber <= 0) {\r
9231         DisplayError(_("Can't back up any further"), 0);\r
9232         return FALSE;\r
9233     }\r
9234     return LoadPosition(lastLoadPositionFP, positionNumber,\r
9235                         lastLoadPositionTitle);\r
9236 }\r
9237 \r
9238 /* Load the nth position from the given file */\r
9239 int\r
9240 LoadPositionFromFile(filename, n, title)\r
9241      char *filename;\r
9242      int n;\r
9243      char *title;\r
9244 {\r
9245     FILE *f;\r
9246     char buf[MSG_SIZ];\r
9247 \r
9248     if (strcmp(filename, "-") == 0) {\r
9249         return LoadPosition(stdin, n, "stdin");\r
9250     } else {\r
9251         f = fopen(filename, "rb");\r
9252         if (f == NULL) {\r
9253             sprintf(buf, _("Can't open \"%s\""), filename);\r
9254             DisplayError(buf, errno);\r
9255             return FALSE;\r
9256         } else {\r
9257             return LoadPosition(f, n, title);\r
9258         }\r
9259     }\r
9260 }\r
9261 \r
9262 /* Load the nth position from the given open file, and close it */\r
9263 int\r
9264 LoadPosition(f, positionNumber, title)\r
9265      FILE *f;\r
9266      int positionNumber;\r
9267      char *title;\r
9268 {\r
9269     char *p, line[MSG_SIZ];\r
9270     Board initial_position;\r
9271     int i, j, fenMode, pn;\r
9272     \r
9273     if (gameMode == Training )\r
9274         SetTrainingModeOff();\r
9275 \r
9276     if (gameMode != BeginningOfGame) {\r
9277         Reset(FALSE, TRUE);\r
9278     }\r
9279     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
9280         fclose(lastLoadPositionFP);\r
9281     }\r
9282     if (positionNumber == 0) positionNumber = 1;\r
9283     lastLoadPositionFP = f;\r
9284     lastLoadPositionNumber = positionNumber;\r
9285     strcpy(lastLoadPositionTitle, title);\r
9286     if (first.pr == NoProc) {\r
9287       StartChessProgram(&first);\r
9288       InitChessProgram(&first, FALSE);\r
9289     }    \r
9290     pn = positionNumber;\r
9291     if (positionNumber < 0) {\r
9292         /* Negative position number means to seek to that byte offset */\r
9293         if (fseek(f, -positionNumber, 0) == -1) {\r
9294             DisplayError(_("Can't seek on position file"), 0);\r
9295             return FALSE;\r
9296         };\r
9297         pn = 1;\r
9298     } else {\r
9299         if (fseek(f, 0, 0) == -1) {\r
9300             if (f == lastLoadPositionFP ?\r
9301                 positionNumber == lastLoadPositionNumber + 1 :\r
9302                 positionNumber == 1) {\r
9303                 pn = 1;\r
9304             } else {\r
9305                 DisplayError(_("Can't seek on position file"), 0);\r
9306                 return FALSE;\r
9307             }\r
9308         }\r
9309     }\r
9310     /* See if this file is FEN or old-style xboard */\r
9311     if (fgets(line, MSG_SIZ, f) == NULL) {\r
9312         DisplayError(_("Position not found in file"), 0);\r
9313         return FALSE;\r
9314     }\r
9315 #if 0\r
9316     switch (line[0]) {\r
9317       case '#':  case 'x':\r
9318       default:\r
9319         fenMode = FALSE;\r
9320         break;\r
9321       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
9322       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
9323       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
9324       case '7':  case '8':  case '9':\r
9325       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
9326       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
9327       case 'C':  case 'W':             case 'c':  case 'w': \r
9328         fenMode = TRUE;\r
9329         break;\r
9330     }\r
9331 #else\r
9332     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
9333     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
9334 #endif\r
9335 \r
9336     if (pn >= 2) {\r
9337         if (fenMode || line[0] == '#') pn--;\r
9338         while (pn > 0) {\r
9339             /* skip positions before number pn */\r
9340             if (fgets(line, MSG_SIZ, f) == NULL) {\r
9341                 Reset(TRUE, TRUE);\r
9342                 DisplayError(_("Position not found in file"), 0);\r
9343                 return FALSE;\r
9344             }\r
9345             if (fenMode || line[0] == '#') pn--;\r
9346         }\r
9347     }\r
9348 \r
9349     if (fenMode) {\r
9350         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
9351             DisplayError(_("Bad FEN position in file"), 0);\r
9352             return FALSE;\r
9353         }\r
9354     } else {\r
9355         (void) fgets(line, MSG_SIZ, f);\r
9356         (void) fgets(line, MSG_SIZ, f);\r
9357     \r
9358         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
9359             (void) fgets(line, MSG_SIZ, f);\r
9360             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
9361                 if (*p == ' ')\r
9362                   continue;\r
9363                 initial_position[i][j++] = CharToPiece(*p);\r
9364             }\r
9365         }\r
9366     \r
9367         blackPlaysFirst = FALSE;\r
9368         if (!feof(f)) {\r
9369             (void) fgets(line, MSG_SIZ, f);\r
9370             if (strncmp(line, "black", strlen("black"))==0)\r
9371               blackPlaysFirst = TRUE;\r
9372         }\r
9373     }\r
9374     startedFromSetupPosition = TRUE;\r
9375     \r
9376     SendToProgram("force\n", &first);\r
9377     CopyBoard(boards[0], initial_position);\r
9378     if (blackPlaysFirst) {\r
9379         currentMove = forwardMostMove = backwardMostMove = 1;\r
9380         strcpy(moveList[0], "");\r
9381         strcpy(parseList[0], "");\r
9382         CopyBoard(boards[1], initial_position);\r
9383         DisplayMessage("", _("Black to play"));\r
9384     } else {\r
9385         currentMove = forwardMostMove = backwardMostMove = 0;\r
9386         DisplayMessage("", _("White to play"));\r
9387     }\r
9388           /* [HGM] copy FEN attributes as well */\r
9389           {   int i;\r
9390               initialRulePlies = FENrulePlies;\r
9391               epStatus[forwardMostMove] = FENepStatus;\r
9392               for( i=0; i< nrCastlingRights; i++ )\r
9393                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
9394           }\r
9395     SendBoard(&first, forwardMostMove);\r
9396     if (appData.debugMode) {\r
9397 int i, j;\r
9398   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}\r
9399   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");\r
9400         fprintf(debugFP, "Load Position\n");\r
9401     }\r
9402 \r
9403     if (positionNumber > 1) {\r
9404         sprintf(line, "%s %d", title, positionNumber);\r
9405         DisplayTitle(line);\r
9406     } else {\r
9407         DisplayTitle(title);\r
9408     }\r
9409     gameMode = EditGame;\r
9410     ModeHighlight();\r
9411     ResetClocks();\r
9412     timeRemaining[0][1] = whiteTimeRemaining;\r
9413     timeRemaining[1][1] = blackTimeRemaining;\r
9414     DrawPosition(FALSE, boards[currentMove]);\r
9415    \r
9416     return TRUE;\r
9417 }\r
9418 \r
9419 \r
9420 void\r
9421 CopyPlayerNameIntoFileName(dest, src)\r
9422      char **dest, *src;\r
9423 {\r
9424     while (*src != NULLCHAR && *src != ',') {\r
9425         if (*src == ' ') {\r
9426             *(*dest)++ = '_';\r
9427             src++;\r
9428         } else {\r
9429             *(*dest)++ = *src++;\r
9430         }\r
9431     }\r
9432 }\r
9433 \r
9434 char *DefaultFileName(ext)\r
9435      char *ext;\r
9436 {\r
9437     static char def[MSG_SIZ];\r
9438     char *p;\r
9439 \r
9440     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
9441         p = def;\r
9442         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
9443         *p++ = '-';\r
9444         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
9445         *p++ = '.';\r
9446         strcpy(p, ext);\r
9447     } else {\r
9448         def[0] = NULLCHAR;\r
9449     }\r
9450     return def;\r
9451 }\r
9452 \r
9453 /* Save the current game to the given file */\r
9454 int\r
9455 SaveGameToFile(filename, append)\r
9456      char *filename;\r
9457      int append;\r
9458 {\r
9459     FILE *f;\r
9460     char buf[MSG_SIZ];\r
9461 \r
9462     if (strcmp(filename, "-") == 0) {\r
9463         return SaveGame(stdout, 0, NULL);\r
9464     } else {\r
9465         f = fopen(filename, append ? "a" : "w");\r
9466         if (f == NULL) {\r
9467             sprintf(buf, _("Can't open \"%s\""), filename);\r
9468             DisplayError(buf, errno);\r
9469             return FALSE;\r
9470         } else {\r
9471             return SaveGame(f, 0, NULL);\r
9472         }\r
9473     }\r
9474 }\r
9475 \r
9476 char *\r
9477 SavePart(str)\r
9478      char *str;\r
9479 {\r
9480     static char buf[MSG_SIZ];\r
9481     char *p;\r
9482     \r
9483     p = strchr(str, ' ');\r
9484     if (p == NULL) return str;\r
9485     strncpy(buf, str, p - str);\r
9486     buf[p - str] = NULLCHAR;\r
9487     return buf;\r
9488 }\r
9489 \r
9490 #define PGN_MAX_LINE 75\r
9491 \r
9492 #define PGN_SIDE_WHITE  0\r
9493 #define PGN_SIDE_BLACK  1\r
9494 \r
9495 /* [AS] */\r
9496 static int FindFirstMoveOutOfBook( int side )\r
9497 {\r
9498     int result = -1;\r
9499 \r
9500     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
9501         int index = backwardMostMove;\r
9502         int has_book_hit = 0;\r
9503 \r
9504         if( (index % 2) != side ) {\r
9505             index++;\r
9506         }\r
9507 \r
9508         while( index < forwardMostMove ) {\r
9509             /* Check to see if engine is in book */\r
9510             int depth = pvInfoList[index].depth;\r
9511             int score = pvInfoList[index].score;\r
9512             int in_book = 0;\r
9513 \r
9514             if( depth <= 2 ) {\r
9515                 in_book = 1;\r
9516             }\r
9517             else if( score == 0 && depth == 63 ) {\r
9518                 in_book = 1; /* Zappa */\r
9519             }\r
9520             else if( score == 2 && depth == 99 ) {\r
9521                 in_book = 1; /* Abrok */\r
9522             }\r
9523 \r
9524             has_book_hit += in_book;\r
9525 \r
9526             if( ! in_book ) {\r
9527                 result = index;\r
9528 \r
9529                 break;\r
9530             }\r
9531 \r
9532             index += 2;\r
9533         }\r
9534     }\r
9535 \r
9536     return result;\r
9537 }\r
9538 \r
9539 /* [AS] */\r
9540 void GetOutOfBookInfo( char * buf )\r
9541 {\r
9542     int oob[2];\r
9543     int i;\r
9544     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9545 \r
9546     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
9547     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
9548 \r
9549     *buf = '\0';\r
9550 \r
9551     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
9552         for( i=0; i<2; i++ ) {\r
9553             int idx = oob[i];\r
9554 \r
9555             if( idx >= 0 ) {\r
9556                 if( i > 0 && oob[0] >= 0 ) {\r
9557                     strcat( buf, "   " );\r
9558                 }\r
9559 \r
9560                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
9561                 sprintf( buf+strlen(buf), "%s%.2f", \r
9562                     pvInfoList[idx].score >= 0 ? "+" : "",\r
9563                     pvInfoList[idx].score / 100.0 );\r
9564             }\r
9565         }\r
9566     }\r
9567 }\r
9568 \r
9569 /* Save game in PGN style and close the file */\r
9570 int\r
9571 SaveGamePGN(f)\r
9572      FILE *f;\r
9573 {\r
9574     int i, offset, linelen, newblock;\r
9575     time_t tm;\r
9576 //    char *movetext;\r
9577     char numtext[32];\r
9578     int movelen, numlen, blank;\r
9579     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
9580 \r
9581     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9582     \r
9583     tm = time((time_t *) NULL);\r
9584     \r
9585     PrintPGNTags(f, &gameInfo);\r
9586     \r
9587     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9588         char *fen = PositionToFEN(backwardMostMove, 1);\r
9589         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
9590         fprintf(f, "\n{--------------\n");\r
9591         PrintPosition(f, backwardMostMove);\r
9592         fprintf(f, "--------------}\n");\r
9593         free(fen);\r
9594     }\r
9595     else {\r
9596         /* [AS] Out of book annotation */\r
9597         if( appData.saveOutOfBookInfo ) {\r
9598             char buf[64];\r
9599 \r
9600             GetOutOfBookInfo( buf );\r
9601 \r
9602             if( buf[0] != '\0' ) {\r
9603                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
9604             }\r
9605         }\r
9606 \r
9607         fprintf(f, "\n");\r
9608     }\r
9609 \r
9610     i = backwardMostMove;\r
9611     linelen = 0;\r
9612     newblock = TRUE;\r
9613 \r
9614     while (i < forwardMostMove) {\r
9615         /* Print comments preceding this move */\r
9616         if (commentList[i] != NULL) {\r
9617             if (linelen > 0) fprintf(f, "\n");\r
9618             fprintf(f, "{\n%s}\n", commentList[i]);\r
9619             linelen = 0;\r
9620             newblock = TRUE;\r
9621         }\r
9622 \r
9623         /* Format move number */\r
9624         if ((i % 2) == 0) {\r
9625             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
9626         } else {\r
9627             if (newblock) {\r
9628                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
9629             } else {\r
9630                 numtext[0] = NULLCHAR;\r
9631             }\r
9632         }\r
9633         numlen = strlen(numtext);\r
9634         newblock = FALSE;\r
9635 \r
9636         /* Print move number */\r
9637         blank = linelen > 0 && numlen > 0;\r
9638         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
9639             fprintf(f, "\n");\r
9640             linelen = 0;\r
9641             blank = 0;\r
9642         }\r
9643         if (blank) {\r
9644             fprintf(f, " ");\r
9645             linelen++;\r
9646         }\r
9647         fprintf(f, numtext);\r
9648         linelen += numlen;\r
9649 \r
9650         /* Get move */\r
9651         movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */\r
9652 \r
9653         /* Print move */\r
9654         blank = linelen > 0 && movelen > 0;\r
9655         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9656             fprintf(f, "\n");\r
9657             linelen = 0;\r
9658             blank = 0;\r
9659         }\r
9660         if (blank) {\r
9661             fprintf(f, " ");\r
9662             linelen++;\r
9663         }\r
9664         fprintf(f, parseList[i]);\r
9665         linelen += movelen;\r
9666 \r
9667         /* [AS] Add PV info if present */\r
9668         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9669             /* [HGM] add time */\r
9670             char buf[MSG_SIZ]; int seconds = 0;\r
9671 \r
9672 #if 0\r
9673             if(i >= backwardMostMove) {\r
9674                 if(WhiteOnMove(i))\r
9675                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]\r
9676                                   + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;\r
9677                 else\r
9678                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]\r
9679                                   + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;\r
9680             }\r
9681             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest\r
9682 #else\r
9683             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time\r
9684 #endif\r
9685 \r
9686             if( seconds <= 0) buf[0] = 0; else\r
9687             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {\r
9688                 seconds = (seconds + 4)/10; // round to full seconds\r
9689                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else\r
9690                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
9691             }\r
9692 \r
9693             sprintf( move_buffer, "{%s%.2f/%d%s}", \r
9694                 pvInfoList[i].score >= 0 ? "+" : "",\r
9695                 pvInfoList[i].score / 100.0,\r
9696                 pvInfoList[i].depth,\r
9697                 buf );\r
9698 \r
9699             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */\r
9700 \r
9701             /* Print score/depth */\r
9702             blank = linelen > 0 && movelen > 0;\r
9703             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9704                 fprintf(f, "\n");\r
9705                 linelen = 0;\r
9706                 blank = 0;\r
9707             }\r
9708             if (blank) {\r
9709                 fprintf(f, " ");\r
9710                 linelen++;\r
9711             }\r
9712             fprintf(f, move_buffer);\r
9713             linelen += movelen;\r
9714         }\r
9715 \r
9716         i++;\r
9717     }\r
9718     \r
9719     /* Start a new line */\r
9720     if (linelen > 0) fprintf(f, "\n");\r
9721 \r
9722     /* Print comments after last move */\r
9723     if (commentList[i] != NULL) {\r
9724         fprintf(f, "{\n%s}\n", commentList[i]);\r
9725     }\r
9726 \r
9727     /* Print result */\r
9728     if (gameInfo.resultDetails != NULL &&\r
9729         gameInfo.resultDetails[0] != NULLCHAR) {\r
9730         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
9731                 PGNResult(gameInfo.result));\r
9732     } else {\r
9733         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9734     }\r
9735 \r
9736     fclose(f);\r
9737     return TRUE;\r
9738 }\r
9739 \r
9740 /* Save game in old style and close the file */\r
9741 int\r
9742 SaveGameOldStyle(f)\r
9743      FILE *f;\r
9744 {\r
9745     int i, offset;\r
9746     time_t tm;\r
9747     \r
9748     tm = time((time_t *) NULL);\r
9749     \r
9750     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
9751     PrintOpponents(f);\r
9752     \r
9753     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9754         fprintf(f, "\n[--------------\n");\r
9755         PrintPosition(f, backwardMostMove);\r
9756         fprintf(f, "--------------]\n");\r
9757     } else {\r
9758         fprintf(f, "\n");\r
9759     }\r
9760 \r
9761     i = backwardMostMove;\r
9762     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9763 \r
9764     while (i < forwardMostMove) {\r
9765         if (commentList[i] != NULL) {\r
9766             fprintf(f, "[%s]\n", commentList[i]);\r
9767         }\r
9768 \r
9769         if ((i % 2) == 1) {\r
9770             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
9771             i++;\r
9772         } else {\r
9773             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
9774             i++;\r
9775             if (commentList[i] != NULL) {\r
9776                 fprintf(f, "\n");\r
9777                 continue;\r
9778             }\r
9779             if (i >= forwardMostMove) {\r
9780                 fprintf(f, "\n");\r
9781                 break;\r
9782             }\r
9783             fprintf(f, "%s\n", parseList[i]);\r
9784             i++;\r
9785         }\r
9786     }\r
9787     \r
9788     if (commentList[i] != NULL) {\r
9789         fprintf(f, "[%s]\n", commentList[i]);\r
9790     }\r
9791 \r
9792     /* This isn't really the old style, but it's close enough */\r
9793     if (gameInfo.resultDetails != NULL &&\r
9794         gameInfo.resultDetails[0] != NULLCHAR) {\r
9795         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
9796                 gameInfo.resultDetails);\r
9797     } else {\r
9798         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9799     }\r
9800 \r
9801     fclose(f);\r
9802     return TRUE;\r
9803 }\r
9804 \r
9805 /* Save the current game to open file f and close the file */\r
9806 int\r
9807 SaveGame(f, dummy, dummy2)\r
9808      FILE *f;\r
9809      int dummy;\r
9810      char *dummy2;\r
9811 {\r
9812     if (gameMode == EditPosition) EditPositionDone();\r
9813     if (appData.oldSaveStyle)\r
9814       return SaveGameOldStyle(f);\r
9815     else\r
9816       return SaveGamePGN(f);\r
9817 }\r
9818 \r
9819 /* Save the current position to the given file */\r
9820 int\r
9821 SavePositionToFile(filename)\r
9822      char *filename;\r
9823 {\r
9824     FILE *f;\r
9825     char buf[MSG_SIZ];\r
9826 \r
9827     if (strcmp(filename, "-") == 0) {\r
9828         return SavePosition(stdout, 0, NULL);\r
9829     } else {\r
9830         f = fopen(filename, "a");\r
9831         if (f == NULL) {\r
9832             sprintf(buf, _("Can't open \"%s\""), filename);\r
9833             DisplayError(buf, errno);\r
9834             return FALSE;\r
9835         } else {\r
9836             SavePosition(f, 0, NULL);\r
9837             return TRUE;\r
9838         }\r
9839     }\r
9840 }\r
9841 \r
9842 /* Save the current position to the given open file and close the file */\r
9843 int\r
9844 SavePosition(f, dummy, dummy2)\r
9845      FILE *f;\r
9846      int dummy;\r
9847      char *dummy2;\r
9848 {\r
9849     time_t tm;\r
9850     char *fen;\r
9851     \r
9852     if (appData.oldSaveStyle) {\r
9853         tm = time((time_t *) NULL);\r
9854     \r
9855         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
9856         PrintOpponents(f);\r
9857         fprintf(f, "[--------------\n");\r
9858         PrintPosition(f, currentMove);\r
9859         fprintf(f, "--------------]\n");\r
9860     } else {\r
9861         fen = PositionToFEN(currentMove, 1);\r
9862         fprintf(f, "%s\n", fen);\r
9863         free(fen);\r
9864     }\r
9865     fclose(f);\r
9866     return TRUE;\r
9867 }\r
9868 \r
9869 void\r
9870 ReloadCmailMsgEvent(unregister)\r
9871      int unregister;\r
9872 {\r
9873 #if !WIN32\r
9874     static char *inFilename = NULL;\r
9875     static char *outFilename;\r
9876     int i;\r
9877     struct stat inbuf, outbuf;\r
9878     int status;\r
9879     \r
9880     /* Any registered moves are unregistered if unregister is set, */\r
9881     /* i.e. invoked by the signal handler */\r
9882     if (unregister) {\r
9883         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9884             cmailMoveRegistered[i] = FALSE;\r
9885             if (cmailCommentList[i] != NULL) {\r
9886                 free(cmailCommentList[i]);\r
9887                 cmailCommentList[i] = NULL;\r
9888             }\r
9889         }\r
9890         nCmailMovesRegistered = 0;\r
9891     }\r
9892 \r
9893     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9894         cmailResult[i] = CMAIL_NOT_RESULT;\r
9895     }\r
9896     nCmailResults = 0;\r
9897 \r
9898     if (inFilename == NULL) {\r
9899         /* Because the filenames are static they only get malloced once  */\r
9900         /* and they never get freed                                      */\r
9901         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
9902         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
9903 \r
9904         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
9905         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
9906     }\r
9907     \r
9908     status = stat(outFilename, &outbuf);\r
9909     if (status < 0) {\r
9910         cmailMailedMove = FALSE;\r
9911     } else {\r
9912         status = stat(inFilename, &inbuf);\r
9913         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
9914     }\r
9915     \r
9916     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
9917        counts the games, notes how each one terminated, etc.\r
9918        \r
9919        It would be nice to remove this kludge and instead gather all\r
9920        the information while building the game list.  (And to keep it\r
9921        in the game list nodes instead of having a bunch of fixed-size\r
9922        parallel arrays.)  Note this will require getting each game's\r
9923        termination from the PGN tags, as the game list builder does\r
9924        not process the game moves.  --mann\r
9925        */\r
9926     cmailMsgLoaded = TRUE;\r
9927     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
9928     \r
9929     /* Load first game in the file or popup game menu */\r
9930     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
9931 \r
9932 #endif /* !WIN32 */\r
9933     return;\r
9934 }\r
9935 \r
9936 int\r
9937 RegisterMove()\r
9938 {\r
9939     FILE *f;\r
9940     char string[MSG_SIZ];\r
9941 \r
9942     if (   cmailMailedMove\r
9943         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9944         return TRUE;            /* Allow free viewing  */\r
9945     }\r
9946 \r
9947     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9948     /* with the move registered when the conditions for registering no   */\r
9949     /* longer hold                                                       */\r
9950     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9951         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9952         nCmailMovesRegistered --;\r
9953 \r
9954         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9955           {\r
9956               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9957               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9958           }\r
9959     }\r
9960 \r
9961     if (cmailOldMove == -1) {\r
9962         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);\r
9963         return FALSE;\r
9964     }\r
9965 \r
9966     if (currentMove > cmailOldMove + 1) {\r
9967         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);\r
9968         return FALSE;\r
9969     }\r
9970 \r
9971     if (currentMove < cmailOldMove) {\r
9972         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);\r
9973         return FALSE;\r
9974     }\r
9975 \r
9976     if (forwardMostMove > currentMove) {\r
9977         /* Silently truncate extra moves */\r
9978         TruncateGame();\r
9979     }\r
9980 \r
9981     if (   (currentMove == cmailOldMove + 1)\r
9982         || (   (currentMove == cmailOldMove)\r
9983             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9984                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9985         if (gameInfo.result != GameUnfinished) {\r
9986             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9987         }\r
9988 \r
9989         if (commentList[currentMove] != NULL) {\r
9990             cmailCommentList[lastLoadGameNumber - 1]\r
9991               = StrSave(commentList[currentMove]);\r
9992         }\r
9993         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9994 \r
9995         if (appData.debugMode)\r
9996           fprintf(debugFP, "Saving %s for game %d\n",\r
9997                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9998 \r
9999         sprintf(string,\r
10000                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
10001         \r
10002         f = fopen(string, "w");\r
10003         if (appData.oldSaveStyle) {\r
10004             SaveGameOldStyle(f); /* also closes the file */\r
10005             \r
10006             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
10007             f = fopen(string, "w");\r
10008             SavePosition(f, 0, NULL); /* also closes the file */\r
10009         } else {\r
10010             fprintf(f, "{--------------\n");\r
10011             PrintPosition(f, currentMove);\r
10012             fprintf(f, "--------------}\n\n");\r
10013             \r
10014             SaveGame(f, 0, NULL); /* also closes the file*/\r
10015         }\r
10016         \r
10017         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
10018         nCmailMovesRegistered ++;\r
10019     } else if (nCmailGames == 1) {\r
10020         DisplayError(_("You have not made a move yet"), 0);\r
10021         return FALSE;\r
10022     }\r
10023 \r
10024     return TRUE;\r
10025 }\r
10026 \r
10027 void\r
10028 MailMoveEvent()\r
10029 {\r
10030 #if !WIN32\r
10031     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
10032     FILE *commandOutput;\r
10033     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
10034     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
10035     int nBuffers;\r
10036     int i;\r
10037     int archived;\r
10038     char *arcDir;\r
10039 \r
10040     if (! cmailMsgLoaded) {\r
10041         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);\r
10042         return;\r
10043     }\r
10044 \r
10045     if (nCmailGames == nCmailResults) {\r
10046         DisplayError(_("No unfinished games"), 0);\r
10047         return;\r
10048     }\r
10049 \r
10050 #if CMAIL_PROHIBIT_REMAIL\r
10051     if (cmailMailedMove) {\r
10052         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
10053         DisplayError(msg, 0);\r
10054         return;\r
10055     }\r
10056 #endif\r
10057 \r
10058     if (! (cmailMailedMove || RegisterMove())) return;\r
10059     \r
10060     if (   cmailMailedMove\r
10061         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
10062         sprintf(string, partCommandString,\r
10063                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
10064         commandOutput = popen(string, "r");\r
10065 \r
10066         if (commandOutput == NULL) {\r
10067             DisplayError(_("Failed to invoke cmail"), 0);\r
10068         } else {\r
10069             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
10070                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
10071             }\r
10072             if (nBuffers > 1) {\r
10073                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
10074                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
10075                 nBytes = MSG_SIZ - 1;\r
10076             } else {\r
10077                 (void) memcpy(msg, buffer, nBytes);\r
10078             }\r
10079             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
10080 \r
10081             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
10082                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
10083 \r
10084                 archived = TRUE;\r
10085                 for (i = 0; i < nCmailGames; i ++) {\r
10086                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
10087                         archived = FALSE;\r
10088                     }\r
10089                 }\r
10090                 if (   archived\r
10091                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
10092                         != NULL)) {\r
10093                     sprintf(buffer, "%s/%s.%s.archive",\r
10094                             arcDir,\r
10095                             appData.cmailGameName,\r
10096                             gameInfo.date);\r
10097                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
10098                     cmailMsgLoaded = FALSE;\r
10099                 }\r
10100             }\r
10101 \r
10102             DisplayInformation(msg);\r
10103             pclose(commandOutput);\r
10104         }\r
10105     } else {\r
10106         if ((*cmailMsg) != '\0') {\r
10107             DisplayInformation(cmailMsg);\r
10108         }\r
10109     }\r
10110 \r
10111     return;\r
10112 #endif /* !WIN32 */\r
10113 }\r
10114 \r
10115 char *\r
10116 CmailMsg()\r
10117 {\r
10118 #if WIN32\r
10119     return NULL;\r
10120 #else\r
10121     int  prependComma = 0;\r
10122     char number[5];\r
10123     char string[MSG_SIZ];       /* Space for game-list */\r
10124     int  i;\r
10125     \r
10126     if (!cmailMsgLoaded) return "";\r
10127 \r
10128     if (cmailMailedMove) {\r
10129         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));\r
10130     } else {\r
10131         /* Create a list of games left */\r
10132         sprintf(string, "[");\r
10133         for (i = 0; i < nCmailGames; i ++) {\r
10134             if (! (   cmailMoveRegistered[i]\r
10135                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
10136                 if (prependComma) {\r
10137                     sprintf(number, ",%d", i + 1);\r
10138                 } else {\r
10139                     sprintf(number, "%d", i + 1);\r
10140                     prependComma = 1;\r
10141                 }\r
10142                 \r
10143                 strcat(string, number);\r
10144             }\r
10145         }\r
10146         strcat(string, "]");\r
10147 \r
10148         if (nCmailMovesRegistered + nCmailResults == 0) {\r
10149             switch (nCmailGames) {\r
10150               case 1:\r
10151                 sprintf(cmailMsg,\r
10152                         _("Still need to make move for game\n"));\r
10153                 break;\r
10154                 \r
10155               case 2:\r
10156                 sprintf(cmailMsg,\r
10157                         _("Still need to make moves for both games\n"));\r
10158                 break;\r
10159                 \r
10160               default:\r
10161                 sprintf(cmailMsg,\r
10162                         _("Still need to make moves for all %d games\n"),\r
10163                         nCmailGames);\r
10164                 break;\r
10165             }\r
10166         } else {\r
10167             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
10168               case 1:\r
10169                 sprintf(cmailMsg,\r
10170                         _("Still need to make a move for game %s\n"),\r
10171                         string);\r
10172                 break;\r
10173                 \r
10174               case 0:\r
10175                 if (nCmailResults == nCmailGames) {\r
10176                     sprintf(cmailMsg, _("No unfinished games\n"));\r
10177                 } else {\r
10178                     sprintf(cmailMsg, _("Ready to send mail\n"));\r
10179                 }\r
10180                 break;\r
10181                 \r
10182               default:\r
10183                 sprintf(cmailMsg,\r
10184                         _("Still need to make moves for games %s\n"),\r
10185                         string);\r
10186             }\r
10187         }\r
10188     }\r
10189     return cmailMsg;\r
10190 #endif /* WIN32 */\r
10191 }\r
10192 \r
10193 void\r
10194 ResetGameEvent()\r
10195 {\r
10196     if (gameMode == Training)\r
10197       SetTrainingModeOff();\r
10198 \r
10199     Reset(TRUE, TRUE);\r
10200     cmailMsgLoaded = FALSE;\r
10201     if (appData.icsActive) {\r
10202       SendToICS(ics_prefix);\r
10203       SendToICS("refresh\n");\r
10204     }\r
10205 }\r
10206 \r
10207 void\r
10208 ExitEvent(status)\r
10209      int status;\r
10210 {\r
10211     exiting++;\r
10212     if (exiting > 2) {\r
10213       /* Give up on clean exit */\r
10214       exit(status);\r
10215     }\r
10216     if (exiting > 1) {\r
10217       /* Keep trying for clean exit */\r
10218       return;\r
10219     }\r
10220 \r
10221     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
10222 \r
10223     if (telnetISR != NULL) {\r
10224       RemoveInputSource(telnetISR);\r
10225     }\r
10226     if (icsPR != NoProc) {\r
10227       DestroyChildProcess(icsPR, TRUE);\r
10228     }\r
10229 #if 0\r
10230     /* Save game if resource set and not already saved by GameEnds() */\r
10231     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
10232                              && forwardMostMove > 0) {\r
10233       if (*appData.saveGameFile != NULLCHAR) {\r
10234         SaveGameToFile(appData.saveGameFile, TRUE);\r
10235       } else if (appData.autoSaveGames) {\r
10236         AutoSaveGame();\r
10237       }\r
10238       if (*appData.savePositionFile != NULLCHAR) {\r
10239         SavePositionToFile(appData.savePositionFile);\r
10240       }\r
10241     }\r
10242     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10243 #else\r
10244     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
10245     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);\r
10246 #endif\r
10247     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
10248     /* make sure this other one finishes before killing it!                  */\r
10249     if(endingGame) { int count = 0;\r
10250         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
10251         while(endingGame && count++ < 10) DoSleep(1);\r
10252         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
10253     }\r
10254 \r
10255     /* Kill off chess programs */\r
10256     if (first.pr != NoProc) {\r
10257         ExitAnalyzeMode();\r
10258         \r
10259         DoSleep( appData.delayBeforeQuit );\r
10260         SendToProgram("quit\n", &first);\r
10261         DoSleep( appData.delayAfterQuit );\r
10262         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
10263     }\r
10264     if (second.pr != NoProc) {\r
10265         DoSleep( appData.delayBeforeQuit );\r
10266         SendToProgram("quit\n", &second);\r
10267         DoSleep( appData.delayAfterQuit );\r
10268         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
10269     }\r
10270     if (first.isr != NULL) {\r
10271         RemoveInputSource(first.isr);\r
10272     }\r
10273     if (second.isr != NULL) {\r
10274         RemoveInputSource(second.isr);\r
10275     }\r
10276 \r
10277     ShutDownFrontEnd();\r
10278     exit(status);\r
10279 }\r
10280 \r
10281 void\r
10282 PauseEvent()\r
10283 {\r
10284     if (appData.debugMode)\r
10285         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
10286     if (pausing) {\r
10287         pausing = FALSE;\r
10288         ModeHighlight();\r
10289         if (gameMode == MachinePlaysWhite ||\r
10290             gameMode == MachinePlaysBlack) {\r
10291             StartClocks();\r
10292         } else {\r
10293             DisplayBothClocks();\r
10294         }\r
10295         if (gameMode == PlayFromGameFile) {\r
10296             if (appData.timeDelay >= 0) \r
10297                 AutoPlayGameLoop();\r
10298         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
10299             Reset(FALSE, TRUE);\r
10300             SendToICS(ics_prefix);\r
10301             SendToICS("refresh\n");\r
10302         } else if (currentMove < forwardMostMove) {\r
10303             ForwardInner(forwardMostMove);\r
10304         }\r
10305         pauseExamInvalid = FALSE;\r
10306     } else {\r
10307         switch (gameMode) {\r
10308           default:\r
10309             return;\r
10310           case IcsExamining:\r
10311             pauseExamForwardMostMove = forwardMostMove;\r
10312             pauseExamInvalid = FALSE;\r
10313             /* fall through */\r
10314           case IcsObserving:\r
10315           case IcsPlayingWhite:\r
10316           case IcsPlayingBlack:\r
10317             pausing = TRUE;\r
10318             ModeHighlight();\r
10319             return;\r
10320           case PlayFromGameFile:\r
10321             (void) StopLoadGameTimer();\r
10322             pausing = TRUE;\r
10323             ModeHighlight();\r
10324             break;\r
10325           case BeginningOfGame:\r
10326             if (appData.icsActive) return;\r
10327             /* else fall through */\r
10328           case MachinePlaysWhite:\r
10329           case MachinePlaysBlack:\r
10330           case TwoMachinesPlay:\r
10331             if (forwardMostMove == 0)\r
10332               return;           /* don't pause if no one has moved */\r
10333             if ((gameMode == MachinePlaysWhite &&\r
10334                  !WhiteOnMove(forwardMostMove)) ||\r
10335                 (gameMode == MachinePlaysBlack &&\r
10336                  WhiteOnMove(forwardMostMove))) {\r
10337                 StopClocks();\r
10338             }\r
10339             pausing = TRUE;\r
10340             ModeHighlight();\r
10341             break;\r
10342         }\r
10343     }\r
10344 }\r
10345 \r
10346 void\r
10347 EditCommentEvent()\r
10348 {\r
10349     char title[MSG_SIZ];\r
10350 \r
10351     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
10352         strcpy(title, _("Edit comment"));\r
10353     } else {\r
10354         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,\r
10355                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
10356                 parseList[currentMove - 1]);\r
10357     }\r
10358 \r
10359     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
10360 }\r
10361 \r
10362 \r
10363 void\r
10364 EditTagsEvent()\r
10365 {\r
10366     char *tags = PGNTags(&gameInfo);\r
10367     EditTagsPopUp(tags);\r
10368     free(tags);\r
10369 }\r
10370 \r
10371 void\r
10372 AnalyzeModeEvent()\r
10373 {\r
10374     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
10375       return;\r
10376 \r
10377     if (gameMode != AnalyzeFile) {\r
10378         if (!appData.icsEngineAnalyze) {\r
10379                EditGameEvent();\r
10380                if (gameMode != EditGame) return;\r
10381         }\r
10382         ResurrectChessProgram();\r
10383         SendToProgram("analyze\n", &first);\r
10384         first.analyzing = TRUE;\r
10385         /*first.maybeThinking = TRUE;*/\r
10386         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10387         AnalysisPopUp(_("Analysis"),\r
10388                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10389     }\r
10390     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;\r
10391     pausing = FALSE;\r
10392     ModeHighlight();\r
10393     SetGameInfo();\r
10394 \r
10395     StartAnalysisClock();\r
10396     GetTimeMark(&lastNodeCountTime);\r
10397     lastNodeCount = 0;\r
10398 }\r
10399 \r
10400 void\r
10401 AnalyzeFileEvent()\r
10402 {\r
10403     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
10404       return;\r
10405 \r
10406     if (gameMode != AnalyzeMode) {\r
10407         EditGameEvent();\r
10408         if (gameMode != EditGame) return;\r
10409         ResurrectChessProgram();\r
10410         SendToProgram("analyze\n", &first);\r
10411         first.analyzing = TRUE;\r
10412         /*first.maybeThinking = TRUE;*/\r
10413         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10414         AnalysisPopUp(_("Analysis"),\r
10415                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10416     }\r
10417     gameMode = AnalyzeFile;\r
10418     pausing = FALSE;\r
10419     ModeHighlight();\r
10420     SetGameInfo();\r
10421 \r
10422     StartAnalysisClock();\r
10423     GetTimeMark(&lastNodeCountTime);\r
10424     lastNodeCount = 0;\r
10425 }\r
10426 \r
10427 void\r
10428 MachineWhiteEvent()\r
10429 {\r
10430     char buf[MSG_SIZ];\r
10431     char *bookHit = NULL;\r
10432 \r
10433     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
10434       return;\r
10435 \r
10436 \r
10437     if (gameMode == PlayFromGameFile || \r
10438         gameMode == TwoMachinesPlay  || \r
10439         gameMode == Training         || \r
10440         gameMode == AnalyzeMode      || \r
10441         gameMode == EndOfGame)\r
10442         EditGameEvent();\r
10443 \r
10444     if (gameMode == EditPosition) \r
10445         EditPositionDone();\r
10446 \r
10447     if (!WhiteOnMove(currentMove)) {\r
10448         DisplayError(_("It is not White's turn"), 0);\r
10449         return;\r
10450     }\r
10451   \r
10452     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10453       ExitAnalyzeMode();\r
10454 \r
10455     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10456         gameMode == AnalyzeFile)\r
10457         TruncateGame();\r
10458 \r
10459     ResurrectChessProgram();    /* in case it isn't running */\r
10460     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */\r
10461         gameMode = MachinePlaysWhite;\r
10462         ResetClocks();\r
10463     } else\r
10464     gameMode = MachinePlaysWhite;\r
10465     pausing = FALSE;\r
10466     ModeHighlight();\r
10467     SetGameInfo();\r
10468     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10469     DisplayTitle(buf);\r
10470     if (first.sendName) {\r
10471       sprintf(buf, "name %s\n", gameInfo.black);\r
10472       SendToProgram(buf, &first);\r
10473     }\r
10474     if (first.sendTime) {\r
10475       if (first.useColors) {\r
10476         SendToProgram("black\n", &first); /*gnu kludge*/\r
10477       }\r
10478       SendTimeRemaining(&first, TRUE);\r
10479     }\r
10480     if (first.useColors) {\r
10481       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately\r
10482     }\r
10483     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10484     SetMachineThinkingEnables();\r
10485     first.maybeThinking = TRUE;\r
10486     StartClocks();\r
10487 \r
10488     if (appData.autoFlipView && !flipView) {\r
10489       flipView = !flipView;\r
10490       DrawPosition(FALSE, NULL);\r
10491       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10492     }\r
10493 \r
10494     if(bookHit) { // [HGM] book: simulate book reply\r
10495         static char bookMove[MSG_SIZ]; // a bit generous?\r
10496 \r
10497         programStats.nodes = programStats.depth = programStats.time = \r
10498         programStats.score = programStats.got_only_move = 0;\r
10499         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10500 \r
10501         strcpy(bookMove, "move ");\r
10502         strcat(bookMove, bookHit);\r
10503         HandleMachineMove(bookMove, &first);\r
10504     }\r
10505 }\r
10506 \r
10507 void\r
10508 MachineBlackEvent()\r
10509 {\r
10510     char buf[MSG_SIZ];\r
10511    char *bookHit = NULL;\r
10512 \r
10513     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
10514         return;\r
10515 \r
10516 \r
10517     if (gameMode == PlayFromGameFile || \r
10518         gameMode == TwoMachinesPlay  || \r
10519         gameMode == Training         || \r
10520         gameMode == AnalyzeMode      || \r
10521         gameMode == EndOfGame)\r
10522         EditGameEvent();\r
10523 \r
10524     if (gameMode == EditPosition) \r
10525         EditPositionDone();\r
10526 \r
10527     if (WhiteOnMove(currentMove)) {\r
10528         DisplayError(_("It is not Black's turn"), 0);\r
10529         return;\r
10530     }\r
10531     \r
10532     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10533       ExitAnalyzeMode();\r
10534 \r
10535     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10536         gameMode == AnalyzeFile)\r
10537         TruncateGame();\r
10538 \r
10539     ResurrectChessProgram();    /* in case it isn't running */\r
10540     gameMode = MachinePlaysBlack;\r
10541     pausing = FALSE;\r
10542     ModeHighlight();\r
10543     SetGameInfo();\r
10544     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10545     DisplayTitle(buf);\r
10546     if (first.sendName) {\r
10547       sprintf(buf, "name %s\n", gameInfo.white);\r
10548       SendToProgram(buf, &first);\r
10549     }\r
10550     if (first.sendTime) {\r
10551       if (first.useColors) {\r
10552         SendToProgram("white\n", &first); /*gnu kludge*/\r
10553       }\r
10554       SendTimeRemaining(&first, FALSE);\r
10555     }\r
10556     if (first.useColors) {\r
10557       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately\r
10558     }\r
10559     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10560     SetMachineThinkingEnables();\r
10561     first.maybeThinking = TRUE;\r
10562     StartClocks();\r
10563 \r
10564     if (appData.autoFlipView && flipView) {\r
10565       flipView = !flipView;\r
10566       DrawPosition(FALSE, NULL);\r
10567       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10568     }\r
10569     if(bookHit) { // [HGM] book: simulate book reply\r
10570         static char bookMove[MSG_SIZ]; // a bit generous?\r
10571 \r
10572         programStats.nodes = programStats.depth = programStats.time = \r
10573         programStats.score = programStats.got_only_move = 0;\r
10574         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10575 \r
10576         strcpy(bookMove, "move ");\r
10577         strcat(bookMove, bookHit);\r
10578         HandleMachineMove(bookMove, &first);\r
10579     }\r
10580 }\r
10581 \r
10582 \r
10583 void\r
10584 DisplayTwoMachinesTitle()\r
10585 {\r
10586     char buf[MSG_SIZ];\r
10587     if (appData.matchGames > 0) {\r
10588         if (first.twoMachinesColor[0] == 'w') {\r
10589             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10590                     gameInfo.white, gameInfo.black,\r
10591                     first.matchWins, second.matchWins,\r
10592                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10593         } else {\r
10594             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10595                     gameInfo.white, gameInfo.black,\r
10596                     second.matchWins, first.matchWins,\r
10597                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10598         }\r
10599     } else {\r
10600         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10601     }\r
10602     DisplayTitle(buf);\r
10603 }\r
10604 \r
10605 void\r
10606 TwoMachinesEvent P((void))\r
10607 {\r
10608     int i;\r
10609     char buf[MSG_SIZ];\r
10610     ChessProgramState *onmove;\r
10611     char *bookHit = NULL;\r
10612     \r
10613     if (appData.noChessProgram) return;\r
10614 \r
10615     switch (gameMode) {\r
10616       case TwoMachinesPlay:\r
10617         return;\r
10618       case MachinePlaysWhite:\r
10619       case MachinePlaysBlack:\r
10620         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10621             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
10622             return;\r
10623         }\r
10624         /* fall through */\r
10625       case BeginningOfGame:\r
10626       case PlayFromGameFile:\r
10627       case EndOfGame:\r
10628         EditGameEvent();\r
10629         if (gameMode != EditGame) return;\r
10630         break;\r
10631       case EditPosition:\r
10632         EditPositionDone();\r
10633         break;\r
10634       case AnalyzeMode:\r
10635       case AnalyzeFile:\r
10636         ExitAnalyzeMode();\r
10637         break;\r
10638       case EditGame:\r
10639       default:\r
10640         break;\r
10641     }\r
10642 \r
10643     forwardMostMove = currentMove;\r
10644     ResurrectChessProgram();    /* in case first program isn't running */\r
10645 \r
10646     if (second.pr == NULL) {\r
10647         StartChessProgram(&second);\r
10648         if (second.protocolVersion == 1) {\r
10649           TwoMachinesEventIfReady();\r
10650         } else {\r
10651           /* kludge: allow timeout for initial "feature" command */\r
10652           FreezeUI();\r
10653           DisplayMessage("", _("Starting second chess program"));\r
10654           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
10655         }\r
10656         return;\r
10657     }\r
10658     DisplayMessage("", "");\r
10659     InitChessProgram(&second, FALSE);\r
10660     SendToProgram("force\n", &second);\r
10661     if (startedFromSetupPosition) {\r
10662         SendBoard(&second, backwardMostMove);\r
10663     if (appData.debugMode) {\r
10664         fprintf(debugFP, "Two Machines\n");\r
10665     }\r
10666     }\r
10667     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
10668         SendMoveToProgram(i, &second);\r
10669     }\r
10670 \r
10671     gameMode = TwoMachinesPlay;\r
10672     pausing = FALSE;\r
10673     ModeHighlight();\r
10674     SetGameInfo();\r
10675     DisplayTwoMachinesTitle();\r
10676     firstMove = TRUE;\r
10677     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
10678         onmove = &first;\r
10679     } else {\r
10680         onmove = &second;\r
10681     }\r
10682 \r
10683     SendToProgram(first.computerString, &first);\r
10684     if (first.sendName) {\r
10685       sprintf(buf, "name %s\n", second.tidy);\r
10686       SendToProgram(buf, &first);\r
10687     }\r
10688     SendToProgram(second.computerString, &second);\r
10689     if (second.sendName) {\r
10690       sprintf(buf, "name %s\n", first.tidy);\r
10691       SendToProgram(buf, &second);\r
10692     }\r
10693 \r
10694     ResetClocks();\r
10695     if (!first.sendTime || !second.sendTime) {\r
10696         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10697         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10698     }\r
10699     if (onmove->sendTime) {\r
10700       if (onmove->useColors) {\r
10701         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
10702       }\r
10703       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
10704     }\r
10705     if (onmove->useColors) {\r
10706       SendToProgram(onmove->twoMachinesColor, onmove);\r
10707     }\r
10708     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move\r
10709 //    SendToProgram("go\n", onmove);\r
10710     onmove->maybeThinking = TRUE;\r
10711     SetMachineThinkingEnables();\r
10712 \r
10713     StartClocks();\r
10714 \r
10715     if(bookHit) { // [HGM] book: simulate book reply\r
10716         static char bookMove[MSG_SIZ]; // a bit generous?\r
10717 \r
10718         programStats.nodes = programStats.depth = programStats.time = \r
10719         programStats.score = programStats.got_only_move = 0;\r
10720         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10721 \r
10722         strcpy(bookMove, "move ");\r
10723         strcat(bookMove, bookHit);\r
10724         HandleMachineMove(bookMove, &first);\r
10725     }\r
10726 }\r
10727 \r
10728 void\r
10729 TrainingEvent()\r
10730 {\r
10731     if (gameMode == Training) {\r
10732       SetTrainingModeOff();\r
10733       gameMode = PlayFromGameFile;\r
10734       DisplayMessage("", _("Training mode off"));\r
10735     } else {\r
10736       gameMode = Training;\r
10737       animateTraining = appData.animate;\r
10738 \r
10739       /* make sure we are not already at the end of the game */\r
10740       if (currentMove < forwardMostMove) {\r
10741         SetTrainingModeOn();\r
10742         DisplayMessage("", _("Training mode on"));\r
10743       } else {\r
10744         gameMode = PlayFromGameFile;\r
10745         DisplayError(_("Already at end of game"), 0);\r
10746       }\r
10747     }\r
10748     ModeHighlight();\r
10749 }\r
10750 \r
10751 void\r
10752 IcsClientEvent()\r
10753 {\r
10754     if (!appData.icsActive) return;\r
10755     switch (gameMode) {\r
10756       case IcsPlayingWhite:\r
10757       case IcsPlayingBlack:\r
10758       case IcsObserving:\r
10759       case IcsIdle:\r
10760       case BeginningOfGame:\r
10761       case IcsExamining:\r
10762         return;\r
10763 \r
10764       case EditGame:\r
10765         break;\r
10766 \r
10767       case EditPosition:\r
10768         EditPositionDone();\r
10769         break;\r
10770 \r
10771       case AnalyzeMode:\r
10772       case AnalyzeFile:\r
10773         ExitAnalyzeMode();\r
10774         break;\r
10775         \r
10776       default:\r
10777         EditGameEvent();\r
10778         break;\r
10779     }\r
10780 \r
10781     gameMode = IcsIdle;\r
10782     ModeHighlight();\r
10783     return;\r
10784 }\r
10785 \r
10786 \r
10787 void\r
10788 EditGameEvent()\r
10789 {\r
10790     int i;\r
10791 \r
10792     switch (gameMode) {\r
10793       case Training:\r
10794         SetTrainingModeOff();\r
10795         break;\r
10796       case MachinePlaysWhite:\r
10797       case MachinePlaysBlack:\r
10798       case BeginningOfGame:\r
10799         SendToProgram("force\n", &first);\r
10800         SetUserThinkingEnables();\r
10801         break;\r
10802       case PlayFromGameFile:\r
10803         (void) StopLoadGameTimer();\r
10804         if (gameFileFP != NULL) {\r
10805             gameFileFP = NULL;\r
10806         }\r
10807         break;\r
10808       case EditPosition:\r
10809         EditPositionDone();\r
10810         break;\r
10811       case AnalyzeMode:\r
10812       case AnalyzeFile:\r
10813         ExitAnalyzeMode();\r
10814         SendToProgram("force\n", &first);\r
10815         break;\r
10816       case TwoMachinesPlay:\r
10817         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10818         ResurrectChessProgram();\r
10819         SetUserThinkingEnables();\r
10820         break;\r
10821       case EndOfGame:\r
10822         ResurrectChessProgram();\r
10823         break;\r
10824       case IcsPlayingBlack:\r
10825       case IcsPlayingWhite:\r
10826         DisplayError(_("Warning: You are still playing a game"), 0);\r
10827         break;\r
10828       case IcsObserving:\r
10829         DisplayError(_("Warning: You are still observing a game"), 0);\r
10830         break;\r
10831       case IcsExamining:\r
10832         DisplayError(_("Warning: You are still examining a game"), 0);\r
10833         break;\r
10834       case IcsIdle:\r
10835         break;\r
10836       case EditGame:\r
10837       default:\r
10838         return;\r
10839     }\r
10840     \r
10841     pausing = FALSE;\r
10842     StopClocks();\r
10843     first.offeredDraw = second.offeredDraw = 0;\r
10844 \r
10845     if (gameMode == PlayFromGameFile) {\r
10846         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10847         blackTimeRemaining = timeRemaining[1][currentMove];\r
10848         DisplayTitle("");\r
10849     }\r
10850 \r
10851     if (gameMode == MachinePlaysWhite ||\r
10852         gameMode == MachinePlaysBlack ||\r
10853         gameMode == TwoMachinesPlay ||\r
10854         gameMode == EndOfGame) {\r
10855         i = forwardMostMove;\r
10856         while (i > currentMove) {\r
10857             SendToProgram("undo\n", &first);\r
10858             i--;\r
10859         }\r
10860         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10861         blackTimeRemaining = timeRemaining[1][currentMove];\r
10862         DisplayBothClocks();\r
10863         if (whiteFlag || blackFlag) {\r
10864             whiteFlag = blackFlag = 0;\r
10865         }\r
10866         DisplayTitle("");\r
10867     }           \r
10868     \r
10869     gameMode = EditGame;\r
10870     ModeHighlight();\r
10871     SetGameInfo();\r
10872 }\r
10873 \r
10874 \r
10875 void\r
10876 EditPositionEvent()\r
10877 {\r
10878     if (gameMode == EditPosition) {\r
10879         EditGameEvent();\r
10880         return;\r
10881     }\r
10882     \r
10883     EditGameEvent();\r
10884     if (gameMode != EditGame) return;\r
10885     \r
10886     gameMode = EditPosition;\r
10887     ModeHighlight();\r
10888     SetGameInfo();\r
10889     if (currentMove > 0)\r
10890       CopyBoard(boards[0], boards[currentMove]);\r
10891     \r
10892     blackPlaysFirst = !WhiteOnMove(currentMove);\r
10893     ResetClocks();\r
10894     currentMove = forwardMostMove = backwardMostMove = 0;\r
10895     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10896     DisplayMove(-1);\r
10897 }\r
10898 \r
10899 void\r
10900 ExitAnalyzeMode()\r
10901 {\r
10902     /* [DM] icsEngineAnalyze - possible call from other functions */\r
10903     if (appData.icsEngineAnalyze) {\r
10904         appData.icsEngineAnalyze = FALSE;\r
10905 \r
10906         DisplayMessage("",_("Close ICS engine analyze..."));\r
10907     }\r
10908     if (first.analysisSupport && first.analyzing) {\r
10909       SendToProgram("exit\n", &first);\r
10910       first.analyzing = FALSE;\r
10911     }\r
10912     AnalysisPopDown();\r
10913     thinkOutput[0] = NULLCHAR;\r
10914 }\r
10915 \r
10916 void\r
10917 EditPositionDone()\r
10918 {\r
10919     startedFromSetupPosition = TRUE;\r
10920     InitChessProgram(&first, FALSE);\r
10921     SendToProgram("force\n", &first);\r
10922     if (blackPlaysFirst) {\r
10923         strcpy(moveList[0], "");\r
10924         strcpy(parseList[0], "");\r
10925         currentMove = forwardMostMove = backwardMostMove = 1;\r
10926         CopyBoard(boards[1], boards[0]);\r
10927         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */\r
10928         { int i;\r
10929           epStatus[1] = epStatus[0];\r
10930           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];\r
10931         }\r
10932     } else {\r
10933         currentMove = forwardMostMove = backwardMostMove = 0;\r
10934     }\r
10935     SendBoard(&first, forwardMostMove);\r
10936     if (appData.debugMode) {\r
10937         fprintf(debugFP, "EditPosDone\n");\r
10938     }\r
10939     DisplayTitle("");\r
10940     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10941     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10942     gameMode = EditGame;\r
10943     ModeHighlight();\r
10944     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10945     ClearHighlights(); /* [AS] */\r
10946 }\r
10947 \r
10948 /* Pause for `ms' milliseconds */\r
10949 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10950 void\r
10951 TimeDelay(ms)\r
10952      long ms;\r
10953 {\r
10954     TimeMark m1, m2;\r
10955 \r
10956     GetTimeMark(&m1);\r
10957     do {\r
10958         GetTimeMark(&m2);\r
10959     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
10960 }\r
10961 \r
10962 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10963 void\r
10964 SendMultiLineToICS(buf)\r
10965      char *buf;\r
10966 {\r
10967     char temp[MSG_SIZ+1], *p;\r
10968     int len;\r
10969 \r
10970     len = strlen(buf);\r
10971     if (len > MSG_SIZ)\r
10972       len = MSG_SIZ;\r
10973   \r
10974     strncpy(temp, buf, len);\r
10975     temp[len] = 0;\r
10976 \r
10977     p = temp;\r
10978     while (*p) {\r
10979         if (*p == '\n' || *p == '\r')\r
10980           *p = ' ';\r
10981         ++p;\r
10982     }\r
10983 \r
10984     strcat(temp, "\n");\r
10985     SendToICS(temp);\r
10986     SendToPlayer(temp, strlen(temp));\r
10987 }\r
10988 \r
10989 void\r
10990 SetWhiteToPlayEvent()\r
10991 {\r
10992     if (gameMode == EditPosition) {\r
10993         blackPlaysFirst = FALSE;\r
10994         DisplayBothClocks();    /* works because currentMove is 0 */\r
10995     } else if (gameMode == IcsExamining) {\r
10996         SendToICS(ics_prefix);\r
10997         SendToICS("tomove white\n");\r
10998     }\r
10999 }\r
11000 \r
11001 void\r
11002 SetBlackToPlayEvent()\r
11003 {\r
11004     if (gameMode == EditPosition) {\r
11005         blackPlaysFirst = TRUE;\r
11006         currentMove = 1;        /* kludge */\r
11007         DisplayBothClocks();\r
11008         currentMove = 0;\r
11009     } else if (gameMode == IcsExamining) {\r
11010         SendToICS(ics_prefix);\r
11011         SendToICS("tomove black\n");\r
11012     }\r
11013 }\r
11014 \r
11015 void\r
11016 EditPositionMenuEvent(selection, x, y)\r
11017      ChessSquare selection;\r
11018      int x, y;\r
11019 {\r
11020     char buf[MSG_SIZ];\r
11021     ChessSquare piece = boards[0][y][x];\r
11022 \r
11023     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
11024 \r
11025     switch (selection) {\r
11026       case ClearBoard:\r
11027         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
11028             SendToICS(ics_prefix);\r
11029             SendToICS("bsetup clear\n");\r
11030         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
11031             SendToICS(ics_prefix);\r
11032             SendToICS("clearboard\n");\r
11033         } else {\r
11034             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;\r
11035                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */\r
11036                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
11037                     if (gameMode == IcsExamining) {\r
11038                         if (boards[currentMove][y][x] != EmptySquare) {\r
11039                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
11040                                     AAA + x, ONE + y);\r
11041                             SendToICS(buf);\r
11042                         }\r
11043                     } else {\r
11044                         boards[0][y][x] = p;\r
11045                     }\r
11046                 }\r
11047             }\r
11048         }\r
11049         if (gameMode == EditPosition) {\r
11050             DrawPosition(FALSE, boards[0]);\r
11051         }\r
11052         break;\r
11053 \r
11054       case WhitePlay:\r
11055         SetWhiteToPlayEvent();\r
11056         break;\r
11057 \r
11058       case BlackPlay:\r
11059         SetBlackToPlayEvent();\r
11060         break;\r
11061 \r
11062       case EmptySquare:\r
11063         if (gameMode == IcsExamining) {\r
11064             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
11065             SendToICS(buf);\r
11066         } else {\r
11067             boards[0][y][x] = EmptySquare;\r
11068             DrawPosition(FALSE, boards[0]);\r
11069         }\r
11070         break;\r
11071 \r
11072       case PromotePiece:\r
11073         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
11074            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
11075             selection = (ChessSquare) (PROMOTED piece);\r
11076         } else if(piece == EmptySquare) selection = WhiteSilver;\r
11077         else selection = (ChessSquare)((int)piece - 1);\r
11078         goto defaultlabel;\r
11079 \r
11080       case DemotePiece:\r
11081         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
11082            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
11083             selection = (ChessSquare) (DEMOTED piece);\r
11084         } else if(piece == EmptySquare) selection = BlackSilver;\r
11085         else selection = (ChessSquare)((int)piece + 1);       \r
11086         goto defaultlabel;\r
11087 \r
11088       case WhiteQueen:\r
11089       case BlackQueen:\r
11090         if(gameInfo.variant == VariantShatranj ||\r
11091            gameInfo.variant == VariantXiangqi  ||\r
11092            gameInfo.variant == VariantCourier    )\r
11093             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
11094         goto defaultlabel;\r
11095 \r
11096       case WhiteKing:\r
11097       case BlackKing:\r
11098         if(gameInfo.variant == VariantXiangqi)\r
11099             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
11100         if(gameInfo.variant == VariantKnightmate)\r
11101             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
11102       default:\r
11103         defaultlabel:\r
11104         if (gameMode == IcsExamining) {\r
11105             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
11106                     PieceToChar(selection), AAA + x, ONE + y);\r
11107             SendToICS(buf);\r
11108         } else {\r
11109             boards[0][y][x] = selection;\r
11110             DrawPosition(FALSE, boards[0]);\r
11111         }\r
11112         break;\r
11113     }\r
11114 }\r
11115 \r
11116 \r
11117 void\r
11118 DropMenuEvent(selection, x, y)\r
11119      ChessSquare selection;\r
11120      int x, y;\r
11121 {\r
11122     ChessMove moveType;\r
11123 \r
11124     switch (gameMode) {\r
11125       case IcsPlayingWhite:\r
11126       case MachinePlaysBlack:\r
11127         if (!WhiteOnMove(currentMove)) {\r
11128             DisplayMoveError(_("It is Black's turn"));\r
11129             return;\r
11130         }\r
11131         moveType = WhiteDrop;\r
11132         break;\r
11133       case IcsPlayingBlack:\r
11134       case MachinePlaysWhite:\r
11135         if (WhiteOnMove(currentMove)) {\r
11136             DisplayMoveError(_("It is White's turn"));\r
11137             return;\r
11138         }\r
11139         moveType = BlackDrop;\r
11140         break;\r
11141       case EditGame:\r
11142         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
11143         break;\r
11144       default:\r
11145         return;\r
11146     }\r
11147 \r
11148     if (moveType == BlackDrop && selection < BlackPawn) {\r
11149       selection = (ChessSquare) ((int) selection\r
11150                                  + (int) BlackPawn - (int) WhitePawn);\r
11151     }\r
11152     if (boards[currentMove][y][x] != EmptySquare) {\r
11153         DisplayMoveError(_("That square is occupied"));\r
11154         return;\r
11155     }\r
11156 \r
11157     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
11158 }\r
11159 \r
11160 void\r
11161 AcceptEvent()\r
11162 {\r
11163     /* Accept a pending offer of any kind from opponent */\r
11164     \r
11165     if (appData.icsActive) {\r
11166         SendToICS(ics_prefix);\r
11167         SendToICS("accept\n");\r
11168     } else if (cmailMsgLoaded) {\r
11169         if (currentMove == cmailOldMove &&\r
11170             commentList[cmailOldMove] != NULL &&\r
11171             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11172                    "Black offers a draw" : "White offers a draw")) {\r
11173             TruncateGame();\r
11174             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11175             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11176         } else {\r
11177             DisplayError(_("There is no pending offer on this move"), 0);\r
11178             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11179         }\r
11180     } else {\r
11181         /* Not used for offers from chess program */\r
11182     }\r
11183 }\r
11184 \r
11185 void\r
11186 DeclineEvent()\r
11187 {\r
11188     /* Decline a pending offer of any kind from opponent */\r
11189     \r
11190     if (appData.icsActive) {\r
11191         SendToICS(ics_prefix);\r
11192         SendToICS("decline\n");\r
11193     } else if (cmailMsgLoaded) {\r
11194         if (currentMove == cmailOldMove &&\r
11195             commentList[cmailOldMove] != NULL &&\r
11196             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11197                    "Black offers a draw" : "White offers a draw")) {\r
11198 #ifdef NOTDEF\r
11199             AppendComment(cmailOldMove, "Draw declined");\r
11200             DisplayComment(cmailOldMove - 1, "Draw declined");\r
11201 #endif /*NOTDEF*/\r
11202         } else {\r
11203             DisplayError(_("There is no pending offer on this move"), 0);\r
11204         }\r
11205     } else {\r
11206         /* Not used for offers from chess program */\r
11207     }\r
11208 }\r
11209 \r
11210 void\r
11211 RematchEvent()\r
11212 {\r
11213     /* Issue ICS rematch command */\r
11214     if (appData.icsActive) {\r
11215         SendToICS(ics_prefix);\r
11216         SendToICS("rematch\n");\r
11217     }\r
11218 }\r
11219 \r
11220 void\r
11221 CallFlagEvent()\r
11222 {\r
11223     /* Call your opponent's flag (claim a win on time) */\r
11224     if (appData.icsActive) {\r
11225         SendToICS(ics_prefix);\r
11226         SendToICS("flag\n");\r
11227     } else {\r
11228         switch (gameMode) {\r
11229           default:\r
11230             return;\r
11231           case MachinePlaysWhite:\r
11232             if (whiteFlag) {\r
11233                 if (blackFlag)\r
11234                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11235                            GE_PLAYER);\r
11236                 else\r
11237                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
11238             } else {\r
11239                 DisplayError(_("Your opponent is not out of time"), 0);\r
11240             }\r
11241             break;\r
11242           case MachinePlaysBlack:\r
11243             if (blackFlag) {\r
11244                 if (whiteFlag)\r
11245                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11246                            GE_PLAYER);\r
11247                 else\r
11248                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
11249             } else {\r
11250                 DisplayError(_("Your opponent is not out of time"), 0);\r
11251             }\r
11252             break;\r
11253         }\r
11254     }\r
11255 }\r
11256 \r
11257 void\r
11258 DrawEvent()\r
11259 {\r
11260     /* Offer draw or accept pending draw offer from opponent */\r
11261     \r
11262     if (appData.icsActive) {\r
11263         /* Note: tournament rules require draw offers to be\r
11264            made after you make your move but before you punch\r
11265            your clock.  Currently ICS doesn't let you do that;\r
11266            instead, you immediately punch your clock after making\r
11267            a move, but you can offer a draw at any time. */\r
11268         \r
11269         SendToICS(ics_prefix);\r
11270         SendToICS("draw\n");\r
11271     } else if (cmailMsgLoaded) {\r
11272         if (currentMove == cmailOldMove &&\r
11273             commentList[cmailOldMove] != NULL &&\r
11274             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11275                    "Black offers a draw" : "White offers a draw")) {\r
11276             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11277             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11278         } else if (currentMove == cmailOldMove + 1) {\r
11279             char *offer = WhiteOnMove(cmailOldMove) ?\r
11280               "White offers a draw" : "Black offers a draw";\r
11281             AppendComment(currentMove, offer);\r
11282             DisplayComment(currentMove - 1, offer);\r
11283             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
11284         } else {\r
11285             DisplayError(_("You must make your move before offering a draw"), 0);\r
11286             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11287         }\r
11288     } else if (first.offeredDraw) {\r
11289         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
11290     } else {\r
11291         if (first.sendDrawOffers) {\r
11292             SendToProgram("draw\n", &first);\r
11293             userOfferedDraw = TRUE;\r
11294         }\r
11295     }\r
11296 }\r
11297 \r
11298 void\r
11299 AdjournEvent()\r
11300 {\r
11301     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
11302     \r
11303     if (appData.icsActive) {\r
11304         SendToICS(ics_prefix);\r
11305         SendToICS("adjourn\n");\r
11306     } else {\r
11307         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
11308     }\r
11309 }\r
11310 \r
11311 \r
11312 void\r
11313 AbortEvent()\r
11314 {\r
11315     /* Offer Abort or accept pending Abort offer from opponent */\r
11316     \r
11317     if (appData.icsActive) {\r
11318         SendToICS(ics_prefix);\r
11319         SendToICS("abort\n");\r
11320     } else {\r
11321         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
11322     }\r
11323 }\r
11324 \r
11325 void\r
11326 ResignEvent()\r
11327 {\r
11328     /* Resign.  You can do this even if it's not your turn. */\r
11329     \r
11330     if (appData.icsActive) {\r
11331         SendToICS(ics_prefix);\r
11332         SendToICS("resign\n");\r
11333     } else {\r
11334         switch (gameMode) {\r
11335           case MachinePlaysWhite:\r
11336             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11337             break;\r
11338           case MachinePlaysBlack:\r
11339             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11340             break;\r
11341           case EditGame:\r
11342             if (cmailMsgLoaded) {\r
11343                 TruncateGame();\r
11344                 if (WhiteOnMove(cmailOldMove)) {\r
11345                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11346                 } else {\r
11347                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11348                 }\r
11349                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
11350             }\r
11351             break;\r
11352           default:\r
11353             break;\r
11354         }\r
11355     }\r
11356 }\r
11357 \r
11358 \r
11359 void\r
11360 StopObservingEvent()\r
11361 {\r
11362     /* Stop observing current games */\r
11363     SendToICS(ics_prefix);\r
11364     SendToICS("unobserve\n");\r
11365 }\r
11366 \r
11367 void\r
11368 StopExaminingEvent()\r
11369 {\r
11370     /* Stop observing current game */\r
11371     SendToICS(ics_prefix);\r
11372     SendToICS("unexamine\n");\r
11373 }\r
11374 \r
11375 void\r
11376 ForwardInner(target)\r
11377      int target;\r
11378 {\r
11379     int limit;\r
11380 \r
11381     if (appData.debugMode)\r
11382         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
11383                 target, currentMove, forwardMostMove);\r
11384 \r
11385     if (gameMode == EditPosition)\r
11386       return;\r
11387 \r
11388     if (gameMode == PlayFromGameFile && !pausing)\r
11389       PauseEvent();\r
11390     \r
11391     if (gameMode == IcsExamining && pausing)\r
11392       limit = pauseExamForwardMostMove;\r
11393     else\r
11394       limit = forwardMostMove;\r
11395     \r
11396     if (target > limit) target = limit;\r
11397 \r
11398     if (target > 0 && moveList[target - 1][0]) {\r
11399         int fromX, fromY, toX, toY;\r
11400         toX = moveList[target - 1][2] - AAA;\r
11401         toY = moveList[target - 1][3] - ONE;\r
11402         if (moveList[target - 1][1] == '@') {\r
11403             if (appData.highlightLastMove) {\r
11404                 SetHighlights(-1, -1, toX, toY);\r
11405             }\r
11406         } else {\r
11407             fromX = moveList[target - 1][0] - AAA;\r
11408             fromY = moveList[target - 1][1] - ONE;\r
11409             if (target == currentMove + 1) {\r
11410                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
11411             }\r
11412             if (appData.highlightLastMove) {\r
11413                 SetHighlights(fromX, fromY, toX, toY);\r
11414             }\r
11415         }\r
11416     }\r
11417     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
11418         gameMode == Training || gameMode == PlayFromGameFile || \r
11419         gameMode == AnalyzeFile) {\r
11420         while (currentMove < target) {\r
11421             SendMoveToProgram(currentMove++, &first);\r
11422         }\r
11423     } else {\r
11424         currentMove = target;\r
11425     }\r
11426     \r
11427     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11428         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11429         blackTimeRemaining = timeRemaining[1][currentMove];\r
11430     }\r
11431     DisplayBothClocks();\r
11432     DisplayMove(currentMove - 1);\r
11433     DrawPosition(FALSE, boards[currentMove]);\r
11434     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11435     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
11436         DisplayComment(currentMove - 1, commentList[currentMove]);\r
11437     }\r
11438 }\r
11439 \r
11440 \r
11441 void\r
11442 ForwardEvent()\r
11443 {\r
11444     if (gameMode == IcsExamining && !pausing) {\r
11445         SendToICS(ics_prefix);\r
11446         SendToICS("forward\n");\r
11447     } else {\r
11448         ForwardInner(currentMove + 1);\r
11449     }\r
11450 }\r
11451 \r
11452 void\r
11453 ToEndEvent()\r
11454 {\r
11455     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11456         /* to optimze, we temporarily turn off analysis mode while we feed\r
11457          * the remaining moves to the engine. Otherwise we get analysis output\r
11458          * after each move.\r
11459          */ \r
11460         if (first.analysisSupport) {\r
11461           SendToProgram("exit\nforce\n", &first);\r
11462           first.analyzing = FALSE;\r
11463         }\r
11464     }\r
11465         \r
11466     if (gameMode == IcsExamining && !pausing) {\r
11467         SendToICS(ics_prefix);\r
11468         SendToICS("forward 999999\n");\r
11469     } else {\r
11470         ForwardInner(forwardMostMove);\r
11471     }\r
11472 \r
11473     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11474         /* we have fed all the moves, so reactivate analysis mode */\r
11475         SendToProgram("analyze\n", &first);\r
11476         first.analyzing = TRUE;\r
11477         /*first.maybeThinking = TRUE;*/\r
11478         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11479     }\r
11480 }\r
11481 \r
11482 void\r
11483 BackwardInner(target)\r
11484      int target;\r
11485 {\r
11486     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
11487 \r
11488     if (appData.debugMode)\r
11489         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
11490                 target, currentMove, forwardMostMove);\r
11491 \r
11492     if (gameMode == EditPosition) return;\r
11493     if (currentMove <= backwardMostMove) {\r
11494         ClearHighlights();\r
11495         DrawPosition(full_redraw, boards[currentMove]);\r
11496         return;\r
11497     }\r
11498     if (gameMode == PlayFromGameFile && !pausing)\r
11499       PauseEvent();\r
11500     \r
11501     if (moveList[target][0]) {\r
11502         int fromX, fromY, toX, toY;\r
11503         toX = moveList[target][2] - AAA;\r
11504         toY = moveList[target][3] - ONE;\r
11505         if (moveList[target][1] == '@') {\r
11506             if (appData.highlightLastMove) {\r
11507                 SetHighlights(-1, -1, toX, toY);\r
11508             }\r
11509         } else {\r
11510             fromX = moveList[target][0] - AAA;\r
11511             fromY = moveList[target][1] - ONE;\r
11512             if (target == currentMove - 1) {\r
11513                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
11514             }\r
11515             if (appData.highlightLastMove) {\r
11516                 SetHighlights(fromX, fromY, toX, toY);\r
11517             }\r
11518         }\r
11519     }\r
11520     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
11521         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
11522         while (currentMove > target) {\r
11523             SendToProgram("undo\n", &first);\r
11524             currentMove--;\r
11525         }\r
11526     } else {\r
11527         currentMove = target;\r
11528     }\r
11529     \r
11530     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11531         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11532         blackTimeRemaining = timeRemaining[1][currentMove];\r
11533     }\r
11534     DisplayBothClocks();\r
11535     DisplayMove(currentMove - 1);\r
11536     DrawPosition(full_redraw, boards[currentMove]);\r
11537     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11538     // [HGM] PV info: routine tests if comment empty\r
11539     DisplayComment(currentMove - 1, commentList[currentMove]);\r
11540 }\r
11541 \r
11542 void\r
11543 BackwardEvent()\r
11544 {\r
11545     if (gameMode == IcsExamining && !pausing) {\r
11546         SendToICS(ics_prefix);\r
11547         SendToICS("backward\n");\r
11548     } else {\r
11549         BackwardInner(currentMove - 1);\r
11550     }\r
11551 }\r
11552 \r
11553 void\r
11554 ToStartEvent()\r
11555 {\r
11556     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11557         /* to optimze, we temporarily turn off analysis mode while we undo\r
11558          * all the moves. Otherwise we get analysis output after each undo.\r
11559          */ \r
11560         if (first.analysisSupport) {\r
11561           SendToProgram("exit\nforce\n", &first);\r
11562           first.analyzing = FALSE;\r
11563         }\r
11564     }\r
11565 \r
11566     if (gameMode == IcsExamining && !pausing) {\r
11567         SendToICS(ics_prefix);\r
11568         SendToICS("backward 999999\n");\r
11569     } else {\r
11570         BackwardInner(backwardMostMove);\r
11571     }\r
11572 \r
11573     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11574         /* we have fed all the moves, so reactivate analysis mode */\r
11575         SendToProgram("analyze\n", &first);\r
11576         first.analyzing = TRUE;\r
11577         /*first.maybeThinking = TRUE;*/\r
11578         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11579     }\r
11580 }\r
11581 \r
11582 void\r
11583 ToNrEvent(int to)\r
11584 {\r
11585   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
11586   if (to >= forwardMostMove) to = forwardMostMove;\r
11587   if (to <= backwardMostMove) to = backwardMostMove;\r
11588   if (to < currentMove) {\r
11589     BackwardInner(to);\r
11590   } else {\r
11591     ForwardInner(to);\r
11592   }\r
11593 }\r
11594 \r
11595 void\r
11596 RevertEvent()\r
11597 {\r
11598     if (gameMode != IcsExamining) {\r
11599         DisplayError(_("You are not examining a game"), 0);\r
11600         return;\r
11601     }\r
11602     if (pausing) {\r
11603         DisplayError(_("You can't revert while pausing"), 0);\r
11604         return;\r
11605     }\r
11606     SendToICS(ics_prefix);\r
11607     SendToICS("revert\n");\r
11608 }\r
11609 \r
11610 void\r
11611 RetractMoveEvent()\r
11612 {\r
11613     switch (gameMode) {\r
11614       case MachinePlaysWhite:\r
11615       case MachinePlaysBlack:\r
11616         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
11617             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
11618             return;\r
11619         }\r
11620         if (forwardMostMove < 2) return;\r
11621         currentMove = forwardMostMove = forwardMostMove - 2;\r
11622         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11623         blackTimeRemaining = timeRemaining[1][currentMove];\r
11624         DisplayBothClocks();\r
11625         DisplayMove(currentMove - 1);\r
11626         ClearHighlights();/*!! could figure this out*/\r
11627         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
11628         SendToProgram("remove\n", &first);\r
11629         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
11630         break;\r
11631 \r
11632       case BeginningOfGame:\r
11633       default:\r
11634         break;\r
11635 \r
11636       case IcsPlayingWhite:\r
11637       case IcsPlayingBlack:\r
11638         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
11639             SendToICS(ics_prefix);\r
11640             SendToICS("takeback 2\n");\r
11641         } else {\r
11642             SendToICS(ics_prefix);\r
11643             SendToICS("takeback 1\n");\r
11644         }\r
11645         break;\r
11646     }\r
11647 }\r
11648 \r
11649 void\r
11650 MoveNowEvent()\r
11651 {\r
11652     ChessProgramState *cps;\r
11653 \r
11654     switch (gameMode) {\r
11655       case MachinePlaysWhite:\r
11656         if (!WhiteOnMove(forwardMostMove)) {\r
11657             DisplayError(_("It is your turn"), 0);\r
11658             return;\r
11659         }\r
11660         cps = &first;\r
11661         break;\r
11662       case MachinePlaysBlack:\r
11663         if (WhiteOnMove(forwardMostMove)) {\r
11664             DisplayError(_("It is your turn"), 0);\r
11665             return;\r
11666         }\r
11667         cps = &first;\r
11668         break;\r
11669       case TwoMachinesPlay:\r
11670         if (WhiteOnMove(forwardMostMove) ==\r
11671             (first.twoMachinesColor[0] == 'w')) {\r
11672             cps = &first;\r
11673         } else {\r
11674             cps = &second;\r
11675         }\r
11676         break;\r
11677       case BeginningOfGame:\r
11678       default:\r
11679         return;\r
11680     }\r
11681     SendToProgram("?\n", cps);\r
11682 }\r
11683 \r
11684 void\r
11685 TruncateGameEvent()\r
11686 {\r
11687     EditGameEvent();\r
11688     if (gameMode != EditGame) return;\r
11689     TruncateGame();\r
11690 }\r
11691 \r
11692 void\r
11693 TruncateGame()\r
11694 {\r
11695     if (forwardMostMove > currentMove) {\r
11696         if (gameInfo.resultDetails != NULL) {\r
11697             free(gameInfo.resultDetails);\r
11698             gameInfo.resultDetails = NULL;\r
11699             gameInfo.result = GameUnfinished;\r
11700         }\r
11701         forwardMostMove = currentMove;\r
11702         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
11703                    currentMove-1);\r
11704     }\r
11705 }\r
11706 \r
11707 void\r
11708 HintEvent()\r
11709 {\r
11710     if (appData.noChessProgram) return;\r
11711     switch (gameMode) {\r
11712       case MachinePlaysWhite:\r
11713         if (WhiteOnMove(forwardMostMove)) {\r
11714             DisplayError(_("Wait until your turn"), 0);\r
11715             return;\r
11716         }\r
11717         break;\r
11718       case BeginningOfGame:\r
11719       case MachinePlaysBlack:\r
11720         if (!WhiteOnMove(forwardMostMove)) {\r
11721             DisplayError(_("Wait until your turn"), 0);\r
11722             return;\r
11723         }\r
11724         break;\r
11725       default:\r
11726         DisplayError(_("No hint available"), 0);\r
11727         return;\r
11728     }\r
11729     SendToProgram("hint\n", &first);\r
11730     hintRequested = TRUE;\r
11731 }\r
11732 \r
11733 void\r
11734 BookEvent()\r
11735 {\r
11736     if (appData.noChessProgram) return;\r
11737     switch (gameMode) {\r
11738       case MachinePlaysWhite:\r
11739         if (WhiteOnMove(forwardMostMove)) {\r
11740             DisplayError(_("Wait until your turn"), 0);\r
11741             return;\r
11742         }\r
11743         break;\r
11744       case BeginningOfGame:\r
11745       case MachinePlaysBlack:\r
11746         if (!WhiteOnMove(forwardMostMove)) {\r
11747             DisplayError(_("Wait until your turn"), 0);\r
11748             return;\r
11749         }\r
11750         break;\r
11751       case EditPosition:\r
11752         EditPositionDone();\r
11753         break;\r
11754       case TwoMachinesPlay:\r
11755         return;\r
11756       default:\r
11757         break;\r
11758     }\r
11759     SendToProgram("bk\n", &first);\r
11760     bookOutput[0] = NULLCHAR;\r
11761     bookRequested = TRUE;\r
11762 }\r
11763 \r
11764 void\r
11765 AboutGameEvent()\r
11766 {\r
11767     char *tags = PGNTags(&gameInfo);\r
11768     TagsPopUp(tags, CmailMsg());\r
11769     free(tags);\r
11770 }\r
11771 \r
11772 /* end button procedures */\r
11773 \r
11774 void\r
11775 PrintPosition(fp, move)\r
11776      FILE *fp;\r
11777      int move;\r
11778 {\r
11779     int i, j;\r
11780     \r
11781     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11782         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11783             char c = PieceToChar(boards[move][i][j]);\r
11784             fputc(c == 'x' ? '.' : c, fp);\r
11785             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
11786         }\r
11787     }\r
11788     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
11789       fprintf(fp, "white to play\n");\r
11790     else\r
11791       fprintf(fp, "black to play\n");\r
11792 }\r
11793 \r
11794 void\r
11795 PrintOpponents(fp)\r
11796      FILE *fp;\r
11797 {\r
11798     if (gameInfo.white != NULL) {\r
11799         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
11800     } else {\r
11801         fprintf(fp, "\n");\r
11802     }\r
11803 }\r
11804 \r
11805 /* Find last component of program's own name, using some heuristics */\r
11806 void\r
11807 TidyProgramName(prog, host, buf)\r
11808      char *prog, *host, buf[MSG_SIZ];\r
11809 {\r
11810     char *p, *q;\r
11811     int local = (strcmp(host, "localhost") == 0);\r
11812     while (!local && (p = strchr(prog, ';')) != NULL) {\r
11813         p++;\r
11814         while (*p == ' ') p++;\r
11815         prog = p;\r
11816     }\r
11817     if (*prog == '"' || *prog == '\'') {\r
11818         q = strchr(prog + 1, *prog);\r
11819     } else {\r
11820         q = strchr(prog, ' ');\r
11821     }\r
11822     if (q == NULL) q = prog + strlen(prog);\r
11823     p = q;\r
11824     while (p >= prog && *p != '/' && *p != '\\') p--;\r
11825     p++;\r
11826     if(p == prog && *p == '"') p++;\r
11827     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
11828     memcpy(buf, p, q - p);\r
11829     buf[q - p] = NULLCHAR;\r
11830     if (!local) {\r
11831         strcat(buf, "@");\r
11832         strcat(buf, host);\r
11833     }\r
11834 }\r
11835 \r
11836 char *\r
11837 TimeControlTagValue()\r
11838 {\r
11839     char buf[MSG_SIZ];\r
11840     if (!appData.clockMode) {\r
11841         strcpy(buf, "-");\r
11842     } else if (movesPerSession > 0) {\r
11843         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
11844     } else if (timeIncrement == 0) {\r
11845         sprintf(buf, "%ld", timeControl/1000);\r
11846     } else {\r
11847         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
11848     }\r
11849     return StrSave(buf);\r
11850 }\r
11851 \r
11852 void\r
11853 SetGameInfo()\r
11854 {\r
11855     /* This routine is used only for certain modes */\r
11856     VariantClass v = gameInfo.variant;\r
11857     ClearGameInfo(&gameInfo);\r
11858     gameInfo.variant = v;\r
11859 \r
11860     switch (gameMode) {\r
11861       case MachinePlaysWhite:\r
11862         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11863         gameInfo.site = StrSave(HostName());\r
11864         gameInfo.date = PGNDate();\r
11865         gameInfo.round = StrSave("-");\r
11866         gameInfo.white = StrSave(first.tidy);\r
11867         gameInfo.black = StrSave(UserName());\r
11868         gameInfo.timeControl = TimeControlTagValue();\r
11869         break;\r
11870 \r
11871       case MachinePlaysBlack:\r
11872         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11873         gameInfo.site = StrSave(HostName());\r
11874         gameInfo.date = PGNDate();\r
11875         gameInfo.round = StrSave("-");\r
11876         gameInfo.white = StrSave(UserName());\r
11877         gameInfo.black = StrSave(first.tidy);\r
11878         gameInfo.timeControl = TimeControlTagValue();\r
11879         break;\r
11880 \r
11881       case TwoMachinesPlay:\r
11882         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11883         gameInfo.site = StrSave(HostName());\r
11884         gameInfo.date = PGNDate();\r
11885         if (matchGame > 0) {\r
11886             char buf[MSG_SIZ];\r
11887             sprintf(buf, "%d", matchGame);\r
11888             gameInfo.round = StrSave(buf);\r
11889         } else {\r
11890             gameInfo.round = StrSave("-");\r
11891         }\r
11892         if (first.twoMachinesColor[0] == 'w') {\r
11893             gameInfo.white = StrSave(first.tidy);\r
11894             gameInfo.black = StrSave(second.tidy);\r
11895         } else {\r
11896             gameInfo.white = StrSave(second.tidy);\r
11897             gameInfo.black = StrSave(first.tidy);\r
11898         }\r
11899         gameInfo.timeControl = TimeControlTagValue();\r
11900         break;\r
11901 \r
11902       case EditGame:\r
11903         gameInfo.event = StrSave("Edited game");\r
11904         gameInfo.site = StrSave(HostName());\r
11905         gameInfo.date = PGNDate();\r
11906         gameInfo.round = StrSave("-");\r
11907         gameInfo.white = StrSave("-");\r
11908         gameInfo.black = StrSave("-");\r
11909         break;\r
11910 \r
11911       case EditPosition:\r
11912         gameInfo.event = StrSave("Edited position");\r
11913         gameInfo.site = StrSave(HostName());\r
11914         gameInfo.date = PGNDate();\r
11915         gameInfo.round = StrSave("-");\r
11916         gameInfo.white = StrSave("-");\r
11917         gameInfo.black = StrSave("-");\r
11918         break;\r
11919 \r
11920       case IcsPlayingWhite:\r
11921       case IcsPlayingBlack:\r
11922       case IcsObserving:\r
11923       case IcsExamining:\r
11924         break;\r
11925 \r
11926       case PlayFromGameFile:\r
11927         gameInfo.event = StrSave("Game from non-PGN file");\r
11928         gameInfo.site = StrSave(HostName());\r
11929         gameInfo.date = PGNDate();\r
11930         gameInfo.round = StrSave("-");\r
11931         gameInfo.white = StrSave("?");\r
11932         gameInfo.black = StrSave("?");\r
11933         break;\r
11934 \r
11935       default:\r
11936         break;\r
11937     }\r
11938 }\r
11939 \r
11940 void\r
11941 ReplaceComment(index, text)\r
11942      int index;\r
11943      char *text;\r
11944 {\r
11945     int len;\r
11946 \r
11947     while (*text == '\n') text++;\r
11948     len = strlen(text);\r
11949     while (len > 0 && text[len - 1] == '\n') len--;\r
11950 \r
11951     if (commentList[index] != NULL)\r
11952       free(commentList[index]);\r
11953 \r
11954     if (len == 0) {\r
11955         commentList[index] = NULL;\r
11956         return;\r
11957     }\r
11958     commentList[index] = (char *) malloc(len + 2);\r
11959     strncpy(commentList[index], text, len);\r
11960     commentList[index][len] = '\n';\r
11961     commentList[index][len + 1] = NULLCHAR;\r
11962 }\r
11963 \r
11964 void\r
11965 CrushCRs(text)\r
11966      char *text;\r
11967 {\r
11968   char *p = text;\r
11969   char *q = text;\r
11970   char ch;\r
11971 \r
11972   do {\r
11973     ch = *p++;\r
11974     if (ch == '\r') continue;\r
11975     *q++ = ch;\r
11976   } while (ch != '\0');\r
11977 }\r
11978 \r
11979 void\r
11980 AppendComment(index, text)\r
11981      int index;\r
11982      char *text;\r
11983 {\r
11984     int oldlen, len;\r
11985     char *old;\r
11986 \r
11987     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11988 \r
11989     CrushCRs(text);\r
11990     while (*text == '\n') text++;\r
11991     len = strlen(text);\r
11992     while (len > 0 && text[len - 1] == '\n') len--;\r
11993 \r
11994     if (len == 0) return;\r
11995 \r
11996     if (commentList[index] != NULL) {\r
11997         old = commentList[index];\r
11998         oldlen = strlen(old);\r
11999         commentList[index] = (char *) malloc(oldlen + len + 2);\r
12000         strcpy(commentList[index], old);\r
12001         free(old);\r
12002         strncpy(&commentList[index][oldlen], text, len);\r
12003         commentList[index][oldlen + len] = '\n';\r
12004         commentList[index][oldlen + len + 1] = NULLCHAR;\r
12005     } else {\r
12006         commentList[index] = (char *) malloc(len + 2);\r
12007         strncpy(commentList[index], text, len);\r
12008         commentList[index][len] = '\n';\r
12009         commentList[index][len + 1] = NULLCHAR;\r
12010     }\r
12011 }\r
12012 \r
12013 static char * FindStr( char * text, char * sub_text )\r
12014 {\r
12015     char * result = strstr( text, sub_text );\r
12016 \r
12017     if( result != NULL ) {\r
12018         result += strlen( sub_text );\r
12019     }\r
12020 \r
12021     return result;\r
12022 }\r
12023 \r
12024 /* [AS] Try to extract PV info from PGN comment */\r
12025 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
12026 char *GetInfoFromComment( int index, char * text )\r
12027 {\r
12028     char * sep = text;\r
12029 \r
12030     if( text != NULL && index > 0 ) {\r
12031         int score = 0;\r
12032         int depth = 0;\r
12033         int time = -1, sec = 0, deci;\r
12034         char * s_eval = FindStr( text, "[%eval " );\r
12035         char * s_emt = FindStr( text, "[%emt " );\r
12036 \r
12037         if( s_eval != NULL || s_emt != NULL ) {\r
12038             /* New style */\r
12039             char delim;\r
12040 \r
12041             if( s_eval != NULL ) {\r
12042                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
12043                     return text;\r
12044                 }\r
12045 \r
12046                 if( delim != ']' ) {\r
12047                     return text;\r
12048                 }\r
12049             }\r
12050 \r
12051             if( s_emt != NULL ) {\r
12052             }\r
12053         }\r
12054         else {\r
12055             /* We expect something like: [+|-]nnn.nn/dd */\r
12056             int score_lo = 0;\r
12057 \r
12058             sep = strchr( text, '/' );\r
12059             if( sep == NULL || sep < (text+4) ) {\r
12060                 return text;\r
12061             }\r
12062 \r
12063             time = -1; sec = -1; deci = -1;\r
12064             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
12065                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&\r
12066                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
12067                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
12068                 return text;\r
12069             }\r
12070 \r
12071             if( score_lo < 0 || score_lo >= 100 ) {\r
12072                 return text;\r
12073             }\r
12074 \r
12075             if(sec >= 0) time = 600*time + 10*sec; else\r
12076             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec\r
12077 \r
12078             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
12079 \r
12080             /* [HGM] PV time: now locate end of PV info */\r
12081             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
12082             if(time >= 0)\r
12083             while( *++sep >= '0' && *sep <= '9'); // strip time\r
12084             if(sec >= 0)\r
12085             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
12086             if(deci >= 0)\r
12087             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds\r
12088             while(*sep == ' ') sep++;\r
12089         }\r
12090 \r
12091         if( depth <= 0 ) {\r
12092             return text;\r
12093         }\r
12094 \r
12095         if( time < 0 ) {\r
12096             time = -1;\r
12097         }\r
12098 \r
12099         pvInfoList[index-1].depth = depth;\r
12100         pvInfoList[index-1].score = score;\r
12101         pvInfoList[index-1].time  = 10*time; // centi-sec\r
12102     }\r
12103     return sep;\r
12104 }\r
12105 \r
12106 void\r
12107 SendToProgram(message, cps)\r
12108      char *message;\r
12109      ChessProgramState *cps;\r
12110 {\r
12111     int count, outCount, error;\r
12112     char buf[MSG_SIZ];\r
12113 \r
12114     if (cps->pr == NULL) return;\r
12115     Attention(cps);\r
12116     \r
12117     if (appData.debugMode) {\r
12118         TimeMark now;\r
12119         GetTimeMark(&now);\r
12120         fprintf(debugFP, "%ld >%-6s: %s", \r
12121                 SubtractTimeMarks(&now, &programStartTime),\r
12122                 cps->which, message);\r
12123     }\r
12124     \r
12125     count = strlen(message);\r
12126     outCount = OutputToProcess(cps->pr, message, count, &error);\r
12127     if (outCount < count && !exiting \r
12128                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
12129         sprintf(buf, _("Error writing to %s chess program"), cps->which);\r
12130         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12131             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12132                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12133                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
12134             } else {\r
12135                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12136             }\r
12137             gameInfo.resultDetails = buf;\r
12138         }\r
12139         DisplayFatalError(buf, error, 1);\r
12140     }\r
12141 }\r
12142 \r
12143 void\r
12144 ReceiveFromProgram(isr, closure, message, count, error)\r
12145      InputSourceRef isr;\r
12146      VOIDSTAR closure;\r
12147      char *message;\r
12148      int count;\r
12149      int error;\r
12150 {\r
12151     char *end_str;\r
12152     char buf[MSG_SIZ];\r
12153     ChessProgramState *cps = (ChessProgramState *)closure;\r
12154 \r
12155     if (isr != cps->isr) return; /* Killed intentionally */\r
12156     if (count <= 0) {\r
12157         if (count == 0) {\r
12158             sprintf(buf,\r
12159                     _("Error: %s chess program (%s) exited unexpectedly"),\r
12160                     cps->which, cps->program);\r
12161         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12162                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12163                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12164                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);\r
12165                 } else {\r
12166                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12167                 }\r
12168                 gameInfo.resultDetails = buf;\r
12169             }\r
12170             RemoveInputSource(cps->isr);\r
12171             DisplayFatalError(buf, 0, 1);\r
12172         } else {\r
12173             sprintf(buf,\r
12174                     _("Error reading from %s chess program (%s)"),\r
12175                     cps->which, cps->program);\r
12176             RemoveInputSource(cps->isr);\r
12177 \r
12178             /* [AS] Program is misbehaving badly... kill it */\r
12179             if( count == -2 ) {\r
12180                 DestroyChildProcess( cps->pr, 9 );\r
12181                 cps->pr = NoProc;\r
12182             }\r
12183 \r
12184             DisplayFatalError(buf, error, 1);\r
12185         }\r
12186         return;\r
12187     }\r
12188     \r
12189     if ((end_str = strchr(message, '\r')) != NULL)\r
12190       *end_str = NULLCHAR;\r
12191     if ((end_str = strchr(message, '\n')) != NULL)\r
12192       *end_str = NULLCHAR;\r
12193     \r
12194     if (appData.debugMode) {\r
12195         TimeMark now; int print = 1;\r
12196         char *quote = ""; char c; int i;\r
12197 \r
12198         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
12199                 char start = message[0];\r
12200                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
12201                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
12202                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&\r
12203                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
12204                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
12205                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&\r
12206                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
12207                         { quote = "# "; print = (appData.engineComments == 2); }\r
12208                 message[0] = start; // restore original message\r
12209         }\r
12210         if(print) {\r
12211                 GetTimeMark(&now);\r
12212                 fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
12213                         SubtractTimeMarks(&now, &programStartTime), cps->which, \r
12214                         quote,\r
12215                         message);\r
12216         }\r
12217     }\r
12218 \r
12219     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */\r
12220     if (appData.icsEngineAnalyze) {\r
12221         if (strstr(message, "whisper") != NULL ||\r
12222              strstr(message, "kibitz") != NULL || \r
12223             strstr(message, "tellics") != NULL) return;\r
12224     }\r
12225 \r
12226     HandleMachineMove(message, cps);\r
12227 }\r
12228 \r
12229 \r
12230 void\r
12231 SendTimeControl(cps, mps, tc, inc, sd, st)\r
12232      ChessProgramState *cps;\r
12233      int mps, inc, sd, st;\r
12234      long tc;\r
12235 {\r
12236     char buf[MSG_SIZ];\r
12237     int seconds;\r
12238 \r
12239     if( timeControl_2 > 0 ) {\r
12240         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
12241             tc = timeControl_2;\r
12242         }\r
12243     }\r
12244     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
12245     inc /= cps->timeOdds;\r
12246     st  /= cps->timeOdds;\r
12247 \r
12248     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
12249 \r
12250     if (st > 0) {\r
12251       /* Set exact time per move, normally using st command */\r
12252       if (cps->stKludge) {\r
12253         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
12254         seconds = st % 60;\r
12255         if (seconds == 0) {\r
12256           sprintf(buf, "level 1 %d\n", st/60);\r
12257         } else {\r
12258           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
12259         }\r
12260       } else {\r
12261         sprintf(buf, "st %d\n", st);\r
12262       }\r
12263     } else {\r
12264       /* Set conventional or incremental time control, using level command */\r
12265       if (seconds == 0) {\r
12266         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
12267            Fixed in later versions, but still avoid :seconds\r
12268            when seconds is 0. */\r
12269         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
12270       } else {\r
12271         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
12272                 seconds, inc/1000);\r
12273       }\r
12274     }\r
12275     SendToProgram(buf, cps);\r
12276 \r
12277     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
12278     /* Orthogonally, limit search to given depth */\r
12279     if (sd > 0) {\r
12280       if (cps->sdKludge) {\r
12281         sprintf(buf, "depth\n%d\n", sd);\r
12282       } else {\r
12283         sprintf(buf, "sd %d\n", sd);\r
12284       }\r
12285       SendToProgram(buf, cps);\r
12286     }\r
12287 \r
12288     if(cps->nps > 0) { /* [HGM] nps */\r
12289         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
12290         else {\r
12291                 sprintf(buf, "nps %d\n", cps->nps);\r
12292               SendToProgram(buf, cps);\r
12293         }\r
12294     }\r
12295 }\r
12296 \r
12297 ChessProgramState *WhitePlayer()\r
12298 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
12299 {\r
12300     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || \r
12301        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)\r
12302         return &second;\r
12303     return &first;\r
12304 }\r
12305 \r
12306 void\r
12307 SendTimeRemaining(cps, machineWhite)\r
12308      ChessProgramState *cps;\r
12309      int /*boolean*/ machineWhite;\r
12310 {\r
12311     char message[MSG_SIZ];\r
12312     long time, otime;\r
12313 \r
12314     /* Note: this routine must be called when the clocks are stopped\r
12315        or when they have *just* been set or switched; otherwise\r
12316        it will be off by the time since the current tick started.\r
12317     */\r
12318     if (machineWhite) {\r
12319         time = whiteTimeRemaining / 10;\r
12320         otime = blackTimeRemaining / 10;\r
12321     } else {\r
12322         time = blackTimeRemaining / 10;\r
12323         otime = whiteTimeRemaining / 10;\r
12324     }\r
12325     /* [HGM] translate opponent's time by time-odds factor */\r
12326     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
12327     if (appData.debugMode) {\r
12328         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
12329     }\r
12330 \r
12331     if (time <= 0) time = 1;\r
12332     if (otime <= 0) otime = 1;\r
12333     \r
12334     sprintf(message, "time %ld\n", time);\r
12335     SendToProgram(message, cps);\r
12336 \r
12337     sprintf(message, "otim %ld\n", otime);\r
12338     SendToProgram(message, cps);\r
12339 }\r
12340 \r
12341 int\r
12342 BoolFeature(p, name, loc, cps)\r
12343      char **p;\r
12344      char *name;\r
12345      int *loc;\r
12346      ChessProgramState *cps;\r
12347 {\r
12348   char buf[MSG_SIZ];\r
12349   int len = strlen(name);\r
12350   int val;\r
12351   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12352     (*p) += len + 1;\r
12353     sscanf(*p, "%d", &val);\r
12354     *loc = (val != 0);\r
12355     while (**p && **p != ' ') (*p)++;\r
12356     sprintf(buf, "accepted %s\n", name);\r
12357     SendToProgram(buf, cps);\r
12358     return TRUE;\r
12359   }\r
12360   return FALSE;\r
12361 }\r
12362 \r
12363 int\r
12364 IntFeature(p, name, loc, cps)\r
12365      char **p;\r
12366      char *name;\r
12367      int *loc;\r
12368      ChessProgramState *cps;\r
12369 {\r
12370   char buf[MSG_SIZ];\r
12371   int len = strlen(name);\r
12372   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12373     (*p) += len + 1;\r
12374     sscanf(*p, "%d", loc);\r
12375     while (**p && **p != ' ') (*p)++;\r
12376     sprintf(buf, "accepted %s\n", name);\r
12377     SendToProgram(buf, cps);\r
12378     return TRUE;\r
12379   }\r
12380   return FALSE;\r
12381 }\r
12382 \r
12383 int\r
12384 StringFeature(p, name, loc, cps)\r
12385      char **p;\r
12386      char *name;\r
12387      char loc[];\r
12388      ChessProgramState *cps;\r
12389 {\r
12390   char buf[MSG_SIZ];\r
12391   int len = strlen(name);\r
12392   if (strncmp((*p), name, len) == 0\r
12393       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
12394     (*p) += len + 2;\r
12395     sscanf(*p, "%[^\"]", loc);\r
12396     while (**p && **p != '\"') (*p)++;\r
12397     if (**p == '\"') (*p)++;\r
12398     sprintf(buf, "accepted %s\n", name);\r
12399     SendToProgram(buf, cps);\r
12400     return TRUE;\r
12401   }\r
12402   return FALSE;\r
12403 }\r
12404 \r
12405 int \r
12406 ParseOption(Option *opt, ChessProgramState *cps)\r
12407 // [HGM] options: process the string that defines an engine option, and determine\r
12408 // name, type, default value, and allowed value range\r
12409 {\r
12410         char *p, *q, buf[MSG_SIZ];\r
12411         int n, min = (-1)<<31, max = 1<<31, def;\r
12412 \r
12413         if(p = strstr(opt->name, " -spin ")) {\r
12414             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;\r
12415             if(max < min) max = min; // enforce consistency\r
12416             if(def < min) def = min;\r
12417             if(def > max) def = max;\r
12418             opt->value = def;\r
12419             opt->min = min;\r
12420             opt->max = max;\r
12421             opt->type = Spin;\r
12422         } else if(p = strstr(opt->name, " -string ")) {\r
12423             opt->textValue = p+9;\r
12424             opt->type = TextBox;\r
12425         } else if(p = strstr(opt->name, " -check ")) {\r
12426             if(sscanf(p, " -check %d", &def) < 1) return FALSE;\r
12427             opt->value = (def != 0);\r
12428             opt->type = CheckBox;\r
12429         } else if(p = strstr(opt->name, " -combo ")) {\r
12430             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type\r
12431             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices\r
12432             opt->value = n = 0;\r
12433             while(q = StrStr(q, " /// ")) {\r
12434                 n++; *q = 0;    // count choices, and null-terminate each of them\r
12435                 q += 5;\r
12436                 if(*q == '*') { // remember default, which is marked with * prefix\r
12437                     q++;\r
12438                     opt->value = n;\r
12439                 }\r
12440                 cps->comboList[cps->comboCnt++] = q;\r
12441             }\r
12442             cps->comboList[cps->comboCnt++] = NULL;\r
12443             opt->max = n + 1;\r
12444             opt->type = ComboBox;\r
12445         } else if(p = strstr(opt->name, " -button")) {\r
12446             opt->type = Button;\r
12447         } else if(p = strstr(opt->name, " -save")) {\r
12448             opt->type = SaveButton;\r
12449         } else return FALSE;\r
12450         *p = 0; // terminate option name\r
12451         // now look if the command-line options define a setting for this engine option.\r
12452         if(cps->optionSettings && cps->optionSettings[0])\r
12453             p = strstr(cps->optionSettings, opt->name); else p = NULL;\r
12454         if(p && (p == cps->optionSettings || p[-1] == ',')) {\r
12455                 sprintf(buf, "option %s", p);\r
12456                 if(p = strstr(buf, ",")) *p = 0;\r
12457                 strcat(buf, "\n");\r
12458                 SendToProgram(buf, cps);\r
12459         }\r
12460         return TRUE;\r
12461 }\r
12462 \r
12463 void\r
12464 FeatureDone(cps, val)\r
12465      ChessProgramState* cps;\r
12466      int val;\r
12467 {\r
12468   DelayedEventCallback cb = GetDelayedEvent();\r
12469   if ((cb == InitBackEnd3 && cps == &first) ||\r
12470       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
12471     CancelDelayedEvent();\r
12472     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
12473   }\r
12474   cps->initDone = val;\r
12475 }\r
12476 \r
12477 /* Parse feature command from engine */\r
12478 void\r
12479 ParseFeatures(args, cps)\r
12480      char* args;\r
12481      ChessProgramState *cps;  \r
12482 {\r
12483   char *p = args;\r
12484   char *q;\r
12485   int val;\r
12486   char buf[MSG_SIZ];\r
12487 \r
12488   for (;;) {\r
12489     while (*p == ' ') p++;\r
12490     if (*p == NULLCHAR) return;\r
12491 \r
12492     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
12493     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
12494     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
12495     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
12496     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
12497     if (BoolFeature(&p, "reuse", &val, cps)) {\r
12498       /* Engine can disable reuse, but can't enable it if user said no */\r
12499       if (!val) cps->reuse = FALSE;\r
12500       continue;\r
12501     }\r
12502     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
12503     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
12504       if (gameMode == TwoMachinesPlay) {\r
12505         DisplayTwoMachinesTitle();\r
12506       } else {\r
12507         DisplayTitle("");\r
12508       }\r
12509       continue;\r
12510     }\r
12511     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
12512     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
12513     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
12514     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
12515     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
12516     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
12517     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
12518     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
12519     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
12520     if (IntFeature(&p, "done", &val, cps)) {\r
12521       FeatureDone(cps, val);\r
12522       continue;\r
12523     }\r
12524     /* Added by Tord: */\r
12525     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
12526     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
12527     /* End of additions by Tord */\r
12528 \r
12529     /* [HGM] added features: */\r
12530     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
12531     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
12532     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
12533     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
12534     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12535     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
12536     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {\r
12537         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature\r
12538         if(cps->nrOptions >= MAX_OPTIONS) {\r
12539             cps->nrOptions--;\r
12540             sprintf(buf, "%s engine has too many options\n", cps->which);\r
12541             DisplayError(buf, 0);\r
12542         }\r
12543         continue;\r
12544     }\r
12545     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12546     /* End of additions by HGM */\r
12547 \r
12548     /* unknown feature: complain and skip */\r
12549     q = p;\r
12550     while (*q && *q != '=') q++;\r
12551     sprintf(buf, "rejected %.*s\n", q-p, p);\r
12552     SendToProgram(buf, cps);\r
12553     p = q;\r
12554     if (*p == '=') {\r
12555       p++;\r
12556       if (*p == '\"') {\r
12557         p++;\r
12558         while (*p && *p != '\"') p++;\r
12559         if (*p == '\"') p++;\r
12560       } else {\r
12561         while (*p && *p != ' ') p++;\r
12562       }\r
12563     }\r
12564   }\r
12565 \r
12566 }\r
12567 \r
12568 void\r
12569 PeriodicUpdatesEvent(newState)\r
12570      int newState;\r
12571 {\r
12572     if (newState == appData.periodicUpdates)\r
12573       return;\r
12574 \r
12575     appData.periodicUpdates=newState;\r
12576 \r
12577     /* Display type changes, so update it now */\r
12578     DisplayAnalysis();\r
12579 \r
12580     /* Get the ball rolling again... */\r
12581     if (newState) {\r
12582         AnalysisPeriodicEvent(1);\r
12583         StartAnalysisClock();\r
12584     }\r
12585 }\r
12586 \r
12587 void\r
12588 PonderNextMoveEvent(newState)\r
12589      int newState;\r
12590 {\r
12591     if (newState == appData.ponderNextMove) return;\r
12592     if (gameMode == EditPosition) EditPositionDone();\r
12593     if (newState) {\r
12594         SendToProgram("hard\n", &first);\r
12595         if (gameMode == TwoMachinesPlay) {\r
12596             SendToProgram("hard\n", &second);\r
12597         }\r
12598     } else {\r
12599         SendToProgram("easy\n", &first);\r
12600         thinkOutput[0] = NULLCHAR;\r
12601         if (gameMode == TwoMachinesPlay) {\r
12602             SendToProgram("easy\n", &second);\r
12603         }\r
12604     }\r
12605     appData.ponderNextMove = newState;\r
12606 }\r
12607 \r
12608 void\r
12609 NewSettingEvent(option, command, value)\r
12610      char *command;\r
12611      int option, value;\r
12612 {\r
12613     char buf[MSG_SIZ];\r
12614 \r
12615     if (gameMode == EditPosition) EditPositionDone();\r
12616     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);\r
12617     SendToProgram(buf, &first);\r
12618     if (gameMode == TwoMachinesPlay) {\r
12619         SendToProgram(buf, &second);\r
12620     }\r
12621 }\r
12622 \r
12623 void\r
12624 ShowThinkingEvent()\r
12625 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup\r
12626 {\r
12627     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated\r
12628     int newState = appData.showThinking\r
12629         // [HGM] thinking: other features now need thinking output as well\r
12630         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();\r
12631     \r
12632     if (oldState == newState) return;\r
12633     oldState = newState;\r
12634     if (gameMode == EditPosition) EditPositionDone();\r
12635     if (oldState) {\r
12636         SendToProgram("post\n", &first);\r
12637         if (gameMode == TwoMachinesPlay) {\r
12638             SendToProgram("post\n", &second);\r
12639         }\r
12640     } else {\r
12641         SendToProgram("nopost\n", &first);\r
12642         thinkOutput[0] = NULLCHAR;\r
12643         if (gameMode == TwoMachinesPlay) {\r
12644             SendToProgram("nopost\n", &second);\r
12645         }\r
12646     }\r
12647 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!\r
12648 }\r
12649 \r
12650 void\r
12651 AskQuestionEvent(title, question, replyPrefix, which)\r
12652      char *title; char *question; char *replyPrefix; char *which;\r
12653 {\r
12654   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
12655   if (pr == NoProc) return;\r
12656   AskQuestion(title, question, replyPrefix, pr);\r
12657 }\r
12658 \r
12659 void\r
12660 DisplayMove(moveNumber)\r
12661      int moveNumber;\r
12662 {\r
12663     char message[MSG_SIZ];\r
12664     char res[MSG_SIZ];\r
12665     char cpThinkOutput[MSG_SIZ];\r
12666 \r
12667     if(appData.noGUI) return; // [HGM] fast: suppress display of moves\r
12668     \r
12669     if (moveNumber == forwardMostMove - 1 || \r
12670         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12671 \r
12672         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
12673 \r
12674         if (strchr(cpThinkOutput, '\n')) {\r
12675             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
12676         }\r
12677     } else {\r
12678         *cpThinkOutput = NULLCHAR;\r
12679     }\r
12680 \r
12681     /* [AS] Hide thinking from human user */\r
12682     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
12683         *cpThinkOutput = NULLCHAR;\r
12684         if( thinkOutput[0] != NULLCHAR ) {\r
12685             int i;\r
12686 \r
12687             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
12688                 cpThinkOutput[i] = '.';\r
12689             }\r
12690             cpThinkOutput[i] = NULLCHAR;\r
12691             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
12692         }\r
12693     }\r
12694 \r
12695     if (moveNumber == forwardMostMove - 1 &&\r
12696         gameInfo.resultDetails != NULL) {\r
12697         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
12698             sprintf(res, " %s", PGNResult(gameInfo.result));\r
12699         } else {\r
12700             sprintf(res, " {%s} %s",\r
12701                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
12702         }\r
12703     } else {\r
12704         res[0] = NULLCHAR;\r
12705     }\r
12706 \r
12707     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12708         DisplayMessage(res, cpThinkOutput);\r
12709     } else {\r
12710         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
12711                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
12712                 parseList[moveNumber], res);\r
12713         DisplayMessage(message, cpThinkOutput);\r
12714     }\r
12715 }\r
12716 \r
12717 void\r
12718 DisplayAnalysisText(text)\r
12719      char *text;\r
12720 {\r
12721     char buf[MSG_SIZ];\r
12722 \r
12723     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile \r
12724                || appData.icsEngineAnalyze) {\r
12725         sprintf(buf, "Analysis (%s)", first.tidy);\r
12726         AnalysisPopUp(buf, text);\r
12727     }\r
12728 }\r
12729 \r
12730 static int\r
12731 only_one_move(str)\r
12732      char *str;\r
12733 {\r
12734     while (*str && isspace(*str)) ++str;\r
12735     while (*str && !isspace(*str)) ++str;\r
12736     if (!*str) return 1;\r
12737     while (*str && isspace(*str)) ++str;\r
12738     if (!*str) return 1;\r
12739     return 0;\r
12740 }\r
12741 \r
12742 void\r
12743 DisplayAnalysis()\r
12744 {\r
12745     char buf[MSG_SIZ];\r
12746     char lst[MSG_SIZ / 2];\r
12747     double nps;\r
12748     static char *xtra[] = { "", " (--)", " (++)" };\r
12749     int h, m, s, cs;\r
12750   \r
12751     if (programStats.time == 0) {\r
12752         programStats.time = 1;\r
12753     }\r
12754   \r
12755     if (programStats.got_only_move) {\r
12756         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
12757     } else {\r
12758         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
12759 \r
12760         nps = (u64ToDouble(programStats.nodes) /\r
12761              ((double)programStats.time /100.0));\r
12762 \r
12763         cs = programStats.time % 100;\r
12764         s = programStats.time / 100;\r
12765         h = (s / (60*60));\r
12766         s = s - h*60*60;\r
12767         m = (s/60);\r
12768         s = s - m*60;\r
12769 \r
12770         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
12771           if (programStats.move_name[0] != NULLCHAR) {\r
12772             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12773                     programStats.depth,\r
12774                     programStats.nr_moves-programStats.moves_left,\r
12775                     programStats.nr_moves, programStats.move_name,\r
12776                     ((float)programStats.score)/100.0, lst,\r
12777                     only_one_move(lst)?\r
12778                     xtra[programStats.got_fail] : "",\r
12779                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12780           } else {\r
12781             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12782                     programStats.depth,\r
12783                     programStats.nr_moves-programStats.moves_left,\r
12784                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
12785                     lst,\r
12786                     only_one_move(lst)?\r
12787                     xtra[programStats.got_fail] : "",\r
12788                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12789           }\r
12790         } else {\r
12791             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12792                     programStats.depth,\r
12793                     ((float)programStats.score)/100.0,\r
12794                     lst,\r
12795                     only_one_move(lst)?\r
12796                     xtra[programStats.got_fail] : "",\r
12797                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12798         }\r
12799     }\r
12800     DisplayAnalysisText(buf);\r
12801 }\r
12802 \r
12803 void\r
12804 DisplayComment(moveNumber, text)\r
12805      int moveNumber;\r
12806      char *text;\r
12807 {\r
12808     char title[MSG_SIZ];\r
12809     char buf[8000]; // comment can be long!\r
12810     int score, depth;\r
12811 \r
12812     if( appData.autoDisplayComment ) {\r
12813         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12814             strcpy(title, "Comment");\r
12815         } else {\r
12816             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
12817                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
12818                     parseList[moveNumber]);\r
12819         }\r
12820     } else title[0] = 0;\r
12821 \r
12822     // [HGM] PV info: display PV info together with (or as) comment\r
12823     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
12824         if(text == NULL) text = "";                                           \r
12825         score = pvInfoList[moveNumber].score;\r
12826         sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
12827                               depth, (pvInfoList[moveNumber].time+50)/100, text);\r
12828         CommentPopUp(title, buf);\r
12829     } else\r
12830     if (text != NULL)\r
12831         CommentPopUp(title, text);\r
12832 }\r
12833 \r
12834 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
12835  * might be busy thinking or pondering.  It can be omitted if your\r
12836  * gnuchess is configured to stop thinking immediately on any user\r
12837  * input.  However, that gnuchess feature depends on the FIONREAD\r
12838  * ioctl, which does not work properly on some flavors of Unix.\r
12839  */\r
12840 void\r
12841 Attention(cps)\r
12842      ChessProgramState *cps;\r
12843 {\r
12844 #if ATTENTION\r
12845     if (!cps->useSigint) return;\r
12846     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
12847     switch (gameMode) {\r
12848       case MachinePlaysWhite:\r
12849       case MachinePlaysBlack:\r
12850       case TwoMachinesPlay:\r
12851       case IcsPlayingWhite:\r
12852       case IcsPlayingBlack:\r
12853       case AnalyzeMode:\r
12854       case AnalyzeFile:\r
12855         /* Skip if we know it isn't thinking */\r
12856         if (!cps->maybeThinking) return;\r
12857         if (appData.debugMode)\r
12858           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
12859         InterruptChildProcess(cps->pr);\r
12860         cps->maybeThinking = FALSE;\r
12861         break;\r
12862       default:\r
12863         break;\r
12864     }\r
12865 #endif /*ATTENTION*/\r
12866 }\r
12867 \r
12868 int\r
12869 CheckFlags()\r
12870 {\r
12871     if (whiteTimeRemaining <= 0) {\r
12872         if (!whiteFlag) {\r
12873             whiteFlag = TRUE;\r
12874             if (appData.icsActive) {\r
12875                 if (appData.autoCallFlag &&\r
12876                     gameMode == IcsPlayingBlack && !blackFlag) {\r
12877                   SendToICS(ics_prefix);\r
12878                   SendToICS("flag\n");\r
12879                 }\r
12880             } else {\r
12881                 if (blackFlag) {\r
12882                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12883                 } else {\r
12884                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));\r
12885                     if (appData.autoCallFlag) {\r
12886                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
12887                         return TRUE;\r
12888                     }\r
12889                 }\r
12890             }\r
12891         }\r
12892     }\r
12893     if (blackTimeRemaining <= 0) {\r
12894         if (!blackFlag) {\r
12895             blackFlag = TRUE;\r
12896             if (appData.icsActive) {\r
12897                 if (appData.autoCallFlag &&\r
12898                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
12899                   SendToICS(ics_prefix);\r
12900                   SendToICS("flag\n");\r
12901                 }\r
12902             } else {\r
12903                 if (whiteFlag) {\r
12904                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12905                 } else {\r
12906                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));\r
12907                     if (appData.autoCallFlag) {\r
12908                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
12909                         return TRUE;\r
12910                     }\r
12911                 }\r
12912             }\r
12913         }\r
12914     }\r
12915     return FALSE;\r
12916 }\r
12917 \r
12918 void\r
12919 CheckTimeControl()\r
12920 {\r
12921     if (!appData.clockMode || appData.icsActive ||\r
12922         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
12923 \r
12924     /*\r
12925      * add time to clocks when time control is achieved ([HGM] now also used for increment)\r
12926      */\r
12927     if ( !WhiteOnMove(forwardMostMove) )\r
12928         /* White made time control */\r
12929         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12930         /* [HGM] time odds: correct new time quota for time odds! */\r
12931                                             / WhitePlayer()->timeOdds;\r
12932       else\r
12933         /* Black made time control */\r
12934         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12935                                             / WhitePlayer()->other->timeOdds;\r
12936 }\r
12937 \r
12938 void\r
12939 DisplayBothClocks()\r
12940 {\r
12941     int wom = gameMode == EditPosition ?\r
12942       !blackPlaysFirst : WhiteOnMove(currentMove);\r
12943     DisplayWhiteClock(whiteTimeRemaining, wom);\r
12944     DisplayBlackClock(blackTimeRemaining, !wom);\r
12945 }\r
12946 \r
12947 \r
12948 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
12949    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
12950    to use other calls if you don't.  Clocks will be less accurate if\r
12951    you have neither ftime nor gettimeofday.\r
12952 */\r
12953 \r
12954 /* VS 2008 requires the #include outside of the function */\r
12955 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME\r
12956 #include <sys/timeb.h>\r
12957 #endif\r
12958 \r
12959 /* Get the current time as a TimeMark */\r
12960 void\r
12961 GetTimeMark(tm)\r
12962      TimeMark *tm;\r
12963 {\r
12964 #if HAVE_GETTIMEOFDAY\r
12965 \r
12966     struct timeval timeVal;\r
12967     struct timezone timeZone;\r
12968 \r
12969     gettimeofday(&timeVal, &timeZone);\r
12970     tm->sec = (long) timeVal.tv_sec; \r
12971     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
12972 \r
12973 #else /*!HAVE_GETTIMEOFDAY*/\r
12974 #if HAVE_FTIME\r
12975 \r
12976 // include <sys/timeb.h> / moved to just above start of function\r
12977     struct timeb timeB;\r
12978 \r
12979     ftime(&timeB);\r
12980     tm->sec = (long) timeB.time;\r
12981     tm->ms = (int) timeB.millitm;\r
12982 \r
12983 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
12984     tm->sec = (long) time(NULL);\r
12985     tm->ms = 0;\r
12986 #endif\r
12987 #endif\r
12988 }\r
12989 \r
12990 /* Return the difference in milliseconds between two\r
12991    time marks.  We assume the difference will fit in a long!\r
12992 */\r
12993 long\r
12994 SubtractTimeMarks(tm2, tm1)\r
12995      TimeMark *tm2, *tm1;\r
12996 {\r
12997     return 1000L*(tm2->sec - tm1->sec) +\r
12998            (long) (tm2->ms - tm1->ms);\r
12999 }\r
13000 \r
13001 \r
13002 /*\r
13003  * Code to manage the game clocks.\r
13004  *\r
13005  * In tournament play, black starts the clock and then white makes a move.\r
13006  * We give the human user a slight advantage if he is playing white---the\r
13007  * clocks don't run until he makes his first move, so it takes zero time.\r
13008  * Also, we don't account for network lag, so we could get out of sync\r
13009  * with GNU Chess's clock -- but then, referees are always right.  \r
13010  */\r
13011 \r
13012 static TimeMark tickStartTM;\r
13013 static long intendedTickLength;\r
13014 \r
13015 long\r
13016 NextTickLength(timeRemaining)\r
13017      long timeRemaining;\r
13018 {\r
13019     long nominalTickLength, nextTickLength;\r
13020 \r
13021     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
13022       nominalTickLength = 100L;\r
13023     else\r
13024       nominalTickLength = 1000L;\r
13025     nextTickLength = timeRemaining % nominalTickLength;\r
13026     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
13027 \r
13028     return nextTickLength;\r
13029 }\r
13030 \r
13031 /* Adjust clock one minute up or down */\r
13032 void\r
13033 AdjustClock(Boolean which, int dir)\r
13034 {\r
13035     if(which) blackTimeRemaining += 60000*dir;\r
13036     else      whiteTimeRemaining += 60000*dir;\r
13037     DisplayBothClocks();\r
13038 }\r
13039 \r
13040 /* Stop clocks and reset to a fresh time control */\r
13041 void\r
13042 ResetClocks() \r
13043 {\r
13044     (void) StopClockTimer();\r
13045     if (appData.icsActive) {\r
13046         whiteTimeRemaining = blackTimeRemaining = 0;\r
13047     } else { /* [HGM] correct new time quote for time odds */\r
13048         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
13049         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
13050     }\r
13051     if (whiteFlag || blackFlag) {\r
13052         DisplayTitle("");\r
13053         whiteFlag = blackFlag = FALSE;\r
13054     }\r
13055     DisplayBothClocks();\r
13056 }\r
13057 \r
13058 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
13059 \r
13060 /* Decrement running clock by amount of time that has passed */\r
13061 void\r
13062 DecrementClocks()\r
13063 {\r
13064     long timeRemaining;\r
13065     long lastTickLength, fudge;\r
13066     TimeMark now;\r
13067 \r
13068     if (!appData.clockMode) return;\r
13069     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
13070         \r
13071     GetTimeMark(&now);\r
13072 \r
13073     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13074 \r
13075     /* Fudge if we woke up a little too soon */\r
13076     fudge = intendedTickLength - lastTickLength;\r
13077     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
13078 \r
13079     if (WhiteOnMove(forwardMostMove)) {\r
13080         if(whiteNPS >= 0) lastTickLength = 0;\r
13081         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
13082         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
13083                           WhiteOnMove(currentMove));\r
13084     } else {\r
13085         if(blackNPS >= 0) lastTickLength = 0;\r
13086         timeRemaining = blackTimeRemaining -= lastTickLength;\r
13087         DisplayBlackClock(blackTimeRemaining - fudge,\r
13088                           !WhiteOnMove(currentMove));\r
13089     }\r
13090 \r
13091     if (CheckFlags()) return;\r
13092         \r
13093     tickStartTM = now;\r
13094     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
13095     StartClockTimer(intendedTickLength);\r
13096 \r
13097     /* if the time remaining has fallen below the alarm threshold, sound the\r
13098      * alarm. if the alarm has sounded and (due to a takeback or time control\r
13099      * with increment) the time remaining has increased to a level above the\r
13100      * threshold, reset the alarm so it can sound again. \r
13101      */\r
13102     \r
13103     if (appData.icsActive && appData.icsAlarm) {\r
13104 \r
13105         /* make sure we are dealing with the user's clock */\r
13106         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
13107                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
13108            )) return;\r
13109 \r
13110         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
13111             alarmSounded = FALSE;\r
13112         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
13113             PlayAlarmSound();\r
13114             alarmSounded = TRUE;\r
13115         }\r
13116     }\r
13117 }\r
13118 \r
13119 \r
13120 /* A player has just moved, so stop the previously running\r
13121    clock and (if in clock mode) start the other one.\r
13122    We redisplay both clocks in case we're in ICS mode, because\r
13123    ICS gives us an update to both clocks after every move.\r
13124    Note that this routine is called *after* forwardMostMove\r
13125    is updated, so the last fractional tick must be subtracted\r
13126    from the color that is *not* on move now.\r
13127 */\r
13128 void\r
13129 SwitchClocks()\r
13130 {\r
13131     long lastTickLength;\r
13132     TimeMark now;\r
13133     int flagged = FALSE;\r
13134 \r
13135     GetTimeMark(&now);\r
13136 \r
13137     if (StopClockTimer() && appData.clockMode) {\r
13138         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13139         if (WhiteOnMove(forwardMostMove)) {\r
13140             if(blackNPS >= 0) lastTickLength = 0;\r
13141             blackTimeRemaining -= lastTickLength;\r
13142            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13143 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13144                  pvInfoList[forwardMostMove-1].time =               // use GUI time\r
13145                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
13146         } else {\r
13147            if(whiteNPS >= 0) lastTickLength = 0;\r
13148            whiteTimeRemaining -= lastTickLength;\r
13149            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13150 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13151                  pvInfoList[forwardMostMove-1].time = \r
13152                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
13153         }\r
13154         flagged = CheckFlags();\r
13155     }\r
13156     CheckTimeControl();\r
13157 \r
13158     if (flagged || !appData.clockMode) return;\r
13159 \r
13160     switch (gameMode) {\r
13161       case MachinePlaysBlack:\r
13162       case MachinePlaysWhite:\r
13163       case BeginningOfGame:\r
13164         if (pausing) return;\r
13165         break;\r
13166 \r
13167       case EditGame:\r
13168       case PlayFromGameFile:\r
13169       case IcsExamining:\r
13170         return;\r
13171 \r
13172       default:\r
13173         break;\r
13174     }\r
13175 \r
13176     tickStartTM = now;\r
13177     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13178       whiteTimeRemaining : blackTimeRemaining);\r
13179     StartClockTimer(intendedTickLength);\r
13180 }\r
13181         \r
13182 \r
13183 /* Stop both clocks */\r
13184 void\r
13185 StopClocks()\r
13186 {       \r
13187     long lastTickLength;\r
13188     TimeMark now;\r
13189 \r
13190     if (!StopClockTimer()) return;\r
13191     if (!appData.clockMode) return;\r
13192 \r
13193     GetTimeMark(&now);\r
13194 \r
13195     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13196     if (WhiteOnMove(forwardMostMove)) {\r
13197         if(whiteNPS >= 0) lastTickLength = 0;\r
13198         whiteTimeRemaining -= lastTickLength;\r
13199         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
13200     } else {\r
13201         if(blackNPS >= 0) lastTickLength = 0;\r
13202         blackTimeRemaining -= lastTickLength;\r
13203         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
13204     }\r
13205     CheckFlags();\r
13206 }\r
13207         \r
13208 /* Start clock of player on move.  Time may have been reset, so\r
13209    if clock is already running, stop and restart it. */\r
13210 void\r
13211 StartClocks()\r
13212 {\r
13213     (void) StopClockTimer(); /* in case it was running already */\r
13214     DisplayBothClocks();\r
13215     if (CheckFlags()) return;\r
13216 \r
13217     if (!appData.clockMode) return;\r
13218     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
13219 \r
13220     GetTimeMark(&tickStartTM);\r
13221     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13222       whiteTimeRemaining : blackTimeRemaining);\r
13223 \r
13224    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
13225     whiteNPS = blackNPS = -1; \r
13226     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
13227        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
13228         whiteNPS = first.nps;\r
13229     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
13230        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
13231         blackNPS = first.nps;\r
13232     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
13233         whiteNPS = second.nps;\r
13234     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
13235         blackNPS = second.nps;\r
13236     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
13237 \r
13238     StartClockTimer(intendedTickLength);\r
13239 }\r
13240 \r
13241 char *\r
13242 TimeString(ms)\r
13243      long ms;\r
13244 {\r
13245     long second, minute, hour, day;\r
13246     char *sign = "";\r
13247     static char buf[32];\r
13248     \r
13249     if (ms > 0 && ms <= 9900) {\r
13250       /* convert milliseconds to tenths, rounding up */\r
13251       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
13252 \r
13253       sprintf(buf, " %03.1f ", tenths/10.0);\r
13254       return buf;\r
13255     }\r
13256 \r
13257     /* convert milliseconds to seconds, rounding up */\r
13258     /* use floating point to avoid strangeness of integer division\r
13259        with negative dividends on many machines */\r
13260     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
13261 \r
13262     if (second < 0) {\r
13263         sign = "-";\r
13264         second = -second;\r
13265     }\r
13266     \r
13267     day = second / (60 * 60 * 24);\r
13268     second = second % (60 * 60 * 24);\r
13269     hour = second / (60 * 60);\r
13270     second = second % (60 * 60);\r
13271     minute = second / 60;\r
13272     second = second % 60;\r
13273     \r
13274     if (day > 0)\r
13275       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
13276               sign, day, hour, minute, second);\r
13277     else if (hour > 0)\r
13278       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
13279     else\r
13280       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
13281     \r
13282     return buf;\r
13283 }\r
13284 \r
13285 \r
13286 /*\r
13287  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
13288  */\r
13289 char *\r
13290 StrStr(string, match)\r
13291      char *string, *match;\r
13292 {\r
13293     int i, length;\r
13294     \r
13295     length = strlen(match);\r
13296     \r
13297     for (i = strlen(string) - length; i >= 0; i--, string++)\r
13298       if (!strncmp(match, string, length))\r
13299         return string;\r
13300     \r
13301     return NULL;\r
13302 }\r
13303 \r
13304 char *\r
13305 StrCaseStr(string, match)\r
13306      char *string, *match;\r
13307 {\r
13308     int i, j, length;\r
13309     \r
13310     length = strlen(match);\r
13311     \r
13312     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
13313         for (j = 0; j < length; j++) {\r
13314             if (ToLower(match[j]) != ToLower(string[j]))\r
13315               break;\r
13316         }\r
13317         if (j == length) return string;\r
13318     }\r
13319 \r
13320     return NULL;\r
13321 }\r
13322 \r
13323 #ifndef _amigados\r
13324 int\r
13325 StrCaseCmp(s1, s2)\r
13326      char *s1, *s2;\r
13327 {\r
13328     char c1, c2;\r
13329     \r
13330     for (;;) {\r
13331         c1 = ToLower(*s1++);\r
13332         c2 = ToLower(*s2++);\r
13333         if (c1 > c2) return 1;\r
13334         if (c1 < c2) return -1;\r
13335         if (c1 == NULLCHAR) return 0;\r
13336     }\r
13337 }\r
13338 \r
13339 \r
13340 int\r
13341 ToLower(c)\r
13342      int c;\r
13343 {\r
13344     return isupper(c) ? tolower(c) : c;\r
13345 }\r
13346 \r
13347 \r
13348 int\r
13349 ToUpper(c)\r
13350      int c;\r
13351 {\r
13352     return islower(c) ? toupper(c) : c;\r
13353 }\r
13354 #endif /* !_amigados    */\r
13355 \r
13356 char *\r
13357 StrSave(s)\r
13358      char *s;\r
13359 {\r
13360     char *ret;\r
13361 \r
13362     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
13363         strcpy(ret, s);\r
13364     }\r
13365     return ret;\r
13366 }\r
13367 \r
13368 char *\r
13369 StrSavePtr(s, savePtr)\r
13370      char *s, **savePtr;\r
13371 {\r
13372     if (*savePtr) {\r
13373         free(*savePtr);\r
13374     }\r
13375     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
13376         strcpy(*savePtr, s);\r
13377     }\r
13378     return(*savePtr);\r
13379 }\r
13380 \r
13381 char *\r
13382 PGNDate()\r
13383 {\r
13384     time_t clock;\r
13385     struct tm *tm;\r
13386     char buf[MSG_SIZ];\r
13387 \r
13388     clock = time((time_t *)NULL);\r
13389     tm = localtime(&clock);\r
13390     sprintf(buf, "%04d.%02d.%02d",\r
13391             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
13392     return StrSave(buf);\r
13393 }\r
13394 \r
13395 \r
13396 char *\r
13397 PositionToFEN(move, useFEN960)\r
13398      int move;\r
13399      int useFEN960;\r
13400 {\r
13401     int i, j, fromX, fromY, toX, toY;\r
13402     int whiteToPlay;\r
13403     char buf[128];\r
13404     char *p, *q;\r
13405     int emptycount;\r
13406     ChessSquare piece;\r
13407 \r
13408     whiteToPlay = (gameMode == EditPosition) ?\r
13409       !blackPlaysFirst : (move % 2 == 0);\r
13410     p = buf;\r
13411 \r
13412     /* Piece placement data */\r
13413     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13414         emptycount = 0;\r
13415         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
13416             if (boards[move][i][j] == EmptySquare) {\r
13417                 emptycount++;\r
13418             } else { ChessSquare piece = boards[move][i][j];\r
13419                 if (emptycount > 0) {\r
13420                     if(emptycount<10) /* [HGM] can be >= 10 */\r
13421                         *p++ = '0' + emptycount;\r
13422                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13423                     emptycount = 0;\r
13424                 }\r
13425                 if(PieceToChar(piece) == '+') {\r
13426                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
13427                     *p++ = '+';\r
13428                     piece = (ChessSquare)(DEMOTED piece);\r
13429                 } \r
13430                 *p++ = PieceToChar(piece);\r
13431                 if(p[-1] == '~') {\r
13432                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
13433                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
13434                     *p++ = '~';\r
13435                 }\r
13436             }\r
13437         }\r
13438         if (emptycount > 0) {\r
13439             if(emptycount<10) /* [HGM] can be >= 10 */\r
13440                 *p++ = '0' + emptycount;\r
13441             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13442             emptycount = 0;\r
13443         }\r
13444         *p++ = '/';\r
13445     }\r
13446     *(p - 1) = ' ';\r
13447 \r
13448     /* [HGM] print Crazyhouse or Shogi holdings */\r
13449     if( gameInfo.holdingsWidth ) {\r
13450         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
13451         q = p;\r
13452         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
13453             piece = boards[move][i][BOARD_WIDTH-1];\r
13454             if( piece != EmptySquare )\r
13455               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
13456                   *p++ = PieceToChar(piece);\r
13457         }\r
13458         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
13459             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
13460             if( piece != EmptySquare )\r
13461               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
13462                   *p++ = PieceToChar(piece);\r
13463         }\r
13464 \r
13465         if( q == p ) *p++ = '-';\r
13466         *p++ = ']';\r
13467         *p++ = ' ';\r
13468     }\r
13469 \r
13470     /* Active color */\r
13471     *p++ = whiteToPlay ? 'w' : 'b';\r
13472     *p++ = ' ';\r
13473 \r
13474   if(nrCastlingRights) {\r
13475      q = p;\r
13476      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
13477        /* [HGM] write directly from rights */\r
13478            if(castlingRights[move][2] >= 0 &&\r
13479               castlingRights[move][0] >= 0   )\r
13480                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
13481            if(castlingRights[move][2] >= 0 &&\r
13482               castlingRights[move][1] >= 0   )\r
13483                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
13484            if(castlingRights[move][5] >= 0 &&\r
13485               castlingRights[move][3] >= 0   )\r
13486                 *p++ = castlingRights[move][3] + AAA;\r
13487            if(castlingRights[move][5] >= 0 &&\r
13488               castlingRights[move][4] >= 0   )\r
13489                 *p++ = castlingRights[move][4] + AAA;\r
13490      } else {\r
13491 \r
13492         /* [HGM] write true castling rights */\r
13493         if( nrCastlingRights == 6 ) {\r
13494             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
13495                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
13496             if(castlingRights[move][1] == BOARD_LEFT &&\r
13497                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
13498             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
13499                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
13500             if(castlingRights[move][4] == BOARD_LEFT &&\r
13501                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
13502         }\r
13503      }\r
13504      if (q == p) *p++ = '-'; /* No castling rights */\r
13505      *p++ = ' ';\r
13506   }\r
13507 \r
13508   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13509      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13510     /* En passant target square */\r
13511     if (move > backwardMostMove) {\r
13512         fromX = moveList[move - 1][0] - AAA;\r
13513         fromY = moveList[move - 1][1] - ONE;\r
13514         toX = moveList[move - 1][2] - AAA;\r
13515         toY = moveList[move - 1][3] - ONE;\r
13516         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
13517             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
13518             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
13519             fromX == toX) {\r
13520             /* 2-square pawn move just happened */\r
13521             *p++ = toX + AAA;\r
13522             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
13523         } else {\r
13524             *p++ = '-';\r
13525         }\r
13526     } else {\r
13527         *p++ = '-';\r
13528     }\r
13529     *p++ = ' ';\r
13530   }\r
13531 \r
13532     /* [HGM] find reversible plies */\r
13533     {   int i = 0, j=move;\r
13534 \r
13535         if (appData.debugMode) { int k;\r
13536             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
13537             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
13538                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
13539 \r
13540         }\r
13541 \r
13542         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
13543         if( j == backwardMostMove ) i += initialRulePlies;\r
13544         sprintf(p, "%d ", i);\r
13545         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
13546     }\r
13547     /* Fullmove number */\r
13548     sprintf(p, "%d", (move / 2) + 1);\r
13549     \r
13550     return StrSave(buf);\r
13551 }\r
13552 \r
13553 Boolean\r
13554 ParseFEN(board, blackPlaysFirst, fen)\r
13555     Board board;\r
13556      int *blackPlaysFirst;\r
13557      char *fen;\r
13558 {\r
13559     int i, j;\r
13560     char *p;\r
13561     int emptycount;\r
13562     ChessSquare piece;\r
13563 \r
13564     p = fen;\r
13565 \r
13566     /* [HGM] by default clear Crazyhouse holdings, if present */\r
13567     if(gameInfo.holdingsWidth) {\r
13568        for(i=0; i<BOARD_HEIGHT; i++) {\r
13569            board[i][0]             = EmptySquare; /* black holdings */\r
13570            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
13571            board[i][1]             = (ChessSquare) 0; /* black counts */\r
13572            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
13573        }\r
13574     }\r
13575 \r
13576     /* Piece placement data */\r
13577     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13578         j = 0;\r
13579         for (;;) {\r
13580             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
13581                 if (*p == '/') p++;\r
13582                 emptycount = gameInfo.boardWidth - j;\r
13583                 while (emptycount--)\r
13584                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13585                 break;\r
13586 #if(BOARD_SIZE >= 10)\r
13587             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
13588                 p++; emptycount=10;\r
13589                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13590                 while (emptycount--)\r
13591                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13592 #endif\r
13593             } else if (isdigit(*p)) {\r
13594                 emptycount = *p++ - '0';\r
13595                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
13596                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13597                 while (emptycount--)\r
13598                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13599             } else if (*p == '+' || isalpha(*p)) {\r
13600                 if (j >= gameInfo.boardWidth) return FALSE;\r
13601                 if(*p=='+') {\r
13602                     piece = CharToPiece(*++p);\r
13603                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
13604                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
13605                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
13606                 } else piece = CharToPiece(*p++);\r
13607 \r
13608                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
13609                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
13610                     piece = (ChessSquare) (PROMOTED piece);\r
13611                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
13612                     p++;\r
13613                 }\r
13614                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
13615             } else {\r
13616                 return FALSE;\r
13617             }\r
13618         }\r
13619     }\r
13620     while (*p == '/' || *p == ' ') p++;\r
13621 \r
13622     /* [HGM] look for Crazyhouse holdings here */\r
13623     while(*p==' ') p++;\r
13624     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
13625         if(*p == '[') p++;\r
13626         if(*p == '-' ) *p++; /* empty holdings */ else {\r
13627             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
13628             /* if we would allow FEN reading to set board size, we would   */\r
13629             /* have to add holdings and shift the board read so far here   */\r
13630             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
13631                 *p++;\r
13632                 if((int) piece >= (int) BlackPawn ) {\r
13633                     i = (int)piece - (int)BlackPawn;\r
13634                     i = PieceToNumber((ChessSquare)i);\r
13635                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13636                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
13637                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
13638                 } else {\r
13639                     i = (int)piece - (int)WhitePawn;\r
13640                     i = PieceToNumber((ChessSquare)i);\r
13641                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13642                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
13643                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
13644                 }\r
13645             }\r
13646         }\r
13647         if(*p == ']') *p++;\r
13648     }\r
13649 \r
13650     while(*p == ' ') p++;\r
13651 \r
13652     /* Active color */\r
13653     switch (*p++) {\r
13654       case 'w':\r
13655         *blackPlaysFirst = FALSE;\r
13656         break;\r
13657       case 'b': \r
13658         *blackPlaysFirst = TRUE;\r
13659         break;\r
13660       default:\r
13661         return FALSE;\r
13662     }\r
13663 \r
13664     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
13665     /* return the extra info in global variiables             */\r
13666 \r
13667     /* set defaults in case FEN is incomplete */\r
13668     FENepStatus = EP_UNKNOWN;\r
13669     for(i=0; i<nrCastlingRights; i++ ) {\r
13670         FENcastlingRights[i] =\r
13671             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
13672     }   /* assume possible unless obviously impossible */\r
13673     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
13674     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
13675     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
13676     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
13677     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
13678     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
13679     FENrulePlies = 0;\r
13680 \r
13681     while(*p==' ') p++;\r
13682     if(nrCastlingRights) {\r
13683       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
13684           /* castling indicator present, so default becomes no castlings */\r
13685           for(i=0; i<nrCastlingRights; i++ ) {\r
13686                  FENcastlingRights[i] = -1;\r
13687           }\r
13688       }\r
13689       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
13690              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
13691              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
13692              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
13693         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
13694 \r
13695         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
13696             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
13697             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
13698         }\r
13699         switch(c) {\r
13700           case'K':\r
13701               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
13702               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
13703               FENcastlingRights[2] = whiteKingFile;\r
13704               break;\r
13705           case'Q':\r
13706               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
13707               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
13708               FENcastlingRights[2] = whiteKingFile;\r
13709               break;\r
13710           case'k':\r
13711               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
13712               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
13713               FENcastlingRights[5] = blackKingFile;\r
13714               break;\r
13715           case'q':\r
13716               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
13717               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
13718               FENcastlingRights[5] = blackKingFile;\r
13719           case '-':\r
13720               break;\r
13721           default: /* FRC castlings */\r
13722               if(c >= 'a') { /* black rights */\r
13723                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13724                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
13725                   if(i == BOARD_RGHT) break;\r
13726                   FENcastlingRights[5] = i;\r
13727                   c -= AAA;\r
13728                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
13729                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
13730                   if(c > i)\r
13731                       FENcastlingRights[3] = c;\r
13732                   else\r
13733                       FENcastlingRights[4] = c;\r
13734               } else { /* white rights */\r
13735                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13736                     if(board[0][i] == WhiteKing) break;\r
13737                   if(i == BOARD_RGHT) break;\r
13738                   FENcastlingRights[2] = i;\r
13739                   c -= AAA - 'a' + 'A';\r
13740                   if(board[0][c] >= WhiteKing) break;\r
13741                   if(c > i)\r
13742                       FENcastlingRights[0] = c;\r
13743                   else\r
13744                       FENcastlingRights[1] = c;\r
13745               }\r
13746         }\r
13747       }\r
13748     if (appData.debugMode) {\r
13749         fprintf(debugFP, "FEN castling rights:");\r
13750         for(i=0; i<nrCastlingRights; i++)\r
13751         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
13752         fprintf(debugFP, "\n");\r
13753     }\r
13754 \r
13755       while(*p==' ') p++;\r
13756     }\r
13757 \r
13758     /* read e.p. field in games that know e.p. capture */\r
13759     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13760        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13761       if(*p=='-') {\r
13762         p++; FENepStatus = EP_NONE;\r
13763       } else {\r
13764          char c = *p++ - AAA;\r
13765 \r
13766          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
13767          if(*p >= '0' && *p <='9') *p++;\r
13768          FENepStatus = c;\r
13769       }\r
13770     }\r
13771 \r
13772 \r
13773     if(sscanf(p, "%d", &i) == 1) {\r
13774         FENrulePlies = i; /* 50-move ply counter */\r
13775         /* (The move number is still ignored)    */\r
13776     }\r
13777 \r
13778     return TRUE;\r
13779 }\r
13780       \r
13781 void\r
13782 EditPositionPasteFEN(char *fen)\r
13783 {\r
13784   if (fen != NULL) {\r
13785     Board initial_position;\r
13786 \r
13787     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
13788       DisplayError(_("Bad FEN position in clipboard"), 0);\r
13789       return ;\r
13790     } else {\r
13791       int savedBlackPlaysFirst = blackPlaysFirst;\r
13792       EditPositionEvent();\r
13793       blackPlaysFirst = savedBlackPlaysFirst;\r
13794       CopyBoard(boards[0], initial_position);\r
13795           /* [HGM] copy FEN attributes as well */\r
13796           {   int i;\r
13797               initialRulePlies = FENrulePlies;\r
13798               epStatus[0] = FENepStatus;\r
13799               for( i=0; i<nrCastlingRights; i++ )\r
13800                   castlingRights[0][i] = FENcastlingRights[i];\r
13801           }\r
13802       EditPositionDone();\r
13803       DisplayBothClocks();\r
13804       DrawPosition(FALSE, boards[currentMove]);\r
13805     }\r
13806   }\r
13807 }\r