fixed castling bug in variant twokings.
[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     break;\r
355   case VariantAtomic:\r
356     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;\r
357     break;\r
358   case VariantKriegspiel:\r
359     flags |= F_KRIEGSPIEL_CAPTURE;\r
360     break;\r
361   case VariantCapaRandom: \r
362   case VariantFischeRandom:\r
363     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */\r
364   case VariantNoCastle:\r
365   case VariantShatranj:\r
366   case VariantCourier:\r
367     flags &= ~F_ALL_CASTLE_OK;\r
368     break;\r
369   default:\r
370     break;\r
371   }\r
372   return flags;\r
373 }\r
374 \r
375 FILE *gameFileFP, *debugFP;\r
376 \r
377 /* \r
378     [AS] Note: sometimes, the sscanf() function is used to parse the input\r
379     into a fixed-size buffer. Because of this, we must be prepared to\r
380     receive strings as long as the size of the input buffer, which is currently\r
381     set to 4K for Windows and 8K for the rest.\r
382     So, we must either allocate sufficiently large buffers here, or\r
383     reduce the size of the input buffer in the input reading part.\r
384 */\r
385 \r
386 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];\r
387 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];\r
388 char thinkOutput1[MSG_SIZ*10];\r
389 \r
390 ChessProgramState first, second;\r
391 \r
392 /* premove variables */\r
393 int premoveToX = 0;\r
394 int premoveToY = 0;\r
395 int premoveFromX = 0;\r
396 int premoveFromY = 0;\r
397 int premovePromoChar = 0;\r
398 int gotPremove = 0;\r
399 Boolean alarmSounded;\r
400 /* end premove variables */\r
401 \r
402 char *ics_prefix = "$";\r
403 int ics_type = ICS_GENERIC;\r
404 \r
405 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;\r
406 int pauseExamForwardMostMove = 0;\r
407 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;\r
408 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];\r
409 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;\r
410 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;\r
411 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;\r
412 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;\r
413 int whiteFlag = FALSE, blackFlag = FALSE;\r
414 int userOfferedDraw = FALSE;\r
415 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;\r
416 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;\r
417 int cmailMoveType[CMAIL_MAX_GAMES];\r
418 long ics_clock_paused = 0;\r
419 ProcRef icsPR = NoProc, cmailPR = NoProc;\r
420 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;\r
421 GameMode gameMode = BeginningOfGame;\r
422 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];\r
423 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];\r
424 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */\r
425 int hiddenThinkOutputState = 0; /* [AS] */\r
426 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */\r
427 int adjudicateLossPlies = 6;\r
428 char white_holding[64], black_holding[64];\r
429 TimeMark lastNodeCountTime;\r
430 long lastNodeCount=0;\r
431 int have_sent_ICS_logon = 0;\r
432 int movesPerSession;\r
433 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;\r
434 long timeControl_2; /* [AS] Allow separate time controls */\r
435 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */\r
436 long timeRemaining[2][MAX_MOVES];\r
437 int matchGame = 0;\r
438 TimeMark programStartTime;\r
439 char ics_handle[MSG_SIZ];\r
440 int have_set_title = 0;\r
441 \r
442 /* animateTraining preserves the state of appData.animate\r
443  * when Training mode is activated. This allows the\r
444  * response to be animated when appData.animate == TRUE and\r
445  * appData.animateDragging == TRUE.\r
446  */\r
447 Boolean animateTraining;\r
448 \r
449 GameInfo gameInfo;\r
450 \r
451 AppData appData;\r
452 \r
453 Board boards[MAX_MOVES];\r
454 /* [HGM] Following 7 needed for accurate legality tests: */\r
455 char  epStatus[MAX_MOVES];\r
456 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1\r
457 char  castlingRank[BOARD_SIZE]; // and corresponding ranks\r
458 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];\r
459 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status\r
460 int   initialRulePlies, FENrulePlies;\r
461 char  FENepStatus;\r
462 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)\r
463 int loadFlag = 0; \r
464 int shuffleOpenings;\r
465 \r
466 ChessSquare  FIDEArray[2][BOARD_SIZE] = {\r
467     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
468         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
469     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
470         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
471 };\r
472 \r
473 ChessSquare twoKingsArray[2][BOARD_SIZE] = {\r
474     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
475         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },\r
476     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
477         BlackKing, BlackKing, BlackKnight, BlackRook }\r
478 };\r
479 \r
480 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {\r
481     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,\r
482         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },\r
483     { BlackRook, BlackMan, BlackBishop, BlackQueen,\r
484         BlackUnicorn, BlackBishop, BlackMan, BlackRook }\r
485 };\r
486 \r
487 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */\r
488     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,\r
489         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
490     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,\r
491         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
492 };\r
493 \r
494 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */\r
495     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,\r
496         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
497     { BlackRook, BlackKnight, BlackAlfil, BlackKing,\r
498         BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
499 };\r
500 \r
501 \r
502 #if (BOARD_SIZE>=10)\r
503 ChessSquare ShogiArray[2][BOARD_SIZE] = {\r
504     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,\r
505         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },\r
506     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,\r
507         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }\r
508 };\r
509 \r
510 ChessSquare XiangqiArray[2][BOARD_SIZE] = {\r
511     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,\r
512         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
513     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,\r
514         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
515 };\r
516 \r
517 ChessSquare CapablancaArray[2][BOARD_SIZE] = {\r
518     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, \r
519         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },\r
520     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, \r
521         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
522 };\r
523 \r
524 ChessSquare GreatArray[2][BOARD_SIZE] = {\r
525     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, \r
526         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },\r
527     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, \r
528         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },\r
529 };\r
530 \r
531 ChessSquare JanusArray[2][BOARD_SIZE] = {\r
532     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, \r
533         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },\r
534     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, \r
535         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }\r
536 };\r
537 \r
538 #ifdef GOTHIC\r
539 ChessSquare GothicArray[2][BOARD_SIZE] = {\r
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, \r
541         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },\r
542     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, \r
543         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }\r
544 };\r
545 #else // !GOTHIC\r
546 #define GothicArray CapablancaArray\r
547 #endif // !GOTHIC\r
548 \r
549 #ifdef FALCON\r
550 ChessSquare FalconArray[2][BOARD_SIZE] = {\r
551     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, \r
552         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },\r
553     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, \r
554         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }\r
555 };\r
556 #else // !FALCON\r
557 #define FalconArray CapablancaArray\r
558 #endif // !FALCON\r
559 \r
560 #else // !(BOARD_SIZE>=10)\r
561 #define XiangqiPosition FIDEArray\r
562 #define CapablancaArray FIDEArray\r
563 #define GothicArray FIDEArray\r
564 #define GreatArray FIDEArray\r
565 #endif // !(BOARD_SIZE>=10)\r
566 \r
567 #if (BOARD_SIZE>=12)\r
568 ChessSquare CourierArray[2][BOARD_SIZE] = {\r
569     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,\r
570         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },\r
571     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,\r
572         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }\r
573 };\r
574 #else // !(BOARD_SIZE>=12)\r
575 #define CourierArray CapablancaArray\r
576 #endif // !(BOARD_SIZE>=12)\r
577 \r
578 \r
579 Board initialPosition;\r
580 \r
581 \r
582 /* Convert str to a rating. Checks for special cases of "----",\r
583 \r
584    "++++", etc. Also strips ()'s */\r
585 int\r
586 string_to_rating(str)\r
587   char *str;\r
588 {\r
589   while(*str && !isdigit(*str)) ++str;\r
590   if (!*str)\r
591     return 0;   /* One of the special "no rating" cases */\r
592   else\r
593     return atoi(str);\r
594 }\r
595 \r
596 void\r
597 ClearProgramStats()\r
598 {\r
599     /* Init programStats */\r
600     programStats.movelist[0] = 0;\r
601     programStats.depth = 0;\r
602     programStats.nr_moves = 0;\r
603     programStats.moves_left = 0;\r
604     programStats.nodes = 0;\r
605     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output\r
606     programStats.score = 0;\r
607     programStats.got_only_move = 0;\r
608     programStats.got_fail = 0;\r
609     programStats.line_is_book = 0;\r
610 }\r
611 \r
612 void\r
613 InitBackEnd1()\r
614 {\r
615     int matched, min, sec;\r
616 \r
617     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options\r
618 \r
619     GetTimeMark(&programStartTime);\r
620 \r
621     ClearProgramStats();\r
622     programStats.ok_to_send = 1;\r
623     programStats.seen_stat = 0;\r
624 \r
625     /*\r
626      * Initialize game list\r
627      */\r
628     ListNew(&gameList);\r
629 \r
630 \r
631     /*\r
632      * Internet chess server status\r
633      */\r
634     if (appData.icsActive) {\r
635         appData.matchMode = FALSE;\r
636         appData.matchGames = 0;\r
637 #if ZIPPY       \r
638         appData.noChessProgram = !appData.zippyPlay;\r
639 #else\r
640         appData.zippyPlay = FALSE;\r
641         appData.zippyTalk = FALSE;\r
642         appData.noChessProgram = TRUE;\r
643 #endif\r
644         if (*appData.icsHelper != NULLCHAR) {\r
645             appData.useTelnet = TRUE;\r
646             appData.telnetProgram = appData.icsHelper;\r
647         }\r
648     } else {\r
649         appData.zippyTalk = appData.zippyPlay = FALSE;\r
650     }\r
651 \r
652     /* [AS] Initialize pv info list [HGM] and game state */\r
653     {\r
654         int i, j;\r
655 \r
656         for( i=0; i<MAX_MOVES; i++ ) {\r
657             pvInfoList[i].depth = -1;\r
658             epStatus[i]=EP_NONE;\r
659             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
660         }\r
661     }\r
662 \r
663     /*\r
664      * Parse timeControl resource\r
665      */\r
666     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,\r
667                           appData.movesPerSession)) {\r
668         char buf[MSG_SIZ];\r
669         sprintf(buf, _("bad timeControl option %s"), appData.timeControl);\r
670         DisplayFatalError(buf, 0, 2);\r
671     }\r
672 \r
673     /*\r
674      * Parse searchTime resource\r
675      */\r
676     if (*appData.searchTime != NULLCHAR) {\r
677         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);\r
678         if (matched == 1) {\r
679             searchTime = min * 60;\r
680         } else if (matched == 2) {\r
681             searchTime = min * 60 + sec;\r
682         } else {\r
683             char buf[MSG_SIZ];\r
684             sprintf(buf, _("bad searchTime option %s"), appData.searchTime);\r
685             DisplayFatalError(buf, 0, 2);\r
686         }\r
687     }\r
688 \r
689     /* [AS] Adjudication threshold */\r
690     adjudicateLossThreshold = appData.adjudicateLossThreshold;\r
691     \r
692     first.which = "first";\r
693     second.which = "second";\r
694     first.maybeThinking = second.maybeThinking = FALSE;\r
695     first.pr = second.pr = NoProc;\r
696     first.isr = second.isr = NULL;\r
697     first.sendTime = second.sendTime = 2;\r
698     first.sendDrawOffers = 1;\r
699     if (appData.firstPlaysBlack) {\r
700         first.twoMachinesColor = "black\n";\r
701         second.twoMachinesColor = "white\n";\r
702     } else {\r
703         first.twoMachinesColor = "white\n";\r
704         second.twoMachinesColor = "black\n";\r
705     }\r
706     first.program = appData.firstChessProgram;\r
707     second.program = appData.secondChessProgram;\r
708     first.host = appData.firstHost;\r
709     second.host = appData.secondHost;\r
710     first.dir = appData.firstDirectory;\r
711     second.dir = appData.secondDirectory;\r
712     first.other = &second;\r
713     second.other = &first;\r
714     first.initString = appData.initString;\r
715     second.initString = appData.secondInitString;\r
716     first.computerString = appData.firstComputerString;\r
717     second.computerString = appData.secondComputerString;\r
718     first.useSigint = second.useSigint = TRUE;\r
719     first.useSigterm = second.useSigterm = TRUE;\r
720     first.reuse = appData.reuseFirst;\r
721     second.reuse = appData.reuseSecond;\r
722     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second\r
723     second.nps = appData.secondNPS;\r
724     first.useSetboard = second.useSetboard = FALSE;\r
725     first.useSAN = second.useSAN = FALSE;\r
726     first.usePing = second.usePing = FALSE;\r
727     first.lastPing = second.lastPing = 0;\r
728     first.lastPong = second.lastPong = 0;\r
729     first.usePlayother = second.usePlayother = FALSE;\r
730     first.useColors = second.useColors = TRUE;\r
731     first.useUsermove = second.useUsermove = FALSE;\r
732     first.sendICS = second.sendICS = FALSE;\r
733     first.sendName = second.sendName = appData.icsActive;\r
734     first.sdKludge = second.sdKludge = FALSE;\r
735     first.stKludge = second.stKludge = FALSE;\r
736     TidyProgramName(first.program, first.host, first.tidy);\r
737     TidyProgramName(second.program, second.host, second.tidy);\r
738     first.matchWins = second.matchWins = 0;\r
739     strcpy(first.variants, appData.variant);\r
740     strcpy(second.variants, appData.variant);\r
741     first.analysisSupport = second.analysisSupport = 2; /* detect */\r
742     first.analyzing = second.analyzing = FALSE;\r
743     first.initDone = second.initDone = FALSE;\r
744 \r
745     /* New features added by Tord: */\r
746     first.useFEN960 = FALSE; second.useFEN960 = FALSE;\r
747     first.useOOCastle = TRUE; second.useOOCastle = TRUE;\r
748     /* End of new features added by Tord. */\r
749 \r
750     /* [HGM] time odds: set factor for each machine */\r
751     first.timeOdds  = appData.firstTimeOdds;\r
752     second.timeOdds = appData.secondTimeOdds;\r
753     { int norm = 1;\r
754         if(appData.timeOddsMode) {\r
755             norm = first.timeOdds;\r
756             if(norm > second.timeOdds) norm = second.timeOdds;\r
757         }\r
758         first.timeOdds /= norm;\r
759         second.timeOdds /= norm;\r
760     }\r
761 \r
762     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/\r
763     first.accumulateTC = appData.firstAccumulateTC;\r
764     second.accumulateTC = appData.secondAccumulateTC;\r
765     first.maxNrOfSessions = second.maxNrOfSessions = 1;\r
766 \r
767     /* [HGM] debug */\r
768     first.debug = second.debug = FALSE;\r
769     first.supportsNPS = second.supportsNPS = UNKNOWN;\r
770 \r
771     /* [HGM] options */\r
772     first.optionSettings  = appData.firstOptions;\r
773     second.optionSettings = appData.secondOptions;\r
774 \r
775     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
776     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
777     first.isUCI = appData.firstIsUCI; /* [AS] */\r
778     second.isUCI = appData.secondIsUCI; /* [AS] */\r
779     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
780     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
781 \r
782     if (appData.firstProtocolVersion > PROTOVER ||\r
783         appData.firstProtocolVersion < 1) {\r
784       char buf[MSG_SIZ];\r
785       sprintf(buf, _("protocol version %d not supported"),\r
786               appData.firstProtocolVersion);\r
787       DisplayFatalError(buf, 0, 2);\r
788     } else {\r
789       first.protocolVersion = appData.firstProtocolVersion;\r
790     }\r
791 \r
792     if (appData.secondProtocolVersion > PROTOVER ||\r
793         appData.secondProtocolVersion < 1) {\r
794       char buf[MSG_SIZ];\r
795       sprintf(buf, _("protocol version %d not supported"),\r
796               appData.secondProtocolVersion);\r
797       DisplayFatalError(buf, 0, 2);\r
798     } else {\r
799       second.protocolVersion = appData.secondProtocolVersion;\r
800     }\r
801 \r
802     if (appData.icsActive) {\r
803         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */\r
804     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
805         appData.clockMode = FALSE;\r
806         first.sendTime = second.sendTime = 0;\r
807     }\r
808     \r
809 #if ZIPPY\r
810     /* Override some settings from environment variables, for backward\r
811        compatibility.  Unfortunately it's not feasible to have the env\r
812        vars just set defaults, at least in xboard.  Ugh.\r
813     */\r
814     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
815       ZippyInit();\r
816     }\r
817 #endif\r
818     \r
819     if (appData.noChessProgram) {\r
820         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
821                                         + strlen(PATCHLEVEL));\r
822         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
823     } else {\r
824 #if 0\r
825         char *p, *q;\r
826         q = first.program;\r
827         while (*q != ' ' && *q != NULLCHAR) q++;\r
828         p = q;\r
829         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */\r
830         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
831                                         + strlen(PATCHLEVEL) + (q - p));\r
832         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
833         strncat(programVersion, p, q - p);\r
834 #else\r
835         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */\r
836         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
837                                         + strlen(PATCHLEVEL) + strlen(first.tidy));\r
838         sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);\r
839 #endif\r
840     }\r
841 \r
842     if (!appData.icsActive) {\r
843       char buf[MSG_SIZ];\r
844       /* Check for variants that are supported only in ICS mode,\r
845          or not at all.  Some that are accepted here nevertheless\r
846          have bugs; see comments below.\r
847       */\r
848       VariantClass variant = StringToVariant(appData.variant);\r
849       switch (variant) {\r
850       case VariantBughouse:     /* need four players and two boards */\r
851       case VariantKriegspiel:   /* need to hide pieces and move details */\r
852       /* case VariantFischeRandom: (Fabien: moved below) */\r
853         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);\r
854         DisplayFatalError(buf, 0, 2);\r
855         return;\r
856 \r
857       case VariantUnknown:\r
858       case VariantLoadable:\r
859       case Variant29:\r
860       case Variant30:\r
861       case Variant31:\r
862       case Variant32:\r
863       case Variant33:\r
864       case Variant34:\r
865       case Variant35:\r
866       case Variant36:\r
867       default:\r
868         sprintf(buf, _("Unknown variant name %s"), appData.variant);\r
869         DisplayFatalError(buf, 0, 2);\r
870         return;\r
871 \r
872       case VariantXiangqi:    /* [HGM] repetition rules not implemented */\r
873       case VariantFairy:      /* [HGM] TestLegality definitely off! */\r
874       case VariantGothic:     /* [HGM] should work */\r
875       case VariantCapablanca: /* [HGM] should work */\r
876       case VariantCourier:    /* [HGM] initial forced moves not implemented */\r
877       case VariantShogi:      /* [HGM] drops not tested for legality */\r
878       case VariantKnightmate: /* [HGM] should work */\r
879       case VariantCylinder:   /* [HGM] untested */\r
880       case VariantFalcon:     /* [HGM] untested */\r
881       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)\r
882                                  offboard interposition not understood */\r
883       case VariantNormal:     /* definitely works! */\r
884       case VariantWildCastle: /* pieces not automatically shuffled */\r
885       case VariantNoCastle:   /* pieces not automatically shuffled */\r
886       case VariantFischeRandom: /* [HGM] works and shuffles pieces */\r
887       case VariantLosers:     /* should work except for win condition,\r
888                                  and doesn't know captures are mandatory */\r
889       case VariantSuicide:    /* should work except for win condition,\r
890                                  and doesn't know captures are mandatory */\r
891       case VariantGiveaway:   /* should work except for win condition,\r
892                                  and doesn't know captures are mandatory */\r
893       case VariantTwoKings:   /* should work */\r
894       case VariantAtomic:     /* should work except for win condition */\r
895       case Variant3Check:     /* should work except for win condition */\r
896       case VariantShatranj:   /* should work except for all win conditions */\r
897       case VariantBerolina:   /* might work if TestLegality is off */\r
898       case VariantCapaRandom: /* should work */\r
899       case VariantJanus:      /* should work */\r
900       case VariantSuper:      /* experimental */\r
901       case VariantGreat:      /* experimental, requires legality testing to be off */\r
902         break;\r
903       }\r
904     }\r
905 \r
906     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard\r
907     InitEngineUCI( installDir, &second );\r
908 }\r
909 \r
910 int NextIntegerFromString( char ** str, long * value )\r
911 {\r
912     int result = -1;\r
913     char * s = *str;\r
914 \r
915     while( *s == ' ' || *s == '\t' ) {\r
916         s++;\r
917     }\r
918 \r
919     *value = 0;\r
920 \r
921     if( *s >= '0' && *s <= '9' ) {\r
922         while( *s >= '0' && *s <= '9' ) {\r
923             *value = *value * 10 + (*s - '0');\r
924             s++;\r
925         }\r
926 \r
927         result = 0;\r
928     }\r
929 \r
930     *str = s;\r
931 \r
932     return result;\r
933 }\r
934 \r
935 int NextTimeControlFromString( char ** str, long * value )\r
936 {\r
937     long temp;\r
938     int result = NextIntegerFromString( str, &temp );\r
939 \r
940     if( result == 0 ) {\r
941         *value = temp * 60; /* Minutes */\r
942         if( **str == ':' ) {\r
943             (*str)++;\r
944             result = NextIntegerFromString( str, &temp );\r
945             *value += temp; /* Seconds */\r
946         }\r
947     }\r
948 \r
949     return result;\r
950 }\r
951 \r
952 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)\r
953 {   /* [HGM] routine added to read '+moves/time' for secondary time control */\r
954     int result = -1; long temp, temp2;\r
955 \r
956     if(**str != '+') return -1; // old params remain in force!\r
957     (*str)++;\r
958     if( NextTimeControlFromString( str, &temp ) ) return -1;\r
959 \r
960     if(**str != '/') {\r
961         /* time only: incremental or sudden-death time control */\r
962         if(**str == '+') { /* increment follows; read it */\r
963             (*str)++;\r
964             if(result = NextIntegerFromString( str, &temp2)) return -1;\r
965             *inc = temp2 * 1000;\r
966         } else *inc = 0;\r
967         *moves = 0; *tc = temp * 1000; \r
968         return 0;\r
969     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */\r
970 \r
971     (*str)++; /* classical time control */\r
972     result = NextTimeControlFromString( str, &temp2);\r
973     if(result == 0) {\r
974         *moves = temp/60;\r
975         *tc    = temp2 * 1000;\r
976         *inc   = 0;\r
977     }\r
978     return result;\r
979 }\r
980 \r
981 int GetTimeQuota(int movenr)\r
982 {   /* [HGM] get time to add from the multi-session time-control string */\r
983     int moves=1; /* kludge to force reading of first session */\r
984     long time, increment;\r
985     char *s = fullTimeControlString;\r
986 \r
987     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);\r
988     do {\r
989         if(moves) NextSessionFromString(&s, &moves, &time, &increment);\r
990         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);\r
991         if(movenr == -1) return time;    /* last move before new session     */\r
992         if(!moves) return increment;     /* current session is incremental   */\r
993         if(movenr >= 0) movenr -= moves; /* we already finished this session */\r
994     } while(movenr >= -1);               /* try again for next session       */\r
995 \r
996     return 0; // no new time quota on this move\r
997 }\r
998 \r
999 int\r
1000 ParseTimeControl(tc, ti, mps)\r
1001      char *tc;\r
1002      int ti;\r
1003      int mps;\r
1004 {\r
1005 #if 0\r
1006     int matched, min, sec;\r
1007 \r
1008     matched = sscanf(tc, "%d:%d", &min, &sec);\r
1009     if (matched == 1) {\r
1010         timeControl = min * 60 * 1000;\r
1011     } else if (matched == 2) {\r
1012         timeControl = (min * 60 + sec) * 1000;\r
1013     } else {\r
1014         return FALSE;\r
1015     }\r
1016 #else\r
1017     long tc1;\r
1018     long tc2;\r
1019     char buf[MSG_SIZ];\r
1020 \r
1021     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;\r
1022     if(ti > 0) {\r
1023         if(mps)\r
1024              sprintf(buf, "+%d/%s+%d", mps, tc, ti);\r
1025         else sprintf(buf, "+%s+%d", tc, ti);\r
1026     } else {\r
1027         if(mps)\r
1028              sprintf(buf, "+%d/%s", mps, tc);\r
1029         else sprintf(buf, "+%s", tc);\r
1030     }\r
1031     fullTimeControlString = StrSave(buf);\r
1032 \r
1033     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
1034         return FALSE;\r
1035     }\r
1036 \r
1037     if( *tc == '/' ) {\r
1038         /* Parse second time control */\r
1039         tc++;\r
1040 \r
1041         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
1042             return FALSE;\r
1043         }\r
1044 \r
1045         if( tc2 == 0 ) {\r
1046             return FALSE;\r
1047         }\r
1048 \r
1049         timeControl_2 = tc2 * 1000;\r
1050     }\r
1051     else {\r
1052         timeControl_2 = 0;\r
1053     }\r
1054 \r
1055     if( tc1 == 0 ) {\r
1056         return FALSE;\r
1057     }\r
1058 \r
1059     timeControl = tc1 * 1000;\r
1060 #endif\r
1061 \r
1062     if (ti >= 0) {\r
1063         timeIncrement = ti * 1000;  /* convert to ms */\r
1064         movesPerSession = 0;\r
1065     } else {\r
1066         timeIncrement = 0;\r
1067         movesPerSession = mps;\r
1068     }\r
1069     return TRUE;\r
1070 }\r
1071 \r
1072 void\r
1073 InitBackEnd2()\r
1074 {\r
1075     if (appData.debugMode) {\r
1076         fprintf(debugFP, "%s\n", programVersion);\r
1077     }\r
1078 \r
1079     if (appData.matchGames > 0) {\r
1080         appData.matchMode = TRUE;\r
1081     } else if (appData.matchMode) {\r
1082         appData.matchGames = 1;\r
1083     }\r
1084     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */\r
1085         appData.matchGames = appData.sameColorGames;\r
1086     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */\r
1087         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;\r
1088         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;\r
1089     }\r
1090     Reset(TRUE, FALSE);\r
1091     if (appData.noChessProgram || first.protocolVersion == 1) {\r
1092       InitBackEnd3();\r
1093     } else {\r
1094       /* kludge: allow timeout for initial "feature" commands */\r
1095       FreezeUI();\r
1096       DisplayMessage("", _("Starting chess program"));\r
1097       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
1098     }\r
1099 }\r
1100 \r
1101 void\r
1102 InitBackEnd3 P((void))\r
1103 {\r
1104     GameMode initialMode;\r
1105     char buf[MSG_SIZ];\r
1106     int err;\r
1107 \r
1108     InitChessProgram(&first, startedFromSetupPosition);\r
1109 \r
1110 \r
1111     if (appData.icsActive) {\r
1112 #ifdef WIN32\r
1113         /* [DM] Make a console window if needed [HGM] merged ifs */\r
1114         ConsoleCreate(); \r
1115 #endif\r
1116         err = establish();\r
1117         if (err != 0) {\r
1118             if (*appData.icsCommPort != NULLCHAR) {\r
1119                 sprintf(buf, _("Could not open comm port %s"),  \r
1120                         appData.icsCommPort);\r
1121             } else {\r
1122                 sprintf(buf, _("Could not connect to host %s, port %s"),  \r
1123                         appData.icsHost, appData.icsPort);\r
1124             }\r
1125             DisplayFatalError(buf, err, 1);\r
1126             return;\r
1127         }\r
1128         SetICSMode();\r
1129         telnetISR =\r
1130           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
1131         fromUserISR =\r
1132           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
1133     } else if (appData.noChessProgram) {\r
1134         SetNCPMode();\r
1135     } else {\r
1136         SetGNUMode();\r
1137     }\r
1138 \r
1139     if (*appData.cmailGameName != NULLCHAR) {\r
1140         SetCmailMode();\r
1141         OpenLoopback(&cmailPR);\r
1142         cmailISR =\r
1143           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
1144     }\r
1145     \r
1146     ThawUI();\r
1147     DisplayMessage("", "");\r
1148     if (StrCaseCmp(appData.initialMode, "") == 0) {\r
1149       initialMode = BeginningOfGame;\r
1150     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
1151       initialMode = TwoMachinesPlay;\r
1152     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
1153       initialMode = AnalyzeFile; \r
1154     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
1155       initialMode = AnalyzeMode;\r
1156     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
1157       initialMode = MachinePlaysWhite;\r
1158     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
1159       initialMode = MachinePlaysBlack;\r
1160     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
1161       initialMode = EditGame;\r
1162     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
1163       initialMode = EditPosition;\r
1164     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
1165       initialMode = Training;\r
1166     } else {\r
1167       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);\r
1168       DisplayFatalError(buf, 0, 2);\r
1169       return;\r
1170     }\r
1171 \r
1172     if (appData.matchMode) {\r
1173         /* Set up machine vs. machine match */\r
1174         if (appData.noChessProgram) {\r
1175             DisplayFatalError(_("Can't have a match with no chess programs"),\r
1176                               0, 2);\r
1177             return;\r
1178         }\r
1179         matchMode = TRUE;\r
1180         matchGame = 1;\r
1181         if (*appData.loadGameFile != NULLCHAR) {\r
1182             int index = appData.loadGameIndex; // [HGM] autoinc\r
1183             if(index<0) lastIndex = index = 1;\r
1184             if (!LoadGameFromFile(appData.loadGameFile,\r
1185                                   index,\r
1186                                   appData.loadGameFile, FALSE)) {\r
1187                 DisplayFatalError(_("Bad game file"), 0, 1);\r
1188                 return;\r
1189             }\r
1190         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1191             int index = appData.loadPositionIndex; // [HGM] autoinc\r
1192             if(index<0) lastIndex = index = 1;\r
1193             if (!LoadPositionFromFile(appData.loadPositionFile,\r
1194                                       index,\r
1195                                       appData.loadPositionFile)) {\r
1196                 DisplayFatalError(_("Bad position file"), 0, 1);\r
1197                 return;\r
1198             }\r
1199         }\r
1200         TwoMachinesEvent();\r
1201     } else if (*appData.cmailGameName != NULLCHAR) {\r
1202         /* Set up cmail mode */\r
1203         ReloadCmailMsgEvent(TRUE);\r
1204     } else {\r
1205         /* Set up other modes */\r
1206         if (initialMode == AnalyzeFile) {\r
1207           if (*appData.loadGameFile == NULLCHAR) {\r
1208             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);\r
1209             return;\r
1210           }\r
1211         }\r
1212         if (*appData.loadGameFile != NULLCHAR) {\r
1213             (void) LoadGameFromFile(appData.loadGameFile,\r
1214                                     appData.loadGameIndex,\r
1215                                     appData.loadGameFile, TRUE);\r
1216         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1217             (void) LoadPositionFromFile(appData.loadPositionFile,\r
1218                                         appData.loadPositionIndex,\r
1219                                         appData.loadPositionFile);\r
1220             /* [HGM] try to make self-starting even after FEN load */\r
1221             /* to allow automatic setup of fairy variants with wtm */\r
1222             if(initialMode == BeginningOfGame && !blackPlaysFirst) {\r
1223                 gameMode = BeginningOfGame;\r
1224                 setboardSpoiledMachineBlack = 1;\r
1225             }\r
1226             /* [HGM] loadPos: make that every new game uses the setup */\r
1227             /* from file as long as we do not switch variant          */\r
1228             if(!blackPlaysFirst) { int i;\r
1229                 startedFromPositionFile = TRUE;\r
1230                 CopyBoard(filePosition, boards[0]);\r
1231                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];\r
1232             }\r
1233         }\r
1234         if (initialMode == AnalyzeMode) {\r
1235           if (appData.noChessProgram) {\r
1236             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);\r
1237             return;\r
1238           }\r
1239           if (appData.icsActive) {\r
1240             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);\r
1241             return;\r
1242           }\r
1243           AnalyzeModeEvent();\r
1244         } else if (initialMode == AnalyzeFile) {\r
1245           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent\r
1246           ShowThinkingEvent();\r
1247           AnalyzeFileEvent();\r
1248           AnalysisPeriodicEvent(1);\r
1249         } else if (initialMode == MachinePlaysWhite) {\r
1250           if (appData.noChessProgram) {\r
1251             DisplayFatalError(_("MachineWhite mode requires a chess engine"),\r
1252                               0, 2);\r
1253             return;\r
1254           }\r
1255           if (appData.icsActive) {\r
1256             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),\r
1257                               0, 2);\r
1258             return;\r
1259           }\r
1260           MachineWhiteEvent();\r
1261         } else if (initialMode == MachinePlaysBlack) {\r
1262           if (appData.noChessProgram) {\r
1263             DisplayFatalError(_("MachineBlack mode requires a chess engine"),\r
1264                               0, 2);\r
1265             return;\r
1266           }\r
1267           if (appData.icsActive) {\r
1268             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),\r
1269                               0, 2);\r
1270             return;\r
1271           }\r
1272           MachineBlackEvent();\r
1273         } else if (initialMode == TwoMachinesPlay) {\r
1274           if (appData.noChessProgram) {\r
1275             DisplayFatalError(_("TwoMachines mode requires a chess engine"),\r
1276                               0, 2);\r
1277             return;\r
1278           }\r
1279           if (appData.icsActive) {\r
1280             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),\r
1281                               0, 2);\r
1282             return;\r
1283           }\r
1284           TwoMachinesEvent();\r
1285         } else if (initialMode == EditGame) {\r
1286           EditGameEvent();\r
1287         } else if (initialMode == EditPosition) {\r
1288           EditPositionEvent();\r
1289         } else if (initialMode == Training) {\r
1290           if (*appData.loadGameFile == NULLCHAR) {\r
1291             DisplayFatalError(_("Training mode requires a game file"), 0, 2);\r
1292             return;\r
1293           }\r
1294           TrainingEvent();\r
1295         }\r
1296     }\r
1297 }\r
1298 \r
1299 /*\r
1300  * Establish will establish a contact to a remote host.port.\r
1301  * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
1302  *  used to talk to the host.\r
1303  * Returns 0 if okay, error code if not.\r
1304  */\r
1305 int\r
1306 establish()\r
1307 {\r
1308     char buf[MSG_SIZ];\r
1309 \r
1310     if (*appData.icsCommPort != NULLCHAR) {\r
1311         /* Talk to the host through a serial comm port */\r
1312         return OpenCommPort(appData.icsCommPort, &icsPR);\r
1313 \r
1314     } else if (*appData.gateway != NULLCHAR) {\r
1315         if (*appData.remoteShell == NULLCHAR) {\r
1316             /* Use the rcmd protocol to run telnet program on a gateway host */\r
1317             sprintf(buf, "%s %s %s",\r
1318                     appData.telnetProgram, appData.icsHost, appData.icsPort);\r
1319             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
1320 \r
1321         } else {\r
1322             /* Use the rsh program to run telnet program on a gateway host */\r
1323             if (*appData.remoteUser == NULLCHAR) {\r
1324                 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,\r
1325                         appData.gateway, appData.telnetProgram,\r
1326                         appData.icsHost, appData.icsPort);\r
1327             } else {\r
1328                 sprintf(buf, "%s %s -l %s %s %s %s",\r
1329                         appData.remoteShell, appData.gateway, \r
1330                         appData.remoteUser, appData.telnetProgram,\r
1331                         appData.icsHost, appData.icsPort);\r
1332             }\r
1333             return StartChildProcess(buf, "", &icsPR);\r
1334 \r
1335         }\r
1336     } else if (appData.useTelnet) {\r
1337         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
1338 \r
1339     } else {\r
1340         /* TCP socket interface differs somewhat between\r
1341            Unix and NT; handle details in the front end.\r
1342            */\r
1343         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
1344     }\r
1345 }\r
1346 \r
1347 void\r
1348 show_bytes(fp, buf, count)\r
1349      FILE *fp;\r
1350      char *buf;\r
1351      int count;\r
1352 {\r
1353     while (count--) {\r
1354         if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
1355             fprintf(fp, "\\%03o", *buf & 0xff);\r
1356         } else {\r
1357             putc(*buf, fp);\r
1358         }\r
1359         buf++;\r
1360     }\r
1361     fflush(fp);\r
1362 }\r
1363 \r
1364 /* Returns an errno value */\r
1365 int\r
1366 OutputMaybeTelnet(pr, message, count, outError)\r
1367      ProcRef pr;\r
1368      char *message;\r
1369      int count;\r
1370      int *outError;\r
1371 {\r
1372     char buf[8192], *p, *q, *buflim;\r
1373     int left, newcount, outcount;\r
1374 \r
1375     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
1376         *appData.gateway != NULLCHAR) {\r
1377         if (appData.debugMode) {\r
1378             fprintf(debugFP, ">ICS: ");\r
1379             show_bytes(debugFP, message, count);\r
1380             fprintf(debugFP, "\n");\r
1381         }\r
1382         return OutputToProcess(pr, message, count, outError);\r
1383     }\r
1384 \r
1385     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
1386     p = message;\r
1387     q = buf;\r
1388     left = count;\r
1389     newcount = 0;\r
1390     while (left) {\r
1391         if (q >= buflim) {\r
1392             if (appData.debugMode) {\r
1393                 fprintf(debugFP, ">ICS: ");\r
1394                 show_bytes(debugFP, buf, newcount);\r
1395                 fprintf(debugFP, "\n");\r
1396             }\r
1397             outcount = OutputToProcess(pr, buf, newcount, outError);\r
1398             if (outcount < newcount) return -1; /* to be sure */\r
1399             q = buf;\r
1400             newcount = 0;\r
1401         }\r
1402         if (*p == '\n') {\r
1403             *q++ = '\r';\r
1404             newcount++;\r
1405         } else if (((unsigned char) *p) == TN_IAC) {\r
1406             *q++ = (char) TN_IAC;\r
1407             newcount ++;\r
1408         }\r
1409         *q++ = *p++;\r
1410         newcount++;\r
1411         left--;\r
1412     }\r
1413     if (appData.debugMode) {\r
1414         fprintf(debugFP, ">ICS: ");\r
1415         show_bytes(debugFP, buf, newcount);\r
1416         fprintf(debugFP, "\n");\r
1417     }\r
1418     outcount = OutputToProcess(pr, buf, newcount, outError);\r
1419     if (outcount < newcount) return -1; /* to be sure */\r
1420     return count;\r
1421 }\r
1422 \r
1423 void\r
1424 read_from_player(isr, closure, message, count, error)\r
1425      InputSourceRef isr;\r
1426      VOIDSTAR closure;\r
1427      char *message;\r
1428      int count;\r
1429      int error;\r
1430 {\r
1431     int outError, outCount;\r
1432     static int gotEof = 0;\r
1433 \r
1434     /* Pass data read from player on to ICS */\r
1435     if (count > 0) {\r
1436         gotEof = 0;\r
1437         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
1438         if (outCount < count) {\r
1439             DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1440         }\r
1441     } else if (count < 0) {\r
1442         RemoveInputSource(isr);\r
1443         DisplayFatalError(_("Error reading from keyboard"), error, 1);\r
1444     } else if (gotEof++ > 0) {\r
1445         RemoveInputSource(isr);\r
1446         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);\r
1447     }\r
1448 }\r
1449 \r
1450 void\r
1451 SendToICS(s)\r
1452      char *s;\r
1453 {\r
1454     int count, outCount, outError;\r
1455 \r
1456     if (icsPR == NULL) return;\r
1457 \r
1458     count = strlen(s);\r
1459     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
1460     if (outCount < count) {\r
1461         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1462     }\r
1463 }\r
1464 \r
1465 /* This is used for sending logon scripts to the ICS. Sending\r
1466    without a delay causes problems when using timestamp on ICC\r
1467    (at least on my machine). */\r
1468 void\r
1469 SendToICSDelayed(s,msdelay)\r
1470      char *s;\r
1471      long msdelay;\r
1472 {\r
1473     int count, outCount, outError;\r
1474 \r
1475     if (icsPR == NULL) return;\r
1476 \r
1477     count = strlen(s);\r
1478     if (appData.debugMode) {\r
1479         fprintf(debugFP, ">ICS: ");\r
1480         show_bytes(debugFP, s, count);\r
1481         fprintf(debugFP, "\n");\r
1482     }\r
1483     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
1484                                       msdelay);\r
1485     if (outCount < count) {\r
1486         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1487     }\r
1488 }\r
1489 \r
1490 \r
1491 /* Remove all highlighting escape sequences in s\r
1492    Also deletes any suffix starting with '(' \r
1493    */\r
1494 char *\r
1495 StripHighlightAndTitle(s)\r
1496      char *s;\r
1497 {\r
1498     static char retbuf[MSG_SIZ];\r
1499     char *p = retbuf;\r
1500 \r
1501     while (*s != NULLCHAR) {\r
1502         while (*s == '\033') {\r
1503             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1504             if (*s != NULLCHAR) s++;\r
1505         }\r
1506         while (*s != NULLCHAR && *s != '\033') {\r
1507             if (*s == '(' || *s == '[') {\r
1508                 *p = NULLCHAR;\r
1509                 return retbuf;\r
1510             }\r
1511             *p++ = *s++;\r
1512         }\r
1513     }\r
1514     *p = NULLCHAR;\r
1515     return retbuf;\r
1516 }\r
1517 \r
1518 /* Remove all highlighting escape sequences in s */\r
1519 char *\r
1520 StripHighlight(s)\r
1521      char *s;\r
1522 {\r
1523     static char retbuf[MSG_SIZ];\r
1524     char *p = retbuf;\r
1525 \r
1526     while (*s != NULLCHAR) {\r
1527         while (*s == '\033') {\r
1528             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1529             if (*s != NULLCHAR) s++;\r
1530         }\r
1531         while (*s != NULLCHAR && *s != '\033') {\r
1532             *p++ = *s++;\r
1533         }\r
1534     }\r
1535     *p = NULLCHAR;\r
1536     return retbuf;\r
1537 }\r
1538 \r
1539 char *variantNames[] = VARIANT_NAMES;\r
1540 char *\r
1541 VariantName(v)\r
1542      VariantClass v;\r
1543 {\r
1544     return variantNames[v];\r
1545 }\r
1546 \r
1547 \r
1548 /* Identify a variant from the strings the chess servers use or the\r
1549    PGN Variant tag names we use. */\r
1550 VariantClass\r
1551 StringToVariant(e)\r
1552      char *e;\r
1553 {\r
1554     char *p;\r
1555     int wnum = -1;\r
1556     VariantClass v = VariantNormal;\r
1557     int i, found = FALSE;\r
1558     char buf[MSG_SIZ];\r
1559 \r
1560     if (!e) return v;\r
1561 \r
1562     /* [HGM] skip over optional board-size prefixes */\r
1563     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||\r
1564         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
1565         while( *e++ != '_');\r
1566     }\r
1567 \r
1568     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1569       if (StrCaseStr(e, variantNames[i])) {\r
1570         v = (VariantClass) i;\r
1571         found = TRUE;\r
1572         break;\r
1573       }\r
1574     }\r
1575 \r
1576     if (!found) {\r
1577       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1578           || StrCaseStr(e, "wild/fr") \r
1579           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {\r
1580         v = VariantFischeRandom;\r
1581       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1582                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1583         p += i;\r
1584         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1585         if (isdigit(*p)) {\r
1586           wnum = atoi(p);\r
1587         } else {\r
1588           wnum = -1;\r
1589         }\r
1590         switch (wnum) {\r
1591         case 0: /* FICS only, actually */\r
1592         case 1:\r
1593           /* Castling legal even if K starts on d-file */\r
1594           v = VariantWildCastle;\r
1595           break;\r
1596         case 2:\r
1597         case 3:\r
1598         case 4:\r
1599           /* Castling illegal even if K & R happen to start in\r
1600              normal positions. */\r
1601           v = VariantNoCastle;\r
1602           break;\r
1603         case 5:\r
1604         case 7:\r
1605         case 8:\r
1606         case 10:\r
1607         case 11:\r
1608         case 12:\r
1609         case 13:\r
1610         case 14:\r
1611         case 15:\r
1612         case 18:\r
1613         case 19:\r
1614           /* Castling legal iff K & R start in normal positions */\r
1615           v = VariantNormal;\r
1616           break;\r
1617         case 6:\r
1618         case 20:\r
1619         case 21:\r
1620           /* Special wilds for position setup; unclear what to do here */\r
1621           v = VariantLoadable;\r
1622           break;\r
1623         case 9:\r
1624           /* Bizarre ICC game */\r
1625           v = VariantTwoKings;\r
1626           break;\r
1627         case 16:\r
1628           v = VariantKriegspiel;\r
1629           break;\r
1630         case 17:\r
1631           v = VariantLosers;\r
1632           break;\r
1633         case 22:\r
1634           v = VariantFischeRandom;\r
1635           break;\r
1636         case 23:\r
1637           v = VariantCrazyhouse;\r
1638           break;\r
1639         case 24:\r
1640           v = VariantBughouse;\r
1641           break;\r
1642         case 25:\r
1643           v = Variant3Check;\r
1644           break;\r
1645         case 26:\r
1646           /* Not quite the same as FICS suicide! */\r
1647           v = VariantGiveaway;\r
1648           break;\r
1649         case 27:\r
1650           v = VariantAtomic;\r
1651           break;\r
1652         case 28:\r
1653           v = VariantShatranj;\r
1654           break;\r
1655 \r
1656         /* Temporary names for future ICC types.  The name *will* change in \r
1657            the next xboard/WinBoard release after ICC defines it. */\r
1658         case 29:\r
1659           v = Variant29;\r
1660           break;\r
1661         case 30:\r
1662           v = Variant30;\r
1663           break;\r
1664         case 31:\r
1665           v = Variant31;\r
1666           break;\r
1667         case 32:\r
1668           v = Variant32;\r
1669           break;\r
1670         case 33:\r
1671           v = Variant33;\r
1672           break;\r
1673         case 34:\r
1674           v = Variant34;\r
1675           break;\r
1676         case 35:\r
1677           v = Variant35;\r
1678           break;\r
1679         case 36:\r
1680           v = Variant36;\r
1681           break;\r
1682         case 37:\r
1683           v = VariantShogi;\r
1684           break;\r
1685         case 38:\r
1686           v = VariantXiangqi;\r
1687           break;\r
1688         case 39:\r
1689           v = VariantCourier;\r
1690           break;\r
1691         case 40:\r
1692           v = VariantGothic;\r
1693           break;\r
1694         case 41:\r
1695           v = VariantCapablanca;\r
1696           break;\r
1697         case 42:\r
1698           v = VariantKnightmate;\r
1699           break;\r
1700         case 43:\r
1701           v = VariantFairy;\r
1702           break;\r
1703         case 44:\r
1704           v = VariantCylinder;\r
1705           break;\r
1706         case 45:\r
1707           v = VariantFalcon;\r
1708           break;\r
1709         case 46:\r
1710           v = VariantCapaRandom;\r
1711           break;\r
1712         case 47:\r
1713           v = VariantBerolina;\r
1714           break;\r
1715         case 48:\r
1716           v = VariantJanus;\r
1717           break;\r
1718         case 49:\r
1719           v = VariantSuper;\r
1720           break;\r
1721         case 50:\r
1722           v = VariantGreat;\r
1723           break;\r
1724         case -1:\r
1725           /* Found "wild" or "w" in the string but no number;\r
1726              must assume it's normal chess. */\r
1727           v = VariantNormal;\r
1728           break;\r
1729         default:\r
1730           sprintf(buf, _("Unknown wild type %d"), wnum);\r
1731           DisplayError(buf, 0);\r
1732           v = VariantUnknown;\r
1733           break;\r
1734         }\r
1735       }\r
1736     }\r
1737     if (appData.debugMode) {\r
1738       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),\r
1739               e, wnum, VariantName(v));\r
1740     }\r
1741     return v;\r
1742 }\r
1743 \r
1744 static int leftover_start = 0, leftover_len = 0;\r
1745 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1746 \r
1747 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1748    advance *index beyond it, and set leftover_start to the new value of\r
1749    *index; else return FALSE.  If pattern contains the character '*', it\r
1750    matches any sequence of characters not containing '\r', '\n', or the\r
1751    character following the '*' (if any), and the matched sequence(s) are\r
1752    copied into star_match.\r
1753    */\r
1754 int\r
1755 looking_at(buf, index, pattern)\r
1756      char *buf;\r
1757      int *index;\r
1758      char *pattern;\r
1759 {\r
1760     char *bufp = &buf[*index], *patternp = pattern;\r
1761     int star_count = 0;\r
1762     char *matchp = star_match[0];\r
1763     \r
1764     for (;;) {\r
1765         if (*patternp == NULLCHAR) {\r
1766             *index = leftover_start = bufp - buf;\r
1767             *matchp = NULLCHAR;\r
1768             return TRUE;\r
1769         }\r
1770         if (*bufp == NULLCHAR) return FALSE;\r
1771         if (*patternp == '*') {\r
1772             if (*bufp == *(patternp + 1)) {\r
1773                 *matchp = NULLCHAR;\r
1774                 matchp = star_match[++star_count];\r
1775                 patternp += 2;\r
1776                 bufp++;\r
1777                 continue;\r
1778             } else if (*bufp == '\n' || *bufp == '\r') {\r
1779                 patternp++;\r
1780                 if (*patternp == NULLCHAR)\r
1781                   continue;\r
1782                 else\r
1783                   return FALSE;\r
1784             } else {\r
1785                 *matchp++ = *bufp++;\r
1786                 continue;\r
1787             }\r
1788         }\r
1789         if (*patternp != *bufp) return FALSE;\r
1790         patternp++;\r
1791         bufp++;\r
1792     }\r
1793 }\r
1794 \r
1795 void\r
1796 SendToPlayer(data, length)\r
1797      char *data;\r
1798      int length;\r
1799 {\r
1800     int error, outCount;\r
1801     outCount = OutputToProcess(NoProc, data, length, &error);\r
1802     if (outCount < length) {\r
1803         DisplayFatalError(_("Error writing to display"), error, 1);\r
1804     }\r
1805 }\r
1806 \r
1807 void\r
1808 PackHolding(packed, holding)\r
1809      char packed[];\r
1810      char *holding;\r
1811 {\r
1812     char *p = holding;\r
1813     char *q = packed;\r
1814     int runlength = 0;\r
1815     int curr = 9999;\r
1816     do {\r
1817         if (*p == curr) {\r
1818             runlength++;\r
1819         } else {\r
1820             switch (runlength) {\r
1821               case 0:\r
1822                 break;\r
1823               case 1:\r
1824                 *q++ = curr;\r
1825                 break;\r
1826               case 2:\r
1827                 *q++ = curr;\r
1828                 *q++ = curr;\r
1829                 break;\r
1830               default:\r
1831                 sprintf(q, "%d", runlength);\r
1832                 while (*q) q++;\r
1833                 *q++ = curr;\r
1834                 break;\r
1835             }\r
1836             runlength = 1;\r
1837             curr = *p;\r
1838         }\r
1839     } while (*p++);\r
1840     *q = NULLCHAR;\r
1841 }\r
1842 \r
1843 /* Telnet protocol requests from the front end */\r
1844 void\r
1845 TelnetRequest(ddww, option)\r
1846      unsigned char ddww, option;\r
1847 {\r
1848     unsigned char msg[3];\r
1849     int outCount, outError;\r
1850 \r
1851     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1852 \r
1853     if (appData.debugMode) {\r
1854         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1855         switch (ddww) {\r
1856           case TN_DO:\r
1857             ddwwStr = "DO";\r
1858             break;\r
1859           case TN_DONT:\r
1860             ddwwStr = "DONT";\r
1861             break;\r
1862           case TN_WILL:\r
1863             ddwwStr = "WILL";\r
1864             break;\r
1865           case TN_WONT:\r
1866             ddwwStr = "WONT";\r
1867             break;\r
1868           default:\r
1869             ddwwStr = buf1;\r
1870             sprintf(buf1, "%d", ddww);\r
1871             break;\r
1872         }\r
1873         switch (option) {\r
1874           case TN_ECHO:\r
1875             optionStr = "ECHO";\r
1876             break;\r
1877           default:\r
1878             optionStr = buf2;\r
1879             sprintf(buf2, "%d", option);\r
1880             break;\r
1881         }\r
1882         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1883     }\r
1884     msg[0] = TN_IAC;\r
1885     msg[1] = ddww;\r
1886     msg[2] = option;\r
1887     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1888     if (outCount < 3) {\r
1889         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1890     }\r
1891 }\r
1892 \r
1893 void\r
1894 DoEcho()\r
1895 {\r
1896     if (!appData.icsActive) return;\r
1897     TelnetRequest(TN_DO, TN_ECHO);\r
1898 }\r
1899 \r
1900 void\r
1901 DontEcho()\r
1902 {\r
1903     if (!appData.icsActive) return;\r
1904     TelnetRequest(TN_DONT, TN_ECHO);\r
1905 }\r
1906 \r
1907 void\r
1908 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
1909 {\r
1910     /* put the holdings sent to us by the server on the board holdings area */\r
1911     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
1912     char p;\r
1913     ChessSquare piece;\r
1914 \r
1915     if(gameInfo.holdingsWidth < 2)  return;\r
1916 \r
1917     if( (int)lowestPiece >= BlackPawn ) {\r
1918         holdingsColumn = 0;\r
1919         countsColumn = 1;\r
1920         holdingsStartRow = BOARD_HEIGHT-1;\r
1921         direction = -1;\r
1922     } else {\r
1923         holdingsColumn = BOARD_WIDTH-1;\r
1924         countsColumn = BOARD_WIDTH-2;\r
1925         holdingsStartRow = 0;\r
1926         direction = 1;\r
1927     }\r
1928 \r
1929     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
1930         board[i][holdingsColumn] = EmptySquare;\r
1931         board[i][countsColumn]   = (ChessSquare) 0;\r
1932     }\r
1933     while( (p=*holdings++) != NULLCHAR ) {\r
1934         piece = CharToPiece( ToUpper(p) );\r
1935         if(piece == EmptySquare) continue;\r
1936         /*j = (int) piece - (int) WhitePawn;*/\r
1937         j = PieceToNumber(piece);\r
1938         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1939         if(j < 0) continue;               /* should not happen */\r
1940         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );\r
1941         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
1942         board[holdingsStartRow+j*direction][countsColumn]++;\r
1943     }\r
1944 \r
1945 }\r
1946 \r
1947 \r
1948 void\r
1949 VariantSwitch(Board board, VariantClass newVariant)\r
1950 {\r
1951    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;\r
1952    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;\r
1953 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;\r
1954 \r
1955    startedFromPositionFile = FALSE;\r
1956    if(gameInfo.variant == newVariant) return;\r
1957 \r
1958    /* [HGM] This routine is called each time an assignment is made to\r
1959     * gameInfo.variant during a game, to make sure the board sizes\r
1960     * are set to match the new variant. If that means adding or deleting\r
1961     * holdings, we shift the playing board accordingly\r
1962     * This kludge is needed because in ICS observe mode, we get boards\r
1963     * of an ongoing game without knowing the variant, and learn about the\r
1964     * latter only later. This can be because of the move list we requested,\r
1965     * in which case the game history is refilled from the beginning anyway,\r
1966     * but also when receiving holdings of a crazyhouse game. In the latter\r
1967     * case we want to add those holdings to the already received position.\r
1968     */\r
1969 \r
1970 \r
1971   if (appData.debugMode) {\r
1972     fprintf(debugFP, "Switch board from %s to %s\n",\r
1973                VariantName(gameInfo.variant), VariantName(newVariant));\r
1974     setbuf(debugFP, NULL);\r
1975   }\r
1976     shuffleOpenings = 0;       /* [HGM] shuffle */\r
1977     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
1978     switch(newVariant) {\r
1979             case VariantShogi:\r
1980               newWidth = 9;  newHeight = 9;\r
1981               gameInfo.holdingsSize = 7;\r
1982             case VariantBughouse:\r
1983             case VariantCrazyhouse:\r
1984               newHoldingsWidth = 2; break;\r
1985             default:\r
1986               newHoldingsWidth = gameInfo.holdingsSize = 0;\r
1987     }\r
1988 \r
1989     if(newWidth  != gameInfo.boardWidth  ||\r
1990        newHeight != gameInfo.boardHeight ||\r
1991        newHoldingsWidth != gameInfo.holdingsWidth ) {\r
1992 \r
1993         /* shift position to new playing area, if needed */\r
1994         if(newHoldingsWidth > gameInfo.holdingsWidth) {\r
1995            for(i=0; i<BOARD_HEIGHT; i++) \r
1996                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)\r
1997                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
1998                                                      board[i][j];\r
1999            for(i=0; i<newHeight; i++) {\r
2000                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;\r
2001                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;\r
2002            }\r
2003         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {\r
2004            for(i=0; i<BOARD_HEIGHT; i++)\r
2005                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
2006                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
2007                                                  board[i][j];\r
2008         }\r
2009 \r
2010         gameInfo.boardWidth  = newWidth;\r
2011         gameInfo.boardHeight = newHeight;\r
2012         gameInfo.holdingsWidth = newHoldingsWidth;\r
2013         gameInfo.variant = newVariant;\r
2014         InitDrawingSizes(-2, 0);\r
2015 \r
2016         /* [HGM] The following should definitely be solved in a better way */\r
2017 #if 0\r
2018         CopyBoard(board, tempBoard); /* save position in case it is board[0] */\r
2019         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];\r
2020         saveEP = epStatus[0];\r
2021 #endif\r
2022         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */\r
2023 #if 0\r
2024         epStatus[0] = saveEP;\r
2025         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];\r
2026         CopyBoard(tempBoard, board); /* restore position received from ICS   */\r
2027 #endif\r
2028     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }\r
2029 \r
2030     forwardMostMove = oldForwardMostMove;\r
2031     backwardMostMove = oldBackwardMostMove;\r
2032     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */\r
2033 }\r
2034 \r
2035 static int loggedOn = FALSE;\r
2036 \r
2037 /*-- Game start info cache: --*/\r
2038 int gs_gamenum;\r
2039 char gs_kind[MSG_SIZ];\r
2040 static char player1Name[128] = "";\r
2041 static char player2Name[128] = "";\r
2042 static int player1Rating = -1;\r
2043 static int player2Rating = -1;\r
2044 /*----------------------------*/\r
2045 \r
2046 ColorClass curColor = ColorNormal;\r
2047 int suppressKibitz = 0;\r
2048 \r
2049 void\r
2050 read_from_ics(isr, closure, data, count, error)\r
2051      InputSourceRef isr;\r
2052      VOIDSTAR closure;\r
2053      char *data;\r
2054      int count;\r
2055      int error;\r
2056 {\r
2057 #define BUF_SIZE 8192\r
2058 #define STARTED_NONE 0\r
2059 #define STARTED_MOVES 1\r
2060 #define STARTED_BOARD 2\r
2061 #define STARTED_OBSERVE 3\r
2062 #define STARTED_HOLDINGS 4\r
2063 #define STARTED_CHATTER 5\r
2064 #define STARTED_COMMENT 6\r
2065 #define STARTED_MOVES_NOHIDE 7\r
2066     \r
2067     static int started = STARTED_NONE;\r
2068     static char parse[20000];\r
2069     static int parse_pos = 0;\r
2070     static char buf[BUF_SIZE + 1];\r
2071     static int firstTime = TRUE, intfSet = FALSE;\r
2072     static ColorClass prevColor = ColorNormal;\r
2073     static int savingComment = FALSE;\r
2074     char str[500];\r
2075     int i, oldi;\r
2076     int buf_len;\r
2077     int next_out;\r
2078     int tkind;\r
2079     int backup;    /* [DM] For zippy color lines */\r
2080     char *p;\r
2081 \r
2082     if (appData.debugMode) {\r
2083       if (!error) {\r
2084         fprintf(debugFP, "<ICS: ");\r
2085         show_bytes(debugFP, data, count);\r
2086         fprintf(debugFP, "\n");\r
2087       }\r
2088     }\r
2089 \r
2090     if (appData.debugMode) { int f = forwardMostMove;\r
2091         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,\r
2092                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
2093     }\r
2094     if (count > 0) {\r
2095         /* If last read ended with a partial line that we couldn't parse,\r
2096            prepend it to the new read and try again. */\r
2097         if (leftover_len > 0) {\r
2098             for (i=0; i<leftover_len; i++)\r
2099               buf[i] = buf[leftover_start + i];\r
2100         }\r
2101 \r
2102         /* Copy in new characters, removing nulls and \r's */\r
2103         buf_len = leftover_len;\r
2104         for (i = 0; i < count; i++) {\r
2105             if (data[i] != NULLCHAR && data[i] != '\r')\r
2106               buf[buf_len++] = data[i];\r
2107             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && \r
2108                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') \r
2109                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous\r
2110         }\r
2111 \r
2112         buf[buf_len] = NULLCHAR;\r
2113         next_out = leftover_len;\r
2114         leftover_start = 0;\r
2115         \r
2116         i = 0;\r
2117         while (i < buf_len) {\r
2118             /* Deal with part of the TELNET option negotiation\r
2119                protocol.  We refuse to do anything beyond the\r
2120                defaults, except that we allow the WILL ECHO option,\r
2121                which ICS uses to turn off password echoing when we are\r
2122                directly connected to it.  We reject this option\r
2123                if localLineEditing mode is on (always on in xboard)\r
2124                and we are talking to port 23, which might be a real\r
2125                telnet server that will try to keep WILL ECHO on permanently.\r
2126              */\r
2127             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
2128                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
2129                 unsigned char option;\r
2130                 oldi = i;\r
2131                 switch ((unsigned char) buf[++i]) {\r
2132                   case TN_WILL:\r
2133                     if (appData.debugMode)\r
2134                       fprintf(debugFP, "\n<WILL ");\r
2135                     switch (option = (unsigned char) buf[++i]) {\r
2136                       case TN_ECHO:\r
2137                         if (appData.debugMode)\r
2138                           fprintf(debugFP, "ECHO ");\r
2139                         /* Reply only if this is a change, according\r
2140                            to the protocol rules. */\r
2141                         if (remoteEchoOption) break;\r
2142                         if (appData.localLineEditing &&\r
2143                             atoi(appData.icsPort) == TN_PORT) {\r
2144                             TelnetRequest(TN_DONT, TN_ECHO);\r
2145                         } else {\r
2146                             EchoOff();\r
2147                             TelnetRequest(TN_DO, TN_ECHO);\r
2148                             remoteEchoOption = TRUE;\r
2149                         }\r
2150                         break;\r
2151                       default:\r
2152                         if (appData.debugMode)\r
2153                           fprintf(debugFP, "%d ", option);\r
2154                         /* Whatever this is, we don't want it. */\r
2155                         TelnetRequest(TN_DONT, option);\r
2156                         break;\r
2157                     }\r
2158                     break;\r
2159                   case TN_WONT:\r
2160                     if (appData.debugMode)\r
2161                       fprintf(debugFP, "\n<WONT ");\r
2162                     switch (option = (unsigned char) buf[++i]) {\r
2163                       case TN_ECHO:\r
2164                         if (appData.debugMode)\r
2165                           fprintf(debugFP, "ECHO ");\r
2166                         /* Reply only if this is a change, according\r
2167                            to the protocol rules. */\r
2168                         if (!remoteEchoOption) break;\r
2169                         EchoOn();\r
2170                         TelnetRequest(TN_DONT, TN_ECHO);\r
2171                         remoteEchoOption = FALSE;\r
2172                         break;\r
2173                       default:\r
2174                         if (appData.debugMode)\r
2175                           fprintf(debugFP, "%d ", (unsigned char) option);\r
2176                         /* Whatever this is, it must already be turned\r
2177                            off, because we never agree to turn on\r
2178                            anything non-default, so according to the\r
2179                            protocol rules, we don't reply. */\r
2180                         break;\r
2181                     }\r
2182                     break;\r
2183                   case TN_DO:\r
2184                     if (appData.debugMode)\r
2185                       fprintf(debugFP, "\n<DO ");\r
2186                     switch (option = (unsigned char) buf[++i]) {\r
2187                       default:\r
2188                         /* Whatever this is, we refuse to do it. */\r
2189                         if (appData.debugMode)\r
2190                           fprintf(debugFP, "%d ", option);\r
2191                         TelnetRequest(TN_WONT, option);\r
2192                         break;\r
2193                     }\r
2194                     break;\r
2195                   case TN_DONT:\r
2196                     if (appData.debugMode)\r
2197                       fprintf(debugFP, "\n<DONT ");\r
2198                     switch (option = (unsigned char) buf[++i]) {\r
2199                       default:\r
2200                         if (appData.debugMode)\r
2201                           fprintf(debugFP, "%d ", option);\r
2202                         /* Whatever this is, we are already not doing\r
2203                            it, because we never agree to do anything\r
2204                            non-default, so according to the protocol\r
2205                            rules, we don't reply. */\r
2206                         break;\r
2207                     }\r
2208                     break;\r
2209                   case TN_IAC:\r
2210                     if (appData.debugMode)\r
2211                       fprintf(debugFP, "\n<IAC ");\r
2212                     /* Doubled IAC; pass it through */\r
2213                     i--;\r
2214                     break;\r
2215                   default:\r
2216                     if (appData.debugMode)\r
2217                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
2218                     /* Drop all other telnet commands on the floor */\r
2219                     break;\r
2220                 }\r
2221                 if (oldi > next_out)\r
2222                   SendToPlayer(&buf[next_out], oldi - next_out);\r
2223                 if (++i > next_out)\r
2224                   next_out = i;\r
2225                 continue;\r
2226             }\r
2227                 \r
2228             /* OK, this at least will *usually* work */\r
2229             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
2230                 loggedOn = TRUE;\r
2231             }\r
2232             \r
2233             if (loggedOn && !intfSet) {\r
2234                 if (ics_type == ICS_ICC) {\r
2235                   sprintf(str,\r
2236                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
2237                           programVersion);\r
2238 \r
2239                 } else if (ics_type == ICS_CHESSNET) {\r
2240                   sprintf(str, "/style 12\n");\r
2241                 } else {\r
2242                   strcpy(str, "alias $ @\n$set interface ");\r
2243                   strcat(str, programVersion);\r
2244                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
2245 #ifdef WIN32\r
2246                   strcat(str, "$iset nohighlight 1\n");\r
2247 #endif\r
2248                   strcat(str, "$iset lock 1\n$style 12\n");\r
2249                 }\r
2250                 SendToICS(str);\r
2251                 intfSet = TRUE;\r
2252             }\r
2253 \r
2254             if (started == STARTED_COMMENT) {\r
2255                 /* Accumulate characters in comment */\r
2256                 parse[parse_pos++] = buf[i];\r
2257                 if (buf[i] == '\n') {\r
2258                     parse[parse_pos] = NULLCHAR;\r
2259                     if(!suppressKibitz) // [HGM] kibitz\r
2260                         AppendComment(forwardMostMove, StripHighlight(parse));\r
2261                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window\r
2262                         int nrDigit = 0, nrAlph = 0, i;\r
2263                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input\r
2264                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }\r
2265                         parse[parse_pos] = NULLCHAR;\r
2266                         // try to be smart: if it does not look like search info, it should go to\r
2267                         // ICS interaction window after all, not to engine-output window.\r
2268                         for(i=0; i<parse_pos; i++) { // count letters and digits\r
2269                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');\r
2270                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');\r
2271                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');\r
2272                         }\r
2273                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info\r
2274                             OutputKibitz(suppressKibitz, parse);\r
2275                         } else {\r
2276                             char tmp[MSG_SIZ];\r
2277                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);\r
2278                             SendToPlayer(tmp, strlen(tmp));\r
2279                         }\r
2280                     }\r
2281                     started = STARTED_NONE;\r
2282                 } else {\r
2283                     /* Don't match patterns against characters in chatter */\r
2284                     i++;\r
2285                     continue;\r
2286                 }\r
2287             }\r
2288             if (started == STARTED_CHATTER) {\r
2289                 if (buf[i] != '\n') {\r
2290                     /* Don't match patterns against characters in chatter */\r
2291                     i++;\r
2292                     continue;\r
2293                 }\r
2294                 started = STARTED_NONE;\r
2295             }\r
2296 \r
2297             /* Kludge to deal with rcmd protocol */\r
2298             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2299                 DisplayFatalError(&buf[1], 0, 1);\r
2300                 continue;\r
2301             } else {\r
2302                 firstTime = FALSE;\r
2303             }\r
2304 \r
2305             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2306                 ics_type = ICS_ICC;\r
2307                 ics_prefix = "/";\r
2308                 if (appData.debugMode)\r
2309                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2310                 continue;\r
2311             }\r
2312             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2313                 ics_type = ICS_FICS;\r
2314                 ics_prefix = "$";\r
2315                 if (appData.debugMode)\r
2316                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2317                 continue;\r
2318             }\r
2319             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2320                 ics_type = ICS_CHESSNET;\r
2321                 ics_prefix = "/";\r
2322                 if (appData.debugMode)\r
2323                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2324                 continue;\r
2325             }\r
2326 \r
2327             if (!loggedOn &&\r
2328                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2329                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2330                  looking_at(buf, &i, "will be \"*\""))) {\r
2331               strcpy(ics_handle, star_match[0]);\r
2332               continue;\r
2333             }\r
2334 \r
2335             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2336               char buf[MSG_SIZ];\r
2337               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2338               DisplayIcsInteractionTitle(buf);\r
2339               have_set_title = TRUE;\r
2340             }\r
2341 \r
2342             /* skip finger notes */\r
2343             if (started == STARTED_NONE &&\r
2344                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2345                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2346                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2347               started = STARTED_CHATTER;\r
2348               i += 3;\r
2349               continue;\r
2350             }\r
2351 \r
2352             /* skip formula vars */\r
2353             if (started == STARTED_NONE &&\r
2354                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2355               started = STARTED_CHATTER;\r
2356               i += 3;\r
2357               continue;\r
2358             }\r
2359 \r
2360             oldi = i;\r
2361             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window\r
2362             if (appData.autoKibitz && started == STARTED_NONE && \r
2363                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze\r
2364                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {\r
2365                 if(looking_at(buf, &i, "* kibitzes: ") &&\r
2366                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || \r
2367                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent\r
2368                         suppressKibitz = TRUE;\r
2369                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]\r
2370                                 && (gameMode == IcsPlayingWhite)) ||\r
2371                            (StrStr(star_match[0], gameInfo.black) == star_match[0]\r
2372                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz\r
2373                             started = STARTED_CHATTER; // own kibitz we simply discard\r
2374                         else {\r
2375                             started = STARTED_COMMENT; // make sure it will be collected in parse[]\r
2376                             parse_pos = 0; parse[0] = NULLCHAR;\r
2377                             savingComment = TRUE;\r
2378                             suppressKibitz = gameMode != IcsObserving ? 2 :\r
2379                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;\r
2380                         } \r
2381                         continue;\r
2382                 } else\r
2383                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz\r
2384                     started = STARTED_CHATTER;\r
2385                     suppressKibitz = TRUE;\r
2386                 }\r
2387             } // [HGM] kibitz: end of patch\r
2388 \r
2389             if (appData.zippyTalk || appData.zippyPlay) {\r
2390                 /* [DM] Backup address for color zippy lines */\r
2391                 backup = i;\r
2392 #if ZIPPY\r
2393        #ifdef WIN32\r
2394                if (loggedOn == TRUE)\r
2395                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||\r
2396                           (appData.zippyPlay && ZippyMatch(buf, &backup)));\r
2397        #else\r
2398                 if (ZippyControl(buf, &i) ||\r
2399                     ZippyConverse(buf, &i) ||\r
2400                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2401                       loggedOn = TRUE;\r
2402                       if (!appData.colorize) continue;\r
2403                 }\r
2404        #endif\r
2405 #endif\r
2406             } // [DM] 'else { ' deleted\r
2407                 if (/* Don't color "message" or "messages" output */\r
2408                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2409                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2410                     looking_at(buf, &i, "--* (*:*): ") ||\r
2411                     /* Regular tells and says */\r
2412                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2413                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2414                     looking_at(buf, &i, "* says: ") ||\r
2415                     /* Message notifications (same color as tells) */\r
2416                     looking_at(buf, &i, "* has left a message ") ||\r
2417                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2418                     /* Whispers and kibitzes */\r
2419                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2420                     looking_at(buf, &i, "* kibitzes: ") ||\r
2421                     /* Channel tells */\r
2422                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2423 \r
2424                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2425                       /* Avoid "tells you:" spoofs in channels */\r
2426                      tkind = 3;\r
2427                   }\r
2428                   if (star_match[0][0] == NULLCHAR ||\r
2429                       strchr(star_match[0], ' ') ||\r
2430                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2431                     /* Reject bogus matches */\r
2432                     i = oldi;\r
2433                   } else {\r
2434                     if (appData.colorize) {\r
2435                       if (oldi > next_out) {\r
2436                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2437                         next_out = oldi;\r
2438                       }\r
2439                       switch (tkind) {\r
2440                       case 1:\r
2441                         Colorize(ColorTell, FALSE);\r
2442                         curColor = ColorTell;\r
2443                         break;\r
2444                       case 2:\r
2445                         Colorize(ColorKibitz, FALSE);\r
2446                         curColor = ColorKibitz;\r
2447                         break;\r
2448                       case 3:\r
2449                         p = strrchr(star_match[1], '(');\r
2450                         if (p == NULL) {\r
2451                           p = star_match[1];\r
2452                         } else {\r
2453                           p++;\r
2454                         }\r
2455                         if (atoi(p) == 1) {\r
2456                           Colorize(ColorChannel1, FALSE);\r
2457                           curColor = ColorChannel1;\r
2458                         } else {\r
2459                           Colorize(ColorChannel, FALSE);\r
2460                           curColor = ColorChannel;\r
2461                         }\r
2462                         break;\r
2463                       case 5:\r
2464                         curColor = ColorNormal;\r
2465                         break;\r
2466                       }\r
2467                     }\r
2468                     if (started == STARTED_NONE && appData.autoComment &&\r
2469                         (gameMode == IcsObserving ||\r
2470                          gameMode == IcsPlayingWhite ||\r
2471                          gameMode == IcsPlayingBlack)) {\r
2472                       parse_pos = i - oldi;\r
2473                       memcpy(parse, &buf[oldi], parse_pos);\r
2474                       parse[parse_pos] = NULLCHAR;\r
2475                       started = STARTED_COMMENT;\r
2476                       savingComment = TRUE;\r
2477                     } else {\r
2478                       started = STARTED_CHATTER;\r
2479                       savingComment = FALSE;\r
2480                     }\r
2481                     loggedOn = TRUE;\r
2482                     continue;\r
2483                   }\r
2484                 }\r
2485 \r
2486                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2487                     looking_at(buf, &i, "* c-shouts: ")) {\r
2488                     if (appData.colorize) {\r
2489                         if (oldi > next_out) {\r
2490                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2491                             next_out = oldi;\r
2492                         }\r
2493                         Colorize(ColorSShout, FALSE);\r
2494                         curColor = ColorSShout;\r
2495                     }\r
2496                     loggedOn = TRUE;\r
2497                     started = STARTED_CHATTER;\r
2498                     continue;\r
2499                 }\r
2500 \r
2501                 if (looking_at(buf, &i, "--->")) {\r
2502                     loggedOn = TRUE;\r
2503                     continue;\r
2504                 }\r
2505 \r
2506                 if (looking_at(buf, &i, "* shouts: ") ||\r
2507                     looking_at(buf, &i, "--> ")) {\r
2508                     if (appData.colorize) {\r
2509                         if (oldi > next_out) {\r
2510                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2511                             next_out = oldi;\r
2512                         }\r
2513                         Colorize(ColorShout, FALSE);\r
2514                         curColor = ColorShout;\r
2515                     }\r
2516                     loggedOn = TRUE;\r
2517                     started = STARTED_CHATTER;\r
2518                     continue;\r
2519                 }\r
2520 \r
2521                 if (looking_at( buf, &i, "Challenge:")) {\r
2522                     if (appData.colorize) {\r
2523                         if (oldi > next_out) {\r
2524                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2525                             next_out = oldi;\r
2526                         }\r
2527                         Colorize(ColorChallenge, FALSE);\r
2528                         curColor = ColorChallenge;\r
2529                     }\r
2530                     loggedOn = TRUE;\r
2531                     continue;\r
2532                 }\r
2533 \r
2534                 if (looking_at(buf, &i, "* offers you") ||\r
2535                     looking_at(buf, &i, "* offers to be") ||\r
2536                     looking_at(buf, &i, "* would like to") ||\r
2537                     looking_at(buf, &i, "* requests to") ||\r
2538                     looking_at(buf, &i, "Your opponent offers") ||\r
2539                     looking_at(buf, &i, "Your opponent requests")) {\r
2540 \r
2541                     if (appData.colorize) {\r
2542                         if (oldi > next_out) {\r
2543                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2544                             next_out = oldi;\r
2545                         }\r
2546                         Colorize(ColorRequest, FALSE);\r
2547                         curColor = ColorRequest;\r
2548                     }\r
2549                     continue;\r
2550                 }\r
2551 \r
2552                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2553                     if (appData.colorize) {\r
2554                         if (oldi > next_out) {\r
2555                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2556                             next_out = oldi;\r
2557                         }\r
2558                         Colorize(ColorSeek, FALSE);\r
2559                         curColor = ColorSeek;\r
2560                     }\r
2561                     continue;\r
2562             }\r
2563 \r
2564             if (looking_at(buf, &i, "\\   ")) {\r
2565                 if (prevColor != ColorNormal) {\r
2566                     if (oldi > next_out) {\r
2567                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2568                         next_out = oldi;\r
2569                     }\r
2570                     Colorize(prevColor, TRUE);\r
2571                     curColor = prevColor;\r
2572                 }\r
2573                 if (savingComment) {\r
2574                     parse_pos = i - oldi;\r
2575                     memcpy(parse, &buf[oldi], parse_pos);\r
2576                     parse[parse_pos] = NULLCHAR;\r
2577                     started = STARTED_COMMENT;\r
2578                 } else {\r
2579                     started = STARTED_CHATTER;\r
2580                 }\r
2581                 continue;\r
2582             }\r
2583 \r
2584             if (looking_at(buf, &i, "Black Strength :") ||\r
2585                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2586                 looking_at(buf, &i, "<10>") ||\r
2587                 looking_at(buf, &i, "#@#")) {\r
2588                 /* Wrong board style */\r
2589                 loggedOn = TRUE;\r
2590                 SendToICS(ics_prefix);\r
2591                 SendToICS("set style 12\n");\r
2592                 SendToICS(ics_prefix);\r
2593                 SendToICS("refresh\n");\r
2594                 continue;\r
2595             }\r
2596             \r
2597             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2598                 ICSInitScript();\r
2599                 have_sent_ICS_logon = 1;\r
2600                 continue;\r
2601             }\r
2602               \r
2603             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2604                 (looking_at(buf, &i, "\n<12> ") ||\r
2605                  looking_at(buf, &i, "<12> "))) {\r
2606                 loggedOn = TRUE;\r
2607                 if (oldi > next_out) {\r
2608                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2609                 }\r
2610                 next_out = i;\r
2611                 started = STARTED_BOARD;\r
2612                 parse_pos = 0;\r
2613                 continue;\r
2614             }\r
2615 \r
2616             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2617                 looking_at(buf, &i, "<b1> ")) {\r
2618                 if (oldi > next_out) {\r
2619                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2620                 }\r
2621                 next_out = i;\r
2622                 started = STARTED_HOLDINGS;\r
2623                 parse_pos = 0;\r
2624                 continue;\r
2625             }\r
2626 \r
2627             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2628                 loggedOn = TRUE;\r
2629                 /* Header for a move list -- first line */\r
2630 \r
2631                 switch (ics_getting_history) {\r
2632                   case H_FALSE:\r
2633                     switch (gameMode) {\r
2634                       case IcsIdle:\r
2635                       case BeginningOfGame:\r
2636                         /* User typed "moves" or "oldmoves" while we\r
2637                            were idle.  Pretend we asked for these\r
2638                            moves and soak them up so user can step\r
2639                            through them and/or save them.\r
2640                            */\r
2641                         Reset(FALSE, TRUE);\r
2642                         gameMode = IcsObserving;\r
2643                         ModeHighlight();\r
2644                         ics_gamenum = -1;\r
2645                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2646                         break;\r
2647                       case EditGame: /*?*/\r
2648                       case EditPosition: /*?*/\r
2649                         /* Should above feature work in these modes too? */\r
2650                         /* For now it doesn't */\r
2651                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2652                         break;\r
2653                       default:\r
2654                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2655                         break;\r
2656                     }\r
2657                     break;\r
2658                   case H_REQUESTED:\r
2659                     /* Is this the right one? */\r
2660                     if (gameInfo.white && gameInfo.black &&\r
2661                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2662                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2663                         /* All is well */\r
2664                         ics_getting_history = H_GOT_REQ_HEADER;\r
2665                     }\r
2666                     break;\r
2667                   case H_GOT_REQ_HEADER:\r
2668                   case H_GOT_UNREQ_HEADER:\r
2669                   case H_GOT_UNWANTED_HEADER:\r
2670                   case H_GETTING_MOVES:\r
2671                     /* Should not happen */\r
2672                     DisplayError(_("Error gathering move list: two headers"), 0);\r
2673                     ics_getting_history = H_FALSE;\r
2674                     break;\r
2675                 }\r
2676 \r
2677                 /* Save player ratings into gameInfo if needed */\r
2678                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2679                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2680                     (gameInfo.whiteRating == -1 ||\r
2681                      gameInfo.blackRating == -1)) {\r
2682 \r
2683                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2684                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2685                     if (appData.debugMode)\r
2686                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), \r
2687                               gameInfo.whiteRating, gameInfo.blackRating);\r
2688                 }\r
2689                 continue;\r
2690             }\r
2691 \r
2692             if (looking_at(buf, &i,\r
2693               "* * match, initial time: * minute*, increment: * second")) {\r
2694                 /* Header for a move list -- second line */\r
2695                 /* Initial board will follow if this is a wild game */\r
2696                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2697                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2698                 gameInfo.event = StrSave(str);\r
2699                 /* [HGM] we switched variant. Translate boards if needed. */\r
2700                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2701                 continue;\r
2702             }\r
2703 \r
2704             if (looking_at(buf, &i, "Move  ")) {\r
2705                 /* Beginning of a move list */\r
2706                 switch (ics_getting_history) {\r
2707                   case H_FALSE:\r
2708                     /* Normally should not happen */\r
2709                     /* Maybe user hit reset while we were parsing */\r
2710                     break;\r
2711                   case H_REQUESTED:\r
2712                     /* Happens if we are ignoring a move list that is not\r
2713                      * the one we just requested.  Common if the user\r
2714                      * tries to observe two games without turning off\r
2715                      * getMoveList */\r
2716                     break;\r
2717                   case H_GETTING_MOVES:\r
2718                     /* Should not happen */\r
2719                     DisplayError(_("Error gathering move list: nested"), 0);\r
2720                     ics_getting_history = H_FALSE;\r
2721                     break;\r
2722                   case H_GOT_REQ_HEADER:\r
2723                     ics_getting_history = H_GETTING_MOVES;\r
2724                     started = STARTED_MOVES;\r
2725                     parse_pos = 0;\r
2726                     if (oldi > next_out) {\r
2727                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2728                     }\r
2729                     break;\r
2730                   case H_GOT_UNREQ_HEADER:\r
2731                     ics_getting_history = H_GETTING_MOVES;\r
2732                     started = STARTED_MOVES_NOHIDE;\r
2733                     parse_pos = 0;\r
2734                     break;\r
2735                   case H_GOT_UNWANTED_HEADER:\r
2736                     ics_getting_history = H_FALSE;\r
2737                     break;\r
2738                 }\r
2739                 continue;\r
2740             }                           \r
2741             \r
2742             if (looking_at(buf, &i, "% ") ||\r
2743                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2744                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book\r
2745                 savingComment = FALSE;\r
2746                 switch (started) {\r
2747                   case STARTED_MOVES:\r
2748                   case STARTED_MOVES_NOHIDE:\r
2749                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2750                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2751                     ParseGameHistory(parse);\r
2752 #if ZIPPY\r
2753                     if (appData.zippyPlay && first.initDone) {\r
2754                         FeedMovesToProgram(&first, forwardMostMove);\r
2755                         if (gameMode == IcsPlayingWhite) {\r
2756                             if (WhiteOnMove(forwardMostMove)) {\r
2757                                 if (first.sendTime) {\r
2758                                   if (first.useColors) {\r
2759                                     SendToProgram("black\n", &first); \r
2760                                   }\r
2761                                   SendTimeRemaining(&first, TRUE);\r
2762                                 }\r
2763 #if 0\r
2764                                 if (first.useColors) {\r
2765                                   SendToProgram("white\ngo\n", &first);\r
2766                                 } else {\r
2767                                   SendToProgram("go\n", &first);\r
2768                                 }\r
2769 #else\r
2770                                 if (first.useColors) {\r
2771                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent\r
2772                                 }\r
2773                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos\r
2774 #endif\r
2775                                 first.maybeThinking = TRUE;\r
2776                             } else {\r
2777                                 if (first.usePlayother) {\r
2778                                   if (first.sendTime) {\r
2779                                     SendTimeRemaining(&first, TRUE);\r
2780                                   }\r
2781                                   SendToProgram("playother\n", &first);\r
2782                                   firstMove = FALSE;\r
2783                                 } else {\r
2784                                   firstMove = TRUE;\r
2785                                 }\r
2786                             }\r
2787                         } else if (gameMode == IcsPlayingBlack) {\r
2788                             if (!WhiteOnMove(forwardMostMove)) {\r
2789                                 if (first.sendTime) {\r
2790                                   if (first.useColors) {\r
2791                                     SendToProgram("white\n", &first);\r
2792                                   }\r
2793                                   SendTimeRemaining(&first, FALSE);\r
2794                                 }\r
2795 #if 0\r
2796                                 if (first.useColors) {\r
2797                                   SendToProgram("black\ngo\n", &first);\r
2798                                 } else {\r
2799                                   SendToProgram("go\n", &first);\r
2800                                 }\r
2801 #else\r
2802                                 if (first.useColors) {\r
2803                                   SendToProgram("black\n", &first);\r
2804                                 }\r
2805                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);\r
2806 #endif\r
2807                                 first.maybeThinking = TRUE;\r
2808                             } else {\r
2809                                 if (first.usePlayother) {\r
2810                                   if (first.sendTime) {\r
2811                                     SendTimeRemaining(&first, FALSE);\r
2812                                   }\r
2813                                   SendToProgram("playother\n", &first);\r
2814                                   firstMove = FALSE;\r
2815                                 } else {\r
2816                                   firstMove = TRUE;\r
2817                                 }\r
2818                             }\r
2819                         }                       \r
2820                     }\r
2821 #endif\r
2822                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2823                         /* Moves came from oldmoves or moves command\r
2824                            while we weren't doing anything else.\r
2825                            */\r
2826                         currentMove = forwardMostMove;\r
2827                         ClearHighlights();/*!!could figure this out*/\r
2828                         flipView = appData.flipView;\r
2829                         DrawPosition(FALSE, boards[currentMove]);\r
2830                         DisplayBothClocks();\r
2831                         sprintf(str, "%s vs. %s",\r
2832                                 gameInfo.white, gameInfo.black);\r
2833                         DisplayTitle(str);\r
2834                         gameMode = IcsIdle;\r
2835                     } else {\r
2836                         /* Moves were history of an active game */\r
2837                         if (gameInfo.resultDetails != NULL) {\r
2838                             free(gameInfo.resultDetails);\r
2839                             gameInfo.resultDetails = NULL;\r
2840                         }\r
2841                     }\r
2842                     HistorySet(parseList, backwardMostMove,\r
2843                                forwardMostMove, currentMove-1);\r
2844                     DisplayMove(currentMove - 1);\r
2845                     if (started == STARTED_MOVES) next_out = i;\r
2846                     started = STARTED_NONE;\r
2847                     ics_getting_history = H_FALSE;\r
2848                     break;\r
2849 \r
2850                   case STARTED_OBSERVE:\r
2851                     started = STARTED_NONE;\r
2852                     SendToICS(ics_prefix);\r
2853                     SendToICS("refresh\n");\r
2854                     break;\r
2855 \r
2856                   default:\r
2857                     break;\r
2858                 }\r
2859                 if(bookHit) { // [HGM] book: simulate book reply\r
2860                     static char bookMove[MSG_SIZ]; // a bit generous?\r
2861 \r
2862                     programStats.nodes = programStats.depth = programStats.time = \r
2863                     programStats.score = programStats.got_only_move = 0;\r
2864                     sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
2865 \r
2866                     strcpy(bookMove, "move ");\r
2867                     strcat(bookMove, bookHit);\r
2868                     HandleMachineMove(bookMove, &first);\r
2869                 }\r
2870                 continue;\r
2871             }\r
2872             \r
2873             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2874                  started == STARTED_HOLDINGS ||\r
2875                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2876                 /* Accumulate characters in move list or board */\r
2877                 parse[parse_pos++] = buf[i];\r
2878             }\r
2879             \r
2880             /* Start of game messages.  Mostly we detect start of game\r
2881                when the first board image arrives.  On some versions\r
2882                of the ICS, though, we need to do a "refresh" after starting\r
2883                to observe in order to get the current board right away. */\r
2884             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2885                 started = STARTED_OBSERVE;\r
2886                 continue;\r
2887             }\r
2888 \r
2889             /* Handle auto-observe */\r
2890             if (appData.autoObserve &&\r
2891                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2892                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2893                 char *player;\r
2894                 /* Choose the player that was highlighted, if any. */\r
2895                 if (star_match[0][0] == '\033' ||\r
2896                     star_match[1][0] != '\033') {\r
2897                     player = star_match[0];\r
2898                 } else {\r
2899                     player = star_match[2];\r
2900                 }\r
2901                 sprintf(str, "%sobserve %s\n",\r
2902                         ics_prefix, StripHighlightAndTitle(player));\r
2903                 SendToICS(str);\r
2904 \r
2905                 /* Save ratings from notify string */\r
2906                 strcpy(player1Name, star_match[0]);\r
2907                 player1Rating = string_to_rating(star_match[1]);\r
2908                 strcpy(player2Name, star_match[2]);\r
2909                 player2Rating = string_to_rating(star_match[3]);\r
2910 \r
2911                 if (appData.debugMode)\r
2912                   fprintf(debugFP, \r
2913                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2914                           player1Name, player1Rating,\r
2915                           player2Name, player2Rating);\r
2916 \r
2917                 continue;\r
2918             }\r
2919 \r
2920             /* Deal with automatic examine mode after a game,\r
2921                and with IcsObserving -> IcsExamining transition */\r
2922             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2923                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2924 \r
2925                 int gamenum = atoi(star_match[0]);\r
2926                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2927                     gamenum == ics_gamenum) {\r
2928                     /* We were already playing or observing this game;\r
2929                        no need to refetch history */\r
2930                     gameMode = IcsExamining;\r
2931                     if (pausing) {\r
2932                         pauseExamForwardMostMove = forwardMostMove;\r
2933                     } else if (currentMove < forwardMostMove) {\r
2934                         ForwardInner(forwardMostMove);\r
2935                     }\r
2936                 } else {\r
2937                     /* I don't think this case really can happen */\r
2938                     SendToICS(ics_prefix);\r
2939                     SendToICS("refresh\n");\r
2940                 }\r
2941                 continue;\r
2942             }    \r
2943             \r
2944             /* Error messages */\r
2945             if (ics_user_moved) {\r
2946                 if (looking_at(buf, &i, "Illegal move") ||\r
2947                     looking_at(buf, &i, "Not a legal move") ||\r
2948                     looking_at(buf, &i, "Your king is in check") ||\r
2949                     looking_at(buf, &i, "It isn't your turn") ||\r
2950                     looking_at(buf, &i, "It is not your move")) {\r
2951                     /* Illegal move */\r
2952                     ics_user_moved = 0;\r
2953                     if (forwardMostMove > backwardMostMove) {\r
2954                         currentMove = --forwardMostMove;\r
2955                         DisplayMove(currentMove - 1); /* before DMError */\r
2956                         DisplayMoveError(_("Illegal move (rejected by ICS)"));\r
2957                         DrawPosition(FALSE, boards[currentMove]);\r
2958                         SwitchClocks();\r
2959                         DisplayBothClocks();\r
2960                     }\r
2961                     continue;\r
2962                 }\r
2963             }\r
2964 \r
2965             if (looking_at(buf, &i, "still have time") ||\r
2966                 looking_at(buf, &i, "not out of time") ||\r
2967                 looking_at(buf, &i, "either player is out of time") ||\r
2968                 looking_at(buf, &i, "has timeseal; checking")) {\r
2969                 /* We must have called his flag a little too soon */\r
2970                 whiteFlag = blackFlag = FALSE;\r
2971                 continue;\r
2972             }\r
2973 \r
2974             if (looking_at(buf, &i, "added * seconds to") ||\r
2975                 looking_at(buf, &i, "seconds were added to")) {\r
2976                 /* Update the clocks */\r
2977                 SendToICS(ics_prefix);\r
2978                 SendToICS("refresh\n");\r
2979                 continue;\r
2980             }\r
2981 \r
2982             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2983                 ics_clock_paused = TRUE;\r
2984                 StopClocks();\r
2985                 continue;\r
2986             }\r
2987 \r
2988             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2989                 ics_clock_paused = FALSE;\r
2990                 StartClocks();\r
2991                 continue;\r
2992             }\r
2993 \r
2994             /* Grab player ratings from the Creating: message.\r
2995                Note we have to check for the special case when\r
2996                the ICS inserts things like [white] or [black]. */\r
2997             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
2998                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
2999                 /* star_matches:\r
3000                    0    player 1 name (not necessarily white)\r
3001                    1    player 1 rating\r
3002                    2    empty, white, or black (IGNORED)\r
3003                    3    player 2 name (not necessarily black)\r
3004                    4    player 2 rating\r
3005                    \r
3006                    The names/ratings are sorted out when the game\r
3007                    actually starts (below).\r
3008                 */\r
3009                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
3010                 player1Rating = string_to_rating(star_match[1]);\r
3011                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
3012                 player2Rating = string_to_rating(star_match[4]);\r
3013 \r
3014                 if (appData.debugMode)\r
3015                   fprintf(debugFP, \r
3016                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
3017                           player1Name, player1Rating,\r
3018                           player2Name, player2Rating);\r
3019 \r
3020                 continue;\r
3021             }\r
3022             \r
3023             /* Improved generic start/end-of-game messages */\r
3024             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
3025                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
3026                 /* If tkind == 0: */\r
3027                 /* star_match[0] is the game number */\r
3028                 /*           [1] is the white player's name */\r
3029                 /*           [2] is the black player's name */\r
3030                 /* For end-of-game: */\r
3031                 /*           [3] is the reason for the game end */\r
3032                 /*           [4] is a PGN end game-token, preceded by " " */\r
3033                 /* For start-of-game: */\r
3034                 /*           [3] begins with "Creating" or "Continuing" */\r
3035                 /*           [4] is " *" or empty (don't care). */\r
3036                 int gamenum = atoi(star_match[0]);\r
3037                 char *whitename, *blackname, *why, *endtoken;\r
3038                 ChessMove endtype = (ChessMove) 0;\r
3039 \r
3040                 if (tkind == 0) {\r
3041                   whitename = star_match[1];\r
3042                   blackname = star_match[2];\r
3043                   why = star_match[3];\r
3044                   endtoken = star_match[4];\r
3045                 } else {\r
3046                   whitename = star_match[1];\r
3047                   blackname = star_match[3];\r
3048                   why = star_match[5];\r
3049                   endtoken = star_match[6];\r
3050                 }\r
3051 \r
3052                 /* Game start messages */\r
3053                 if (strncmp(why, "Creating ", 9) == 0 ||\r
3054                     strncmp(why, "Continuing ", 11) == 0) {\r
3055                     gs_gamenum = gamenum;\r
3056                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
3057 #if ZIPPY\r
3058                     if (appData.zippyPlay) {\r
3059                         ZippyGameStart(whitename, blackname);\r
3060                     }\r
3061 #endif /*ZIPPY*/\r
3062                     continue;\r
3063                 }\r
3064 \r
3065                 /* Game end messages */\r
3066                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
3067                     ics_gamenum != gamenum) {\r
3068                     continue;\r
3069                 }\r
3070                 while (endtoken[0] == ' ') endtoken++;\r
3071                 switch (endtoken[0]) {\r
3072                   case '*':\r
3073                   default:\r
3074                     endtype = GameUnfinished;\r
3075                     break;\r
3076                   case '0':\r
3077                     endtype = BlackWins;\r
3078                     break;\r
3079                   case '1':\r
3080                     if (endtoken[1] == '/')\r
3081                       endtype = GameIsDrawn;\r
3082                     else\r
3083                       endtype = WhiteWins;\r
3084                     break;\r
3085                 }\r
3086                 GameEnds(endtype, why, GE_ICS);\r
3087 #if ZIPPY\r
3088                 if (appData.zippyPlay && first.initDone) {\r
3089                     ZippyGameEnd(endtype, why);\r
3090                     if (first.pr == NULL) {\r
3091                       /* Start the next process early so that we'll\r
3092                          be ready for the next challenge */\r
3093                       StartChessProgram(&first);\r
3094                     }\r
3095                     /* Send "new" early, in case this command takes\r
3096                        a long time to finish, so that we'll be ready\r
3097                        for the next challenge. */\r
3098                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'\r
3099                     Reset(TRUE, TRUE);\r
3100                 }\r
3101 #endif /*ZIPPY*/\r
3102                 continue;\r
3103             }\r
3104 \r
3105             if (looking_at(buf, &i, "Removing game * from observation") ||\r
3106                 looking_at(buf, &i, "no longer observing game *") ||\r
3107                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
3108                 if (gameMode == IcsObserving &&\r
3109                     atoi(star_match[0]) == ics_gamenum)\r
3110                   {\r
3111                       /* icsEngineAnalyze */\r
3112                       if (appData.icsEngineAnalyze) {\r
3113                             ExitAnalyzeMode();\r
3114                             ModeHighlight();\r
3115                       }\r
3116                       StopClocks();\r
3117                       gameMode = IcsIdle;\r
3118                       ics_gamenum = -1;\r
3119                       ics_user_moved = FALSE;\r
3120                   }\r
3121                 continue;\r
3122             }\r
3123 \r
3124             if (looking_at(buf, &i, "no longer examining game *")) {\r
3125                 if (gameMode == IcsExamining &&\r
3126                     atoi(star_match[0]) == ics_gamenum)\r
3127                   {\r
3128                       gameMode = IcsIdle;\r
3129                       ics_gamenum = -1;\r
3130                       ics_user_moved = FALSE;\r
3131                   }\r
3132                 continue;\r
3133             }\r
3134 \r
3135             /* Advance leftover_start past any newlines we find,\r
3136                so only partial lines can get reparsed */\r
3137             if (looking_at(buf, &i, "\n")) {\r
3138                 prevColor = curColor;\r
3139                 if (curColor != ColorNormal) {\r
3140                     if (oldi > next_out) {\r
3141                         SendToPlayer(&buf[next_out], oldi - next_out);\r
3142                         next_out = oldi;\r
3143                     }\r
3144                     Colorize(ColorNormal, FALSE);\r
3145                     curColor = ColorNormal;\r
3146                 }\r
3147                 if (started == STARTED_BOARD) {\r
3148                     started = STARTED_NONE;\r
3149                     parse[parse_pos] = NULLCHAR;\r
3150                     ParseBoard12(parse);\r
3151                     ics_user_moved = 0;\r
3152 \r
3153                     /* Send premove here */\r
3154                     if (appData.premove) {\r
3155                       char str[MSG_SIZ];\r
3156                       if (currentMove == 0 &&\r
3157                           gameMode == IcsPlayingWhite &&\r
3158                           appData.premoveWhite) {\r
3159                         sprintf(str, "%s%s\n", ics_prefix,\r
3160                                 appData.premoveWhiteText);\r
3161                         if (appData.debugMode)\r
3162                           fprintf(debugFP, "Sending premove:\n");\r
3163                         SendToICS(str);\r
3164                       } else if (currentMove == 1 &&\r
3165                                  gameMode == IcsPlayingBlack &&\r
3166                                  appData.premoveBlack) {\r
3167                         sprintf(str, "%s%s\n", ics_prefix,\r
3168                                 appData.premoveBlackText);\r
3169                         if (appData.debugMode)\r
3170                           fprintf(debugFP, "Sending premove:\n");\r
3171                         SendToICS(str);\r
3172                       } else if (gotPremove) {\r
3173                         gotPremove = 0;\r
3174                         ClearPremoveHighlights();\r
3175                         if (appData.debugMode)\r
3176                           fprintf(debugFP, "Sending premove:\n");\r
3177                           UserMoveEvent(premoveFromX, premoveFromY, \r
3178                                         premoveToX, premoveToY, \r
3179                                         premovePromoChar);\r
3180                       }\r
3181                     }\r
3182 \r
3183                     /* Usually suppress following prompt */\r
3184                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
3185                         if (looking_at(buf, &i, "*% ")) {\r
3186                             savingComment = FALSE;\r
3187                         }\r
3188                     }\r
3189                     next_out = i;\r
3190                 } else if (started == STARTED_HOLDINGS) {\r
3191                     int gamenum;\r
3192                     char new_piece[MSG_SIZ];\r
3193                     started = STARTED_NONE;\r
3194                     parse[parse_pos] = NULLCHAR;\r
3195                     if (appData.debugMode)\r
3196                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
3197                                                         parse, currentMove);\r
3198                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
3199                         gamenum == ics_gamenum) {\r
3200                         if (gameInfo.variant == VariantNormal) {\r
3201                           /* [HGM] We seem to switch variant during a game!\r
3202                            * Presumably no holdings were displayed, so we have\r
3203                            * to move the position two files to the right to\r
3204                            * create room for them!\r
3205                            */\r
3206                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
3207                           /* Get a move list just to see the header, which\r
3208                              will tell us whether this is really bug or zh */\r
3209                           if (ics_getting_history == H_FALSE) {\r
3210                             ics_getting_history = H_REQUESTED;\r
3211                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3212                             SendToICS(str);\r
3213                           }\r
3214                         }\r
3215                         new_piece[0] = NULLCHAR;\r
3216                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
3217                                &gamenum, white_holding, black_holding,\r
3218                                new_piece);\r
3219                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
3220                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
3221                         /* [HGM] copy holdings to board holdings area */\r
3222                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
3223                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
3224 #if ZIPPY\r
3225                         if (appData.zippyPlay && first.initDone) {\r
3226                             ZippyHoldings(white_holding, black_holding,\r
3227                                           new_piece);\r
3228                         }\r
3229 #endif /*ZIPPY*/\r
3230                         if (tinyLayout || smallLayout) {\r
3231                             char wh[16], bh[16];\r
3232                             PackHolding(wh, white_holding);\r
3233                             PackHolding(bh, black_holding);\r
3234                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
3235                                     gameInfo.white, gameInfo.black);\r
3236                         } else {\r
3237                             sprintf(str, "%s [%s] vs. %s [%s]",\r
3238                                     gameInfo.white, white_holding,\r
3239                                     gameInfo.black, black_holding);\r
3240                         }\r
3241 \r
3242                         DrawPosition(FALSE, boards[currentMove]);\r
3243                         DisplayTitle(str);\r
3244                     }\r
3245                     /* Suppress following prompt */\r
3246                     if (looking_at(buf, &i, "*% ")) {\r
3247                         savingComment = FALSE;\r
3248                     }\r
3249                     next_out = i;\r
3250                 }\r
3251                 continue;\r
3252             }\r
3253 \r
3254             i++;                /* skip unparsed character and loop back */\r
3255         }\r
3256         \r
3257         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window\r
3258             started != STARTED_HOLDINGS && i > next_out) {\r
3259             SendToPlayer(&buf[next_out], i - next_out);\r
3260             next_out = i;\r
3261         }\r
3262         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above\r
3263         \r
3264         leftover_len = buf_len - leftover_start;\r
3265         /* if buffer ends with something we couldn't parse,\r
3266            reparse it after appending the next read */\r
3267         \r
3268     } else if (count == 0) {\r
3269         RemoveInputSource(isr);\r
3270         DisplayFatalError(_("Connection closed by ICS"), 0, 0);\r
3271     } else {\r
3272         DisplayFatalError(_("Error reading from ICS"), error, 1);\r
3273     }\r
3274 }\r
3275 \r
3276 \r
3277 /* Board style 12 looks like this:\r
3278    \r
3279    <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
3280    \r
3281  * The "<12> " is stripped before it gets to this routine.  The two\r
3282  * trailing 0's (flip state and clock ticking) are later addition, and\r
3283  * some chess servers may not have them, or may have only the first.\r
3284  * Additional trailing fields may be added in the future.  \r
3285  */\r
3286 \r
3287 #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
3288 \r
3289 #define RELATION_OBSERVING_PLAYED    0\r
3290 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
3291 #define RELATION_PLAYING_MYMOVE      1\r
3292 #define RELATION_PLAYING_NOTMYMOVE  -1\r
3293 #define RELATION_EXAMINING           2\r
3294 #define RELATION_ISOLATED_BOARD     -3\r
3295 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
3296 \r
3297 void\r
3298 ParseBoard12(string)\r
3299      char *string;\r
3300\r
3301     GameMode newGameMode;\r
3302     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;\r
3303     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;\r
3304     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
3305     char to_play, board_chars[200];\r
3306     char move_str[500], str[500], elapsed_time[500];\r
3307     char black[32], white[32];\r
3308     Board board;\r
3309     int prevMove = currentMove;\r
3310     int ticking = 2;\r
3311     ChessMove moveType;\r
3312     int fromX, fromY, toX, toY;\r
3313     char promoChar;\r
3314     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */\r
3315     char *bookHit = NULL; // [HGM] book\r
3316 \r
3317     fromX = fromY = toX = toY = -1;\r
3318     \r
3319     newGame = FALSE;\r
3320 \r
3321     if (appData.debugMode)\r
3322       fprintf(debugFP, _("Parsing board: %s\n"), string);\r
3323 \r
3324     move_str[0] = NULLCHAR;\r
3325     elapsed_time[0] = NULLCHAR;\r
3326     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */\r
3327         int  i = 0, j;\r
3328         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {\r
3329             if(string[i] == ' ') { ranks++; files = 0; }\r
3330             else files++;\r
3331             i++;\r
3332         }\r
3333         for(j = 0; j <i; j++) board_chars[j] = string[j];\r
3334         board_chars[i] = '\0';\r
3335         string += i + 1;\r
3336     }\r
3337     n = sscanf(string, PATTERN, &to_play, &double_push,\r
3338                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
3339                &gamenum, white, black, &relation, &basetime, &increment,\r
3340                &white_stren, &black_stren, &white_time, &black_time,\r
3341                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
3342                &ticking);\r
3343 \r
3344     if (n < 21) {\r
3345         sprintf(str, _("Failed to parse board string:\n\"%s\""), string);\r
3346         DisplayError(str, 0);\r
3347         return;\r
3348     }\r
3349 \r
3350     /* Convert the move number to internal form */\r
3351     moveNum = (moveNum - 1) * 2;\r
3352     if (to_play == 'B') moveNum++;\r
3353     if (moveNum >= MAX_MOVES) {\r
3354       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
3355                         0, 1);\r
3356       return;\r
3357     }\r
3358     \r
3359     switch (relation) {\r
3360       case RELATION_OBSERVING_PLAYED:\r
3361       case RELATION_OBSERVING_STATIC:\r
3362         if (gamenum == -1) {\r
3363             /* Old ICC buglet */\r
3364             relation = RELATION_OBSERVING_STATIC;\r
3365         }\r
3366         newGameMode = IcsObserving;\r
3367         break;\r
3368       case RELATION_PLAYING_MYMOVE:\r
3369       case RELATION_PLAYING_NOTMYMOVE:\r
3370         newGameMode =\r
3371           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3372             IcsPlayingWhite : IcsPlayingBlack;\r
3373         break;\r
3374       case RELATION_EXAMINING:\r
3375         newGameMode = IcsExamining;\r
3376         break;\r
3377       case RELATION_ISOLATED_BOARD:\r
3378       default:\r
3379         /* Just display this board.  If user was doing something else,\r
3380            we will forget about it until the next board comes. */ \r
3381         newGameMode = IcsIdle;\r
3382         break;\r
3383       case RELATION_STARTING_POSITION:\r
3384         newGameMode = gameMode;\r
3385         break;\r
3386     }\r
3387     \r
3388     /* Modify behavior for initial board display on move listing\r
3389        of wild games.\r
3390        */\r
3391     switch (ics_getting_history) {\r
3392       case H_FALSE:\r
3393       case H_REQUESTED:\r
3394         break;\r
3395       case H_GOT_REQ_HEADER:\r
3396       case H_GOT_UNREQ_HEADER:\r
3397         /* This is the initial position of the current game */\r
3398         gamenum = ics_gamenum;\r
3399         moveNum = 0;            /* old ICS bug workaround */\r
3400         if (to_play == 'B') {\r
3401           startedFromSetupPosition = TRUE;\r
3402           blackPlaysFirst = TRUE;\r
3403           moveNum = 1;\r
3404           if (forwardMostMove == 0) forwardMostMove = 1;\r
3405           if (backwardMostMove == 0) backwardMostMove = 1;\r
3406           if (currentMove == 0) currentMove = 1;\r
3407         }\r
3408         newGameMode = gameMode;\r
3409         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3410         break;\r
3411       case H_GOT_UNWANTED_HEADER:\r
3412         /* This is an initial board that we don't want */\r
3413         return;\r
3414       case H_GETTING_MOVES:\r
3415         /* Should not happen */\r
3416         DisplayError(_("Error gathering move list: extra board"), 0);\r
3417         ics_getting_history = H_FALSE;\r
3418         return;\r
3419     }\r
3420     \r
3421     /* Take action if this is the first board of a new game, or of a\r
3422        different game than is currently being displayed.  */\r
3423     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3424         relation == RELATION_ISOLATED_BOARD) {\r
3425         \r
3426         /* Forget the old game and get the history (if any) of the new one */\r
3427         if (gameMode != BeginningOfGame) {\r
3428           Reset(FALSE, TRUE);\r
3429         }\r
3430         newGame = TRUE;\r
3431         if (appData.autoRaiseBoard) BoardToTop();\r
3432         prevMove = -3;\r
3433         if (gamenum == -1) {\r
3434             newGameMode = IcsIdle;\r
3435         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3436                    appData.getMoveList) {\r
3437             /* Need to get game history */\r
3438             ics_getting_history = H_REQUESTED;\r
3439             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3440             SendToICS(str);\r
3441         }\r
3442         \r
3443         /* Initially flip the board to have black on the bottom if playing\r
3444            black or if the ICS flip flag is set, but let the user change\r
3445            it with the Flip View button. */\r
3446         flipView = appData.autoFlipView ? \r
3447           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3448           appData.flipView;\r
3449         \r
3450         /* Done with values from previous mode; copy in new ones */\r
3451         gameMode = newGameMode;\r
3452         ModeHighlight();\r
3453         ics_gamenum = gamenum;\r
3454         if (gamenum == gs_gamenum) {\r
3455             int klen = strlen(gs_kind);\r
3456             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3457             sprintf(str, "ICS %s", gs_kind);\r
3458             gameInfo.event = StrSave(str);\r
3459         } else {\r
3460             gameInfo.event = StrSave("ICS game");\r
3461         }\r
3462         gameInfo.site = StrSave(appData.icsHost);\r
3463         gameInfo.date = PGNDate();\r
3464         gameInfo.round = StrSave("-");\r
3465         gameInfo.white = StrSave(white);\r
3466         gameInfo.black = StrSave(black);\r
3467         timeControl = basetime * 60 * 1000;\r
3468         timeControl_2 = 0;\r
3469         timeIncrement = increment * 1000;\r
3470         movesPerSession = 0;\r
3471         gameInfo.timeControl = TimeControlTagValue();\r
3472         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3473   if (appData.debugMode) {\r
3474     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3475     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3476     setbuf(debugFP, NULL);\r
3477   }\r
3478 \r
3479         gameInfo.outOfBook = NULL;\r
3480         \r
3481         /* Do we have the ratings? */\r
3482         if (strcmp(player1Name, white) == 0 &&\r
3483             strcmp(player2Name, black) == 0) {\r
3484             if (appData.debugMode)\r
3485               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3486                       player1Rating, player2Rating);\r
3487             gameInfo.whiteRating = player1Rating;\r
3488             gameInfo.blackRating = player2Rating;\r
3489         } else if (strcmp(player2Name, white) == 0 &&\r
3490                    strcmp(player1Name, black) == 0) {\r
3491             if (appData.debugMode)\r
3492               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3493                       player2Rating, player1Rating);\r
3494             gameInfo.whiteRating = player2Rating;\r
3495             gameInfo.blackRating = player1Rating;\r
3496         }\r
3497         player1Name[0] = player2Name[0] = NULLCHAR;\r
3498 \r
3499         /* Silence shouts if requested */\r
3500         if (appData.quietPlay &&\r
3501             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3502             SendToICS(ics_prefix);\r
3503             SendToICS("set shout 0\n");\r
3504         }\r
3505     }\r
3506     \r
3507     /* Deal with midgame name changes */\r
3508     if (!newGame) {\r
3509         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3510             if (gameInfo.white) free(gameInfo.white);\r
3511             gameInfo.white = StrSave(white);\r
3512         }\r
3513         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3514             if (gameInfo.black) free(gameInfo.black);\r
3515             gameInfo.black = StrSave(black);\r
3516         }\r
3517     }\r
3518     \r
3519     /* Throw away game result if anything actually changes in examine mode */\r
3520     if (gameMode == IcsExamining && !newGame) {\r
3521         gameInfo.result = GameUnfinished;\r
3522         if (gameInfo.resultDetails != NULL) {\r
3523             free(gameInfo.resultDetails);\r
3524             gameInfo.resultDetails = NULL;\r
3525         }\r
3526     }\r
3527     \r
3528     /* In pausing && IcsExamining mode, we ignore boards coming\r
3529        in if they are in a different variation than we are. */\r
3530     if (pauseExamInvalid) return;\r
3531     if (pausing && gameMode == IcsExamining) {\r
3532         if (moveNum <= pauseExamForwardMostMove) {\r
3533             pauseExamInvalid = TRUE;\r
3534             forwardMostMove = pauseExamForwardMostMove;\r
3535             return;\r
3536         }\r
3537     }\r
3538     \r
3539   if (appData.debugMode) {\r
3540     fprintf(debugFP, "load %dx%d board\n", files, ranks);\r
3541   }\r
3542     /* Parse the board */\r
3543     for (k = 0; k < ranks; k++) {\r
3544       for (j = 0; j < files; j++)\r
3545         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);\r
3546       if(gameInfo.holdingsWidth > 1) {\r
3547            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3548            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3549       }\r
3550     }\r
3551     CopyBoard(boards[moveNum], board);\r
3552     if (moveNum == 0) {\r
3553         startedFromSetupPosition =\r
3554           !CompareBoards(board, initialPosition);\r
3555         if(startedFromSetupPosition)\r
3556             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */\r
3557     }\r
3558 \r
3559     /* [HGM] Set castling rights. Take the outermost Rooks,\r
3560        to make it also work for FRC opening positions. Note that board12\r
3561        is really defective for later FRC positions, as it has no way to\r
3562        indicate which Rook can castle if they are on the same side of King.\r
3563        For the initial position we grant rights to the outermost Rooks,\r
3564        and remember thos rights, and we then copy them on positions\r
3565        later in an FRC game. This means WB might not recognize castlings with\r
3566        Rooks that have moved back to their original position as illegal,\r
3567        but in ICS mode that is not its job anyway.\r
3568     */\r
3569     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)\r
3570     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;\r
3571 \r
3572         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3573             if(board[0][i] == WhiteRook) j = i;\r
3574         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3575         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3576             if(board[0][i] == WhiteRook) j = i;\r
3577         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3578         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3579             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3580         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3581         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3582             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3583         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3584 \r
3585         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }\r
3586         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3587             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;\r
3588         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3589             if(board[BOARD_HEIGHT-1][k] == bKing)\r
3590                 initialRights[5] = castlingRights[moveNum][5] = k;\r
3591     } else { int r;\r
3592         r = castlingRights[moveNum][0] = initialRights[0];\r
3593         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;\r
3594         r = castlingRights[moveNum][1] = initialRights[1];\r
3595         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;\r
3596         r = castlingRights[moveNum][3] = initialRights[3];\r
3597         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;\r
3598         r = castlingRights[moveNum][4] = initialRights[4];\r
3599         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;\r
3600         /* wildcastle kludge: always assume King has rights */\r
3601         r = castlingRights[moveNum][2] = initialRights[2];\r
3602         r = castlingRights[moveNum][5] = initialRights[5];\r
3603     }\r
3604     /* [HGM] e.p. rights. Assume that ICS sends file number here? */\r
3605     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;\r
3606 \r
3607     \r
3608     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3609         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3610         /* This was an initial position from a move list, not\r
3611            the current position */\r
3612         return;\r
3613     }\r
3614     \r
3615     /* Update currentMove and known move number limits */\r
3616     newMove = newGame || moveNum > forwardMostMove;\r
3617 \r
3618     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */\r
3619     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {\r
3620         takeback = forwardMostMove - moveNum;\r
3621         for (i = 0; i < takeback; i++) {\r
3622              if (appData.debugMode) fprintf(debugFP, "take back move\n");\r
3623              SendToProgram("undo\n", &first);\r
3624         }\r
3625     }\r
3626 \r
3627     if (newGame) {\r
3628         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3629         if (gameMode == IcsExamining && moveNum == 0) {\r
3630           /* Workaround for ICS limitation: we are not told the wild\r
3631              type when starting to examine a game.  But if we ask for\r
3632              the move list, the move list header will tell us */\r
3633             ics_getting_history = H_REQUESTED;\r
3634             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3635             SendToICS(str);\r
3636         }\r
3637     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3638                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3639         forwardMostMove = moveNum;\r
3640         if (!pausing || currentMove > forwardMostMove)\r
3641           currentMove = forwardMostMove;\r
3642     } else {\r
3643         /* New part of history that is not contiguous with old part */ \r
3644         if (pausing && gameMode == IcsExamining) {\r
3645             pauseExamInvalid = TRUE;\r
3646             forwardMostMove = pauseExamForwardMostMove;\r
3647             return;\r
3648         }\r
3649         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3650         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3651             ics_getting_history = H_REQUESTED;\r
3652             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3653             SendToICS(str);\r
3654         }\r
3655     }\r
3656     \r
3657     /* Update the clocks */\r
3658     if (strchr(elapsed_time, '.')) {\r
3659       /* Time is in ms */\r
3660       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3661       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3662     } else {\r
3663       /* Time is in seconds */\r
3664       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3665       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3666     }\r
3667       \r
3668 \r
3669 #if ZIPPY\r
3670     if (appData.zippyPlay && newGame &&\r
3671         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3672         gameMode != IcsExamining)\r
3673       ZippyFirstBoard(moveNum, basetime, increment);\r
3674 #endif\r
3675     \r
3676     /* Put the move on the move list, first converting\r
3677        to canonical algebraic form. */\r
3678     if (moveNum > 0) {\r
3679   if (appData.debugMode) {\r
3680     if (appData.debugMode) { int f = forwardMostMove;\r
3681         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,\r
3682                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
3683     }\r
3684     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3685     fprintf(debugFP, "moveNum = %d\n", moveNum);\r
3686     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3687     setbuf(debugFP, NULL);\r
3688   }\r
3689         if (moveNum <= backwardMostMove) {\r
3690             /* We don't know what the board looked like before\r
3691                this move.  Punt. */\r
3692             strcpy(parseList[moveNum - 1], move_str);\r
3693             strcat(parseList[moveNum - 1], " ");\r
3694             strcat(parseList[moveNum - 1], elapsed_time);\r
3695             moveList[moveNum - 1][0] = NULLCHAR;\r
3696         } else if (strcmp(move_str, "none") == 0) {\r
3697             // [HGM] long SAN: swapped order; test for 'none' before parsing move\r
3698             /* Again, we don't know what the board looked like;\r
3699                this is really the start of the game. */\r
3700             parseList[moveNum - 1][0] = NULLCHAR;\r
3701             moveList[moveNum - 1][0] = NULLCHAR;\r
3702             backwardMostMove = moveNum;\r
3703             startedFromSetupPosition = TRUE;\r
3704             fromX = fromY = toX = toY = -1;\r
3705         } else {\r
3706           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. \r
3707           //                 So we parse the long-algebraic move string in stead of the SAN move\r
3708           int valid; char buf[MSG_SIZ], *prom;\r
3709 \r
3710           // str looks something like "Q/a1-a2"; kill the slash\r
3711           if(str[1] == '/') \r
3712                 sprintf(buf, "%c%s", str[0], str+2);\r
3713           else  strcpy(buf, str); // might be castling\r
3714           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) \r
3715                 strcat(buf, prom); // long move lacks promo specification!\r
3716           if(!appData.testLegality) {\r
3717                 if(appData.debugMode) \r
3718                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);\r
3719                 strcpy(move_str, buf);\r
3720           }\r
3721           valid = ParseOneMove(move_str, moveNum - 1, &moveType,\r
3722                                 &fromX, &fromY, &toX, &toY, &promoChar)\r
3723                || ParseOneMove(buf, moveNum - 1, &moveType,\r
3724                                 &fromX, &fromY, &toX, &toY, &promoChar);\r
3725           // end of long SAN patch\r
3726           if (valid) {\r
3727             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3728                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3729                                      fromY, fromX, toY, toX, promoChar,\r
3730                                      parseList[moveNum-1]);\r
3731             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3732                              castlingRights[moveNum]) ) {\r
3733               case MT_NONE:\r
3734               case MT_STALEMATE:\r
3735               default:\r
3736                 break;\r
3737               case MT_CHECK:\r
3738                 if(gameInfo.variant != VariantShogi)\r
3739                     strcat(parseList[moveNum - 1], "+");\r
3740                 break;\r
3741               case MT_CHECKMATE:\r
3742                 strcat(parseList[moveNum - 1], "#");\r
3743                 break;\r
3744             }\r
3745             strcat(parseList[moveNum - 1], " ");\r
3746             strcat(parseList[moveNum - 1], elapsed_time);\r
3747             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3748             strcpy(moveList[moveNum - 1], currentMoveString);\r
3749             strcat(moveList[moveNum - 1], "\n");\r
3750           } else {\r
3751             /* Move from ICS was illegal!?  Punt. */\r
3752   if (appData.debugMode) {\r
3753     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3754     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3755   }\r
3756 #if 0\r
3757             if (appData.testLegality && appData.debugMode) {\r
3758                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3759                 DisplayError(str, 0);\r
3760             }\r
3761 #endif\r
3762             strcpy(parseList[moveNum - 1], move_str);\r
3763             strcat(parseList[moveNum - 1], " ");\r
3764             strcat(parseList[moveNum - 1], elapsed_time);\r
3765             moveList[moveNum - 1][0] = NULLCHAR;\r
3766             fromX = fromY = toX = toY = -1;\r
3767           }\r
3768         }\r
3769   if (appData.debugMode) {\r
3770     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3771     setbuf(debugFP, NULL);\r
3772   }\r
3773 \r
3774 #if ZIPPY\r
3775         /* Send move to chess program (BEFORE animating it). */\r
3776         if (appData.zippyPlay && !newGame && newMove && \r
3777            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3778 \r
3779             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3780                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3781                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3782                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),\r
3783                             move_str);\r
3784                     DisplayError(str, 0);\r
3785                 } else {\r
3786                     if (first.sendTime) {\r
3787                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3788                     }\r
3789                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book\r
3790                     if (firstMove && !bookHit) {\r
3791                         firstMove = FALSE;\r
3792                         if (first.useColors) {\r
3793                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3794                                         "white\ngo\n" :\r
3795                                         "black\ngo\n", &first);\r
3796                         } else {\r
3797                           SendToProgram("go\n", &first);\r
3798                         }\r
3799                         first.maybeThinking = TRUE;\r
3800                     }\r
3801                 }\r
3802             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3803               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3804                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);\r
3805                 DisplayError(str, 0);\r
3806               } else {\r
3807                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!\r
3808                 SendMoveToProgram(moveNum - 1, &first);\r
3809               }\r
3810             }\r
3811         }\r
3812 #endif\r
3813     }\r
3814 \r
3815     if (moveNum > 0 && !gotPremove) {\r
3816         /* If move comes from a remote source, animate it.  If it\r
3817            isn't remote, it will have already been animated. */\r
3818         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3819             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3820         }\r
3821         if (!pausing && appData.highlightLastMove) {\r
3822             SetHighlights(fromX, fromY, toX, toY);\r
3823         }\r
3824     }\r
3825     \r
3826     /* Start the clocks */\r
3827     whiteFlag = blackFlag = FALSE;\r
3828     appData.clockMode = !(basetime == 0 && increment == 0);\r
3829     if (ticking == 0) {\r
3830       ics_clock_paused = TRUE;\r
3831       StopClocks();\r
3832     } else if (ticking == 1) {\r
3833       ics_clock_paused = FALSE;\r
3834     }\r
3835     if (gameMode == IcsIdle ||\r
3836         relation == RELATION_OBSERVING_STATIC ||\r
3837         relation == RELATION_EXAMINING ||\r
3838         ics_clock_paused)\r
3839       DisplayBothClocks();\r
3840     else\r
3841       StartClocks();\r
3842     \r
3843     /* Display opponents and material strengths */\r
3844     if (gameInfo.variant != VariantBughouse &&\r
3845         gameInfo.variant != VariantCrazyhouse) {\r
3846         if (tinyLayout || smallLayout) {\r
3847             if(gameInfo.variant == VariantNormal)\r
3848                 sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3849                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3850                     basetime, increment);\r
3851             else\r
3852                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", \r
3853                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3854                     basetime, increment, (int) gameInfo.variant);\r
3855         } else {\r
3856             if(gameInfo.variant == VariantNormal)\r
3857                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3858                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3859                     basetime, increment);\r
3860             else\r
3861                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", \r
3862                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3863                     basetime, increment, VariantName(gameInfo.variant));\r
3864         }\r
3865         DisplayTitle(str);\r
3866   if (appData.debugMode) {\r
3867     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);\r
3868   }\r
3869     }\r
3870 \r
3871    \r
3872     /* Display the board */\r
3873     if (!pausing) {\r
3874       \r
3875       if (appData.premove)\r
3876           if (!gotPremove || \r
3877              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3878              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3879               ClearPremoveHighlights();\r
3880 \r
3881       DrawPosition(FALSE, boards[currentMove]);\r
3882       DisplayMove(moveNum - 1);\r
3883       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3884         RingBell();\r
3885     }\r
3886 \r
3887     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3888 #if ZIPPY\r
3889     if(bookHit) { // [HGM] book: simulate book reply\r
3890         static char bookMove[MSG_SIZ]; // a bit generous?\r
3891 \r
3892         programStats.nodes = programStats.depth = programStats.time = \r
3893         programStats.score = programStats.got_only_move = 0;\r
3894         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
3895 \r
3896         strcpy(bookMove, "move ");\r
3897         strcat(bookMove, bookHit);\r
3898         HandleMachineMove(bookMove, &first);\r
3899     }\r
3900 #endif\r
3901 }\r
3902 \r
3903 void\r
3904 GetMoveListEvent()\r
3905 {\r
3906     char buf[MSG_SIZ];\r
3907     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3908         ics_getting_history = H_REQUESTED;\r
3909         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3910         SendToICS(buf);\r
3911     }\r
3912 }\r
3913 \r
3914 void\r
3915 AnalysisPeriodicEvent(force)\r
3916      int force;\r
3917 {\r
3918     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3919          && !force) || !appData.periodicUpdates)\r
3920       return;\r
3921 \r
3922     /* Send . command to Crafty to collect stats */\r
3923     SendToProgram(".\n", &first);\r
3924 \r
3925     /* Don't send another until we get a response (this makes\r
3926        us stop sending to old Crafty's which don't understand\r
3927        the "." command (sending illegal cmds resets node count & time,\r
3928        which looks bad)) */\r
3929     programStats.ok_to_send = 0;\r
3930 }\r
3931 \r
3932 void\r
3933 SendMoveToProgram(moveNum, cps)\r
3934      int moveNum;\r
3935      ChessProgramState *cps;\r
3936 {\r
3937     char buf[MSG_SIZ];\r
3938 \r
3939     if (cps->useUsermove) {\r
3940       SendToProgram("usermove ", cps);\r
3941     }\r
3942     if (cps->useSAN) {\r
3943       char *space;\r
3944       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3945         int len = space - parseList[moveNum];\r
3946         memcpy(buf, parseList[moveNum], len);\r
3947         buf[len++] = '\n';\r
3948         buf[len] = NULLCHAR;\r
3949       } else {\r
3950         sprintf(buf, "%s\n", parseList[moveNum]);\r
3951       }\r
3952       SendToProgram(buf, cps);\r
3953     } else {\r
3954       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */\r
3955         AlphaRank(moveList[moveNum], 4);\r
3956         SendToProgram(moveList[moveNum], cps);\r
3957         AlphaRank(moveList[moveNum], 4); // and back\r
3958       } else\r
3959       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3960        * the engine. It would be nice to have a better way to identify castle \r
3961        * moves here. */\r
3962       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)\r
3963                                                                          && cps->useOOCastle) {\r
3964         int fromX = moveList[moveNum][0] - AAA; \r
3965         int fromY = moveList[moveNum][1] - ONE;\r
3966         int toX = moveList[moveNum][2] - AAA; \r
3967         int toY = moveList[moveNum][3] - ONE;\r
3968         if((boards[moveNum][fromY][fromX] == WhiteKing \r
3969             && boards[moveNum][toY][toX] == WhiteRook)\r
3970            || (boards[moveNum][fromY][fromX] == BlackKing \r
3971                && boards[moveNum][toY][toX] == BlackRook)) {\r
3972           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3973           else SendToProgram("O-O-O\n", cps);\r
3974         }\r
3975         else SendToProgram(moveList[moveNum], cps);\r
3976       }\r
3977       else SendToProgram(moveList[moveNum], cps);\r
3978       /* End of additions by Tord */\r
3979     }\r
3980 \r
3981     /* [HGM] setting up the opening has brought engine in force mode! */\r
3982     /*       Send 'go' if we are in a mode where machine should play. */\r
3983     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
3984         (gameMode == TwoMachinesPlay   ||\r
3985 #ifdef ZIPPY\r
3986          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||\r
3987 #endif\r
3988          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
3989         SendToProgram("go\n", cps);\r
3990   if (appData.debugMode) {\r
3991     fprintf(debugFP, "(extra)\n");\r
3992   }\r
3993     }\r
3994     setboardSpoiledMachineBlack = 0;\r
3995 }\r
3996 \r
3997 void\r
3998 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
3999      ChessMove moveType;\r
4000      int fromX, fromY, toX, toY;\r
4001 {\r
4002     char user_move[MSG_SIZ];\r
4003 \r
4004     switch (moveType) {\r
4005       default:\r
4006         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),\r
4007                 (int)moveType, fromX, fromY, toX, toY);\r
4008         DisplayError(user_move + strlen("say "), 0);\r
4009         break;\r
4010       case WhiteKingSideCastle:\r
4011       case BlackKingSideCastle:\r
4012       case WhiteQueenSideCastleWild:\r
4013       case BlackQueenSideCastleWild:\r
4014       /* PUSH Fabien */\r
4015       case WhiteHSideCastleFR:\r
4016       case BlackHSideCastleFR:\r
4017       /* POP Fabien */\r
4018         sprintf(user_move, "o-o\n");\r
4019         break;\r
4020       case WhiteQueenSideCastle:\r
4021       case BlackQueenSideCastle:\r
4022       case WhiteKingSideCastleWild:\r
4023       case BlackKingSideCastleWild:\r
4024       /* PUSH Fabien */\r
4025       case WhiteASideCastleFR:\r
4026       case BlackASideCastleFR:\r
4027       /* POP Fabien */\r
4028         sprintf(user_move, "o-o-o\n");\r
4029         break;\r
4030       case WhitePromotionQueen:\r
4031       case BlackPromotionQueen:\r
4032       case WhitePromotionRook:\r
4033       case BlackPromotionRook:\r
4034       case WhitePromotionBishop:\r
4035       case BlackPromotionBishop:\r
4036       case WhitePromotionKnight:\r
4037       case BlackPromotionKnight:\r
4038       case WhitePromotionKing:\r
4039       case BlackPromotionKing:\r
4040       case WhitePromotionChancellor:\r
4041       case BlackPromotionChancellor:\r
4042       case WhitePromotionArchbishop:\r
4043       case BlackPromotionArchbishop:\r
4044         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)\r
4045             sprintf(user_move, "%c%c%c%c=%c\n",\r
4046                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4047                 PieceToChar(WhiteFerz));\r
4048         else if(gameInfo.variant == VariantGreat)\r
4049             sprintf(user_move, "%c%c%c%c=%c\n",\r
4050                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4051                 PieceToChar(WhiteMan));\r
4052         else\r
4053             sprintf(user_move, "%c%c%c%c=%c\n",\r
4054                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4055                 PieceToChar(PromoPiece(moveType)));\r
4056         break;\r
4057       case WhiteDrop:\r
4058       case BlackDrop:\r
4059         sprintf(user_move, "%c@%c%c\n",\r
4060                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
4061                 AAA + toX, ONE + toY);\r
4062         break;\r
4063       case NormalMove:\r
4064       case WhiteCapturesEnPassant:\r
4065       case BlackCapturesEnPassant:\r
4066       case IllegalMove:  /* could be a variant we don't quite understand */\r
4067         sprintf(user_move, "%c%c%c%c\n",\r
4068                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
4069         break;\r
4070     }\r
4071     SendToICS(user_move);\r
4072 }\r
4073 \r
4074 void\r
4075 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
4076      int rf, ff, rt, ft;\r
4077      char promoChar;\r
4078      char move[7];\r
4079 {\r
4080     if (rf == DROP_RANK) {\r
4081         sprintf(move, "%c@%c%c\n",\r
4082                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
4083     } else {\r
4084         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
4085             sprintf(move, "%c%c%c%c\n",\r
4086                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
4087         } else {\r
4088             sprintf(move, "%c%c%c%c%c\n",\r
4089                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
4090         }\r
4091     }\r
4092 }\r
4093 \r
4094 void\r
4095 ProcessICSInitScript(f)\r
4096      FILE *f;\r
4097 {\r
4098     char buf[MSG_SIZ];\r
4099 \r
4100     while (fgets(buf, MSG_SIZ, f)) {\r
4101         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
4102     }\r
4103 \r
4104     fclose(f);\r
4105 }\r
4106 \r
4107 \r
4108 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
4109 void\r
4110 AlphaRank(char *move, int n)\r
4111 {\r
4112 //    char *p = move, c; int x, y;\r
4113 \r
4114     if (appData.debugMode) {\r
4115         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
4116     }\r
4117 \r
4118     if(move[1]=='*' && \r
4119        move[2]>='0' && move[2]<='9' &&\r
4120        move[3]>='a' && move[3]<='x'    ) {\r
4121         move[1] = '@';\r
4122         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4123         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4124     } else\r
4125     if(move[0]>='0' && move[0]<='9' &&\r
4126        move[1]>='a' && move[1]<='x' &&\r
4127        move[2]>='0' && move[2]<='9' &&\r
4128        move[3]>='a' && move[3]<='x'    ) {\r
4129         /* input move, Shogi -> normal */\r
4130         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;\r
4131         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\r
4132         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4133         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4134     } else\r
4135     if(move[1]=='@' &&\r
4136        move[3]>='0' && move[3]<='9' &&\r
4137        move[2]>='a' && move[2]<='x'    ) {\r
4138         move[1] = '*';\r
4139         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4140         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4141     } else\r
4142     if(\r
4143        move[0]>='a' && move[0]<='x' &&\r
4144        move[3]>='0' && move[3]<='9' &&\r
4145        move[2]>='a' && move[2]<='x'    ) {\r
4146          /* output move, normal -> Shogi */\r
4147         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
4148         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
4149         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4150         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4151         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
4152     }\r
4153     if (appData.debugMode) {\r
4154         fprintf(debugFP, "   out = '%s'\n", move);\r
4155     }\r
4156 }\r
4157 \r
4158 /* Parser for moves from gnuchess, ICS, or user typein box */\r
4159 Boolean\r
4160 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
4161      char *move;\r
4162      int moveNum;\r
4163      ChessMove *moveType;\r
4164      int *fromX, *fromY, *toX, *toY;\r
4165      char *promoChar;\r
4166 {       \r
4167     if (appData.debugMode) {\r
4168         fprintf(debugFP, "move to parse: %s\n", move);\r
4169     }\r
4170     *moveType = yylexstr(moveNum, move);\r
4171 \r
4172     switch (*moveType) {\r
4173       case WhitePromotionChancellor:\r
4174       case BlackPromotionChancellor:\r
4175       case WhitePromotionArchbishop:\r
4176       case BlackPromotionArchbishop:\r
4177       case WhitePromotionQueen:\r
4178       case BlackPromotionQueen:\r
4179       case WhitePromotionRook:\r
4180       case BlackPromotionRook:\r
4181       case WhitePromotionBishop:\r
4182       case BlackPromotionBishop:\r
4183       case WhitePromotionKnight:\r
4184       case BlackPromotionKnight:\r
4185       case WhitePromotionKing:\r
4186       case BlackPromotionKing:\r
4187       case NormalMove:\r
4188       case WhiteCapturesEnPassant:\r
4189       case BlackCapturesEnPassant:\r
4190       case WhiteKingSideCastle:\r
4191       case WhiteQueenSideCastle:\r
4192       case BlackKingSideCastle:\r
4193       case BlackQueenSideCastle:\r
4194       case WhiteKingSideCastleWild:\r
4195       case WhiteQueenSideCastleWild:\r
4196       case BlackKingSideCastleWild:\r
4197       case BlackQueenSideCastleWild:\r
4198       /* Code added by Tord: */\r
4199       case WhiteHSideCastleFR:\r
4200       case WhiteASideCastleFR:\r
4201       case BlackHSideCastleFR:\r
4202       case BlackASideCastleFR:\r
4203       /* End of code added by Tord */\r
4204       case IllegalMove:         /* bug or odd chess variant */\r
4205         *fromX = currentMoveString[0] - AAA;\r
4206         *fromY = currentMoveString[1] - ONE;\r
4207         *toX = currentMoveString[2] - AAA;\r
4208         *toY = currentMoveString[3] - ONE;\r
4209         *promoChar = currentMoveString[4];\r
4210         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
4211             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
4212     if (appData.debugMode) {\r
4213         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
4214     }\r
4215             *fromX = *fromY = *toX = *toY = 0;\r
4216             return FALSE;\r
4217         }\r
4218         if (appData.testLegality) {\r
4219           return (*moveType != IllegalMove);\r
4220         } else {\r
4221           return !(fromX == fromY && toX == toY);\r
4222         }\r
4223 \r
4224       case WhiteDrop:\r
4225       case BlackDrop:\r
4226         *fromX = *moveType == WhiteDrop ?\r
4227           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
4228           (int) CharToPiece(ToLower(currentMoveString[0]));\r
4229         *fromY = DROP_RANK;\r
4230         *toX = currentMoveString[2] - AAA;\r
4231         *toY = currentMoveString[3] - ONE;\r
4232         *promoChar = NULLCHAR;\r
4233         return TRUE;\r
4234 \r
4235       case AmbiguousMove:\r
4236       case ImpossibleMove:\r
4237       case (ChessMove) 0:       /* end of file */\r
4238       case ElapsedTime:\r
4239       case Comment:\r
4240       case PGNTag:\r
4241       case NAG:\r
4242       case WhiteWins:\r
4243       case BlackWins:\r
4244       case GameIsDrawn:\r
4245       default:\r
4246     if (appData.debugMode) {\r
4247         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
4248     }\r
4249         /* bug? */\r
4250         *fromX = *fromY = *toX = *toY = 0;\r
4251         *promoChar = NULLCHAR;\r
4252         return FALSE;\r
4253     }\r
4254 }\r
4255 \r
4256 #if 0\r
4257 /* [AS] FRC game initialization */\r
4258 static int FindEmptySquare( Board board, int n )\r
4259 {\r
4260     int i = 0;\r
4261 \r
4262     while( 1 ) {\r
4263         while( board[0][i] != EmptySquare ) i++;\r
4264         if( n == 0 )\r
4265             break;\r
4266         n--;\r
4267         i++;\r
4268     }\r
4269 \r
4270     return i;\r
4271 }\r
4272 \r
4273 static void ShuffleFRC( Board board )\r
4274 {\r
4275     int i;\r
4276 \r
4277     srand( time(0) );\r
4278     \r
4279     for( i=0; i<8; i++ ) {\r
4280         board[0][i] = EmptySquare;\r
4281     }\r
4282 \r
4283     board[0][(rand() % 4)*2  ] = WhiteBishop; /* On dark square */\r
4284     board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */\r
4285     board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;\r
4286     board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;\r
4287     board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;\r
4288     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4289     initialRights[1]  = initialRights[4]  =\r
4290     castlingRights[0][1] = castlingRights[0][4] = i;\r
4291     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4292     initialRights[2]  = initialRights[5]  =\r
4293     castlingRights[0][2] = castlingRights[0][5] = i;\r
4294     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4295     initialRights[0]  = initialRights[3]  =\r
4296     castlingRights[0][0] = castlingRights[0][3] = i;\r
4297 \r
4298     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4299         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4300     }\r
4301 }\r
4302 \r
4303 static unsigned char FRC_KnightTable[10] = {\r
4304     0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33\r
4305 };\r
4306 \r
4307 static void SetupFRC( Board board, int pos_index )\r
4308 {\r
4309     int i;\r
4310     unsigned char knights;\r
4311 \r
4312     /* Bring the position index into a safe range (just in case...) */\r
4313     if( pos_index < 0 ) pos_index = 0;\r
4314 \r
4315     pos_index %= 960;\r
4316 \r
4317     /* Clear the board */\r
4318     for( i=0; i<8; i++ ) {\r
4319         board[0][i] = EmptySquare;\r
4320     }\r
4321 \r
4322     /* Place bishops and queen */\r
4323     board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */\r
4324     pos_index /= 4;\r
4325     \r
4326     board[0][ (pos_index % 4)*2     ] = WhiteBishop; /* On dark square */\r
4327     pos_index /= 4;\r
4328 \r
4329     board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;\r
4330     pos_index /= 6;\r
4331 \r
4332     /* Place knigths */\r
4333     knights = FRC_KnightTable[ pos_index ];\r
4334 \r
4335     board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;\r
4336     board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;\r
4337 \r
4338     /* Place rooks and king */\r
4339     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4340     initialRights[1]  = initialRights[4]  =\r
4341     castlingRights[0][1] = castlingRights[0][4] = i;\r
4342     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4343     initialRights[2]  = initialRights[5]  =\r
4344     castlingRights[0][2] = castlingRights[0][5] = i;\r
4345     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4346     initialRights[0]  = initialRights[3]  =\r
4347     castlingRights[0][0] = castlingRights[0][3] = i;\r
4348 \r
4349     /* Mirror piece placement for black */\r
4350     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4351         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4352     }\r
4353 }\r
4354 #else\r
4355 // [HGM] shuffle: a more general way to suffle opening setups, applicable to arbitrry variants.\r
4356 // All positions will have equal probability, but the current method will not provide a unique\r
4357 // numbering scheme for arrays that contain 3 or more pieces of the same kind.\r
4358 #define DARK 1\r
4359 #define LITE 2\r
4360 #define ANY 3\r
4361 \r
4362 int squaresLeft[4];\r
4363 int piecesLeft[(int)BlackPawn];\r
4364 u64 seed, nrOfShuffles;\r
4365 \r
4366 void GetPositionNumber()\r
4367 {       // sets global variable seed\r
4368         int i;\r
4369 \r
4370         seed = appData.defaultFrcPosition;\r
4371         if(seed < 0) { // randomize based on time for negative FRC position numbers\r
4372                 srandom(time(0)); \r
4373                 for(i=0; i<50; i++) seed += random();\r
4374                 seed = random() ^ random() >> 8 ^ random() << 8;\r
4375                 if(seed<0) seed = -seed;\r
4376         }\r
4377 }\r
4378 \r
4379 int put(Board board, int pieceType, int rank, int n, int shade)\r
4380 // put the piece on the (n-1)-th empty squares of the given shade\r
4381 {\r
4382         int i;\r
4383 \r
4384         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
4385                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {\r
4386                         board[rank][i] = (ChessSquare) pieceType;\r
4387                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;\r
4388                         squaresLeft[ANY]--;\r
4389                         piecesLeft[pieceType]--; \r
4390                         return i;\r
4391                 }\r
4392         }\r
4393         return -1;\r
4394 }\r
4395 \r
4396 \r
4397 void AddOnePiece(Board board, int pieceType, int rank, int shade)\r
4398 // calculate where the next piece goes, (any empty square), and put it there\r
4399 {\r
4400         int i;\r
4401 \r
4402         i = seed % squaresLeft[shade];\r
4403         nrOfShuffles *= squaresLeft[shade];\r
4404         seed /= squaresLeft[shade];\r
4405         put(board, pieceType, rank, i, shade);\r
4406 }\r
4407 \r
4408 void AddTwoPieces(Board board, int pieceType, int rank)\r
4409 // calculate where the next 2 identical pieces go, (any empty square), and put it there\r
4410 {\r
4411         int i, n=squaresLeft[ANY], j=n-1, k;\r
4412 \r
4413         k = n*(n-1)/2; // nr of possibilities, not counting permutations\r
4414         i = seed % k;  // pick one\r
4415         nrOfShuffles *= k;\r
4416         seed /= k;\r
4417         while(i >= j) i -= j--;\r
4418         j = n - 1 - j; i += j;\r
4419         put(board, pieceType, rank, j, ANY);\r
4420         put(board, pieceType, rank, i, ANY);\r
4421 }\r
4422 \r
4423 void SetUpShuffle(Board board, int number)\r
4424 {\r
4425         int i, p, first=1;\r
4426 \r
4427         GetPositionNumber(); nrOfShuffles = 1;\r
4428 \r
4429         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;\r
4430         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;\r
4431         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];\r
4432 \r
4433         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;\r
4434 \r
4435         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board\r
4436             p = (int) board[0][i];\r
4437             if(p < (int) BlackPawn) piecesLeft[p] ++;\r
4438             board[0][i] = EmptySquare;\r
4439         }\r
4440 \r
4441         if(PosFlags(0) & F_ALL_CASTLE_OK) {\r
4442             // shuffles restricted to allow normal castling put KRR first\r
4443             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle\r
4444                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4445             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles\r
4446                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4447             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling\r
4448                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);\r
4449             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling\r
4450                 put(board, WhiteRook, 0, 0, ANY);\r
4451             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle\r
4452         }\r
4453 \r
4454         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)\r
4455             // only for even boards make effort to put pairs of colorbound pieces on opposite colors\r
4456             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {\r
4457                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;\r
4458                 while(piecesLeft[p] >= 2) {\r
4459                     AddOnePiece(board, p, 0, LITE);\r
4460                     AddOnePiece(board, p, 0, DARK);\r
4461                 }\r
4462                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)\r
4463             }\r
4464 \r
4465         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {\r
4466             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere\r
4467             // but we leave King and Rooks for last, to possibly obey FRC restriction\r
4468             if(p == (int)WhiteRook) continue;\r
4469             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations\r
4470             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece\r
4471         }\r
4472 \r
4473         // now everything is placed, except perhaps King (Unicorn) and Rooks\r
4474 \r
4475         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {\r
4476             // Last King gets castling rights\r
4477             while(piecesLeft[(int)WhiteUnicorn]) {\r
4478                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4479                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4480             }\r
4481 \r
4482             while(piecesLeft[(int)WhiteKing]) {\r
4483                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4484                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4485             }\r
4486 \r
4487 \r
4488         } else {\r
4489             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);\r
4490             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);\r
4491         }\r
4492 \r
4493         // Only Rooks can be left; simply place them all\r
4494         while(piecesLeft[(int)WhiteRook]) {\r
4495                 i = put(board, WhiteRook, 0, 0, ANY);\r
4496                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights\r
4497                         if(first) {\r
4498                                 first=0;\r
4499                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;\r
4500                         }\r
4501                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;\r
4502                 }\r
4503         }\r
4504         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white\r
4505             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;\r
4506         }\r
4507 \r
4508         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize\r
4509 }\r
4510 \r
4511 #endif\r
4512 \r
4513 int SetCharTable( char *table, const char * map )\r
4514 /* [HGM] moved here from winboard.c because of its general usefulness */\r
4515 /*       Basically a safe strcpy that uses the last character as King */\r
4516 {\r
4517     int result = FALSE; int NrPieces;\r
4518 \r
4519     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
4520                     && NrPieces >= 12 && !(NrPieces&1)) {\r
4521         int i; /* [HGM] Accept even length from 12 to 34 */\r
4522 \r
4523         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
4524         for( i=0; i<NrPieces/2-1; i++ ) {\r
4525             table[i] = map[i];\r
4526             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
4527         }\r
4528         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
4529         table[(int) BlackKing]  = map[NrPieces-1];\r
4530 \r
4531         result = TRUE;\r
4532     }\r
4533 \r
4534     return result;\r
4535 }\r
4536 \r
4537 void Prelude(Board board)\r
4538 {       // [HGM] superchess: random selection of exo-pieces\r
4539         int i, j, k; ChessSquare p; \r
4540         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };\r
4541 \r
4542         GetPositionNumber(); // use FRC position number\r
4543 \r
4544         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table\r
4545             SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4546             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) \r
4547                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;\r
4548         }\r
4549 \r
4550         j = seed%4;                 seed /= 4; \r
4551         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4552         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4553         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4554         j = seed%3 + (seed%3 >= j); seed /= 3; \r
4555         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4556         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4557         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4558         j = seed%3;                 seed /= 3; \r
4559         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4560         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4561         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4562         j = seed%2 + (seed%2 >= j); seed /= 2; \r
4563         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4564         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4565         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4566         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);\r
4567         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);\r
4568         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);\r
4569         put(board, exoPieces[0],    0, 0, ANY);\r
4570         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];\r
4571 }\r
4572 \r
4573 void\r
4574 InitPosition(redraw)\r
4575      int redraw;\r
4576 {\r
4577     ChessSquare (* pieces)[BOARD_SIZE];\r
4578     int i, j, pawnRow, overrule,\r
4579     oldx = gameInfo.boardWidth,\r
4580     oldy = gameInfo.boardHeight,\r
4581     oldh = gameInfo.holdingsWidth,\r
4582     oldv = gameInfo.variant;\r
4583 \r
4584     currentMove = forwardMostMove = backwardMostMove = 0;\r
4585     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request\r
4586 \r
4587     /* [AS] Initialize pv info list [HGM] and game status */\r
4588     {\r
4589         for( i=0; i<MAX_MOVES; i++ ) {\r
4590             pvInfoList[i].depth = 0;\r
4591             epStatus[i]=EP_NONE;\r
4592             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
4593         }\r
4594 \r
4595         initialRulePlies = 0; /* 50-move counter start */\r
4596 \r
4597         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4598         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4599     }\r
4600 \r
4601     \r
4602     /* [HGM] logic here is completely changed. In stead of full positions */\r
4603     /* the initialized data only consist of the two backranks. The switch */\r
4604     /* selects which one we will use, which is than copied to the Board   */\r
4605     /* initialPosition, which for the rest is initialized by Pawns and    */\r
4606     /* empty squares. This initial position is then copied to boards[0],  */\r
4607     /* possibly after shuffling, so that it remains available.            */\r
4608 \r
4609     gameInfo.holdingsWidth = 0; /* default board sizes */\r
4610     gameInfo.boardWidth    = 8;\r
4611     gameInfo.boardHeight   = 8;\r
4612     gameInfo.holdingsSize  = 0;\r
4613     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
4614     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
4615     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
4616 \r
4617     switch (gameInfo.variant) {\r
4618     case VariantFischeRandom:\r
4619       shuffleOpenings = TRUE;\r
4620     default:\r
4621       pieces = FIDEArray;\r
4622       break;\r
4623     case VariantShatranj:\r
4624       pieces = ShatranjArray;\r
4625       nrCastlingRights = 0;\r
4626       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
4627       break;\r
4628     case VariantTwoKings:\r
4629       pieces = twoKingsArray;\r
4630       break;\r
4631     case VariantCapaRandom:\r
4632       shuffleOpenings = TRUE;\r
4633     case VariantCapablanca:\r
4634       pieces = CapablancaArray;\r
4635       gameInfo.boardWidth = 10;\r
4636       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4637       break;\r
4638     case VariantGothic:\r
4639       pieces = GothicArray;\r
4640       gameInfo.boardWidth = 10;\r
4641       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4642       break;\r
4643     case VariantJanus:\r
4644       pieces = JanusArray;\r
4645       gameInfo.boardWidth = 10;\r
4646       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
4647       nrCastlingRights = 6;\r
4648         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4649         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4650         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;\r
4651         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4652         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4653         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;\r
4654       break;\r
4655     case VariantFalcon:\r
4656       pieces = FalconArray;\r
4657       gameInfo.boardWidth = 10;\r
4658       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4659       break;\r
4660     case VariantXiangqi:\r
4661       pieces = XiangqiArray;\r
4662       gameInfo.boardWidth  = 9;\r
4663       gameInfo.boardHeight = 10;\r
4664       nrCastlingRights = 0;\r
4665       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4666       break;\r
4667     case VariantShogi:\r
4668       pieces = ShogiArray;\r
4669       gameInfo.boardWidth  = 9;\r
4670       gameInfo.boardHeight = 9;\r
4671       gameInfo.holdingsSize = 7;\r
4672       nrCastlingRights = 0;\r
4673       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4674       break;\r
4675     case VariantCourier:\r
4676       pieces = CourierArray;\r
4677       gameInfo.boardWidth  = 12;\r
4678       nrCastlingRights = 0;\r
4679       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4680       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4681       break;\r
4682     case VariantKnightmate:\r
4683       pieces = KnightmateArray;\r
4684       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4685       break;\r
4686     case VariantFairy:\r
4687       pieces = fairyArray;\r
4688       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4689       break;\r
4690     case VariantGreat:\r
4691       pieces = GreatArray;\r
4692       gameInfo.boardWidth = 10;\r
4693       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");\r
4694       gameInfo.holdingsSize = 8;\r
4695       break;\r
4696     case VariantSuper:\r
4697       pieces = FIDEArray;\r
4698       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
4699       gameInfo.holdingsSize = 8;\r
4700       startedFromSetupPosition = TRUE;\r
4701       break;\r
4702     case VariantCrazyhouse:\r
4703     case VariantBughouse:\r
4704       pieces = FIDEArray;\r
4705       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4706       gameInfo.holdingsSize = 5;\r
4707       break;\r
4708     case VariantWildCastle:\r
4709       pieces = FIDEArray;\r
4710       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4711       shuffleOpenings = 1;\r
4712       break;\r
4713     case VariantNoCastle:\r
4714       pieces = FIDEArray;\r
4715       nrCastlingRights = 0;\r
4716       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4717       /* !!?unconstrained back-rank shuffle */\r
4718       shuffleOpenings = 1;\r
4719       break;\r
4720     }\r
4721 \r
4722     overrule = 0;\r
4723     if(appData.NrFiles >= 0) {\r
4724         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4725         gameInfo.boardWidth = appData.NrFiles;\r
4726     }\r
4727     if(appData.NrRanks >= 0) {\r
4728         gameInfo.boardHeight = appData.NrRanks;\r
4729     }\r
4730     if(appData.holdingsSize >= 0) {\r
4731         i = appData.holdingsSize;\r
4732         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4733         gameInfo.holdingsSize = i;\r
4734     }\r
4735     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4736     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4737         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);\r
4738 \r
4739     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4740     if(pawnRow < 1) pawnRow = 1;\r
4741 \r
4742     /* User pieceToChar list overrules defaults */\r
4743     if(appData.pieceToCharTable != NULL)\r
4744         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4745 \r
4746     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4747 \r
4748         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4749             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4750         for( i=0; i<BOARD_HEIGHT; i++ )\r
4751             initialPosition[i][j] = s;\r
4752 \r
4753         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4754         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4755         initialPosition[pawnRow][j] = WhitePawn;\r
4756         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4757         if(gameInfo.variant == VariantXiangqi) {\r
4758             if(j&1) {\r
4759                 initialPosition[pawnRow][j] = \r
4760                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4761                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4762                    initialPosition[2][j] = WhiteCannon;\r
4763                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4764                 }\r
4765             }\r
4766         }\r
4767         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4768     }\r
4769     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4770 \r
4771             j=BOARD_LEFT+1;\r
4772             initialPosition[1][j] = WhiteBishop;\r
4773             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4774             j=BOARD_RGHT-2;\r
4775             initialPosition[1][j] = WhiteRook;\r
4776             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4777     }\r
4778 \r
4779     if( nrCastlingRights == -1) {\r
4780         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4781         /*       This sets default castling rights from none to normal corners   */\r
4782         /* Variants with other castling rights must set them themselves above    */\r
4783         nrCastlingRights = 6;\r
4784        \r
4785         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4786         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4787         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4788         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4789         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4790         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4791      }\r
4792 \r
4793      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
4794      if(gameInfo.variant == VariantGreat) { // promotion commoners\r
4795         initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-1] = WhiteMan;\r
4796         initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-2] = 9;\r
4797         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;\r
4798         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;\r
4799      }\r
4800 #if 0\r
4801     if(gameInfo.variant == VariantFischeRandom) {\r
4802       if( appData.defaultFrcPosition < 0 ) {\r
4803         ShuffleFRC( initialPosition );\r
4804       }\r
4805       else {\r
4806         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4807       }\r
4808       startedFromSetupPosition = TRUE;\r
4809     } else \r
4810 #else\r
4811   if (appData.debugMode) {\r
4812     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
4813   }\r
4814     if(shuffleOpenings) {\r
4815         SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
4816         startedFromSetupPosition = TRUE;\r
4817     }\r
4818 #endif\r
4819     if(startedFromPositionFile) {\r
4820       /* [HGM] loadPos: use PositionFile for every new game */\r
4821       CopyBoard(initialPosition, filePosition);\r
4822       for(i=0; i<nrCastlingRights; i++)\r
4823           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4824       startedFromSetupPosition = TRUE;\r
4825     }\r
4826 \r
4827     CopyBoard(boards[0], initialPosition);\r
4828 \r
4829     if(oldx != gameInfo.boardWidth ||\r
4830        oldy != gameInfo.boardHeight ||\r
4831        oldh != gameInfo.holdingsWidth\r
4832 #ifdef GOTHIC\r
4833        || oldv == VariantGothic ||        // For licensing popups\r
4834        gameInfo.variant == VariantGothic\r
4835 #endif\r
4836 #ifdef FALCON\r
4837        || oldv == VariantFalcon ||\r
4838        gameInfo.variant == VariantFalcon\r
4839 #endif\r
4840                                          )\r
4841             InitDrawingSizes(-2 ,0);\r
4842 \r
4843     if (redraw)\r
4844       DrawPosition(TRUE, boards[currentMove]);\r
4845 }\r
4846 \r
4847 void\r
4848 SendBoard(cps, moveNum)\r
4849      ChessProgramState *cps;\r
4850      int moveNum;\r
4851 {\r
4852     char message[MSG_SIZ];\r
4853     \r
4854     if (cps->useSetboard) {\r
4855       char* fen = PositionToFEN(moveNum, cps->useFEN960);\r
4856       sprintf(message, "setboard %s\n", fen);\r
4857       SendToProgram(message, cps);\r
4858       free(fen);\r
4859 \r
4860     } else {\r
4861       ChessSquare *bp;\r
4862       int i, j;\r
4863       /* Kludge to set black to move, avoiding the troublesome and now\r
4864        * deprecated "black" command.\r
4865        */\r
4866       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4867 \r
4868       SendToProgram("edit\n", cps);\r
4869       SendToProgram("#\n", cps);\r
4870       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4871         bp = &boards[moveNum][i][BOARD_LEFT];\r
4872         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4873           if ((int) *bp < (int) BlackPawn) {\r
4874             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4875                     AAA + j, ONE + i);\r
4876             if(message[0] == '+' || message[0] == '~') {\r
4877                 sprintf(message, "%c%c%c+\n",\r
4878                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4879                         AAA + j, ONE + i);\r
4880             }\r
4881             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4882                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4883                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4884             }\r
4885             SendToProgram(message, cps);\r
4886           }\r
4887         }\r
4888       }\r
4889     \r
4890       SendToProgram("c\n", cps);\r
4891       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4892         bp = &boards[moveNum][i][BOARD_LEFT];\r
4893         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4894           if (((int) *bp != (int) EmptySquare)\r
4895               && ((int) *bp >= (int) BlackPawn)) {\r
4896             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4897                     AAA + j, ONE + i);\r
4898             if(message[0] == '+' || message[0] == '~') {\r
4899                 sprintf(message, "%c%c%c+\n",\r
4900                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4901                         AAA + j, ONE + i);\r
4902             }\r
4903             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4904                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4905                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4906             }\r
4907             SendToProgram(message, cps);\r
4908           }\r
4909         }\r
4910       }\r
4911     \r
4912       SendToProgram(".\n", cps);\r
4913     }\r
4914     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4915 }\r
4916 \r
4917 int\r
4918 IsPromotion(fromX, fromY, toX, toY)\r
4919      int fromX, fromY, toX, toY;\r
4920 {\r
4921     /* [HGM] add Shogi promotions */\r
4922     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4923     ChessSquare piece;\r
4924 \r
4925     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4926       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4927    /* [HGM] Note to self: line above also weeds out drops */\r
4928     piece = boards[currentMove][fromY][fromX];\r
4929     if(gameInfo.variant == VariantShogi) {\r
4930         promotionZoneSize = 3;\r
4931         highestPromotingPiece = (int)WhiteKing;\r
4932         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4933            and if in normal chess we then allow promotion to King, why not\r
4934            allow promotion of other piece in Shogi?                         */\r
4935     }\r
4936     if((int)piece >= BlackPawn) {\r
4937         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4938              return FALSE;\r
4939         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4940     } else {\r
4941         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4942            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4943     }\r
4944     return ( (int)piece <= highestPromotingPiece );\r
4945 }\r
4946 \r
4947 int\r
4948 InPalace(row, column)\r
4949      int row, column;\r
4950 {   /* [HGM] for Xiangqi */\r
4951     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4952          column < (BOARD_WIDTH + 4)/2 &&\r
4953          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4954     return FALSE;\r
4955 }\r
4956 \r
4957 int\r
4958 PieceForSquare (x, y)\r
4959      int x;\r
4960      int y;\r
4961 {\r
4962   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
4963      return -1;\r
4964   else\r
4965      return boards[currentMove][y][x];\r
4966 }\r
4967 \r
4968 int\r
4969 OKToStartUserMove(x, y)\r
4970      int x, y;\r
4971 {\r
4972     ChessSquare from_piece;\r
4973     int white_piece;\r
4974 \r
4975     if (matchMode) return FALSE;\r
4976     if (gameMode == EditPosition) return TRUE;\r
4977 \r
4978     if (x >= 0 && y >= 0)\r
4979       from_piece = boards[currentMove][y][x];\r
4980     else\r
4981       from_piece = EmptySquare;\r
4982 \r
4983     if (from_piece == EmptySquare) return FALSE;\r
4984 \r
4985     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4986       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4987 \r
4988     switch (gameMode) {\r
4989       case PlayFromGameFile:\r
4990       case AnalyzeFile:\r
4991       case TwoMachinesPlay:\r
4992       case EndOfGame:\r
4993         return FALSE;\r
4994 \r
4995       case IcsObserving:\r
4996       case IcsIdle:\r
4997         return FALSE;\r
4998 \r
4999       case MachinePlaysWhite:\r
5000       case IcsPlayingBlack:\r
5001         if (appData.zippyPlay) return FALSE;\r
5002         if (white_piece) {\r
5003             DisplayMoveError(_("You are playing Black"));\r
5004             return FALSE;\r
5005         }\r
5006         break;\r
5007 \r
5008       case MachinePlaysBlack:\r
5009       case IcsPlayingWhite:\r
5010         if (appData.zippyPlay) return FALSE;\r
5011         if (!white_piece) {\r
5012             DisplayMoveError(_("You are playing White"));\r
5013             return FALSE;\r
5014         }\r
5015         break;\r
5016 \r
5017       case EditGame:\r
5018         if (!white_piece && WhiteOnMove(currentMove)) {\r
5019             DisplayMoveError(_("It is White's turn"));\r
5020             return FALSE;\r
5021         }           \r
5022         if (white_piece && !WhiteOnMove(currentMove)) {\r
5023             DisplayMoveError(_("It is Black's turn"));\r
5024             return FALSE;\r
5025         }           \r
5026         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
5027             /* Editing correspondence game history */\r
5028             /* Could disallow this or prompt for confirmation */\r
5029             cmailOldMove = -1;\r
5030         }\r
5031         if (currentMove < forwardMostMove) {\r
5032             /* Discarding moves */\r
5033             /* Could prompt for confirmation here,\r
5034                but I don't think that's such a good idea */\r
5035             forwardMostMove = currentMove;\r
5036         }\r
5037         break;\r
5038 \r
5039       case BeginningOfGame:\r
5040         if (appData.icsActive) return FALSE;\r
5041         if (!appData.noChessProgram) {\r
5042             if (!white_piece) {\r
5043                 DisplayMoveError(_("You are playing White"));\r
5044                 return FALSE;\r
5045             }\r
5046         }\r
5047         break;\r
5048         \r
5049       case Training:\r
5050         if (!white_piece && WhiteOnMove(currentMove)) {\r
5051             DisplayMoveError(_("It is White's turn"));\r
5052             return FALSE;\r
5053         }           \r
5054         if (white_piece && !WhiteOnMove(currentMove)) {\r
5055             DisplayMoveError(_("It is Black's turn"));\r
5056             return FALSE;\r
5057         }           \r
5058         break;\r
5059 \r
5060       default:\r
5061       case IcsExamining:\r
5062         break;\r
5063     }\r
5064     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
5065         && gameMode != AnalyzeFile && gameMode != Training) {\r
5066         DisplayMoveError(_("Displayed position is not current"));\r
5067         return FALSE;\r
5068     }\r
5069     return TRUE;\r
5070 }\r
5071 \r
5072 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
5073 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
5074 int lastLoadGameUseList = FALSE;\r
5075 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
5076 ChessMove lastLoadGameStart = (ChessMove) 0;\r
5077 \r
5078 \r
5079 ChessMove\r
5080 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
5081      int fromX, fromY, toX, toY;\r
5082      int promoChar;\r
5083 {\r
5084     ChessMove moveType;\r
5085     ChessSquare pdown, pup;\r
5086 \r
5087     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
5088     if ((fromX == toX) && (fromY == toY)) {\r
5089         return ImpossibleMove;\r
5090     }\r
5091 \r
5092     /* [HGM] suppress all moves into holdings area and guard band */\r
5093     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
5094             return ImpossibleMove;\r
5095 \r
5096     /* [HGM] <sameColor> moved to here from winboard.c */\r
5097     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
5098     pdown = boards[currentMove][fromY][fromX];\r
5099     pup = boards[currentMove][toY][toX];\r
5100     if (    gameMode != EditPosition &&\r
5101             (WhitePawn <= pdown && pdown < BlackPawn &&\r
5102              WhitePawn <= pup && pup < BlackPawn  ||\r
5103              BlackPawn <= pdown && pdown < EmptySquare &&\r
5104              BlackPawn <= pup && pup < EmptySquare \r
5105             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
5106                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
5107                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
5108         )           )\r
5109          return ImpossibleMove;\r
5110 \r
5111     /* Check if the user is playing in turn.  This is complicated because we\r
5112        let the user "pick up" a piece before it is his turn.  So the piece he\r
5113        tried to pick up may have been captured by the time he puts it down!\r
5114        Therefore we use the color the user is supposed to be playing in this\r
5115        test, not the color of the piece that is currently on the starting\r
5116        square---except in EditGame mode, where the user is playing both\r
5117        sides; fortunately there the capture race can't happen.  (It can\r
5118        now happen in IcsExamining mode, but that's just too bad.  The user\r
5119        will get a somewhat confusing message in that case.)\r
5120        */\r
5121 \r
5122     switch (gameMode) {\r
5123       case PlayFromGameFile:\r
5124       case AnalyzeFile:\r
5125       case TwoMachinesPlay:\r
5126       case EndOfGame:\r
5127       case IcsObserving:\r
5128       case IcsIdle:\r
5129         /* We switched into a game mode where moves are not accepted,\r
5130            perhaps while the mouse button was down. */\r
5131         return ImpossibleMove;\r
5132 \r
5133       case MachinePlaysWhite:\r
5134         /* User is moving for Black */\r
5135         if (WhiteOnMove(currentMove)) {\r
5136             DisplayMoveError(_("It is White's turn"));\r
5137             return ImpossibleMove;\r
5138         }\r
5139         break;\r
5140 \r
5141       case MachinePlaysBlack:\r
5142         /* User is moving for White */\r
5143         if (!WhiteOnMove(currentMove)) {\r
5144             DisplayMoveError(_("It is Black's turn"));\r
5145             return ImpossibleMove;\r
5146         }\r
5147         break;\r
5148 \r
5149       case EditGame:\r
5150       case IcsExamining:\r
5151       case BeginningOfGame:\r
5152       case AnalyzeMode:\r
5153       case Training:\r
5154         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
5155             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
5156             /* User is moving for Black */\r
5157             if (WhiteOnMove(currentMove)) {\r
5158                 DisplayMoveError(_("It is White's turn"));\r
5159                 return ImpossibleMove;\r
5160             }\r
5161         } else {\r
5162             /* User is moving for White */\r
5163             if (!WhiteOnMove(currentMove)) {\r
5164                 DisplayMoveError(_("It is Black's turn"));\r
5165                 return ImpossibleMove;\r
5166             }\r
5167         }\r
5168         break;\r
5169 \r
5170       case IcsPlayingBlack:\r
5171         /* User is moving for Black */\r
5172         if (WhiteOnMove(currentMove)) {\r
5173             if (!appData.premove) {\r
5174                 DisplayMoveError(_("It is White's turn"));\r
5175             } else if (toX >= 0 && toY >= 0) {\r
5176                 premoveToX = toX;\r
5177                 premoveToY = toY;\r
5178                 premoveFromX = fromX;\r
5179                 premoveFromY = fromY;\r
5180                 premovePromoChar = promoChar;\r
5181                 gotPremove = 1;\r
5182                 if (appData.debugMode) \r
5183                     fprintf(debugFP, "Got premove: fromX %d,"\r
5184                             "fromY %d, toX %d, toY %d\n",\r
5185                             fromX, fromY, toX, toY);\r
5186             }\r
5187             return ImpossibleMove;\r
5188         }\r
5189         break;\r
5190 \r
5191       case IcsPlayingWhite:\r
5192         /* User is moving for White */\r
5193         if (!WhiteOnMove(currentMove)) {\r
5194             if (!appData.premove) {\r
5195                 DisplayMoveError(_("It is Black's turn"));\r
5196             } else if (toX >= 0 && toY >= 0) {\r
5197                 premoveToX = toX;\r
5198                 premoveToY = toY;\r
5199                 premoveFromX = fromX;\r
5200                 premoveFromY = fromY;\r
5201                 premovePromoChar = promoChar;\r
5202                 gotPremove = 1;\r
5203                 if (appData.debugMode) \r
5204                     fprintf(debugFP, "Got premove: fromX %d,"\r
5205                             "fromY %d, toX %d, toY %d\n",\r
5206                             fromX, fromY, toX, toY);\r
5207             }\r
5208             return ImpossibleMove;\r
5209         }\r
5210         break;\r
5211 \r
5212       default:\r
5213         break;\r
5214 \r
5215       case EditPosition:\r
5216         /* EditPosition, empty square, or different color piece;\r
5217            click-click move is possible */\r
5218         if (toX == -2 || toY == -2) {\r
5219             boards[0][fromY][fromX] = EmptySquare;\r
5220             return AmbiguousMove;\r
5221         } else if (toX >= 0 && toY >= 0) {\r
5222             boards[0][toY][toX] = boards[0][fromY][fromX];\r
5223             boards[0][fromY][fromX] = EmptySquare;\r
5224             return AmbiguousMove;\r
5225         }\r
5226         return ImpossibleMove;\r
5227     }\r
5228 \r
5229     /* [HGM] If move started in holdings, it means a drop */\r
5230     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
5231          if( pup != EmptySquare ) return ImpossibleMove;\r
5232          if(appData.testLegality) {\r
5233              /* it would be more logical if LegalityTest() also figured out\r
5234               * which drops are legal. For now we forbid pawns on back rank.\r
5235               * Shogi is on its own here...\r
5236               */\r
5237              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
5238                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
5239                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
5240          }\r
5241          return WhiteDrop; /* Not needed to specify white or black yet */\r
5242     }\r
5243 \r
5244     userOfferedDraw = FALSE;\r
5245         \r
5246     /* [HGM] always test for legality, to get promotion info */\r
5247     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
5248                           epStatus[currentMove], castlingRights[currentMove],\r
5249                                          fromY, fromX, toY, toX, promoChar);\r
5250 \r
5251     /* [HGM] but possibly ignore an IllegalMove result */\r
5252     if (appData.testLegality) {\r
5253         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
5254             DisplayMoveError(_("Illegal move"));\r
5255             return ImpossibleMove;\r
5256         }\r
5257     }\r
5258 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);\r
5259     return moveType;\r
5260     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
5261        function is made into one that returns an OK move type if FinishMove\r
5262        should be called. This to give the calling driver routine the\r
5263        opportunity to finish the userMove input with a promotion popup,\r
5264        without bothering the user with this for invalid or illegal moves */\r
5265 \r
5266 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
5267 }\r
5268 \r
5269 /* Common tail of UserMoveEvent and DropMenuEvent */\r
5270 int\r
5271 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
5272      ChessMove moveType;\r
5273      int fromX, fromY, toX, toY;\r
5274      /*char*/int promoChar;\r
5275 {\r
5276     char *bookHit = 0;\r
5277 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);\r
5278     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { \r
5279         // [HGM] superchess: suppress promotions to non-available piece\r
5280         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5281         if(WhiteOnMove(currentMove)) {\r
5282             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;\r
5283         } else {\r
5284             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;\r
5285         }\r
5286     }\r
5287 \r
5288     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion\r
5289        move type in caller when we know the move is a legal promotion */\r
5290     if(moveType == NormalMove && promoChar)\r
5291         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
5292 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);\r
5293     /* [HGM] convert drag-and-drop piece drops to standard form */\r
5294     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
5295          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
5296          fromX = boards[currentMove][fromY][fromX];\r
5297          fromY = DROP_RANK;\r
5298     }\r
5299 \r
5300     /* [HGM] <popupFix> The following if has been moved here from\r
5301        UserMoveEvent(). Because it seemed to belon here (why not allow\r
5302        piece drops in training games?), and because it can only be\r
5303        performed after it is known to what we promote. */\r
5304     if (gameMode == Training) {\r
5305       /* compare the move played on the board to the next move in the\r
5306        * game. If they match, display the move and the opponent's response. \r
5307        * If they don't match, display an error message.\r
5308        */\r
5309       int saveAnimate;\r
5310       Board testBoard;\r
5311       CopyBoard(testBoard, boards[currentMove]);\r
5312       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
5313 \r
5314       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
5315         ForwardInner(currentMove+1);\r
5316 \r
5317         /* Autoplay the opponent's response.\r
5318          * if appData.animate was TRUE when Training mode was entered,\r
5319          * the response will be animated.\r
5320          */\r
5321         saveAnimate = appData.animate;\r
5322         appData.animate = animateTraining;\r
5323         ForwardInner(currentMove+1);\r
5324         appData.animate = saveAnimate;\r
5325 \r
5326         /* check for the end of the game */\r
5327         if (currentMove >= forwardMostMove) {\r
5328           gameMode = PlayFromGameFile;\r
5329           ModeHighlight();\r
5330           SetTrainingModeOff();\r
5331           DisplayInformation(_("End of game"));\r
5332         }\r
5333       } else {\r
5334         DisplayError(_("Incorrect move"), 0);\r
5335       }\r
5336       return 1;\r
5337     }\r
5338 \r
5339   /* Ok, now we know that the move is good, so we can kill\r
5340      the previous line in Analysis Mode */\r
5341   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
5342     forwardMostMove = currentMove;\r
5343   }\r
5344 \r
5345   /* If we need the chess program but it's dead, restart it */\r
5346   ResurrectChessProgram();\r
5347 \r
5348   /* A user move restarts a paused game*/\r
5349   if (pausing)\r
5350     PauseEvent();\r
5351 \r
5352   thinkOutput[0] = NULLCHAR;\r
5353 \r
5354   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
5355 \r
5356     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) \r
5357                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
5358         // [HGM] superchess: take promotion piece out of holdings\r
5359         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5360         if(WhiteOnMove(forwardMostMove-1)) {\r
5361             if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])\r
5362                 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;\r
5363         } else {\r
5364             if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])\r
5365                 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;\r
5366         }\r
5367     }\r
5368 \r
5369   if (gameMode == BeginningOfGame) {\r
5370     if (appData.noChessProgram) {\r
5371       gameMode = EditGame;\r
5372       SetGameInfo();\r
5373     } else {\r
5374       char buf[MSG_SIZ];\r
5375       gameMode = MachinePlaysBlack;\r
5376       StartClocks();\r
5377       SetGameInfo();\r
5378       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
5379       DisplayTitle(buf);\r
5380       if (first.sendName) {\r
5381         sprintf(buf, "name %s\n", gameInfo.white);\r
5382         SendToProgram(buf, &first);\r
5383       }\r
5384       StartClocks();\r
5385     }\r
5386     ModeHighlight();\r
5387   }\r
5388 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);\r
5389   /* Relay move to ICS or chess engine */\r
5390   if (appData.icsActive) {\r
5391     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
5392         gameMode == IcsExamining) {\r
5393       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5394       ics_user_moved = 1;\r
5395     }\r
5396   } else {\r
5397     if (first.sendTime && (gameMode == BeginningOfGame ||\r
5398                            gameMode == MachinePlaysWhite ||\r
5399                            gameMode == MachinePlaysBlack)) {\r
5400       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
5401     }\r
5402     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
5403          // [HGM] book: if program might be playing, let it use book\r
5404         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);\r
5405         first.maybeThinking = TRUE;\r
5406     } else SendMoveToProgram(forwardMostMove-1, &first);\r
5407     if (currentMove == cmailOldMove + 1) {\r
5408       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
5409     }\r
5410   }\r
5411 \r
5412   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5413 \r
5414   switch (gameMode) {\r
5415   case EditGame:\r
5416     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
5417                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
5418     case MT_NONE:\r
5419     case MT_CHECK:\r
5420       break;\r
5421     case MT_CHECKMATE:\r
5422       if (WhiteOnMove(currentMove)) {\r
5423         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
5424       } else {\r
5425         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
5426       }\r
5427       break;\r
5428     case MT_STALEMATE:\r
5429       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
5430       break;\r
5431     }\r
5432     break;\r
5433     \r
5434   case MachinePlaysBlack:\r
5435   case MachinePlaysWhite:\r
5436     /* disable certain menu options while machine is thinking */\r
5437     SetMachineThinkingEnables();\r
5438     break;\r
5439 \r
5440   default:\r
5441     break;\r
5442   }\r
5443 \r
5444   if(bookHit) { // [HGM] book: simulate book reply\r
5445         static char bookMove[MSG_SIZ]; // a bit generous?\r
5446 \r
5447         programStats.nodes = programStats.depth = programStats.time = \r
5448         programStats.score = programStats.got_only_move = 0;\r
5449         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
5450 \r
5451         strcpy(bookMove, "move ");\r
5452         strcat(bookMove, bookHit);\r
5453         HandleMachineMove(bookMove, &first);\r
5454   }\r
5455   return 1;\r
5456 }\r
5457 \r
5458 void\r
5459 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
5460      int fromX, fromY, toX, toY;\r
5461      int promoChar;\r
5462 {\r
5463     /* [HGM] This routine was added to allow calling of its two logical\r
5464        parts from other modules in the old way. Before, UserMoveEvent()\r
5465        automatically called FinishMove() if the move was OK, and returned\r
5466        otherwise. I separated the two, in order to make it possible to\r
5467        slip a promotion popup in between. But that it always needs two\r
5468        calls, to the first part, (now called UserMoveTest() ), and to\r
5469        FinishMove if the first part succeeded. Calls that do not need\r
5470        to do anything in between, can call this routine the old way. \r
5471     */\r
5472     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
5473 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);\r
5474     if(moveType != ImpossibleMove)\r
5475         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
5476 }\r
5477 \r
5478 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
5479 {\r
5480 //    char * hint = lastHint;\r
5481     FrontEndProgramStats stats;\r
5482 \r
5483     stats.which = cps == &first ? 0 : 1;\r
5484     stats.depth = cpstats->depth;\r
5485     stats.nodes = cpstats->nodes;\r
5486     stats.score = cpstats->score;\r
5487     stats.time = cpstats->time;\r
5488     stats.pv = cpstats->movelist;\r
5489     stats.hint = lastHint;\r
5490     stats.an_move_index = 0;\r
5491     stats.an_move_count = 0;\r
5492 \r
5493     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
5494         stats.hint = cpstats->move_name;\r
5495         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
5496         stats.an_move_count = cpstats->nr_moves;\r
5497     }\r
5498 \r
5499     SetProgramStats( &stats );\r
5500 }\r
5501 \r
5502 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)\r
5503 {   // [HGM] book: this routine intercepts moves to simulate book replies\r
5504     char *bookHit = NULL;\r
5505 \r
5506     //first determine if the incoming move brings opponent into his book\r
5507     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
5508         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
5509     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");\r
5510     if(bookHit != NULL && !cps->bookSuspend) {\r
5511         // make sure opponent is not going to reply after receiving move to book position\r
5512         SendToProgram("force\n", cps);\r
5513         cps->bookSuspend = TRUE; // flag indicating it has to be restarted\r
5514     }\r
5515     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move\r
5516     // now arrange restart after book miss\r
5517     if(bookHit) {\r
5518         // after a book hit we never send 'go', and the code after the call to this routine\r
5519         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').\r
5520         char buf[MSG_SIZ];\r
5521         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(\r
5522         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it\r
5523         SendToProgram(buf, cps);\r
5524         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'\r
5525     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine\r
5526         SendToProgram("go\n", cps);\r
5527         cps->bookSuspend = FALSE; // after a 'go' we are never suspended\r
5528     } else { // 'go' might be sent based on 'firstMove' after this routine returns\r
5529         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return\r
5530             SendToProgram("go\n", cps); \r
5531         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss\r
5532     }\r
5533     return bookHit; // notify caller of hit, so it can take action to send move to opponent\r
5534 }\r
5535 \r
5536 char *savedMessage;\r
5537 ChessProgramState *savedState;\r
5538 void DeferredBookMove(void)\r
5539 {\r
5540         if(savedState->lastPing != savedState->lastPong)\r
5541                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
5542         else\r
5543         HandleMachineMove(savedMessage, savedState);\r
5544 }\r
5545 \r
5546 void\r
5547 HandleMachineMove(message, cps)\r
5548      char *message;\r
5549      ChessProgramState *cps;\r
5550 {\r
5551     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
5552     char realname[MSG_SIZ];\r
5553     int fromX, fromY, toX, toY;\r
5554     ChessMove moveType;\r
5555     char promoChar;\r
5556     char *p;\r
5557     int machineWhite;\r
5558     char *bookHit;\r
5559 \r
5560 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit\r
5561     /*\r
5562      * Kludge to ignore BEL characters\r
5563      */\r
5564     while (*message == '\007') message++;\r
5565 \r
5566     /*\r
5567      * [HGM] engine debug message: ignore lines starting with '#' character\r
5568      */\r
5569     if(cps->debug && *message == '#') return;\r
5570 \r
5571     /*\r
5572      * Look for book output\r
5573      */\r
5574     if (cps == &first && bookRequested) {\r
5575         if (message[0] == '\t' || message[0] == ' ') {\r
5576             /* Part of the book output is here; append it */\r
5577             strcat(bookOutput, message);\r
5578             strcat(bookOutput, "  \n");\r
5579             return;\r
5580         } else if (bookOutput[0] != NULLCHAR) {\r
5581             /* All of book output has arrived; display it */\r
5582             char *p = bookOutput;\r
5583             while (*p != NULLCHAR) {\r
5584                 if (*p == '\t') *p = ' ';\r
5585                 p++;\r
5586             }\r
5587             DisplayInformation(bookOutput);\r
5588             bookRequested = FALSE;\r
5589             /* Fall through to parse the current output */\r
5590         }\r
5591     }\r
5592 \r
5593     /*\r
5594      * Look for machine move.\r
5595      */\r
5596     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
5597         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
5598     {\r
5599         /* This method is only useful on engines that support ping */\r
5600         if (cps->lastPing != cps->lastPong) {\r
5601           if (gameMode == BeginningOfGame) {\r
5602             /* Extra move from before last new; ignore */\r
5603             if (appData.debugMode) {\r
5604                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5605             }\r
5606           } else {\r
5607             if (appData.debugMode) {\r
5608                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5609                         cps->which, gameMode);\r
5610             }\r
5611 \r
5612             SendToProgram("undo\n", cps);\r
5613           }\r
5614           return;\r
5615         }\r
5616 \r
5617         switch (gameMode) {\r
5618           case BeginningOfGame:\r
5619             /* Extra move from before last reset; ignore */\r
5620             if (appData.debugMode) {\r
5621                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5622             }\r
5623             return;\r
5624 \r
5625           case EndOfGame:\r
5626           case IcsIdle:\r
5627           default:\r
5628             /* Extra move after we tried to stop.  The mode test is\r
5629                not a reliable way of detecting this problem, but it's\r
5630                the best we can do on engines that don't support ping.\r
5631             */\r
5632             if (appData.debugMode) {\r
5633                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5634                         cps->which, gameMode);\r
5635             }\r
5636             SendToProgram("undo\n", cps);\r
5637             return;\r
5638 \r
5639           case MachinePlaysWhite:\r
5640           case IcsPlayingWhite:\r
5641             machineWhite = TRUE;\r
5642             break;\r
5643 \r
5644           case MachinePlaysBlack:\r
5645           case IcsPlayingBlack:\r
5646             machineWhite = FALSE;\r
5647             break;\r
5648 \r
5649           case TwoMachinesPlay:\r
5650             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5651             break;\r
5652         }\r
5653         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5654             if (appData.debugMode) {\r
5655                 fprintf(debugFP,\r
5656                         "Ignoring move out of turn by %s, gameMode %d"\r
5657                         ", forwardMost %d\n",\r
5658                         cps->which, gameMode, forwardMostMove);\r
5659             }\r
5660             return;\r
5661         }\r
5662 \r
5663     if (appData.debugMode) { int f = forwardMostMove;\r
5664         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5665                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5666     }\r
5667         if(cps->alphaRank) AlphaRank(machineMove, 4);\r
5668         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5669                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5670             /* Machine move could not be parsed; ignore it. */\r
5671             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),\r
5672                     machineMove, cps->which);\r
5673             DisplayError(buf1, 0);\r
5674             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",\r
5675                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);\r
5676             if (gameMode == TwoMachinesPlay) {\r
5677               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5678                        buf1, GE_XBOARD);\r
5679             }\r
5680             return;\r
5681         }\r
5682 \r
5683         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5684         /* So we have to redo legality test with true e.p. status here,  */\r
5685         /* to make sure an illegal e.p. capture does not slip through,   */\r
5686         /* to cause a forfeit on a justified illegal-move complaint      */\r
5687         /* of the opponent.                                              */\r
5688         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5689             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5690                                                               ) {\r
5691            ChessMove moveType;\r
5692            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5693                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5694                              fromY, fromX, toY, toX, promoChar);\r
5695             if (appData.debugMode) {\r
5696                 int i;\r
5697                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5698                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5699                 fprintf(debugFP, "castling rights\n");\r
5700             }\r
5701             if(moveType == IllegalMove) {\r
5702                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5703                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5704                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5705                            buf1, GE_XBOARD);\r
5706            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
5707            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5708               when they shouldn't (like TSCP-Gothic) */\r
5709            switch(moveType) {\r
5710              case WhiteASideCastleFR:\r
5711              case BlackASideCastleFR:\r
5712                toX+=2;\r
5713                currentMoveString[2]++;\r
5714                break;\r
5715              case WhiteHSideCastleFR:\r
5716              case BlackHSideCastleFR:\r
5717                toX--;\r
5718                currentMoveString[2]--;\r
5719                break;\r
5720              default: ; // nothing to do, but suppresses warning of pedantic compilers\r
5721            }\r
5722         }\r
5723         hintRequested = FALSE;\r
5724         lastHint[0] = NULLCHAR;\r
5725         bookRequested = FALSE;\r
5726         /* Program may be pondering now */\r
5727         cps->maybeThinking = TRUE;\r
5728         if (cps->sendTime == 2) cps->sendTime = 1;\r
5729         if (cps->offeredDraw) cps->offeredDraw--;\r
5730 \r
5731 #if ZIPPY\r
5732         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5733             first.initDone) {\r
5734           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5735           ics_user_moved = 1;\r
5736           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
5737                 char buf[3*MSG_SIZ];\r
5738 \r
5739                 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",\r
5740                         programStats.depth,\r
5741                         programStats.score / 100.,\r
5742                         programStats.time / 100.,\r
5743                         u64ToDouble(programStats.nodes),\r
5744                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),\r
5745                         programStats.movelist);\r
5746                 SendToICS(buf);\r
5747           }\r
5748         }\r
5749 #endif\r
5750         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5751         strcpy(machineMove, currentMoveString);\r
5752         strcat(machineMove, "\n");\r
5753         strcpy(moveList[forwardMostMove], machineMove);\r
5754 \r
5755         /* [AS] Save move info and clear stats for next move */\r
5756         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5757         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5758         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5759         ClearProgramStats();\r
5760         thinkOutput[0] = NULLCHAR;\r
5761         hiddenThinkOutputState = 0;\r
5762 \r
5763         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5764 \r
5765         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5766         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5767             int count = 0;\r
5768 \r
5769             while( count < adjudicateLossPlies ) {\r
5770                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5771 \r
5772                 if( count & 1 ) {\r
5773                     score = -score; /* Flip score for winning side */\r
5774                 }\r
5775 \r
5776                 if( score > adjudicateLossThreshold ) {\r
5777                     break;\r
5778                 }\r
5779 \r
5780                 count++;\r
5781             }\r
5782 \r
5783             if( count >= adjudicateLossPlies ) {\r
5784                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5785 \r
5786                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5787                     "Xboard adjudication", \r
5788                     GE_XBOARD );\r
5789 \r
5790                 return;\r
5791             }\r
5792         }\r
5793 \r
5794         if( gameMode == TwoMachinesPlay ) {\r
5795           // [HGM] some adjudications useful with buggy engines\r
5796             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
5797           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {\r
5798 \r
5799             if(appData.testLegality)\r
5800             // don't wait for engine to announce game end if we can judge ourselves\r
5801             switch (MateTest(boards[forwardMostMove],\r
5802                                  PosFlags(forwardMostMove), epFile,\r
5803                                        castlingRights[forwardMostMove]) ) {\r
5804               case MT_NONE:\r
5805               case MT_CHECK:\r
5806               default:\r
5807                 break;\r
5808               case MT_STALEMATE:\r
5809                 epStatus[forwardMostMove] = EP_STALEMATE;\r
5810                 if(appData.checkMates) {\r
5811                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5812                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5813                     GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",\r
5814                         GE_XBOARD );\r
5815                 }\r
5816                 break;\r
5817               case MT_CHECKMATE:\r
5818                 epStatus[forwardMostMove] = EP_CHECKMATE;\r
5819                 if(appData.checkMates) {\r
5820                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5821                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5822                     GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5823                     "Xboard adjudication: Checkmate", \r
5824                     GE_XBOARD );\r
5825                 }\r
5826                 break;\r
5827             }\r
5828 \r
5829             if( appData.testLegality )\r
5830             {   /* [HGM] Some more adjudications for obstinate engines */\r
5831                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5832                     NrWQ=0, NrBQ=0, NrW=0, bishopsColor = 0,\r
5833                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;\r
5834                 static int moveCount = 6;\r
5835 \r
5836                 /* First absolutely insufficient mating material. Count what is on board. */\r
5837                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5838                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5839                     int m=i;\r
5840 \r
5841                     switch((int) p)\r
5842                     {   /* count B,N,R and other of each side */\r
5843                         case WhiteKnight:\r
5844                              NrWN++; break;\r
5845                         case WhiteBishop:\r
5846                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5847                              bishopsColor |= 1 << ((i^j)&1);\r
5848                              NrWB++; break;\r
5849                         case BlackKnight:\r
5850                              NrBN++; break;\r
5851                         case BlackBishop:\r
5852                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5853                              bishopsColor |= 1 << ((i^j)&1);\r
5854                              NrBB++; break;\r
5855                         case WhiteRook:\r
5856                              NrWR++; break;\r
5857                         case BlackRook:\r
5858                              NrBR++; break;\r
5859                         case WhiteQueen:\r
5860                              NrWQ++; break;\r
5861                         case BlackQueen:\r
5862                              NrBQ++; break;\r
5863                         case EmptySquare: \r
5864                              break;\r
5865                         case BlackPawn:\r
5866                              m = 7-i;\r
5867                         case WhitePawn:\r
5868                              PawnAdvance += m; NrPawns++;\r
5869                     }\r
5870                     NrPieces += (p != EmptySquare);\r
5871                     NrW += ((int)p < (int)BlackPawn);\r
5872                     if(gameInfo.variant == VariantXiangqi && \r
5873                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {\r
5874                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces\r
5875                         NrW -= ((int)p < (int)BlackPawn);\r
5876                     }\r
5877                 }\r
5878 \r
5879                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && \r
5880                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible\r
5881                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||\r
5882                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color\r
5883                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5884 \r
5885                      /* always flag draws, for judging claims */\r
5886                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5887 \r
5888                      if(appData.materialDraws) {\r
5889                          /* but only adjudicate them if adjudication enabled */\r
5890                          SendToProgram("force\n", cps->other); // suppress reply\r
5891                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */\r
5892                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5893                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5894                          return;\r
5895                      }\r
5896                 }\r
5897 \r
5898                 /* Shatranj baring rule */\r
5899                 if( gameInfo.variant == VariantShatranj && (NrW == 1 || NrPieces - NrW == 1) )\r
5900                 {    /* bare King */\r
5901 \r
5902                      if(--bare < 0 && appData.checkMates) {\r
5903                          /* but only adjudicate them if adjudication enabled */\r
5904                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5905                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5906                          GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
5907                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5908                          return;\r
5909                      }\r
5910                 } else bare = 1;\r
5911 \r
5912                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5913                 if(NrPieces == 4 && \r
5914                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5915                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5916                    || NrWN==2 || NrBN==2     /* KNNK */\r
5917                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5918                   ) ) {\r
5919                      if(--moveCount < 0 && appData.trivialDraws)\r
5920                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5921                           SendToProgram("force\n", cps->other); // suppress reply\r
5922                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5923                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5924                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5925                           return;\r
5926                      }\r
5927                 } else moveCount = 6;\r
5928             }\r
5929           }\r
5930 #if 1\r
5931     if (appData.debugMode) { int i;\r
5932       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5933               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5934               appData.drawRepeats);\r
5935       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5936            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5937 \r
5938     }\r
5939 #endif\r
5940                 /* Check for rep-draws */\r
5941                 count = 0;\r
5942                 for(k = forwardMostMove-2;\r
5943                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5944                         epStatus[k] < EP_UNKNOWN &&\r
5945                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5946                     k-=2)\r
5947                 {   int rights=0;\r
5948 #if 0\r
5949     if (appData.debugMode) {\r
5950       fprintf(debugFP, " loop\n");\r
5951     }\r
5952 #endif\r
5953                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5954 #if 0\r
5955     if (appData.debugMode) {\r
5956       fprintf(debugFP, "match\n");\r
5957     }\r
5958 #endif\r
5959                         /* compare castling rights */\r
5960                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5961                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5962                                 rights++; /* King lost rights, while rook still had them */\r
5963                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5964                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5965                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5966                                    rights++; /* but at least one rook lost them */\r
5967                         }\r
5968                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5969                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5970                                 rights++; \r
5971                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5972                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5973                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5974                                    rights++;\r
5975                         }\r
5976 #if 0\r
5977     if (appData.debugMode) {\r
5978       for(i=0; i<nrCastlingRights; i++)\r
5979       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5980     }\r
5981 \r
5982     if (appData.debugMode) {\r
5983       fprintf(debugFP, " %d %d\n", rights, k);\r
5984     }\r
5985 #endif\r
5986                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5987                             && appData.drawRepeats > 1) {\r
5988                              /* adjudicate after user-specified nr of repeats */\r
5989                              SendToProgram("force\n", cps->other); // suppress reply\r
5990                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5991                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5992                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { \r
5993                                 // [HGM] xiangqi: check for forbidden perpetuals\r
5994                                 int m, ourPerpetual = 1, hisPerpetual = 1;\r
5995                                 for(m=forwardMostMove; m>k; m-=2) {\r
5996                                     if(MateTest(boards[m], PosFlags(m), \r
5997                                                         EP_NONE, castlingRights[m]) != MT_CHECK)\r
5998                                         ourPerpetual = 0; // the current mover did not always check\r
5999                                     if(MateTest(boards[m-1], PosFlags(m-1), \r
6000                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)\r
6001                                         hisPerpetual = 0; // the opponent did not always check\r
6002                                 }\r
6003                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",\r
6004                                                                         ourPerpetual, hisPerpetual);\r
6005                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
6006                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
6007                                            "Xboard adjudication: perpetual checking", GE_XBOARD );\r
6008                                     return;\r
6009                                 }\r
6010                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet\r
6011                                     break; // (or we would have caught him before). Abort repetition-checking loop.\r
6012                                 // Now check for perpetual chases\r
6013                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase\r
6014                                     hisPerpetual = PerpetualChase(k, forwardMostMove);\r
6015                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);\r
6016                                     if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
6017                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
6018                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );\r
6019                                         return;\r
6020                                     }\r
6021                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet\r
6022                                         break; // Abort repetition-checking loop.\r
6023                                 }\r
6024                                 // if neither of us is checking or chasing all the time, or both are, it is draw\r
6025                              }\r
6026                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
6027                              return;\r
6028                         }\r
6029                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
6030                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
6031                     }\r
6032                 }\r
6033 \r
6034                 /* Now we test for 50-move draws. Determine ply count */\r
6035                 count = forwardMostMove;\r
6036                 /* look for last irreversble move */\r
6037                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
6038                     count--;\r
6039                 /* if we hit starting position, add initial plies */\r
6040                 if( count == backwardMostMove )\r
6041                     count -= initialRulePlies;\r
6042                 count = forwardMostMove - count; \r
6043                 if( count >= 100)\r
6044                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
6045                          /* this is used to judge if draw claims are legal */\r
6046                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
6047                          SendToProgram("force\n", cps->other); // suppress reply\r
6048                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6049                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6050                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
6051                          return;\r
6052                 }\r
6053 \r
6054                 /* if draw offer is pending, treat it as a draw claim\r
6055                  * when draw condition present, to allow engines a way to\r
6056                  * claim draws before making their move to avoid a race\r
6057                  * condition occurring after their move\r
6058                  */\r
6059                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
6060                          char *p = NULL;\r
6061                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
6062                              p = "Draw claim: 50-move rule";\r
6063                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
6064                              p = "Draw claim: 3-fold repetition";\r
6065                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
6066                              p = "Draw claim: insufficient mating material";\r
6067                          if( p != NULL ) {\r
6068                              SendToProgram("force\n", cps->other); // suppress reply\r
6069                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6070                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
6071                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6072                              return;\r
6073                          }\r
6074                 }\r
6075 \r
6076 \r
6077                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
6078                     SendToProgram("force\n", cps->other); // suppress reply\r
6079                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6080                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6081 \r
6082                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
6083 \r
6084                     return;\r
6085                 }\r
6086         }\r
6087 \r
6088         bookHit = NULL;\r
6089         if (gameMode == TwoMachinesPlay) {\r
6090             /* [HGM] relaying draw offers moved to after reception of move */\r
6091             /* and interpreting offer as claim if it brings draw condition */\r
6092             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
6093                 SendToProgram("draw\n", cps->other);\r
6094             }\r
6095             if (cps->other->sendTime) {\r
6096                 SendTimeRemaining(cps->other,\r
6097                                   cps->other->twoMachinesColor[0] == 'w');\r
6098             }\r
6099             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);\r
6100             if (firstMove && !bookHit) {\r
6101                 firstMove = FALSE;\r
6102                 if (cps->other->useColors) {\r
6103                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
6104                 }\r
6105                 SendToProgram("go\n", cps->other);\r
6106             }\r
6107             cps->other->maybeThinking = TRUE;\r
6108         }\r
6109 \r
6110         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6111         \r
6112         if (!pausing && appData.ringBellAfterMoves) {\r
6113             RingBell();\r
6114         }\r
6115 \r
6116         /* \r
6117          * Reenable menu items that were disabled while\r
6118          * machine was thinking\r
6119          */\r
6120         if (gameMode != TwoMachinesPlay)\r
6121             SetUserThinkingEnables();\r
6122 \r
6123         // [HGM] book: after book hit opponent has received move and is now in force mode\r
6124         // force the book reply into it, and then fake that it outputted this move by jumping\r
6125         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move\r
6126         if(bookHit) {\r
6127                 static char bookMove[MSG_SIZ]; // a bit generous?\r
6128 \r
6129                 strcpy(bookMove, "move ");\r
6130                 strcat(bookMove, bookHit);\r
6131                 message = bookMove;\r
6132                 cps = cps->other;\r
6133                 programStats.nodes = programStats.depth = programStats.time = \r
6134                 programStats.score = programStats.got_only_move = 0;\r
6135                 sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
6136 \r
6137                 if(cps->lastPing != cps->lastPong) {\r
6138                     savedMessage = message; // args for deferred call\r
6139                     savedState = cps;\r
6140                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
6141                     return;\r
6142                 }\r
6143                 goto FakeBookMove;\r
6144         }\r
6145 \r
6146         return;\r
6147     }\r
6148 \r
6149     /* Set special modes for chess engines.  Later something general\r
6150      *  could be added here; for now there is just one kludge feature,\r
6151      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
6152      *  when "xboard" is given as an interactive command.\r
6153      */\r
6154     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
6155         cps->useSigint = FALSE;\r
6156         cps->useSigterm = FALSE;\r
6157     }\r
6158 \r
6159     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
6160      * want this, I was asked to put it in, and obliged.\r
6161      */\r
6162     if (!strncmp(message, "setboard ", 9)) {\r
6163         Board initial_position; int i;\r
6164 \r
6165         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
6166 \r
6167         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
6168             DisplayError(_("Bad FEN received from engine"), 0);\r
6169             return ;\r
6170         } else {\r
6171            Reset(FALSE, FALSE);\r
6172            CopyBoard(boards[0], initial_position);\r
6173            initialRulePlies = FENrulePlies;\r
6174            epStatus[0] = FENepStatus;\r
6175            for( i=0; i<nrCastlingRights; i++ )\r
6176                 castlingRights[0][i] = FENcastlingRights[i];\r
6177            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
6178            else gameMode = MachinePlaysBlack;                 \r
6179            DrawPosition(FALSE, boards[currentMove]);\r
6180         }\r
6181         return;\r
6182     }\r
6183 \r
6184     /*\r
6185      * Look for communication commands\r
6186      */\r
6187     if (!strncmp(message, "telluser ", 9)) {\r
6188         DisplayNote(message + 9);\r
6189         return;\r
6190     }\r
6191     if (!strncmp(message, "tellusererror ", 14)) {\r
6192         DisplayError(message + 14, 0);\r
6193         return;\r
6194     }\r
6195     if (!strncmp(message, "tellopponent ", 13)) {\r
6196       if (appData.icsActive) {\r
6197         if (loggedOn) {\r
6198           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
6199           SendToICS(buf1);\r
6200         }\r
6201       } else {\r
6202         DisplayNote(message + 13);\r
6203       }\r
6204       return;\r
6205     }\r
6206     if (!strncmp(message, "tellothers ", 11)) {\r
6207       if (appData.icsActive) {\r
6208         if (loggedOn) {\r
6209           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
6210           SendToICS(buf1);\r
6211         }\r
6212       }\r
6213       return;\r
6214     }\r
6215     if (!strncmp(message, "tellall ", 8)) {\r
6216       if (appData.icsActive) {\r
6217         if (loggedOn) {\r
6218           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
6219           SendToICS(buf1);\r
6220         }\r
6221       } else {\r
6222         DisplayNote(message + 8);\r
6223       }\r
6224       return;\r
6225     }\r
6226     if (strncmp(message, "warning", 7) == 0) {\r
6227         /* Undocumented feature, use tellusererror in new code */\r
6228         DisplayError(message, 0);\r
6229         return;\r
6230     }\r
6231     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
6232         strcpy(realname, cps->tidy);\r
6233         strcat(realname, " query");\r
6234         AskQuestion(realname, buf2, buf1, cps->pr);\r
6235         return;\r
6236     }\r
6237     /* Commands from the engine directly to ICS.  We don't allow these to be \r
6238      *  sent until we are logged on. Crafty kibitzes have been known to \r
6239      *  interfere with the login process.\r
6240      */\r
6241     if (loggedOn) {\r
6242         if (!strncmp(message, "tellics ", 8)) {\r
6243             SendToICS(message + 8);\r
6244             SendToICS("\n");\r
6245             return;\r
6246         }\r
6247         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
6248             SendToICS(ics_prefix);\r
6249             SendToICS(message + 15);\r
6250             SendToICS("\n");\r
6251             return;\r
6252         }\r
6253         /* The following are for backward compatibility only */\r
6254         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
6255             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
6256             SendToICS(ics_prefix);\r
6257             SendToICS(message);\r
6258             SendToICS("\n");\r
6259             return;\r
6260         }\r
6261     }\r
6262     if (strncmp(message, "feature ", 8) == 0) {\r
6263       ParseFeatures(message+8, cps);\r
6264     }\r
6265     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
6266         return;\r
6267     }\r
6268     /*\r
6269      * If the move is illegal, cancel it and redraw the board.\r
6270      * Also deal with other error cases.  Matching is rather loose\r
6271      * here to accommodate engines written before the spec.\r
6272      */\r
6273     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
6274         strncmp(message, "Error", 5) == 0) {\r
6275         if (StrStr(message, "name") || \r
6276             StrStr(message, "rating") || StrStr(message, "?") ||\r
6277             StrStr(message, "result") || StrStr(message, "board") ||\r
6278             StrStr(message, "bk") || StrStr(message, "computer") ||\r
6279             StrStr(message, "variant") || StrStr(message, "hint") ||\r
6280             StrStr(message, "random") || StrStr(message, "depth") ||\r
6281             StrStr(message, "accepted")) {\r
6282             return;\r
6283         }\r
6284         if (StrStr(message, "protover")) {\r
6285           /* Program is responding to input, so it's apparently done\r
6286              initializing, and this error message indicates it is\r
6287              protocol version 1.  So we don't need to wait any longer\r
6288              for it to initialize and send feature commands. */\r
6289           FeatureDone(cps, 1);\r
6290           cps->protocolVersion = 1;\r
6291           return;\r
6292         }\r
6293         cps->maybeThinking = FALSE;\r
6294 \r
6295         if (StrStr(message, "draw")) {\r
6296             /* Program doesn't have "draw" command */\r
6297             cps->sendDrawOffers = 0;\r
6298             return;\r
6299         }\r
6300         if (cps->sendTime != 1 &&\r
6301             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
6302           /* Program apparently doesn't have "time" or "otim" command */\r
6303           cps->sendTime = 0;\r
6304           return;\r
6305         }\r
6306         if (StrStr(message, "analyze")) {\r
6307             cps->analysisSupport = FALSE;\r
6308             cps->analyzing = FALSE;\r
6309             Reset(FALSE, TRUE);\r
6310             sprintf(buf2, _("%s does not support analysis"), cps->tidy);\r
6311             DisplayError(buf2, 0);\r
6312             return;\r
6313         }\r
6314         if (StrStr(message, "(no matching move)st")) {\r
6315           /* Special kludge for GNU Chess 4 only */\r
6316           cps->stKludge = TRUE;\r
6317           SendTimeControl(cps, movesPerSession, timeControl,\r
6318                           timeIncrement, appData.searchDepth,\r
6319                           searchTime);\r
6320           return;\r
6321         }\r
6322         if (StrStr(message, "(no matching move)sd")) {\r
6323           /* Special kludge for GNU Chess 4 only */\r
6324           cps->sdKludge = TRUE;\r
6325           SendTimeControl(cps, movesPerSession, timeControl,\r
6326                           timeIncrement, appData.searchDepth,\r
6327                           searchTime);\r
6328           return;\r
6329         }\r
6330         if (!StrStr(message, "llegal")) {\r
6331             return;\r
6332         }\r
6333         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6334             gameMode == IcsIdle) return;\r
6335         if (forwardMostMove <= backwardMostMove) return;\r
6336 #if 0\r
6337         /* Following removed: it caused a bug where a real illegal move\r
6338            message in analyze mored would be ignored. */\r
6339         if (cps == &first && programStats.ok_to_send == 0) {\r
6340             /* Bogus message from Crafty responding to "."  This filtering\r
6341                can miss some of the bad messages, but fortunately the bug \r
6342                is fixed in current Crafty versions, so it doesn't matter. */\r
6343             return;\r
6344         }\r
6345 #endif\r
6346         if (pausing) PauseEvent();\r
6347         if (gameMode == PlayFromGameFile) {\r
6348             /* Stop reading this game file */\r
6349             gameMode = EditGame;\r
6350             ModeHighlight();\r
6351         }\r
6352         currentMove = --forwardMostMove;\r
6353         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
6354         SwitchClocks();\r
6355         DisplayBothClocks();\r
6356         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),\r
6357                 parseList[currentMove], cps->which);\r
6358         DisplayMoveError(buf1);\r
6359         DrawPosition(FALSE, boards[currentMove]);\r
6360 \r
6361         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
6362         /* only passes fully legal moves                            */\r
6363         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
6364             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
6365                                 "False illegal-move claim", GE_XBOARD );\r
6366         }\r
6367         return;\r
6368     }\r
6369     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
6370         /* Program has a broken "time" command that\r
6371            outputs a string not ending in newline.\r
6372            Don't use it. */\r
6373         cps->sendTime = 0;\r
6374     }\r
6375     \r
6376     /*\r
6377      * If chess program startup fails, exit with an error message.\r
6378      * Attempts to recover here are futile.\r
6379      */\r
6380     if ((StrStr(message, "unknown host") != NULL)\r
6381         || (StrStr(message, "No remote directory") != NULL)\r
6382         || (StrStr(message, "not found") != NULL)\r
6383         || (StrStr(message, "No such file") != NULL)\r
6384         || (StrStr(message, "can't alloc") != NULL)\r
6385         || (StrStr(message, "Permission denied") != NULL)) {\r
6386 \r
6387         cps->maybeThinking = FALSE;\r
6388         sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),\r
6389                 cps->which, cps->program, cps->host, message);\r
6390         RemoveInputSource(cps->isr);\r
6391         DisplayFatalError(buf1, 0, 1);\r
6392         return;\r
6393     }\r
6394     \r
6395     /* \r
6396      * Look for hint output\r
6397      */\r
6398     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
6399         if (cps == &first && hintRequested) {\r
6400             hintRequested = FALSE;\r
6401             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
6402                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
6403                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
6404                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
6405                                     fromY, fromX, toY, toX, promoChar, buf1);\r
6406                 sprintf(buf2, _("Hint: %s"), buf1);\r
6407                 DisplayInformation(buf2);\r
6408             } else {\r
6409                 /* Hint move could not be parsed!? */\r
6410                 sprintf(buf2,\r
6411                         _("Illegal hint move \"%s\"\nfrom %s chess program"),\r
6412                         buf1, cps->which);\r
6413                 DisplayError(buf2, 0);\r
6414             }\r
6415         } else {\r
6416             strcpy(lastHint, buf1);\r
6417         }\r
6418         return;\r
6419     }\r
6420 \r
6421     /*\r
6422      * Ignore other messages if game is not in progress\r
6423      */\r
6424     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6425         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
6426 \r
6427     /*\r
6428      * look for win, lose, draw, or draw offer\r
6429      */\r
6430     if (strncmp(message, "1-0", 3) == 0) {\r
6431         char *p, *q, *r = "";\r
6432         p = strchr(message, '{');\r
6433         if (p) {\r
6434             q = strchr(p, '}');\r
6435             if (q) {\r
6436                 *q = NULLCHAR;\r
6437                 r = p + 1;\r
6438             }\r
6439         }\r
6440         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
6441         return;\r
6442     } else if (strncmp(message, "0-1", 3) == 0) {\r
6443         char *p, *q, *r = "";\r
6444         p = strchr(message, '{');\r
6445         if (p) {\r
6446             q = strchr(p, '}');\r
6447             if (q) {\r
6448                 *q = NULLCHAR;\r
6449                 r = p + 1;\r
6450             }\r
6451         }\r
6452         /* Kludge for Arasan 4.1 bug */\r
6453         if (strcmp(r, "Black resigns") == 0) {\r
6454             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
6455             return;\r
6456         }\r
6457         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
6458         return;\r
6459     } else if (strncmp(message, "1/2", 3) == 0) {\r
6460         char *p, *q, *r = "";\r
6461         p = strchr(message, '{');\r
6462         if (p) {\r
6463             q = strchr(p, '}');\r
6464             if (q) {\r
6465                 *q = NULLCHAR;\r
6466                 r = p + 1;\r
6467             }\r
6468         }\r
6469             \r
6470         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
6471         return;\r
6472 \r
6473     } else if (strncmp(message, "White resign", 12) == 0) {\r
6474         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6475         return;\r
6476     } else if (strncmp(message, "Black resign", 12) == 0) {\r
6477         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6478         return;\r
6479     } else if (strncmp(message, "White matches", 13) == 0 ||\r
6480                strncmp(message, "Black matches", 13) == 0   ) {\r
6481         /* [HGM] ignore GNUShogi noises */\r
6482         return;\r
6483     } else if (strncmp(message, "White", 5) == 0 &&\r
6484                message[5] != '(' &&\r
6485                StrStr(message, "Black") == NULL) {\r
6486         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6487         return;\r
6488     } else if (strncmp(message, "Black", 5) == 0 &&\r
6489                message[5] != '(') {\r
6490         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6491         return;\r
6492     } else if (strcmp(message, "resign") == 0 ||\r
6493                strcmp(message, "computer resigns") == 0) {\r
6494         switch (gameMode) {\r
6495           case MachinePlaysBlack:\r
6496           case IcsPlayingBlack:\r
6497             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
6498             break;\r
6499           case MachinePlaysWhite:\r
6500           case IcsPlayingWhite:\r
6501             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
6502             break;\r
6503           case TwoMachinesPlay:\r
6504             if (cps->twoMachinesColor[0] == 'w')\r
6505               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6506             else\r
6507               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6508             break;\r
6509           default:\r
6510             /* can't happen */\r
6511             break;\r
6512         }\r
6513         return;\r
6514     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
6515         switch (gameMode) {\r
6516           case MachinePlaysBlack:\r
6517           case IcsPlayingBlack:\r
6518             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6519             break;\r
6520           case MachinePlaysWhite:\r
6521           case IcsPlayingWhite:\r
6522             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
6523             break;\r
6524           case TwoMachinesPlay:\r
6525             if (cps->twoMachinesColor[0] == 'w')\r
6526               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6527             else\r
6528               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6529             break;\r
6530           default:\r
6531             /* can't happen */\r
6532             break;\r
6533         }\r
6534         return;\r
6535     } else if (strncmp(message, "computer mates", 14) == 0) {\r
6536         switch (gameMode) {\r
6537           case MachinePlaysBlack:\r
6538           case IcsPlayingBlack:\r
6539             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
6540             break;\r
6541           case MachinePlaysWhite:\r
6542           case IcsPlayingWhite:\r
6543             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6544             break;\r
6545           case TwoMachinesPlay:\r
6546             if (cps->twoMachinesColor[0] == 'w')\r
6547               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6548             else\r
6549               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6550             break;\r
6551           default:\r
6552             /* can't happen */\r
6553             break;\r
6554         }\r
6555         return;\r
6556     } else if (strncmp(message, "checkmate", 9) == 0) {\r
6557         if (WhiteOnMove(forwardMostMove)) {\r
6558             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6559         } else {\r
6560             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6561         }\r
6562         return;\r
6563     } else if (strstr(message, "Draw") != NULL ||\r
6564                strstr(message, "game is a draw") != NULL) {\r
6565         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
6566         return;\r
6567     } else if (strstr(message, "offer") != NULL &&\r
6568                strstr(message, "draw") != NULL) {\r
6569 #if ZIPPY\r
6570         if (appData.zippyPlay && first.initDone) {\r
6571             /* Relay offer to ICS */\r
6572             SendToICS(ics_prefix);\r
6573             SendToICS("draw\n");\r
6574         }\r
6575 #endif\r
6576         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
6577         if (gameMode == TwoMachinesPlay) {\r
6578             if (cps->other->offeredDraw) {\r
6579                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6580             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
6581             /* until after we also have move, to see if it is really claim */\r
6582             }\r
6583 #if 0\r
6584               else {\r
6585                 if (cps->other->sendDrawOffers) {\r
6586                     SendToProgram("draw\n", cps->other);\r
6587                 }\r
6588             }\r
6589 #endif\r
6590         } else if (gameMode == MachinePlaysWhite ||\r
6591                    gameMode == MachinePlaysBlack) {\r
6592           if (userOfferedDraw) {\r
6593             DisplayInformation(_("Machine accepts your draw offer"));\r
6594             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6595           } else {\r
6596             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));\r
6597           }\r
6598         }\r
6599     }\r
6600 \r
6601     \r
6602     /*\r
6603      * Look for thinking output\r
6604      */\r
6605     if ( appData.showThinking // [HGM] thinking: test all options that cause this output\r
6606           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
6607                                 ) {\r
6608         int plylev, mvleft, mvtot, curscore, time;\r
6609         char mvname[MOVE_LEN];\r
6610         u64 nodes; // [DM]\r
6611         char plyext;\r
6612         int ignore = FALSE;\r
6613         int prefixHint = FALSE;\r
6614         mvname[0] = NULLCHAR;\r
6615 \r
6616         switch (gameMode) {\r
6617           case MachinePlaysBlack:\r
6618           case IcsPlayingBlack:\r
6619             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6620             break;\r
6621           case MachinePlaysWhite:\r
6622           case IcsPlayingWhite:\r
6623             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6624             break;\r
6625           case AnalyzeMode:\r
6626           case AnalyzeFile:\r
6627             break;\r
6628           case IcsObserving: /* [DM] icsEngineAnalyze */\r
6629             if (!appData.icsEngineAnalyze) ignore = TRUE;\r
6630             break;\r
6631           case TwoMachinesPlay:\r
6632             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
6633                 ignore = TRUE;\r
6634             }\r
6635             break;\r
6636           default:\r
6637             ignore = TRUE;\r
6638             break;\r
6639         }\r
6640 \r
6641         if (!ignore) {\r
6642             buf1[0] = NULLCHAR;\r
6643             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6644                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
6645 \r
6646                 if (plyext != ' ' && plyext != '\t') {\r
6647                     time *= 100;\r
6648                 }\r
6649 \r
6650                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6651                 if( cps->scoreIsAbsolute && \r
6652                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
6653                 {\r
6654                     curscore = -curscore;\r
6655                 }\r
6656 \r
6657 \r
6658                 programStats.depth = plylev;\r
6659                 programStats.nodes = nodes;\r
6660                 programStats.time = time;\r
6661                 programStats.score = curscore;\r
6662                 programStats.got_only_move = 0;\r
6663 \r
6664                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
6665                         int ticklen;\r
6666 \r
6667                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time\r
6668                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time\r
6669                         if(WhiteOnMove(forwardMostMove)) \r
6670                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
6671                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
6672                 }\r
6673 \r
6674                 /* Buffer overflow protection */\r
6675                 if (buf1[0] != NULLCHAR) {\r
6676                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
6677                         && appData.debugMode) {\r
6678                         fprintf(debugFP,\r
6679                                 "PV is too long; using the first %d bytes.\n",\r
6680                                 sizeof(programStats.movelist) - 1);\r
6681                     }\r
6682 \r
6683                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
6684                 } else {\r
6685                     sprintf(programStats.movelist, " no PV\n");\r
6686                 }\r
6687 \r
6688                 if (programStats.seen_stat) {\r
6689                     programStats.ok_to_send = 1;\r
6690                 }\r
6691 \r
6692                 if (strchr(programStats.movelist, '(') != NULL) {\r
6693                     programStats.line_is_book = 1;\r
6694                     programStats.nr_moves = 0;\r
6695                     programStats.moves_left = 0;\r
6696                 } else {\r
6697                     programStats.line_is_book = 0;\r
6698                 }\r
6699 \r
6700                 SendProgramStatsToFrontend( cps, &programStats );\r
6701 \r
6702                 /* \r
6703                     [AS] Protect the thinkOutput buffer from overflow... this\r
6704                     is only useful if buf1 hasn't overflowed first!\r
6705                 */\r
6706                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
6707                         plylev, \r
6708                         (gameMode == TwoMachinesPlay ?\r
6709                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
6710                         ((double) curscore) / 100.0,\r
6711                         prefixHint ? lastHint : "",\r
6712                         prefixHint ? " " : "" );\r
6713 \r
6714                 if( buf1[0] != NULLCHAR ) {\r
6715                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
6716 \r
6717                     if( strlen(buf1) > max_len ) {\r
6718                         if( appData.debugMode) {\r
6719                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
6720                         }\r
6721                         buf1[max_len+1] = '\0';\r
6722                     }\r
6723 \r
6724                     strcat( thinkOutput, buf1 );\r
6725                 }\r
6726 \r
6727                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode\r
6728                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6729                     DisplayMove(currentMove - 1);\r
6730                     DisplayAnalysis();\r
6731                 }\r
6732                 return;\r
6733 \r
6734             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6735                 /* crafty (9.25+) says "(only move) <move>"\r
6736                  * if there is only 1 legal move\r
6737                  */\r
6738                 sscanf(p, "(only move) %s", buf1);\r
6739                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6740                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6741                 programStats.depth = 1;\r
6742                 programStats.nr_moves = 1;\r
6743                 programStats.moves_left = 1;\r
6744                 programStats.nodes = 1;\r
6745                 programStats.time = 1;\r
6746                 programStats.got_only_move = 1;\r
6747 \r
6748                 /* Not really, but we also use this member to\r
6749                    mean "line isn't going to change" (Crafty\r
6750                    isn't searching, so stats won't change) */\r
6751                 programStats.line_is_book = 1;\r
6752 \r
6753                 SendProgramStatsToFrontend( cps, &programStats );\r
6754                 \r
6755                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || \r
6756                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6757                     DisplayMove(currentMove - 1);\r
6758                     DisplayAnalysis();\r
6759                 }\r
6760                 return;\r
6761             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",\r
6762                               &time, &nodes, &plylev, &mvleft,\r
6763                               &mvtot, mvname) >= 5) {\r
6764                 /* The stat01: line is from Crafty (9.29+) in response\r
6765                    to the "." command */\r
6766                 programStats.seen_stat = 1;\r
6767                 cps->maybeThinking = TRUE;\r
6768 \r
6769                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6770                   return;\r
6771 \r
6772                 programStats.depth = plylev;\r
6773                 programStats.time = time;\r
6774                 programStats.nodes = nodes;\r
6775                 programStats.moves_left = mvleft;\r
6776                 programStats.nr_moves = mvtot;\r
6777                 strcpy(programStats.move_name, mvname);\r
6778                 programStats.ok_to_send = 1;\r
6779                 programStats.movelist[0] = '\0';\r
6780 \r
6781                 SendProgramStatsToFrontend( cps, &programStats );\r
6782 \r
6783                 DisplayAnalysis();\r
6784                 return;\r
6785 \r
6786             } else if (strncmp(message,"++",2) == 0) {\r
6787                 /* Crafty 9.29+ outputs this */\r
6788                 programStats.got_fail = 2;\r
6789                 return;\r
6790 \r
6791             } else if (strncmp(message,"--",2) == 0) {\r
6792                 /* Crafty 9.29+ outputs this */\r
6793                 programStats.got_fail = 1;\r
6794                 return;\r
6795 \r
6796             } else if (thinkOutput[0] != NULLCHAR &&\r
6797                        strncmp(message, "    ", 4) == 0) {\r
6798                 unsigned message_len;\r
6799 \r
6800                 p = message;\r
6801                 while (*p && *p == ' ') p++;\r
6802 \r
6803                 message_len = strlen( p );\r
6804 \r
6805                 /* [AS] Avoid buffer overflow */\r
6806                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6807                     strcat(thinkOutput, " ");\r
6808                     strcat(thinkOutput, p);\r
6809                 }\r
6810 \r
6811                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6812                     strcat(programStats.movelist, " ");\r
6813                     strcat(programStats.movelist, p);\r
6814                 }\r
6815 \r
6816                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||\r
6817                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6818                     DisplayMove(currentMove - 1);\r
6819                     DisplayAnalysis();\r
6820                 }\r
6821                 return;\r
6822             }\r
6823         }\r
6824         else {\r
6825             buf1[0] = NULLCHAR;\r
6826 \r
6827             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6828                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6829             {\r
6830                 ChessProgramStats cpstats;\r
6831 \r
6832                 if (plyext != ' ' && plyext != '\t') {\r
6833                     time *= 100;\r
6834                 }\r
6835 \r
6836                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6837                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6838                     curscore = -curscore;\r
6839                 }\r
6840 \r
6841                 cpstats.depth = plylev;\r
6842                 cpstats.nodes = nodes;\r
6843                 cpstats.time = time;\r
6844                 cpstats.score = curscore;\r
6845                 cpstats.got_only_move = 0;\r
6846                 cpstats.movelist[0] = '\0';\r
6847 \r
6848                 if (buf1[0] != NULLCHAR) {\r
6849                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6850                 }\r
6851 \r
6852                 cpstats.ok_to_send = 0;\r
6853                 cpstats.line_is_book = 0;\r
6854                 cpstats.nr_moves = 0;\r
6855                 cpstats.moves_left = 0;\r
6856 \r
6857                 SendProgramStatsToFrontend( cps, &cpstats );\r
6858             }\r
6859         }\r
6860     }\r
6861 }\r
6862 \r
6863 \r
6864 /* Parse a game score from the character string "game", and\r
6865    record it as the history of the current game.  The game\r
6866    score is NOT assumed to start from the standard position. \r
6867    The display is not updated in any way.\r
6868    */\r
6869 void\r
6870 ParseGameHistory(game)\r
6871      char *game;\r
6872 {\r
6873     ChessMove moveType;\r
6874     int fromX, fromY, toX, toY, boardIndex;\r
6875     char promoChar;\r
6876     char *p, *q;\r
6877     char buf[MSG_SIZ];\r
6878 \r
6879     if (appData.debugMode)\r
6880       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6881 \r
6882     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6883     gameInfo.site = StrSave(appData.icsHost);\r
6884     gameInfo.date = PGNDate();\r
6885     gameInfo.round = StrSave("-");\r
6886 \r
6887     /* Parse out names of players */\r
6888     while (*game == ' ') game++;\r
6889     p = buf;\r
6890     while (*game != ' ') *p++ = *game++;\r
6891     *p = NULLCHAR;\r
6892     gameInfo.white = StrSave(buf);\r
6893     while (*game == ' ') game++;\r
6894     p = buf;\r
6895     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6896     *p = NULLCHAR;\r
6897     gameInfo.black = StrSave(buf);\r
6898 \r
6899     /* Parse moves */\r
6900     boardIndex = blackPlaysFirst ? 1 : 0;\r
6901     yynewstr(game);\r
6902     for (;;) {\r
6903         yyboardindex = boardIndex;\r
6904         moveType = (ChessMove) yylex();\r
6905         switch (moveType) {\r
6906           case IllegalMove:             /* maybe suicide chess, etc. */\r
6907   if (appData.debugMode) {\r
6908     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6909     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6910     setbuf(debugFP, NULL);\r
6911   }\r
6912           case WhitePromotionChancellor:\r
6913           case BlackPromotionChancellor:\r
6914           case WhitePromotionArchbishop:\r
6915           case BlackPromotionArchbishop:\r
6916           case WhitePromotionQueen:\r
6917           case BlackPromotionQueen:\r
6918           case WhitePromotionRook:\r
6919           case BlackPromotionRook:\r
6920           case WhitePromotionBishop:\r
6921           case BlackPromotionBishop:\r
6922           case WhitePromotionKnight:\r
6923           case BlackPromotionKnight:\r
6924           case WhitePromotionKing:\r
6925           case BlackPromotionKing:\r
6926           case NormalMove:\r
6927           case WhiteCapturesEnPassant:\r
6928           case BlackCapturesEnPassant:\r
6929           case WhiteKingSideCastle:\r
6930           case WhiteQueenSideCastle:\r
6931           case BlackKingSideCastle:\r
6932           case BlackQueenSideCastle:\r
6933           case WhiteKingSideCastleWild:\r
6934           case WhiteQueenSideCastleWild:\r
6935           case BlackKingSideCastleWild:\r
6936           case BlackQueenSideCastleWild:\r
6937           /* PUSH Fabien */\r
6938           case WhiteHSideCastleFR:\r
6939           case WhiteASideCastleFR:\r
6940           case BlackHSideCastleFR:\r
6941           case BlackASideCastleFR:\r
6942           /* POP Fabien */\r
6943             fromX = currentMoveString[0] - AAA;\r
6944             fromY = currentMoveString[1] - ONE;\r
6945             toX = currentMoveString[2] - AAA;\r
6946             toY = currentMoveString[3] - ONE;\r
6947             promoChar = currentMoveString[4];\r
6948             break;\r
6949           case WhiteDrop:\r
6950           case BlackDrop:\r
6951             fromX = moveType == WhiteDrop ?\r
6952               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6953             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6954             fromY = DROP_RANK;\r
6955             toX = currentMoveString[2] - AAA;\r
6956             toY = currentMoveString[3] - ONE;\r
6957             promoChar = NULLCHAR;\r
6958             break;\r
6959           case AmbiguousMove:\r
6960             /* bug? */\r
6961             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);\r
6962   if (appData.debugMode) {\r
6963     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
6964     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6965     setbuf(debugFP, NULL);\r
6966   }\r
6967             DisplayError(buf, 0);\r
6968             return;\r
6969           case ImpossibleMove:\r
6970             /* bug? */\r
6971             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);\r
6972   if (appData.debugMode) {\r
6973     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
6974     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6975     setbuf(debugFP, NULL);\r
6976   }\r
6977             DisplayError(buf, 0);\r
6978             return;\r
6979           case (ChessMove) 0:   /* end of file */\r
6980             if (boardIndex < backwardMostMove) {\r
6981                 /* Oops, gap.  How did that happen? */\r
6982                 DisplayError(_("Gap in move list"), 0);\r
6983                 return;\r
6984             }\r
6985             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
6986             if (boardIndex > forwardMostMove) {\r
6987                 forwardMostMove = boardIndex;\r
6988             }\r
6989             return;\r
6990           case ElapsedTime:\r
6991             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
6992                 strcat(parseList[boardIndex-1], " ");\r
6993                 strcat(parseList[boardIndex-1], yy_text);\r
6994             }\r
6995             continue;\r
6996           case Comment:\r
6997           case PGNTag:\r
6998           case NAG:\r
6999           default:\r
7000             /* ignore */\r
7001             continue;\r
7002           case WhiteWins:\r
7003           case BlackWins:\r
7004           case GameIsDrawn:\r
7005           case GameUnfinished:\r
7006             if (gameMode == IcsExamining) {\r
7007                 if (boardIndex < backwardMostMove) {\r
7008                     /* Oops, gap.  How did that happen? */\r
7009                     return;\r
7010                 }\r
7011                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
7012                 return;\r
7013             }\r
7014             gameInfo.result = moveType;\r
7015             p = strchr(yy_text, '{');\r
7016             if (p == NULL) p = strchr(yy_text, '(');\r
7017             if (p == NULL) {\r
7018                 p = yy_text;\r
7019                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
7020             } else {\r
7021                 q = strchr(p, *p == '{' ? '}' : ')');\r
7022                 if (q != NULL) *q = NULLCHAR;\r
7023                 p++;\r
7024             }\r
7025             gameInfo.resultDetails = StrSave(p);\r
7026             continue;\r
7027         }\r
7028         if (boardIndex >= forwardMostMove &&\r
7029             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
7030             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
7031             return;\r
7032         }\r
7033         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
7034                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
7035                                  parseList[boardIndex]);\r
7036         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
7037         /* currentMoveString is set as a side-effect of yylex */\r
7038         strcpy(moveList[boardIndex], currentMoveString);\r
7039         strcat(moveList[boardIndex], "\n");\r
7040         boardIndex++;\r
7041         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
7042         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
7043                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
7044           case MT_NONE:\r
7045           case MT_STALEMATE:\r
7046           default:\r
7047             break;\r
7048           case MT_CHECK:\r
7049             if(gameInfo.variant != VariantShogi)\r
7050                 strcat(parseList[boardIndex - 1], "+");\r
7051             break;\r
7052           case MT_CHECKMATE:\r
7053             strcat(parseList[boardIndex - 1], "#");\r
7054             break;\r
7055         }\r
7056     }\r
7057 }\r
7058 \r
7059 \r
7060 /* Apply a move to the given board  */\r
7061 void\r
7062 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
7063      int fromX, fromY, toX, toY;\r
7064      int promoChar;\r
7065      Board board;\r
7066 {\r
7067   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
7068 \r
7069     /* [HGM] compute & store e.p. status and castling rights for new position */\r
7070     /* if we are updating a board for which those exist (i.e. in boards[])    */\r
7071     if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)\r
7072     { int i;\r
7073 \r
7074       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
7075       oldEP = epStatus[p-1];\r
7076       epStatus[p] = EP_NONE;\r
7077 \r
7078       if( board[toY][toX] != EmptySquare ) \r
7079            epStatus[p] = EP_CAPTURE;  \r
7080 \r
7081       if( board[fromY][fromX] == WhitePawn ) {\r
7082            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7083                epStatus[p] = EP_PAWN_MOVE;\r
7084            if( toY-fromY==2) {\r
7085                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
7086                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7087                       epStatus[p] = toX | berolina;\r
7088                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
7089                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7090                       epStatus[p] = toX;\r
7091            }\r
7092       } else \r
7093       if( board[fromY][fromX] == BlackPawn ) {\r
7094            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7095                epStatus[p] = EP_PAWN_MOVE; \r
7096            if( toY-fromY== -2) {\r
7097                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
7098                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7099                       epStatus[p] = toX | berolina;\r
7100                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
7101                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7102                       epStatus[p] = toX;\r
7103            }\r
7104        }\r
7105 \r
7106        for(i=0; i<nrCastlingRights; i++) {\r
7107            castlingRights[p][i] = castlingRights[p-1][i];\r
7108            if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||\r
7109               castlingRights[p][i] == toX   && castlingRank[i] == toY   \r
7110              ) castlingRights[p][i] = -1; // revoke for moved or captured piece\r
7111        }\r
7112 \r
7113     }\r
7114 \r
7115   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
7116   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
7117        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
7118          \r
7119   if (fromX == toX && fromY == toY) return;\r
7120 \r
7121   if (fromY == DROP_RANK) {\r
7122         /* must be first */\r
7123         piece = board[toY][toX] = (ChessSquare) fromX;\r
7124   } else {\r
7125      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
7126      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
7127      if(gameInfo.variant == VariantKnightmate)\r
7128          king += (int) WhiteUnicorn - (int) WhiteKing;\r
7129 \r
7130     /* Code added by Tord: */\r
7131     /* FRC castling assumed when king captures friendly rook. */\r
7132     if (board[fromY][fromX] == WhiteKing &&\r
7133              board[toY][toX] == WhiteRook) {\r
7134       board[fromY][fromX] = EmptySquare;\r
7135       board[toY][toX] = EmptySquare;\r
7136       if(toX > fromX) {\r
7137         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
7138       } else {\r
7139         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
7140       }\r
7141     } else if (board[fromY][fromX] == BlackKing &&\r
7142                board[toY][toX] == BlackRook) {\r
7143       board[fromY][fromX] = EmptySquare;\r
7144       board[toY][toX] = EmptySquare;\r
7145       if(toX > fromX) {\r
7146         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
7147       } else {\r
7148         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
7149       }\r
7150     /* End of code added by Tord */\r
7151 \r
7152     } else if (board[fromY][fromX] == king\r
7153         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7154         && toY == fromY && toX > fromX+1) {\r
7155         board[fromY][fromX] = EmptySquare;\r
7156         board[toY][toX] = king;\r
7157         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7158         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7159     } else if (board[fromY][fromX] == king\r
7160         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7161                && toY == fromY && toX < fromX-1) {\r
7162         board[fromY][fromX] = EmptySquare;\r
7163         board[toY][toX] = king;\r
7164         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7165         board[fromY][BOARD_LEFT] = EmptySquare;\r
7166     } else if (board[fromY][fromX] == WhitePawn\r
7167                && toY == BOARD_HEIGHT-1\r
7168                && gameInfo.variant != VariantXiangqi\r
7169                ) {\r
7170         /* white pawn promotion */\r
7171         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
7172         if (board[toY][toX] == EmptySquare) {\r
7173             board[toY][toX] = WhiteQueen;\r
7174         }\r
7175         if(gameInfo.variant==VariantBughouse ||\r
7176            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7177             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7178         board[fromY][fromX] = EmptySquare;\r
7179     } else if ((fromY == BOARD_HEIGHT-4)\r
7180                && (toX != fromX)\r
7181                && gameInfo.variant != VariantXiangqi\r
7182                && gameInfo.variant != VariantBerolina\r
7183                && (board[fromY][fromX] == WhitePawn)\r
7184                && (board[toY][toX] == EmptySquare)) {\r
7185         board[fromY][fromX] = EmptySquare;\r
7186         board[toY][toX] = WhitePawn;\r
7187         captured = board[toY - 1][toX];\r
7188         board[toY - 1][toX] = EmptySquare;\r
7189     } else if ((fromY == BOARD_HEIGHT-4)\r
7190                && (toX == fromX)\r
7191                && gameInfo.variant == VariantBerolina\r
7192                && (board[fromY][fromX] == WhitePawn)\r
7193                && (board[toY][toX] == EmptySquare)) {\r
7194         board[fromY][fromX] = EmptySquare;\r
7195         board[toY][toX] = WhitePawn;\r
7196         if(oldEP & EP_BEROLIN_A) {\r
7197                 captured = board[fromY][fromX-1];\r
7198                 board[fromY][fromX-1] = EmptySquare;\r
7199         }else{  captured = board[fromY][fromX+1];\r
7200                 board[fromY][fromX+1] = EmptySquare;\r
7201         }\r
7202     } else if (board[fromY][fromX] == king\r
7203         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7204                && toY == fromY && toX > fromX+1) {\r
7205         board[fromY][fromX] = EmptySquare;\r
7206         board[toY][toX] = king;\r
7207         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7208         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7209     } else if (board[fromY][fromX] == king\r
7210         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7211                && toY == fromY && toX < fromX-1) {\r
7212         board[fromY][fromX] = EmptySquare;\r
7213         board[toY][toX] = king;\r
7214         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7215         board[fromY][BOARD_LEFT] = EmptySquare;\r
7216     } else if (fromY == 7 && fromX == 3\r
7217                && board[fromY][fromX] == BlackKing\r
7218                && toY == 7 && toX == 5) {\r
7219         board[fromY][fromX] = EmptySquare;\r
7220         board[toY][toX] = BlackKing;\r
7221         board[fromY][7] = EmptySquare;\r
7222         board[toY][4] = BlackRook;\r
7223     } else if (fromY == 7 && fromX == 3\r
7224                && board[fromY][fromX] == BlackKing\r
7225                && toY == 7 && toX == 1) {\r
7226         board[fromY][fromX] = EmptySquare;\r
7227         board[toY][toX] = BlackKing;\r
7228         board[fromY][0] = EmptySquare;\r
7229         board[toY][2] = BlackRook;\r
7230     } else if (board[fromY][fromX] == BlackPawn\r
7231                && toY == 0\r
7232                && gameInfo.variant != VariantXiangqi\r
7233                ) {\r
7234         /* black pawn promotion */\r
7235         board[0][toX] = CharToPiece(ToLower(promoChar));\r
7236         if (board[0][toX] == EmptySquare) {\r
7237             board[0][toX] = BlackQueen;\r
7238         }\r
7239         if(gameInfo.variant==VariantBughouse ||\r
7240            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7241             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7242         board[fromY][fromX] = EmptySquare;\r
7243     } else if ((fromY == 3)\r
7244                && (toX != fromX)\r
7245                && gameInfo.variant != VariantXiangqi\r
7246                && gameInfo.variant != VariantBerolina\r
7247                && (board[fromY][fromX] == BlackPawn)\r
7248                && (board[toY][toX] == EmptySquare)) {\r
7249         board[fromY][fromX] = EmptySquare;\r
7250         board[toY][toX] = BlackPawn;\r
7251         captured = board[toY + 1][toX];\r
7252         board[toY + 1][toX] = EmptySquare;\r
7253     } else if ((fromY == 3)\r
7254                && (toX == fromX)\r
7255                && gameInfo.variant == VariantBerolina\r
7256                && (board[fromY][fromX] == BlackPawn)\r
7257                && (board[toY][toX] == EmptySquare)) {\r
7258         board[fromY][fromX] = EmptySquare;\r
7259         board[toY][toX] = BlackPawn;\r
7260         if(oldEP & EP_BEROLIN_A) {\r
7261                 captured = board[fromY][fromX-1];\r
7262                 board[fromY][fromX-1] = EmptySquare;\r
7263         }else{  captured = board[fromY][fromX+1];\r
7264                 board[fromY][fromX+1] = EmptySquare;\r
7265         }\r
7266     } else {\r
7267         board[toY][toX] = board[fromY][fromX];\r
7268         board[fromY][fromX] = EmptySquare;\r
7269     }\r
7270 \r
7271     /* [HGM] now we promote for Shogi, if needed */\r
7272     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
7273         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7274   }\r
7275 \r
7276     if (gameInfo.holdingsWidth != 0) {\r
7277 \r
7278       /* !!A lot more code needs to be written to support holdings  */\r
7279       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
7280       /* penultimate board files, so they are automaticlly stored   */\r
7281       /* in the game history.                                       */\r
7282       if (fromY == DROP_RANK) {\r
7283         /* Delete from holdings, by decreasing count */\r
7284         /* and erasing image if necessary            */\r
7285         p = (int) fromX;\r
7286         if(p < (int) BlackPawn) { /* white drop */\r
7287              p -= (int)WhitePawn;\r
7288              if(p >= gameInfo.holdingsSize) p = 0;\r
7289              if(--board[p][BOARD_WIDTH-2] == 0)\r
7290                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
7291         } else {                  /* black drop */\r
7292              p -= (int)BlackPawn;\r
7293              if(p >= gameInfo.holdingsSize) p = 0;\r
7294              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
7295                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
7296         }\r
7297       }\r
7298       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
7299           && gameInfo.variant != VariantBughouse        ) {\r
7300         /* [HGM] holdings: Add to holdings, if holdings exist */\r
7301         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { \r
7302                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
7303                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
7304         }\r
7305         p = (int) captured;\r
7306         if (p >= (int) BlackPawn) {\r
7307           p -= (int)BlackPawn;\r
7308           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7309                   /* in Shogi restore piece to its original  first */\r
7310                   captured = (ChessSquare) (DEMOTED captured);\r
7311                   p = DEMOTED p;\r
7312           }\r
7313           p = PieceToNumber((ChessSquare)p);\r
7314           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
7315           board[p][BOARD_WIDTH-2]++;\r
7316           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;\r
7317         } else {\r
7318           p -= (int)WhitePawn;\r
7319           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7320                   captured = (ChessSquare) (DEMOTED captured);\r
7321                   p = DEMOTED p;\r
7322           }\r
7323           p = PieceToNumber((ChessSquare)p);\r
7324           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
7325           board[BOARD_HEIGHT-1-p][1]++;\r
7326           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;\r
7327         }\r
7328       }\r
7329 \r
7330     } else if (gameInfo.variant == VariantAtomic) {\r
7331       if (captured != EmptySquare) {\r
7332         int y, x;\r
7333         for (y = toY-1; y <= toY+1; y++) {\r
7334           for (x = toX-1; x <= toX+1; x++) {\r
7335             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
7336                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
7337               board[y][x] = EmptySquare;\r
7338             }\r
7339           }\r
7340         }\r
7341         board[toY][toX] = EmptySquare;\r
7342       }\r
7343     }\r
7344     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
7345         /* [HGM] Shogi promotions */\r
7346         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7347     }\r
7348 \r
7349 }\r
7350 \r
7351 /* Updates forwardMostMove */\r
7352 void\r
7353 MakeMove(fromX, fromY, toX, toY, promoChar)\r
7354      int fromX, fromY, toX, toY;\r
7355      int promoChar;\r
7356 {\r
7357 //    forwardMostMove++; // [HGM] bare: moved downstream\r
7358 \r
7359     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */\r
7360         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
7361         piece = boards[forwardMostMove][fromY][fromX];\r
7362         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
7363         if(gameInfo.variant == VariantKnightmate)\r
7364             king += (int) WhiteUnicorn - (int) WhiteKing;\r
7365         if(forwardMostMove == 0) {\r
7366             if(blackPlaysFirst) \r
7367                 fprintf(serverMoves, "%s;", second.tidy);\r
7368             fprintf(serverMoves, "%s;", first.tidy);\r
7369             if(!blackPlaysFirst) \r
7370                 fprintf(serverMoves, "%s;", second.tidy);\r
7371         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
7372         lastLoadFlag = loadFlag;\r
7373         // print base move\r
7374         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
7375         // print castling suffix\r
7376         if( toY == fromY && piece == king ) {\r
7377             if(toX-fromX > 1)\r
7378                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
7379             if(fromX-toX >1)\r
7380                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
7381         }\r
7382         // e.p. suffix\r
7383         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||\r
7384              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&\r
7385              boards[forwardMostMove][toY][toX] == EmptySquare\r
7386              && fromX != toX )\r
7387                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
7388         // promotion suffix\r
7389         if(promoChar != NULLCHAR)\r
7390                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
7391         if(!loadFlag) {\r
7392             fprintf(serverMoves, "/%d/%d",\r
7393                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);\r
7394             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;\r
7395             else                      timeLeft = blackTimeRemaining/1000;\r
7396             fprintf(serverMoves, "/%d", timeLeft);\r
7397         }\r
7398         fflush(serverMoves);\r
7399     }\r
7400 \r
7401     if (forwardMostMove+1 >= MAX_MOVES) {\r
7402       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
7403                         0, 1);\r
7404       return;\r
7405     }\r
7406     SwitchClocks();\r
7407     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;\r
7408     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;\r
7409     if (commentList[forwardMostMove+1] != NULL) {\r
7410         free(commentList[forwardMostMove+1]);\r
7411         commentList[forwardMostMove+1] = NULL;\r
7412     }\r
7413     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);\r
7414     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);\r
7415     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board\r
7416     gameInfo.result = GameUnfinished;\r
7417     if (gameInfo.resultDetails != NULL) {\r
7418         free(gameInfo.resultDetails);\r
7419         gameInfo.resultDetails = NULL;\r
7420     }\r
7421     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
7422                               moveList[forwardMostMove - 1]);\r
7423     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
7424                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
7425                              fromY, fromX, toY, toX, promoChar,\r
7426                              parseList[forwardMostMove - 1]);\r
7427     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
7428                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
7429                             castlingRights[forwardMostMove]) ) {\r
7430       case MT_NONE:\r
7431       case MT_STALEMATE:\r
7432       default:\r
7433         break;\r
7434       case MT_CHECK:\r
7435         if(gameInfo.variant != VariantShogi)\r
7436             strcat(parseList[forwardMostMove - 1], "+");\r
7437         break;\r
7438       case MT_CHECKMATE:\r
7439         strcat(parseList[forwardMostMove - 1], "#");\r
7440         break;\r
7441     }\r
7442     if (appData.debugMode) {\r
7443         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
7444     }\r
7445 \r
7446 }\r
7447 \r
7448 /* Updates currentMove if not pausing */\r
7449 void\r
7450 ShowMove(fromX, fromY, toX, toY)\r
7451 {\r
7452     int instant = (gameMode == PlayFromGameFile) ?\r
7453         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
7454     if(appData.noGUI) return;\r
7455     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
7456         if (!instant) {\r
7457             if (forwardMostMove == currentMove + 1) {\r
7458                 AnimateMove(boards[forwardMostMove - 1],\r
7459                             fromX, fromY, toX, toY);\r
7460             }\r
7461             if (appData.highlightLastMove) {\r
7462                 SetHighlights(fromX, fromY, toX, toY);\r
7463             }\r
7464         }\r
7465         currentMove = forwardMostMove;\r
7466     }\r
7467 \r
7468     if (instant) return;\r
7469 \r
7470     DisplayMove(currentMove - 1);\r
7471     DrawPosition(FALSE, boards[currentMove]);\r
7472     DisplayBothClocks();\r
7473     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
7474 }\r
7475 \r
7476 void SendEgtPath(ChessProgramState *cps)\r
7477 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */\r
7478         char buf[MSG_SIZ], name[MSG_SIZ], *p;\r
7479 \r
7480         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;\r
7481 \r
7482         while(*p) {\r
7483             char c, *q = name+1, *r, *s;\r
7484 \r
7485             name[0] = ','; // extract next format name from feature and copy with prefixed ','\r
7486             while(*p && *p != ',') *q++ = *p++;\r
7487             *q++ = ':'; *q = 0;\r
7488             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && \r
7489                 strcmp(name, ",nalimov:") == 0 ) {\r
7490                 // take nalimov path from the menu-changeable option first, if it is defined\r
7491                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);\r
7492                 SendToProgram(buf,cps);     // send egtbpath command for nalimov\r
7493             } else\r
7494             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||\r
7495                 (s = StrStr(appData.egtFormats, name)) != NULL) {\r
7496                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma\r
7497                 s = r = StrStr(s, ":") + 1; // beginning of path info\r
7498                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string\r
7499                 c = *r; *r = 0;             // temporarily null-terminate path info\r
7500                     *--q = 0;               // strip of trailig ':' from name\r
7501                     sprintf(buf, "egtbpath %s %s\n", name+1, s);\r
7502                 *r = c;\r
7503                 SendToProgram(buf,cps);     // send egtbpath command for this format\r
7504             }\r
7505             if(*p == ',') p++; // read away comma to position for next format name\r
7506         }\r
7507 }\r
7508 \r
7509 void\r
7510 InitChessProgram(cps, setup)\r
7511      ChessProgramState *cps;\r
7512      int setup; /* [HGM] needed to setup FRC opening position */\r
7513 {\r
7514     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
7515     if (appData.noChessProgram) return;\r
7516     hintRequested = FALSE;\r
7517     bookRequested = FALSE;\r
7518 \r
7519     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */\r
7520     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */\r
7521     if(cps->memSize) { /* [HGM] memory */\r
7522         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);\r
7523         SendToProgram(buf, cps);\r
7524     }\r
7525     SendEgtPath(cps); /* [HGM] EGT */\r
7526     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */\r
7527         sprintf(buf, "cores %d\n", appData.smpCores);\r
7528         SendToProgram(buf, cps);\r
7529     }\r
7530 \r
7531     SendToProgram(cps->initString, cps);\r
7532     if (gameInfo.variant != VariantNormal &&\r
7533         gameInfo.variant != VariantLoadable\r
7534         /* [HGM] also send variant if board size non-standard */\r
7535         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
7536                                             ) {\r
7537       char *v = VariantName(gameInfo.variant);\r
7538       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
7539         /* [HGM] in protocol 1 we have to assume all variants valid */\r
7540         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);\r
7541         DisplayFatalError(buf, 0, 1);\r
7542         return;\r
7543       }\r
7544 \r
7545       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
7546       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7547       if( gameInfo.variant == VariantXiangqi )\r
7548            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
7549       if( gameInfo.variant == VariantShogi )\r
7550            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
7551       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
7552            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
7553       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
7554                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
7555            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7556       if( gameInfo.variant == VariantCourier )\r
7557            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7558       if( gameInfo.variant == VariantSuper )\r
7559            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7560       if( gameInfo.variant == VariantGreat )\r
7561            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7562 \r
7563       if(overruled) {\r
7564            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
7565                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
7566            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
7567            if(StrStr(cps->variants, b) == NULL) { \r
7568                // specific sized variant not known, check if general sizing allowed\r
7569                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
7570                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
7571                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
7572                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
7573                        DisplayFatalError(buf, 0, 1);\r
7574                        return;\r
7575                    }\r
7576                    /* [HGM] here we really should compare with the maximum supported board size */\r
7577                }\r
7578            }\r
7579       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
7580       sprintf(buf, "variant %s\n", b);\r
7581       SendToProgram(buf, cps);\r
7582     }\r
7583     currentlyInitializedVariant = gameInfo.variant;\r
7584 \r
7585     /* [HGM] send opening position in FRC to first engine */\r
7586     if(setup) {\r
7587           SendToProgram("force\n", cps);\r
7588           SendBoard(cps, 0);\r
7589           /* engine is now in force mode! Set flag to wake it up after first move. */\r
7590           setboardSpoiledMachineBlack = 1;\r
7591     }\r
7592 \r
7593     if (cps->sendICS) {\r
7594       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
7595       SendToProgram(buf, cps);\r
7596     }\r
7597     cps->maybeThinking = FALSE;\r
7598     cps->offeredDraw = 0;\r
7599     if (!appData.icsActive) {\r
7600         SendTimeControl(cps, movesPerSession, timeControl,\r
7601                         timeIncrement, appData.searchDepth,\r
7602                         searchTime);\r
7603     }\r
7604     if (appData.showThinking \r
7605         // [HGM] thinking: four options require thinking output to be sent\r
7606         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
7607                                 ) {\r
7608         SendToProgram("post\n", cps);\r
7609     }\r
7610     SendToProgram("hard\n", cps);\r
7611     if (!appData.ponderNextMove) {\r
7612         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
7613            it without being sure what state we are in first.  "hard"\r
7614            is not a toggle, so that one is OK.\r
7615          */\r
7616         SendToProgram("easy\n", cps);\r
7617     }\r
7618     if (cps->usePing) {\r
7619       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
7620       SendToProgram(buf, cps);\r
7621     }\r
7622     cps->initDone = TRUE;\r
7623 }   \r
7624 \r
7625 \r
7626 void\r
7627 StartChessProgram(cps)\r
7628      ChessProgramState *cps;\r
7629 {\r
7630     char buf[MSG_SIZ];\r
7631     int err;\r
7632 \r
7633     if (appData.noChessProgram) return;\r
7634     cps->initDone = FALSE;\r
7635 \r
7636     if (strcmp(cps->host, "localhost") == 0) {\r
7637         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
7638     } else if (*appData.remoteShell == NULLCHAR) {\r
7639         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
7640     } else {\r
7641         if (*appData.remoteUser == NULLCHAR) {\r
7642             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
7643                     cps->program);\r
7644         } else {\r
7645             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
7646                     cps->host, appData.remoteUser, cps->program);\r
7647         }\r
7648         err = StartChildProcess(buf, "", &cps->pr);\r
7649     }\r
7650     \r
7651     if (err != 0) {\r
7652         sprintf(buf, _("Startup failure on '%s'"), cps->program);\r
7653         DisplayFatalError(buf, err, 1);\r
7654         cps->pr = NoProc;\r
7655         cps->isr = NULL;\r
7656         return;\r
7657     }\r
7658     \r
7659     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
7660     if (cps->protocolVersion > 1) {\r
7661       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
7662       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options\r
7663       cps->comboCnt = 0;  //                and values of combo boxes\r
7664       SendToProgram(buf, cps);\r
7665     } else {\r
7666       SendToProgram("xboard\n", cps);\r
7667     }\r
7668 }\r
7669 \r
7670 \r
7671 void\r
7672 TwoMachinesEventIfReady P((void))\r
7673 {\r
7674   if (first.lastPing != first.lastPong) {\r
7675     DisplayMessage("", _("Waiting for first chess program"));\r
7676     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7677     return;\r
7678   }\r
7679   if (second.lastPing != second.lastPong) {\r
7680     DisplayMessage("", _("Waiting for second chess program"));\r
7681     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7682     return;\r
7683   }\r
7684   ThawUI();\r
7685   TwoMachinesEvent();\r
7686 }\r
7687 \r
7688 void\r
7689 NextMatchGame P((void))\r
7690 {\r
7691     int index; /* [HGM] autoinc: step lod index during match */\r
7692     Reset(FALSE, TRUE);\r
7693     if (*appData.loadGameFile != NULLCHAR) {\r
7694         index = appData.loadGameIndex;\r
7695         if(index < 0) { // [HGM] autoinc\r
7696             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7697             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7698         } \r
7699         LoadGameFromFile(appData.loadGameFile,\r
7700                          index,\r
7701                          appData.loadGameFile, FALSE);\r
7702     } else if (*appData.loadPositionFile != NULLCHAR) {\r
7703         index = appData.loadPositionIndex;\r
7704         if(index < 0) { // [HGM] autoinc\r
7705             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7706             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7707         } \r
7708         LoadPositionFromFile(appData.loadPositionFile,\r
7709                              index,\r
7710                              appData.loadPositionFile);\r
7711     }\r
7712     TwoMachinesEventIfReady();\r
7713 }\r
7714 \r
7715 void UserAdjudicationEvent( int result )\r
7716 {\r
7717     ChessMove gameResult = GameIsDrawn;\r
7718 \r
7719     if( result > 0 ) {\r
7720         gameResult = WhiteWins;\r
7721     }\r
7722     else if( result < 0 ) {\r
7723         gameResult = BlackWins;\r
7724     }\r
7725 \r
7726     if( gameMode == TwoMachinesPlay ) {\r
7727         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
7728     }\r
7729 }\r
7730 \r
7731 \r
7732 void\r
7733 GameEnds(result, resultDetails, whosays)\r
7734      ChessMove result;\r
7735      char *resultDetails;\r
7736      int whosays;\r
7737 {\r
7738     GameMode nextGameMode;\r
7739     int isIcsGame;\r
7740     char buf[MSG_SIZ];\r
7741 \r
7742     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
7743     endingGame = 1;\r
7744 \r
7745     if (appData.debugMode) {\r
7746       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
7747               result, resultDetails ? resultDetails : "(null)", whosays);\r
7748     }\r
7749 \r
7750     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
7751         /* If we are playing on ICS, the server decides when the\r
7752            game is over, but the engine can offer to draw, claim \r
7753            a draw, or resign. \r
7754          */\r
7755 #if ZIPPY\r
7756         if (appData.zippyPlay && first.initDone) {\r
7757             if (result == GameIsDrawn) {\r
7758                 /* In case draw still needs to be claimed */\r
7759                 SendToICS(ics_prefix);\r
7760                 SendToICS("draw\n");\r
7761             } else if (StrCaseStr(resultDetails, "resign")) {\r
7762                 SendToICS(ics_prefix);\r
7763                 SendToICS("resign\n");\r
7764             }\r
7765         }\r
7766 #endif\r
7767         endingGame = 0; /* [HGM] crash */\r
7768         return;\r
7769     }\r
7770 \r
7771     /* If we're loading the game from a file, stop */\r
7772     if (whosays == GE_FILE) {\r
7773       (void) StopLoadGameTimer();\r
7774       gameFileFP = NULL;\r
7775     }\r
7776 \r
7777     /* Cancel draw offers */\r
7778     first.offeredDraw = second.offeredDraw = 0;\r
7779 \r
7780     /* If this is an ICS game, only ICS can really say it's done;\r
7781        if not, anyone can. */\r
7782     isIcsGame = (gameMode == IcsPlayingWhite || \r
7783                  gameMode == IcsPlayingBlack || \r
7784                  gameMode == IcsObserving    || \r
7785                  gameMode == IcsExamining);\r
7786 \r
7787     if (!isIcsGame || whosays == GE_ICS) {\r
7788         /* OK -- not an ICS game, or ICS said it was done */\r
7789         StopClocks();\r
7790         if (!isIcsGame && !appData.noChessProgram) \r
7791           SetUserThinkingEnables();\r
7792     \r
7793         /* [HGM] if a machine claims the game end we verify this claim */\r
7794         if(gameMode == TwoMachinesPlay && appData.testClaims) {\r
7795             if(appData.testLegality && whosays >= GE_ENGINE1 ) {\r
7796                 char claimer;\r
7797 \r
7798                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
7799                                             first.twoMachinesColor[0] :\r
7800                                             second.twoMachinesColor[0] ;\r
7801                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) &&\r
7802                     (result == WhiteWins && claimer == 'w' ||\r
7803                      result == BlackWins && claimer == 'b'   ) ) {\r
7804                 if (appData.debugMode) {\r
7805                      fprintf(debugFP, "result=%d sp=%d move=%d\n",\r
7806                         result, epStatus[forwardMostMove], forwardMostMove);\r
7807                 }\r
7808                       /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7809                       if(epStatus[forwardMostMove] != EP_CHECKMATE &&\r
7810                          result != (WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins)) {\r
7811                               sprintf(buf, "False win claim: '%s'", resultDetails);\r
7812                               result = claimer == 'w' ? BlackWins : WhiteWins;\r
7813                               resultDetails = buf;\r
7814                       }\r
7815                 } else\r
7816                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
7817                     && (forwardMostMove <= backwardMostMove ||\r
7818                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
7819                         (claimer=='b')==(forwardMostMove&1))\r
7820                                                                                   ) {\r
7821                       /* [HGM] verify: draws that were not flagged are false claims */\r
7822                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
7823                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7824                       resultDetails = buf;\r
7825                 }\r
7826                 /* (Claiming a loss is accepted no questions asked!) */\r
7827             }\r
7828             /* [HGM] bare: don't allow bare King to win */\r
7829             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)\r
7830                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway \r
7831                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...\r
7832                && result != GameIsDrawn)\r
7833             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
7834                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
7835                         int p = (int)boards[forwardMostMove][i][j] - color;\r
7836                         if(p >= 0 && p <= (int)WhiteKing) k++;\r
7837                 }\r
7838                 if (appData.debugMode) {\r
7839                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",\r
7840                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);\r
7841                 }\r
7842                 if(k <= 1) {\r
7843                         result = GameIsDrawn;\r
7844                         sprintf(buf, "%s but bare king", resultDetails);\r
7845                         resultDetails = buf;\r
7846                 }\r
7847             }\r
7848         }\r
7849 \r
7850 \r
7851         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
7852             if(result==WhiteWins) c = '+';\r
7853             if(result==BlackWins) c = '-';\r
7854             if(resultDetails != NULL)\r
7855                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
7856         }\r
7857         if (resultDetails != NULL) {\r
7858             gameInfo.result = result;\r
7859             gameInfo.resultDetails = StrSave(resultDetails);\r
7860 \r
7861             /* display last move only if game was not loaded from file */\r
7862             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7863                 DisplayMove(currentMove - 1);\r
7864     \r
7865             if (forwardMostMove != 0) {\r
7866                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7867                     if (*appData.saveGameFile != NULLCHAR) {\r
7868                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7869                     } else if (appData.autoSaveGames) {\r
7870                         AutoSaveGame();\r
7871                     }\r
7872                     if (*appData.savePositionFile != NULLCHAR) {\r
7873                         SavePositionToFile(appData.savePositionFile);\r
7874                     }\r
7875                 }\r
7876             }\r
7877 \r
7878             /* Tell program how game ended in case it is learning */\r
7879             /* [HGM] Moved this to after saving the PGN, just in case */\r
7880             /* engine died and we got here through time loss. In that */\r
7881             /* case we will get a fatal error writing the pipe, which */\r
7882             /* would otherwise lose us the PGN.                       */\r
7883             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7884             /* output during GameEnds should never be fatal anymore   */\r
7885             if (gameMode == MachinePlaysWhite ||\r
7886                 gameMode == MachinePlaysBlack ||\r
7887                 gameMode == TwoMachinesPlay ||\r
7888                 gameMode == IcsPlayingWhite ||\r
7889                 gameMode == IcsPlayingBlack ||\r
7890                 gameMode == BeginningOfGame) {\r
7891                 char buf[MSG_SIZ];\r
7892                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7893                         resultDetails);\r
7894                 if (first.pr != NoProc) {\r
7895                     SendToProgram(buf, &first);\r
7896                 }\r
7897                 if (second.pr != NoProc &&\r
7898                     gameMode == TwoMachinesPlay) {\r
7899                     SendToProgram(buf, &second);\r
7900                 }\r
7901             }\r
7902         }\r
7903 \r
7904         if (appData.icsActive) {\r
7905             if (appData.quietPlay &&\r
7906                 (gameMode == IcsPlayingWhite ||\r
7907                  gameMode == IcsPlayingBlack)) {\r
7908                 SendToICS(ics_prefix);\r
7909                 SendToICS("set shout 1\n");\r
7910             }\r
7911             nextGameMode = IcsIdle;\r
7912             ics_user_moved = FALSE;\r
7913             /* clean up premove.  It's ugly when the game has ended and the\r
7914              * premove highlights are still on the board.\r
7915              */\r
7916             if (gotPremove) {\r
7917               gotPremove = FALSE;\r
7918               ClearPremoveHighlights();\r
7919               DrawPosition(FALSE, boards[currentMove]);\r
7920             }\r
7921             if (whosays == GE_ICS) {\r
7922                 switch (result) {\r
7923                 case WhiteWins:\r
7924                     if (gameMode == IcsPlayingWhite)\r
7925                         PlayIcsWinSound();\r
7926                     else if(gameMode == IcsPlayingBlack)\r
7927                         PlayIcsLossSound();\r
7928                     break;\r
7929                 case BlackWins:\r
7930                     if (gameMode == IcsPlayingBlack)\r
7931                         PlayIcsWinSound();\r
7932                     else if(gameMode == IcsPlayingWhite)\r
7933                         PlayIcsLossSound();\r
7934                     break;\r
7935                 case GameIsDrawn:\r
7936                     PlayIcsDrawSound();\r
7937                     break;\r
7938                 default:\r
7939                     PlayIcsUnfinishedSound();\r
7940                 }\r
7941             }\r
7942         } else if (gameMode == EditGame ||\r
7943                    gameMode == PlayFromGameFile || \r
7944                    gameMode == AnalyzeMode || \r
7945                    gameMode == AnalyzeFile) {\r
7946             nextGameMode = gameMode;\r
7947         } else {\r
7948             nextGameMode = EndOfGame;\r
7949         }\r
7950         pausing = FALSE;\r
7951         ModeHighlight();\r
7952     } else {\r
7953         nextGameMode = gameMode;\r
7954     }\r
7955 \r
7956     if (appData.noChessProgram) {\r
7957         gameMode = nextGameMode;\r
7958         ModeHighlight();\r
7959         endingGame = 0; /* [HGM] crash */\r
7960         return;\r
7961     }\r
7962 \r
7963     if (first.reuse) {\r
7964         /* Put first chess program into idle state */\r
7965         if (first.pr != NoProc &&\r
7966             (gameMode == MachinePlaysWhite ||\r
7967              gameMode == MachinePlaysBlack ||\r
7968              gameMode == TwoMachinesPlay ||\r
7969              gameMode == IcsPlayingWhite ||\r
7970              gameMode == IcsPlayingBlack ||\r
7971              gameMode == BeginningOfGame)) {\r
7972             SendToProgram("force\n", &first);\r
7973             if (first.usePing) {\r
7974               char buf[MSG_SIZ];\r
7975               sprintf(buf, "ping %d\n", ++first.lastPing);\r
7976               SendToProgram(buf, &first);\r
7977             }\r
7978         }\r
7979     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7980         /* Kill off first chess program */\r
7981         if (first.isr != NULL)\r
7982           RemoveInputSource(first.isr);\r
7983         first.isr = NULL;\r
7984     \r
7985         if (first.pr != NoProc) {\r
7986             ExitAnalyzeMode();\r
7987             DoSleep( appData.delayBeforeQuit );\r
7988             SendToProgram("quit\n", &first);\r
7989             DoSleep( appData.delayAfterQuit );\r
7990             DestroyChildProcess(first.pr, first.useSigterm);\r
7991         }\r
7992         first.pr = NoProc;\r
7993     }\r
7994     if (second.reuse) {\r
7995         /* Put second chess program into idle state */\r
7996         if (second.pr != NoProc &&\r
7997             gameMode == TwoMachinesPlay) {\r
7998             SendToProgram("force\n", &second);\r
7999             if (second.usePing) {\r
8000               char buf[MSG_SIZ];\r
8001               sprintf(buf, "ping %d\n", ++second.lastPing);\r
8002               SendToProgram(buf, &second);\r
8003             }\r
8004         }\r
8005     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
8006         /* Kill off second chess program */\r
8007         if (second.isr != NULL)\r
8008           RemoveInputSource(second.isr);\r
8009         second.isr = NULL;\r
8010     \r
8011         if (second.pr != NoProc) {\r
8012             DoSleep( appData.delayBeforeQuit );\r
8013             SendToProgram("quit\n", &second);\r
8014             DoSleep( appData.delayAfterQuit );\r
8015             DestroyChildProcess(second.pr, second.useSigterm);\r
8016         }\r
8017         second.pr = NoProc;\r
8018     }\r
8019 \r
8020     if (matchMode && gameMode == TwoMachinesPlay) {\r
8021         switch (result) {\r
8022         case WhiteWins:\r
8023           if (first.twoMachinesColor[0] == 'w') {\r
8024             first.matchWins++;\r
8025           } else {\r
8026             second.matchWins++;\r
8027           }\r
8028           break;\r
8029         case BlackWins:\r
8030           if (first.twoMachinesColor[0] == 'b') {\r
8031             first.matchWins++;\r
8032           } else {\r
8033             second.matchWins++;\r
8034           }\r
8035           break;\r
8036         default:\r
8037           break;\r
8038         }\r
8039         if (matchGame < appData.matchGames) {\r
8040             char *tmp;\r
8041             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */\r
8042                 tmp = first.twoMachinesColor;\r
8043                 first.twoMachinesColor = second.twoMachinesColor;\r
8044                 second.twoMachinesColor = tmp;\r
8045             }\r
8046             gameMode = nextGameMode;\r
8047             matchGame++;\r
8048             if(appData.matchPause>10000 || appData.matchPause<10)\r
8049                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
8050             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
8051             endingGame = 0; /* [HGM] crash */\r
8052             return;\r
8053         } else {\r
8054             char buf[MSG_SIZ];\r
8055             gameMode = nextGameMode;\r
8056             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),\r
8057                     first.tidy, second.tidy,\r
8058                     first.matchWins, second.matchWins,\r
8059                     appData.matchGames - (first.matchWins + second.matchWins));\r
8060             DisplayFatalError(buf, 0, 0);\r
8061         }\r
8062     }\r
8063     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
8064         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
8065       ExitAnalyzeMode();\r
8066     gameMode = nextGameMode;\r
8067     ModeHighlight();\r
8068     endingGame = 0;  /* [HGM] crash */\r
8069 }\r
8070 \r
8071 /* Assumes program was just initialized (initString sent).\r
8072    Leaves program in force mode. */\r
8073 void\r
8074 FeedMovesToProgram(cps, upto) \r
8075      ChessProgramState *cps;\r
8076      int upto;\r
8077 {\r
8078     int i;\r
8079     \r
8080     if (appData.debugMode)\r
8081       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
8082               startedFromSetupPosition ? "position and " : "",\r
8083               backwardMostMove, upto, cps->which);\r
8084     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
8085         // [HGM] variantswitch: make engine aware of new variant\r
8086         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
8087                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
8088         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
8089         SendToProgram(buf, cps);\r
8090         currentlyInitializedVariant = gameInfo.variant;\r
8091     }\r
8092     SendToProgram("force\n", cps);\r
8093     if (startedFromSetupPosition) {\r
8094         SendBoard(cps, backwardMostMove);\r
8095     if (appData.debugMode) {\r
8096         fprintf(debugFP, "feedMoves\n");\r
8097     }\r
8098     }\r
8099     for (i = backwardMostMove; i < upto; i++) {\r
8100         SendMoveToProgram(i, cps);\r
8101     }\r
8102 }\r
8103 \r
8104 \r
8105 void\r
8106 ResurrectChessProgram()\r
8107 {\r
8108      /* The chess program may have exited.\r
8109         If so, restart it and feed it all the moves made so far. */\r
8110 \r
8111     if (appData.noChessProgram || first.pr != NoProc) return;\r
8112     \r
8113     StartChessProgram(&first);\r
8114     InitChessProgram(&first, FALSE);\r
8115     FeedMovesToProgram(&first, currentMove);\r
8116 \r
8117     if (!first.sendTime) {\r
8118         /* can't tell gnuchess what its clock should read,\r
8119            so we bow to its notion. */\r
8120         ResetClocks();\r
8121         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
8122         timeRemaining[1][currentMove] = blackTimeRemaining;\r
8123     }\r
8124 \r
8125     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||\r
8126                 appData.icsEngineAnalyze) && first.analysisSupport) {\r
8127       SendToProgram("analyze\n", &first);\r
8128       first.analyzing = TRUE;\r
8129     }\r
8130 }\r
8131 \r
8132 /*\r
8133  * Button procedures\r
8134  */\r
8135 void\r
8136 Reset(redraw, init)\r
8137      int redraw, init;\r
8138 {\r
8139     int i;\r
8140 \r
8141     if (appData.debugMode) {\r
8142         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
8143                 redraw, init, gameMode);\r
8144     }\r
8145     pausing = pauseExamInvalid = FALSE;\r
8146     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
8147     firstMove = TRUE;\r
8148     whiteFlag = blackFlag = FALSE;\r
8149     userOfferedDraw = FALSE;\r
8150     hintRequested = bookRequested = FALSE;\r
8151     first.maybeThinking = FALSE;\r
8152     second.maybeThinking = FALSE;\r
8153     first.bookSuspend = FALSE; // [HGM] book\r
8154     second.bookSuspend = FALSE;\r
8155     thinkOutput[0] = NULLCHAR;\r
8156     lastHint[0] = NULLCHAR;\r
8157     ClearGameInfo(&gameInfo);\r
8158     gameInfo.variant = StringToVariant(appData.variant);\r
8159     ics_user_moved = ics_clock_paused = FALSE;\r
8160     ics_getting_history = H_FALSE;\r
8161     ics_gamenum = -1;\r
8162     white_holding[0] = black_holding[0] = NULLCHAR;\r
8163     ClearProgramStats();\r
8164     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode\r
8165     \r
8166     ResetFrontEnd();\r
8167     ClearHighlights();\r
8168     flipView = appData.flipView;\r
8169     ClearPremoveHighlights();\r
8170     gotPremove = FALSE;\r
8171     alarmSounded = FALSE;\r
8172 \r
8173     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8174     if(appData.serverMovesName != NULL) {\r
8175         /* [HGM] prepare to make moves file for broadcasting */\r
8176         clock_t t = clock();\r
8177         if(serverMoves != NULL) fclose(serverMoves);\r
8178         serverMoves = fopen(appData.serverMovesName, "r");\r
8179         if(serverMoves != NULL) {\r
8180             fclose(serverMoves);\r
8181             /* delay 15 sec before overwriting, so all clients can see end */\r
8182             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
8183         }\r
8184         serverMoves = fopen(appData.serverMovesName, "w");\r
8185     }\r
8186 \r
8187     ExitAnalyzeMode();\r
8188     gameMode = BeginningOfGame;\r
8189     ModeHighlight();\r
8190     if(appData.icsActive) gameInfo.variant = VariantNormal;\r
8191     InitPosition(redraw);\r
8192     for (i = 0; i < MAX_MOVES; i++) {\r
8193         if (commentList[i] != NULL) {\r
8194             free(commentList[i]);\r
8195             commentList[i] = NULL;\r
8196         }\r
8197     }\r
8198     ResetClocks();\r
8199     timeRemaining[0][0] = whiteTimeRemaining;\r
8200     timeRemaining[1][0] = blackTimeRemaining;\r
8201     if (first.pr == NULL) {\r
8202         StartChessProgram(&first);\r
8203     }\r
8204     if (init) {\r
8205             InitChessProgram(&first, startedFromSetupPosition);\r
8206     }\r
8207     DisplayTitle("");\r
8208     DisplayMessage("", "");\r
8209     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8210 }\r
8211 \r
8212 void\r
8213 AutoPlayGameLoop()\r
8214 {\r
8215     for (;;) {\r
8216         if (!AutoPlayOneMove())\r
8217           return;\r
8218         if (matchMode || appData.timeDelay == 0)\r
8219           continue;\r
8220         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
8221           return;\r
8222         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
8223         break;\r
8224     }\r
8225 }\r
8226 \r
8227 \r
8228 int\r
8229 AutoPlayOneMove()\r
8230 {\r
8231     int fromX, fromY, toX, toY;\r
8232 \r
8233     if (appData.debugMode) {\r
8234       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
8235     }\r
8236 \r
8237     if (gameMode != PlayFromGameFile)\r
8238       return FALSE;\r
8239 \r
8240     if (currentMove >= forwardMostMove) {\r
8241       gameMode = EditGame;\r
8242       ModeHighlight();\r
8243 \r
8244       /* [AS] Clear current move marker at the end of a game */\r
8245       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
8246 \r
8247       return FALSE;\r
8248     }\r
8249     \r
8250     toX = moveList[currentMove][2] - AAA;\r
8251     toY = moveList[currentMove][3] - ONE;\r
8252 \r
8253     if (moveList[currentMove][1] == '@') {\r
8254         if (appData.highlightLastMove) {\r
8255             SetHighlights(-1, -1, toX, toY);\r
8256         }\r
8257     } else {\r
8258         fromX = moveList[currentMove][0] - AAA;\r
8259         fromY = moveList[currentMove][1] - ONE;\r
8260 \r
8261         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
8262 \r
8263         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
8264 \r
8265         if (appData.highlightLastMove) {\r
8266             SetHighlights(fromX, fromY, toX, toY);\r
8267         }\r
8268     }\r
8269     DisplayMove(currentMove);\r
8270     SendMoveToProgram(currentMove++, &first);\r
8271     DisplayBothClocks();\r
8272     DrawPosition(FALSE, boards[currentMove]);\r
8273     // [HGM] PV info: always display, routine tests if empty\r
8274     DisplayComment(currentMove - 1, commentList[currentMove]);\r
8275     return TRUE;\r
8276 }\r
8277 \r
8278 \r
8279 int\r
8280 LoadGameOneMove(readAhead)\r
8281      ChessMove readAhead;\r
8282 {\r
8283     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
8284     char promoChar = NULLCHAR;\r
8285     ChessMove moveType;\r
8286     char move[MSG_SIZ];\r
8287     char *p, *q;\r
8288     \r
8289     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
8290         gameMode != AnalyzeMode && gameMode != Training) {\r
8291         gameFileFP = NULL;\r
8292         return FALSE;\r
8293     }\r
8294     \r
8295     yyboardindex = forwardMostMove;\r
8296     if (readAhead != (ChessMove)0) {\r
8297       moveType = readAhead;\r
8298     } else {\r
8299       if (gameFileFP == NULL)\r
8300           return FALSE;\r
8301       moveType = (ChessMove) yylex();\r
8302     }\r
8303     \r
8304     done = FALSE;\r
8305     switch (moveType) {\r
8306       case Comment:\r
8307         if (appData.debugMode) \r
8308           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8309         p = yy_text;\r
8310         if (*p == '{' || *p == '[' || *p == '(') {\r
8311             p[strlen(p) - 1] = NULLCHAR;\r
8312             p++;\r
8313         }\r
8314 \r
8315         /* append the comment but don't display it */\r
8316         while (*p == '\n') p++;\r
8317         AppendComment(currentMove, p);\r
8318         return TRUE;\r
8319 \r
8320       case WhiteCapturesEnPassant:\r
8321       case BlackCapturesEnPassant:\r
8322       case WhitePromotionChancellor:\r
8323       case BlackPromotionChancellor:\r
8324       case WhitePromotionArchbishop:\r
8325       case BlackPromotionArchbishop:\r
8326       case WhitePromotionCentaur:\r
8327       case BlackPromotionCentaur:\r
8328       case WhitePromotionQueen:\r
8329       case BlackPromotionQueen:\r
8330       case WhitePromotionRook:\r
8331       case BlackPromotionRook:\r
8332       case WhitePromotionBishop:\r
8333       case BlackPromotionBishop:\r
8334       case WhitePromotionKnight:\r
8335       case BlackPromotionKnight:\r
8336       case WhitePromotionKing:\r
8337       case BlackPromotionKing:\r
8338       case NormalMove:\r
8339       case WhiteKingSideCastle:\r
8340       case WhiteQueenSideCastle:\r
8341       case BlackKingSideCastle:\r
8342       case BlackQueenSideCastle:\r
8343       case WhiteKingSideCastleWild:\r
8344       case WhiteQueenSideCastleWild:\r
8345       case BlackKingSideCastleWild:\r
8346       case BlackQueenSideCastleWild:\r
8347       /* PUSH Fabien */\r
8348       case WhiteHSideCastleFR:\r
8349       case WhiteASideCastleFR:\r
8350       case BlackHSideCastleFR:\r
8351       case BlackASideCastleFR:\r
8352       /* POP Fabien */\r
8353         if (appData.debugMode)\r
8354           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8355         fromX = currentMoveString[0] - AAA;\r
8356         fromY = currentMoveString[1] - ONE;\r
8357         toX = currentMoveString[2] - AAA;\r
8358         toY = currentMoveString[3] - ONE;\r
8359         promoChar = currentMoveString[4];\r
8360         break;\r
8361 \r
8362       case WhiteDrop:\r
8363       case BlackDrop:\r
8364         if (appData.debugMode)\r
8365           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8366         fromX = moveType == WhiteDrop ?\r
8367           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
8368         (int) CharToPiece(ToLower(currentMoveString[0]));\r
8369         fromY = DROP_RANK;\r
8370         toX = currentMoveString[2] - AAA;\r
8371         toY = currentMoveString[3] - ONE;\r
8372         break;\r
8373 \r
8374       case WhiteWins:\r
8375       case BlackWins:\r
8376       case GameIsDrawn:\r
8377       case GameUnfinished:\r
8378         if (appData.debugMode)\r
8379           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
8380         p = strchr(yy_text, '{');\r
8381         if (p == NULL) p = strchr(yy_text, '(');\r
8382         if (p == NULL) {\r
8383             p = yy_text;\r
8384             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
8385         } else {\r
8386             q = strchr(p, *p == '{' ? '}' : ')');\r
8387             if (q != NULL) *q = NULLCHAR;\r
8388             p++;\r
8389         }\r
8390         GameEnds(moveType, p, GE_FILE);\r
8391         done = TRUE;\r
8392         if (cmailMsgLoaded) {\r
8393             ClearHighlights();\r
8394             flipView = WhiteOnMove(currentMove);\r
8395             if (moveType == GameUnfinished) flipView = !flipView;\r
8396             if (appData.debugMode)\r
8397               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
8398         }\r
8399         break;\r
8400 \r
8401       case (ChessMove) 0:       /* end of file */\r
8402         if (appData.debugMode)\r
8403           fprintf(debugFP, "Parser hit end of file\n");\r
8404         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8405                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8406           case MT_NONE:\r
8407           case MT_CHECK:\r
8408             break;\r
8409           case MT_CHECKMATE:\r
8410             if (WhiteOnMove(currentMove)) {\r
8411                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8412             } else {\r
8413                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8414             }\r
8415             break;\r
8416           case MT_STALEMATE:\r
8417             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8418             break;\r
8419         }\r
8420         done = TRUE;\r
8421         break;\r
8422 \r
8423       case MoveNumberOne:\r
8424         if (lastLoadGameStart == GNUChessGame) {\r
8425             /* GNUChessGames have numbers, but they aren't move numbers */\r
8426             if (appData.debugMode)\r
8427               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8428                       yy_text, (int) moveType);\r
8429             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8430         }\r
8431         /* else fall thru */\r
8432 \r
8433       case XBoardGame:\r
8434       case GNUChessGame:\r
8435       case PGNTag:\r
8436         /* Reached start of next game in file */\r
8437         if (appData.debugMode)\r
8438           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
8439         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8440                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8441           case MT_NONE:\r
8442           case MT_CHECK:\r
8443             break;\r
8444           case MT_CHECKMATE:\r
8445             if (WhiteOnMove(currentMove)) {\r
8446                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8447             } else {\r
8448                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8449             }\r
8450             break;\r
8451           case MT_STALEMATE:\r
8452             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8453             break;\r
8454         }\r
8455         done = TRUE;\r
8456         break;\r
8457 \r
8458       case PositionDiagram:     /* should not happen; ignore */\r
8459       case ElapsedTime:         /* ignore */\r
8460       case NAG:                 /* ignore */\r
8461         if (appData.debugMode)\r
8462           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8463                   yy_text, (int) moveType);\r
8464         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8465 \r
8466       case IllegalMove:\r
8467         if (appData.testLegality) {\r
8468             if (appData.debugMode)\r
8469               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
8470             sprintf(move, _("Illegal move: %d.%s%s"),\r
8471                     (forwardMostMove / 2) + 1,\r
8472                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8473             DisplayError(move, 0);\r
8474             done = TRUE;\r
8475         } else {\r
8476             if (appData.debugMode)\r
8477               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
8478                       yy_text, currentMoveString);\r
8479             fromX = currentMoveString[0] - AAA;\r
8480             fromY = currentMoveString[1] - ONE;\r
8481             toX = currentMoveString[2] - AAA;\r
8482             toY = currentMoveString[3] - ONE;\r
8483             promoChar = currentMoveString[4];\r
8484         }\r
8485         break;\r
8486 \r
8487       case AmbiguousMove:\r
8488         if (appData.debugMode)\r
8489           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
8490         sprintf(move, _("Ambiguous move: %d.%s%s"),\r
8491                 (forwardMostMove / 2) + 1,\r
8492                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8493         DisplayError(move, 0);\r
8494         done = TRUE;\r
8495         break;\r
8496 \r
8497       default:\r
8498       case ImpossibleMove:\r
8499         if (appData.debugMode)\r
8500           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);\r
8501         sprintf(move, _("Illegal move: %d.%s%s"),\r
8502                 (forwardMostMove / 2) + 1,\r
8503                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8504         DisplayError(move, 0);\r
8505         done = TRUE;\r
8506         break;\r
8507     }\r
8508 \r
8509     if (done) {\r
8510         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
8511             DrawPosition(FALSE, boards[currentMove]);\r
8512             DisplayBothClocks();\r
8513             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
8514               DisplayComment(currentMove - 1, commentList[currentMove]);\r
8515         }\r
8516         (void) StopLoadGameTimer();\r
8517         gameFileFP = NULL;\r
8518         cmailOldMove = forwardMostMove;\r
8519         return FALSE;\r
8520     } else {\r
8521         /* currentMoveString is set as a side-effect of yylex */\r
8522         strcat(currentMoveString, "\n");\r
8523         strcpy(moveList[forwardMostMove], currentMoveString);\r
8524         \r
8525         thinkOutput[0] = NULLCHAR;\r
8526         MakeMove(fromX, fromY, toX, toY, promoChar);\r
8527         currentMove = forwardMostMove;\r
8528         return TRUE;\r
8529     }\r
8530 }\r
8531 \r
8532 /* Load the nth game from the given file */\r
8533 int\r
8534 LoadGameFromFile(filename, n, title, useList)\r
8535      char *filename;\r
8536      int n;\r
8537      char *title;\r
8538      /*Boolean*/ int useList;\r
8539 {\r
8540     FILE *f;\r
8541     char buf[MSG_SIZ];\r
8542 \r
8543     if (strcmp(filename, "-") == 0) {\r
8544         f = stdin;\r
8545         title = "stdin";\r
8546     } else {\r
8547         f = fopen(filename, "rb");\r
8548         if (f == NULL) {\r
8549             sprintf(buf, _("Can't open \"%s\""), filename);\r
8550             DisplayError(buf, errno);\r
8551             return FALSE;\r
8552         }\r
8553     }\r
8554     if (fseek(f, 0, 0) == -1) {\r
8555         /* f is not seekable; probably a pipe */\r
8556         useList = FALSE;\r
8557     }\r
8558     if (useList && n == 0) {\r
8559         int error = GameListBuild(f);\r
8560         if (error) {\r
8561             DisplayError(_("Cannot build game list"), error);\r
8562         } else if (!ListEmpty(&gameList) &&\r
8563                    ((ListGame *) gameList.tailPred)->number > 1) {\r
8564             GameListPopUp(f, title);\r
8565             return TRUE;\r
8566         }\r
8567         GameListDestroy();\r
8568         n = 1;\r
8569     }\r
8570     if (n == 0) n = 1;\r
8571     return LoadGame(f, n, title, FALSE);\r
8572 }\r
8573 \r
8574 \r
8575 void\r
8576 MakeRegisteredMove()\r
8577 {\r
8578     int fromX, fromY, toX, toY;\r
8579     char promoChar;\r
8580     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8581         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
8582           case CMAIL_MOVE:\r
8583           case CMAIL_DRAW:\r
8584             if (appData.debugMode)\r
8585               fprintf(debugFP, "Restoring %s for game %d\n",\r
8586                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8587     \r
8588             thinkOutput[0] = NULLCHAR;\r
8589             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
8590             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
8591             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
8592             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
8593             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
8594             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
8595             MakeMove(fromX, fromY, toX, toY, promoChar);\r
8596             ShowMove(fromX, fromY, toX, toY);\r
8597               \r
8598             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8599                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8600               case MT_NONE:\r
8601               case MT_CHECK:\r
8602                 break;\r
8603                 \r
8604               case MT_CHECKMATE:\r
8605                 if (WhiteOnMove(currentMove)) {\r
8606                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
8607                 } else {\r
8608                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
8609                 }\r
8610                 break;\r
8611                 \r
8612               case MT_STALEMATE:\r
8613                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
8614                 break;\r
8615             }\r
8616 \r
8617             break;\r
8618             \r
8619           case CMAIL_RESIGN:\r
8620             if (WhiteOnMove(currentMove)) {\r
8621                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
8622             } else {\r
8623                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
8624             }\r
8625             break;\r
8626             \r
8627           case CMAIL_ACCEPT:\r
8628             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
8629             break;\r
8630               \r
8631           default:\r
8632             break;\r
8633         }\r
8634     }\r
8635 \r
8636     return;\r
8637 }\r
8638 \r
8639 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
8640 int\r
8641 CmailLoadGame(f, gameNumber, title, useList)\r
8642      FILE *f;\r
8643      int gameNumber;\r
8644      char *title;\r
8645      int useList;\r
8646 {\r
8647     int retVal;\r
8648 \r
8649     if (gameNumber > nCmailGames) {\r
8650         DisplayError(_("No more games in this message"), 0);\r
8651         return FALSE;\r
8652     }\r
8653     if (f == lastLoadGameFP) {\r
8654         int offset = gameNumber - lastLoadGameNumber;\r
8655         if (offset == 0) {\r
8656             cmailMsg[0] = NULLCHAR;\r
8657             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8658                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8659                 nCmailMovesRegistered--;\r
8660             }\r
8661             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
8662             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
8663                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
8664             }\r
8665         } else {\r
8666             if (! RegisterMove()) return FALSE;\r
8667         }\r
8668     }\r
8669 \r
8670     retVal = LoadGame(f, gameNumber, title, useList);\r
8671 \r
8672     /* Make move registered during previous look at this game, if any */\r
8673     MakeRegisteredMove();\r
8674 \r
8675     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
8676         commentList[currentMove]\r
8677           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
8678         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8679     }\r
8680 \r
8681     return retVal;\r
8682 }\r
8683 \r
8684 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
8685 int\r
8686 ReloadGame(offset)\r
8687      int offset;\r
8688 {\r
8689     int gameNumber = lastLoadGameNumber + offset;\r
8690     if (lastLoadGameFP == NULL) {\r
8691         DisplayError(_("No game has been loaded yet"), 0);\r
8692         return FALSE;\r
8693     }\r
8694     if (gameNumber <= 0) {\r
8695         DisplayError(_("Can't back up any further"), 0);\r
8696         return FALSE;\r
8697     }\r
8698     if (cmailMsgLoaded) {\r
8699         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
8700                              lastLoadGameTitle, lastLoadGameUseList);\r
8701     } else {\r
8702         return LoadGame(lastLoadGameFP, gameNumber,\r
8703                         lastLoadGameTitle, lastLoadGameUseList);\r
8704     }\r
8705 }\r
8706 \r
8707 \r
8708 \r
8709 /* Load the nth game from open file f */\r
8710 int\r
8711 LoadGame(f, gameNumber, title, useList)\r
8712      FILE *f;\r
8713      int gameNumber;\r
8714      char *title;\r
8715      int useList;\r
8716 {\r
8717     ChessMove cm;\r
8718     char buf[MSG_SIZ];\r
8719     int gn = gameNumber;\r
8720     ListGame *lg = NULL;\r
8721     int numPGNTags = 0;\r
8722     int err;\r
8723     GameMode oldGameMode;\r
8724     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
8725 \r
8726     if (appData.debugMode) \r
8727         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
8728 \r
8729     if (gameMode == Training )\r
8730         SetTrainingModeOff();\r
8731 \r
8732     oldGameMode = gameMode;\r
8733     if (gameMode != BeginningOfGame) {\r
8734       Reset(FALSE, TRUE);\r
8735     }\r
8736 \r
8737     gameFileFP = f;\r
8738     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
8739         fclose(lastLoadGameFP);\r
8740     }\r
8741 \r
8742     if (useList) {\r
8743         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
8744         \r
8745         if (lg) {\r
8746             fseek(f, lg->offset, 0);\r
8747             GameListHighlight(gameNumber);\r
8748             gn = 1;\r
8749         }\r
8750         else {\r
8751             DisplayError(_("Game number out of range"), 0);\r
8752             return FALSE;\r
8753         }\r
8754     } else {\r
8755         GameListDestroy();\r
8756         if (fseek(f, 0, 0) == -1) {\r
8757             if (f == lastLoadGameFP ?\r
8758                 gameNumber == lastLoadGameNumber + 1 :\r
8759                 gameNumber == 1) {\r
8760                 gn = 1;\r
8761             } else {\r
8762                 DisplayError(_("Can't seek on game file"), 0);\r
8763                 return FALSE;\r
8764             }\r
8765         }\r
8766     }\r
8767     lastLoadGameFP = f;\r
8768     lastLoadGameNumber = gameNumber;\r
8769     strcpy(lastLoadGameTitle, title);\r
8770     lastLoadGameUseList = useList;\r
8771 \r
8772     yynewfile(f);\r
8773 \r
8774     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
8775         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
8776                 lg->gameInfo.black);\r
8777             DisplayTitle(buf);\r
8778     } else if (*title != NULLCHAR) {\r
8779         if (gameNumber > 1) {\r
8780             sprintf(buf, "%s %d", title, gameNumber);\r
8781             DisplayTitle(buf);\r
8782         } else {\r
8783             DisplayTitle(title);\r
8784         }\r
8785     }\r
8786 \r
8787     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
8788         gameMode = PlayFromGameFile;\r
8789         ModeHighlight();\r
8790     }\r
8791 \r
8792     currentMove = forwardMostMove = backwardMostMove = 0;\r
8793     CopyBoard(boards[0], initialPosition);\r
8794     StopClocks();\r
8795 \r
8796     /*\r
8797      * Skip the first gn-1 games in the file.\r
8798      * Also skip over anything that precedes an identifiable \r
8799      * start of game marker, to avoid being confused by \r
8800      * garbage at the start of the file.  Currently \r
8801      * recognized start of game markers are the move number "1",\r
8802      * the pattern "gnuchess .* game", the pattern\r
8803      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
8804      * A game that starts with one of the latter two patterns\r
8805      * will also have a move number 1, possibly\r
8806      * following a position diagram.\r
8807      * 5-4-02: Let's try being more lenient and allowing a game to\r
8808      * start with an unnumbered move.  Does that break anything?\r
8809      */\r
8810     cm = lastLoadGameStart = (ChessMove) 0;\r
8811     while (gn > 0) {\r
8812         yyboardindex = forwardMostMove;\r
8813         cm = (ChessMove) yylex();\r
8814         switch (cm) {\r
8815           case (ChessMove) 0:\r
8816             if (cmailMsgLoaded) {\r
8817                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
8818             } else {\r
8819                 Reset(TRUE, TRUE);\r
8820                 DisplayError(_("Game not found in file"), 0);\r
8821             }\r
8822             return FALSE;\r
8823 \r
8824           case GNUChessGame:\r
8825           case XBoardGame:\r
8826             gn--;\r
8827             lastLoadGameStart = cm;\r
8828             break;\r
8829             \r
8830           case MoveNumberOne:\r
8831             switch (lastLoadGameStart) {\r
8832               case GNUChessGame:\r
8833               case XBoardGame:\r
8834               case PGNTag:\r
8835                 break;\r
8836               case MoveNumberOne:\r
8837               case (ChessMove) 0:\r
8838                 gn--;           /* count this game */\r
8839                 lastLoadGameStart = cm;\r
8840                 break;\r
8841               default:\r
8842                 /* impossible */\r
8843                 break;\r
8844             }\r
8845             break;\r
8846 \r
8847           case PGNTag:\r
8848             switch (lastLoadGameStart) {\r
8849               case GNUChessGame:\r
8850               case PGNTag:\r
8851               case MoveNumberOne:\r
8852               case (ChessMove) 0:\r
8853                 gn--;           /* count this game */\r
8854                 lastLoadGameStart = cm;\r
8855                 break;\r
8856               case XBoardGame:\r
8857                 lastLoadGameStart = cm; /* game counted already */\r
8858                 break;\r
8859               default:\r
8860                 /* impossible */\r
8861                 break;\r
8862             }\r
8863             if (gn > 0) {\r
8864                 do {\r
8865                     yyboardindex = forwardMostMove;\r
8866                     cm = (ChessMove) yylex();\r
8867                 } while (cm == PGNTag || cm == Comment);\r
8868             }\r
8869             break;\r
8870 \r
8871           case WhiteWins:\r
8872           case BlackWins:\r
8873           case GameIsDrawn:\r
8874             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8875                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8876                     != CMAIL_OLD_RESULT) {\r
8877                     nCmailResults ++ ;\r
8878                     cmailResult[  CMAIL_MAX_GAMES\r
8879                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8880                 }\r
8881             }\r
8882             break;\r
8883 \r
8884           case NormalMove:\r
8885             /* Only a NormalMove can be at the start of a game\r
8886              * without a position diagram. */\r
8887             if (lastLoadGameStart == (ChessMove) 0) {\r
8888               gn--;\r
8889               lastLoadGameStart = MoveNumberOne;\r
8890             }\r
8891             break;\r
8892 \r
8893           default:\r
8894             break;\r
8895         }\r
8896     }\r
8897     \r
8898     if (appData.debugMode)\r
8899       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8900 \r
8901     if (cm == XBoardGame) {\r
8902         /* Skip any header junk before position diagram and/or move 1 */\r
8903         for (;;) {\r
8904             yyboardindex = forwardMostMove;\r
8905             cm = (ChessMove) yylex();\r
8906 \r
8907             if (cm == (ChessMove) 0 ||\r
8908                 cm == GNUChessGame || cm == XBoardGame) {\r
8909                 /* Empty game; pretend end-of-file and handle later */\r
8910                 cm = (ChessMove) 0;\r
8911                 break;\r
8912             }\r
8913 \r
8914             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8915                 cm == PGNTag || cm == Comment)\r
8916               break;\r
8917         }\r
8918     } else if (cm == GNUChessGame) {\r
8919         if (gameInfo.event != NULL) {\r
8920             free(gameInfo.event);\r
8921         }\r
8922         gameInfo.event = StrSave(yy_text);\r
8923     }   \r
8924 \r
8925     startedFromSetupPosition = FALSE;\r
8926     while (cm == PGNTag) {\r
8927         if (appData.debugMode) \r
8928           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8929         err = ParsePGNTag(yy_text, &gameInfo);\r
8930         if (!err) numPGNTags++;\r
8931 \r
8932         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8933         if(gameInfo.variant != oldVariant) {\r
8934             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8935             InitPosition(TRUE);\r
8936             oldVariant = gameInfo.variant;\r
8937             if (appData.debugMode) \r
8938               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8939         }\r
8940 \r
8941 \r
8942         if (gameInfo.fen != NULL) {\r
8943           Board initial_position;\r
8944           startedFromSetupPosition = TRUE;\r
8945           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8946             Reset(TRUE, TRUE);\r
8947             DisplayError(_("Bad FEN position in file"), 0);\r
8948             return FALSE;\r
8949           }\r
8950           CopyBoard(boards[0], initial_position);\r
8951           if (blackPlaysFirst) {\r
8952             currentMove = forwardMostMove = backwardMostMove = 1;\r
8953             CopyBoard(boards[1], initial_position);\r
8954             strcpy(moveList[0], "");\r
8955             strcpy(parseList[0], "");\r
8956             timeRemaining[0][1] = whiteTimeRemaining;\r
8957             timeRemaining[1][1] = blackTimeRemaining;\r
8958             if (commentList[0] != NULL) {\r
8959               commentList[1] = commentList[0];\r
8960               commentList[0] = NULL;\r
8961             }\r
8962           } else {\r
8963             currentMove = forwardMostMove = backwardMostMove = 0;\r
8964           }\r
8965           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */\r
8966           {   int i;\r
8967               initialRulePlies = FENrulePlies;\r
8968               epStatus[forwardMostMove] = FENepStatus;\r
8969               for( i=0; i< nrCastlingRights; i++ )\r
8970                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
8971           }\r
8972           yyboardindex = forwardMostMove;\r
8973           free(gameInfo.fen);\r
8974           gameInfo.fen = NULL;\r
8975         }\r
8976 \r
8977         yyboardindex = forwardMostMove;\r
8978         cm = (ChessMove) yylex();\r
8979 \r
8980         /* Handle comments interspersed among the tags */\r
8981         while (cm == Comment) {\r
8982             char *p;\r
8983             if (appData.debugMode) \r
8984               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8985             p = yy_text;\r
8986             if (*p == '{' || *p == '[' || *p == '(') {\r
8987                 p[strlen(p) - 1] = NULLCHAR;\r
8988                 p++;\r
8989             }\r
8990             while (*p == '\n') p++;\r
8991             AppendComment(currentMove, p);\r
8992             yyboardindex = forwardMostMove;\r
8993             cm = (ChessMove) yylex();\r
8994         }\r
8995     }\r
8996 \r
8997     /* don't rely on existence of Event tag since if game was\r
8998      * pasted from clipboard the Event tag may not exist\r
8999      */\r
9000     if (numPGNTags > 0){\r
9001         char *tags;\r
9002         if (gameInfo.variant == VariantNormal) {\r
9003           gameInfo.variant = StringToVariant(gameInfo.event);\r
9004         }\r
9005         if (!matchMode) {\r
9006           if( appData.autoDisplayTags ) {\r
9007             tags = PGNTags(&gameInfo);\r
9008             TagsPopUp(tags, CmailMsg());\r
9009             free(tags);\r
9010           }\r
9011         }\r
9012     } else {\r
9013         /* Make something up, but don't display it now */\r
9014         SetGameInfo();\r
9015         TagsPopDown();\r
9016     }\r
9017 \r
9018     if (cm == PositionDiagram) {\r
9019         int i, j;\r
9020         char *p;\r
9021         Board initial_position;\r
9022 \r
9023         if (appData.debugMode)\r
9024           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
9025 \r
9026         if (!startedFromSetupPosition) {\r
9027             p = yy_text;\r
9028             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
9029               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
9030                 switch (*p) {\r
9031                   case '[':\r
9032                   case '-':\r
9033                   case ' ':\r
9034                   case '\t':\r
9035                   case '\n':\r
9036                   case '\r':\r
9037                     break;\r
9038                   default:\r
9039                     initial_position[i][j++] = CharToPiece(*p);\r
9040                     break;\r
9041                 }\r
9042             while (*p == ' ' || *p == '\t' ||\r
9043                    *p == '\n' || *p == '\r') p++;\r
9044         \r
9045             if (strncmp(p, "black", strlen("black"))==0)\r
9046               blackPlaysFirst = TRUE;\r
9047             else\r
9048               blackPlaysFirst = FALSE;\r
9049             startedFromSetupPosition = TRUE;\r
9050         \r
9051             CopyBoard(boards[0], initial_position);\r
9052             if (blackPlaysFirst) {\r
9053                 currentMove = forwardMostMove = backwardMostMove = 1;\r
9054                 CopyBoard(boards[1], initial_position);\r
9055                 strcpy(moveList[0], "");\r
9056                 strcpy(parseList[0], "");\r
9057                 timeRemaining[0][1] = whiteTimeRemaining;\r
9058                 timeRemaining[1][1] = blackTimeRemaining;\r
9059                 if (commentList[0] != NULL) {\r
9060                     commentList[1] = commentList[0];\r
9061                     commentList[0] = NULL;\r
9062                 }\r
9063             } else {\r
9064                 currentMove = forwardMostMove = backwardMostMove = 0;\r
9065             }\r
9066         }\r
9067         yyboardindex = forwardMostMove;\r
9068         cm = (ChessMove) yylex();\r
9069     }\r
9070 \r
9071     if (first.pr == NoProc) {\r
9072         StartChessProgram(&first);\r
9073     }\r
9074     InitChessProgram(&first, FALSE);\r
9075     SendToProgram("force\n", &first);\r
9076     if (startedFromSetupPosition) {\r
9077         SendBoard(&first, forwardMostMove);\r
9078     if (appData.debugMode) {\r
9079         fprintf(debugFP, "Load Game\n");\r
9080     }\r
9081         DisplayBothClocks();\r
9082     }      \r
9083 \r
9084     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
9085     loadFlag = appData.suppressLoadMoves;\r
9086 \r
9087     while (cm == Comment) {\r
9088         char *p;\r
9089         if (appData.debugMode) \r
9090           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
9091         p = yy_text;\r
9092         if (*p == '{' || *p == '[' || *p == '(') {\r
9093             p[strlen(p) - 1] = NULLCHAR;\r
9094             p++;\r
9095         }\r
9096         while (*p == '\n') p++;\r
9097         AppendComment(currentMove, p);\r
9098         yyboardindex = forwardMostMove;\r
9099         cm = (ChessMove) yylex();\r
9100     }\r
9101 \r
9102     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
9103         cm == WhiteWins || cm == BlackWins ||\r
9104         cm == GameIsDrawn || cm == GameUnfinished) {\r
9105         DisplayMessage("", _("No moves in game"));\r
9106         if (cmailMsgLoaded) {\r
9107             if (appData.debugMode)\r
9108               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
9109             ClearHighlights();\r
9110             flipView = FALSE;\r
9111         }\r
9112         DrawPosition(FALSE, boards[currentMove]);\r
9113         DisplayBothClocks();\r
9114         gameMode = EditGame;\r
9115         ModeHighlight();\r
9116         gameFileFP = NULL;\r
9117         cmailOldMove = 0;\r
9118         return TRUE;\r
9119     }\r
9120 \r
9121     // [HGM] PV info: routine tests if comment empty\r
9122     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
9123         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9124     }\r
9125     if (!matchMode && appData.timeDelay != 0) \r
9126       DrawPosition(FALSE, boards[currentMove]);\r
9127 \r
9128     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
9129       programStats.ok_to_send = 1;\r
9130     }\r
9131 \r
9132     /* if the first token after the PGN tags is a move\r
9133      * and not move number 1, retrieve it from the parser \r
9134      */\r
9135     if (cm != MoveNumberOne)\r
9136         LoadGameOneMove(cm);\r
9137 \r
9138     /* load the remaining moves from the file */\r
9139     while (LoadGameOneMove((ChessMove)0)) {\r
9140       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9141       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9142     }\r
9143 \r
9144     /* rewind to the start of the game */\r
9145     currentMove = backwardMostMove;\r
9146 \r
9147     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9148 \r
9149     if (oldGameMode == AnalyzeFile ||\r
9150         oldGameMode == AnalyzeMode) {\r
9151       AnalyzeFileEvent();\r
9152     }\r
9153 \r
9154     if (matchMode || appData.timeDelay == 0) {\r
9155       ToEndEvent();\r
9156       gameMode = EditGame;\r
9157       ModeHighlight();\r
9158     } else if (appData.timeDelay > 0) {\r
9159       AutoPlayGameLoop();\r
9160     }\r
9161 \r
9162     if (appData.debugMode) \r
9163         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
9164 \r
9165     loadFlag = 0; /* [HGM] true game starts */\r
9166     return TRUE;\r
9167 }\r
9168 \r
9169 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
9170 int\r
9171 ReloadPosition(offset)\r
9172      int offset;\r
9173 {\r
9174     int positionNumber = lastLoadPositionNumber + offset;\r
9175     if (lastLoadPositionFP == NULL) {\r
9176         DisplayError(_("No position has been loaded yet"), 0);\r
9177         return FALSE;\r
9178     }\r
9179     if (positionNumber <= 0) {\r
9180         DisplayError(_("Can't back up any further"), 0);\r
9181         return FALSE;\r
9182     }\r
9183     return LoadPosition(lastLoadPositionFP, positionNumber,\r
9184                         lastLoadPositionTitle);\r
9185 }\r
9186 \r
9187 /* Load the nth position from the given file */\r
9188 int\r
9189 LoadPositionFromFile(filename, n, title)\r
9190      char *filename;\r
9191      int n;\r
9192      char *title;\r
9193 {\r
9194     FILE *f;\r
9195     char buf[MSG_SIZ];\r
9196 \r
9197     if (strcmp(filename, "-") == 0) {\r
9198         return LoadPosition(stdin, n, "stdin");\r
9199     } else {\r
9200         f = fopen(filename, "rb");\r
9201         if (f == NULL) {\r
9202             sprintf(buf, _("Can't open \"%s\""), filename);\r
9203             DisplayError(buf, errno);\r
9204             return FALSE;\r
9205         } else {\r
9206             return LoadPosition(f, n, title);\r
9207         }\r
9208     }\r
9209 }\r
9210 \r
9211 /* Load the nth position from the given open file, and close it */\r
9212 int\r
9213 LoadPosition(f, positionNumber, title)\r
9214      FILE *f;\r
9215      int positionNumber;\r
9216      char *title;\r
9217 {\r
9218     char *p, line[MSG_SIZ];\r
9219     Board initial_position;\r
9220     int i, j, fenMode, pn;\r
9221     \r
9222     if (gameMode == Training )\r
9223         SetTrainingModeOff();\r
9224 \r
9225     if (gameMode != BeginningOfGame) {\r
9226         Reset(FALSE, TRUE);\r
9227     }\r
9228     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
9229         fclose(lastLoadPositionFP);\r
9230     }\r
9231     if (positionNumber == 0) positionNumber = 1;\r
9232     lastLoadPositionFP = f;\r
9233     lastLoadPositionNumber = positionNumber;\r
9234     strcpy(lastLoadPositionTitle, title);\r
9235     if (first.pr == NoProc) {\r
9236       StartChessProgram(&first);\r
9237       InitChessProgram(&first, FALSE);\r
9238     }    \r
9239     pn = positionNumber;\r
9240     if (positionNumber < 0) {\r
9241         /* Negative position number means to seek to that byte offset */\r
9242         if (fseek(f, -positionNumber, 0) == -1) {\r
9243             DisplayError(_("Can't seek on position file"), 0);\r
9244             return FALSE;\r
9245         };\r
9246         pn = 1;\r
9247     } else {\r
9248         if (fseek(f, 0, 0) == -1) {\r
9249             if (f == lastLoadPositionFP ?\r
9250                 positionNumber == lastLoadPositionNumber + 1 :\r
9251                 positionNumber == 1) {\r
9252                 pn = 1;\r
9253             } else {\r
9254                 DisplayError(_("Can't seek on position file"), 0);\r
9255                 return FALSE;\r
9256             }\r
9257         }\r
9258     }\r
9259     /* See if this file is FEN or old-style xboard */\r
9260     if (fgets(line, MSG_SIZ, f) == NULL) {\r
9261         DisplayError(_("Position not found in file"), 0);\r
9262         return FALSE;\r
9263     }\r
9264 #if 0\r
9265     switch (line[0]) {\r
9266       case '#':  case 'x':\r
9267       default:\r
9268         fenMode = FALSE;\r
9269         break;\r
9270       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
9271       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
9272       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
9273       case '7':  case '8':  case '9':\r
9274       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
9275       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
9276       case 'C':  case 'W':             case 'c':  case 'w': \r
9277         fenMode = TRUE;\r
9278         break;\r
9279     }\r
9280 #else\r
9281     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
9282     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
9283 #endif\r
9284 \r
9285     if (pn >= 2) {\r
9286         if (fenMode || line[0] == '#') pn--;\r
9287         while (pn > 0) {\r
9288             /* skip positions before number pn */\r
9289             if (fgets(line, MSG_SIZ, f) == NULL) {\r
9290                 Reset(TRUE, TRUE);\r
9291                 DisplayError(_("Position not found in file"), 0);\r
9292                 return FALSE;\r
9293             }\r
9294             if (fenMode || line[0] == '#') pn--;\r
9295         }\r
9296     }\r
9297 \r
9298     if (fenMode) {\r
9299         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
9300             DisplayError(_("Bad FEN position in file"), 0);\r
9301             return FALSE;\r
9302         }\r
9303     } else {\r
9304         (void) fgets(line, MSG_SIZ, f);\r
9305         (void) fgets(line, MSG_SIZ, f);\r
9306     \r
9307         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
9308             (void) fgets(line, MSG_SIZ, f);\r
9309             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
9310                 if (*p == ' ')\r
9311                   continue;\r
9312                 initial_position[i][j++] = CharToPiece(*p);\r
9313             }\r
9314         }\r
9315     \r
9316         blackPlaysFirst = FALSE;\r
9317         if (!feof(f)) {\r
9318             (void) fgets(line, MSG_SIZ, f);\r
9319             if (strncmp(line, "black", strlen("black"))==0)\r
9320               blackPlaysFirst = TRUE;\r
9321         }\r
9322     }\r
9323     startedFromSetupPosition = TRUE;\r
9324     \r
9325     SendToProgram("force\n", &first);\r
9326     CopyBoard(boards[0], initial_position);\r
9327     if (blackPlaysFirst) {\r
9328         currentMove = forwardMostMove = backwardMostMove = 1;\r
9329         strcpy(moveList[0], "");\r
9330         strcpy(parseList[0], "");\r
9331         CopyBoard(boards[1], initial_position);\r
9332         DisplayMessage("", _("Black to play"));\r
9333     } else {\r
9334         currentMove = forwardMostMove = backwardMostMove = 0;\r
9335         DisplayMessage("", _("White to play"));\r
9336     }\r
9337           /* [HGM] copy FEN attributes as well */\r
9338           {   int i;\r
9339               initialRulePlies = FENrulePlies;\r
9340               epStatus[forwardMostMove] = FENepStatus;\r
9341               for( i=0; i< nrCastlingRights; i++ )\r
9342                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
9343           }\r
9344     SendBoard(&first, forwardMostMove);\r
9345     if (appData.debugMode) {\r
9346 int i, j;\r
9347   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}\r
9348   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");\r
9349         fprintf(debugFP, "Load Position\n");\r
9350     }\r
9351 \r
9352     if (positionNumber > 1) {\r
9353         sprintf(line, "%s %d", title, positionNumber);\r
9354         DisplayTitle(line);\r
9355     } else {\r
9356         DisplayTitle(title);\r
9357     }\r
9358     gameMode = EditGame;\r
9359     ModeHighlight();\r
9360     ResetClocks();\r
9361     timeRemaining[0][1] = whiteTimeRemaining;\r
9362     timeRemaining[1][1] = blackTimeRemaining;\r
9363     DrawPosition(FALSE, boards[currentMove]);\r
9364    \r
9365     return TRUE;\r
9366 }\r
9367 \r
9368 \r
9369 void\r
9370 CopyPlayerNameIntoFileName(dest, src)\r
9371      char **dest, *src;\r
9372 {\r
9373     while (*src != NULLCHAR && *src != ',') {\r
9374         if (*src == ' ') {\r
9375             *(*dest)++ = '_';\r
9376             src++;\r
9377         } else {\r
9378             *(*dest)++ = *src++;\r
9379         }\r
9380     }\r
9381 }\r
9382 \r
9383 char *DefaultFileName(ext)\r
9384      char *ext;\r
9385 {\r
9386     static char def[MSG_SIZ];\r
9387     char *p;\r
9388 \r
9389     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
9390         p = def;\r
9391         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
9392         *p++ = '-';\r
9393         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
9394         *p++ = '.';\r
9395         strcpy(p, ext);\r
9396     } else {\r
9397         def[0] = NULLCHAR;\r
9398     }\r
9399     return def;\r
9400 }\r
9401 \r
9402 /* Save the current game to the given file */\r
9403 int\r
9404 SaveGameToFile(filename, append)\r
9405      char *filename;\r
9406      int append;\r
9407 {\r
9408     FILE *f;\r
9409     char buf[MSG_SIZ];\r
9410 \r
9411     if (strcmp(filename, "-") == 0) {\r
9412         return SaveGame(stdout, 0, NULL);\r
9413     } else {\r
9414         f = fopen(filename, append ? "a" : "w");\r
9415         if (f == NULL) {\r
9416             sprintf(buf, _("Can't open \"%s\""), filename);\r
9417             DisplayError(buf, errno);\r
9418             return FALSE;\r
9419         } else {\r
9420             return SaveGame(f, 0, NULL);\r
9421         }\r
9422     }\r
9423 }\r
9424 \r
9425 char *\r
9426 SavePart(str)\r
9427      char *str;\r
9428 {\r
9429     static char buf[MSG_SIZ];\r
9430     char *p;\r
9431     \r
9432     p = strchr(str, ' ');\r
9433     if (p == NULL) return str;\r
9434     strncpy(buf, str, p - str);\r
9435     buf[p - str] = NULLCHAR;\r
9436     return buf;\r
9437 }\r
9438 \r
9439 #define PGN_MAX_LINE 75\r
9440 \r
9441 #define PGN_SIDE_WHITE  0\r
9442 #define PGN_SIDE_BLACK  1\r
9443 \r
9444 /* [AS] */\r
9445 static int FindFirstMoveOutOfBook( int side )\r
9446 {\r
9447     int result = -1;\r
9448 \r
9449     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
9450         int index = backwardMostMove;\r
9451         int has_book_hit = 0;\r
9452 \r
9453         if( (index % 2) != side ) {\r
9454             index++;\r
9455         }\r
9456 \r
9457         while( index < forwardMostMove ) {\r
9458             /* Check to see if engine is in book */\r
9459             int depth = pvInfoList[index].depth;\r
9460             int score = pvInfoList[index].score;\r
9461             int in_book = 0;\r
9462 \r
9463             if( depth <= 2 ) {\r
9464                 in_book = 1;\r
9465             }\r
9466             else if( score == 0 && depth == 63 ) {\r
9467                 in_book = 1; /* Zappa */\r
9468             }\r
9469             else if( score == 2 && depth == 99 ) {\r
9470                 in_book = 1; /* Abrok */\r
9471             }\r
9472 \r
9473             has_book_hit += in_book;\r
9474 \r
9475             if( ! in_book ) {\r
9476                 result = index;\r
9477 \r
9478                 break;\r
9479             }\r
9480 \r
9481             index += 2;\r
9482         }\r
9483     }\r
9484 \r
9485     return result;\r
9486 }\r
9487 \r
9488 /* [AS] */\r
9489 void GetOutOfBookInfo( char * buf )\r
9490 {\r
9491     int oob[2];\r
9492     int i;\r
9493     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9494 \r
9495     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
9496     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
9497 \r
9498     *buf = '\0';\r
9499 \r
9500     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
9501         for( i=0; i<2; i++ ) {\r
9502             int idx = oob[i];\r
9503 \r
9504             if( idx >= 0 ) {\r
9505                 if( i > 0 && oob[0] >= 0 ) {\r
9506                     strcat( buf, "   " );\r
9507                 }\r
9508 \r
9509                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
9510                 sprintf( buf+strlen(buf), "%s%.2f", \r
9511                     pvInfoList[idx].score >= 0 ? "+" : "",\r
9512                     pvInfoList[idx].score / 100.0 );\r
9513             }\r
9514         }\r
9515     }\r
9516 }\r
9517 \r
9518 /* Save game in PGN style and close the file */\r
9519 int\r
9520 SaveGamePGN(f)\r
9521      FILE *f;\r
9522 {\r
9523     int i, offset, linelen, newblock;\r
9524     time_t tm;\r
9525 //    char *movetext;\r
9526     char numtext[32];\r
9527     int movelen, numlen, blank;\r
9528     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
9529 \r
9530     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9531     \r
9532     tm = time((time_t *) NULL);\r
9533     \r
9534     PrintPGNTags(f, &gameInfo);\r
9535     \r
9536     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9537         char *fen = PositionToFEN(backwardMostMove, 1);\r
9538         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
9539         fprintf(f, "\n{--------------\n");\r
9540         PrintPosition(f, backwardMostMove);\r
9541         fprintf(f, "--------------}\n");\r
9542         free(fen);\r
9543     }\r
9544     else {\r
9545         /* [AS] Out of book annotation */\r
9546         if( appData.saveOutOfBookInfo ) {\r
9547             char buf[64];\r
9548 \r
9549             GetOutOfBookInfo( buf );\r
9550 \r
9551             if( buf[0] != '\0' ) {\r
9552                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
9553             }\r
9554         }\r
9555 \r
9556         fprintf(f, "\n");\r
9557     }\r
9558 \r
9559     i = backwardMostMove;\r
9560     linelen = 0;\r
9561     newblock = TRUE;\r
9562 \r
9563     while (i < forwardMostMove) {\r
9564         /* Print comments preceding this move */\r
9565         if (commentList[i] != NULL) {\r
9566             if (linelen > 0) fprintf(f, "\n");\r
9567             fprintf(f, "{\n%s}\n", commentList[i]);\r
9568             linelen = 0;\r
9569             newblock = TRUE;\r
9570         }\r
9571 \r
9572         /* Format move number */\r
9573         if ((i % 2) == 0) {\r
9574             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
9575         } else {\r
9576             if (newblock) {\r
9577                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
9578             } else {\r
9579                 numtext[0] = NULLCHAR;\r
9580             }\r
9581         }\r
9582         numlen = strlen(numtext);\r
9583         newblock = FALSE;\r
9584 \r
9585         /* Print move number */\r
9586         blank = linelen > 0 && numlen > 0;\r
9587         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
9588             fprintf(f, "\n");\r
9589             linelen = 0;\r
9590             blank = 0;\r
9591         }\r
9592         if (blank) {\r
9593             fprintf(f, " ");\r
9594             linelen++;\r
9595         }\r
9596         fprintf(f, numtext);\r
9597         linelen += numlen;\r
9598 \r
9599         /* Get move */\r
9600         movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */\r
9601 \r
9602         /* Print move */\r
9603         blank = linelen > 0 && movelen > 0;\r
9604         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9605             fprintf(f, "\n");\r
9606             linelen = 0;\r
9607             blank = 0;\r
9608         }\r
9609         if (blank) {\r
9610             fprintf(f, " ");\r
9611             linelen++;\r
9612         }\r
9613         fprintf(f, parseList[i]);\r
9614         linelen += movelen;\r
9615 \r
9616         /* [AS] Add PV info if present */\r
9617         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9618             /* [HGM] add time */\r
9619             char buf[MSG_SIZ]; int seconds = 0;\r
9620 \r
9621 #if 0\r
9622             if(i >= backwardMostMove) {\r
9623                 if(WhiteOnMove(i))\r
9624                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]\r
9625                                   + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;\r
9626                 else\r
9627                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]\r
9628                                   + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;\r
9629             }\r
9630             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest\r
9631 #else\r
9632             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time\r
9633 #endif\r
9634 \r
9635             if( seconds <= 0) buf[0] = 0; else\r
9636             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {\r
9637                 seconds = (seconds + 4)/10; // round to full seconds\r
9638                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else\r
9639                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
9640             }\r
9641 \r
9642             sprintf( move_buffer, "{%s%.2f/%d%s}", \r
9643                 pvInfoList[i].score >= 0 ? "+" : "",\r
9644                 pvInfoList[i].score / 100.0,\r
9645                 pvInfoList[i].depth,\r
9646                 buf );\r
9647 \r
9648             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */\r
9649 \r
9650             /* Print score/depth */\r
9651             blank = linelen > 0 && movelen > 0;\r
9652             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9653                 fprintf(f, "\n");\r
9654                 linelen = 0;\r
9655                 blank = 0;\r
9656             }\r
9657             if (blank) {\r
9658                 fprintf(f, " ");\r
9659                 linelen++;\r
9660             }\r
9661             fprintf(f, move_buffer);\r
9662             linelen += movelen;\r
9663         }\r
9664 \r
9665         i++;\r
9666     }\r
9667     \r
9668     /* Start a new line */\r
9669     if (linelen > 0) fprintf(f, "\n");\r
9670 \r
9671     /* Print comments after last move */\r
9672     if (commentList[i] != NULL) {\r
9673         fprintf(f, "{\n%s}\n", commentList[i]);\r
9674     }\r
9675 \r
9676     /* Print result */\r
9677     if (gameInfo.resultDetails != NULL &&\r
9678         gameInfo.resultDetails[0] != NULLCHAR) {\r
9679         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
9680                 PGNResult(gameInfo.result));\r
9681     } else {\r
9682         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9683     }\r
9684 \r
9685     fclose(f);\r
9686     return TRUE;\r
9687 }\r
9688 \r
9689 /* Save game in old style and close the file */\r
9690 int\r
9691 SaveGameOldStyle(f)\r
9692      FILE *f;\r
9693 {\r
9694     int i, offset;\r
9695     time_t tm;\r
9696     \r
9697     tm = time((time_t *) NULL);\r
9698     \r
9699     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
9700     PrintOpponents(f);\r
9701     \r
9702     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9703         fprintf(f, "\n[--------------\n");\r
9704         PrintPosition(f, backwardMostMove);\r
9705         fprintf(f, "--------------]\n");\r
9706     } else {\r
9707         fprintf(f, "\n");\r
9708     }\r
9709 \r
9710     i = backwardMostMove;\r
9711     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9712 \r
9713     while (i < forwardMostMove) {\r
9714         if (commentList[i] != NULL) {\r
9715             fprintf(f, "[%s]\n", commentList[i]);\r
9716         }\r
9717 \r
9718         if ((i % 2) == 1) {\r
9719             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
9720             i++;\r
9721         } else {\r
9722             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
9723             i++;\r
9724             if (commentList[i] != NULL) {\r
9725                 fprintf(f, "\n");\r
9726                 continue;\r
9727             }\r
9728             if (i >= forwardMostMove) {\r
9729                 fprintf(f, "\n");\r
9730                 break;\r
9731             }\r
9732             fprintf(f, "%s\n", parseList[i]);\r
9733             i++;\r
9734         }\r
9735     }\r
9736     \r
9737     if (commentList[i] != NULL) {\r
9738         fprintf(f, "[%s]\n", commentList[i]);\r
9739     }\r
9740 \r
9741     /* This isn't really the old style, but it's close enough */\r
9742     if (gameInfo.resultDetails != NULL &&\r
9743         gameInfo.resultDetails[0] != NULLCHAR) {\r
9744         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
9745                 gameInfo.resultDetails);\r
9746     } else {\r
9747         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9748     }\r
9749 \r
9750     fclose(f);\r
9751     return TRUE;\r
9752 }\r
9753 \r
9754 /* Save the current game to open file f and close the file */\r
9755 int\r
9756 SaveGame(f, dummy, dummy2)\r
9757      FILE *f;\r
9758      int dummy;\r
9759      char *dummy2;\r
9760 {\r
9761     if (gameMode == EditPosition) EditPositionDone();\r
9762     if (appData.oldSaveStyle)\r
9763       return SaveGameOldStyle(f);\r
9764     else\r
9765       return SaveGamePGN(f);\r
9766 }\r
9767 \r
9768 /* Save the current position to the given file */\r
9769 int\r
9770 SavePositionToFile(filename)\r
9771      char *filename;\r
9772 {\r
9773     FILE *f;\r
9774     char buf[MSG_SIZ];\r
9775 \r
9776     if (strcmp(filename, "-") == 0) {\r
9777         return SavePosition(stdout, 0, NULL);\r
9778     } else {\r
9779         f = fopen(filename, "a");\r
9780         if (f == NULL) {\r
9781             sprintf(buf, _("Can't open \"%s\""), filename);\r
9782             DisplayError(buf, errno);\r
9783             return FALSE;\r
9784         } else {\r
9785             SavePosition(f, 0, NULL);\r
9786             return TRUE;\r
9787         }\r
9788     }\r
9789 }\r
9790 \r
9791 /* Save the current position to the given open file and close the file */\r
9792 int\r
9793 SavePosition(f, dummy, dummy2)\r
9794      FILE *f;\r
9795      int dummy;\r
9796      char *dummy2;\r
9797 {\r
9798     time_t tm;\r
9799     char *fen;\r
9800     \r
9801     if (appData.oldSaveStyle) {\r
9802         tm = time((time_t *) NULL);\r
9803     \r
9804         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
9805         PrintOpponents(f);\r
9806         fprintf(f, "[--------------\n");\r
9807         PrintPosition(f, currentMove);\r
9808         fprintf(f, "--------------]\n");\r
9809     } else {\r
9810         fen = PositionToFEN(currentMove, 1);\r
9811         fprintf(f, "%s\n", fen);\r
9812         free(fen);\r
9813     }\r
9814     fclose(f);\r
9815     return TRUE;\r
9816 }\r
9817 \r
9818 void\r
9819 ReloadCmailMsgEvent(unregister)\r
9820      int unregister;\r
9821 {\r
9822 #if !WIN32\r
9823     static char *inFilename = NULL;\r
9824     static char *outFilename;\r
9825     int i;\r
9826     struct stat inbuf, outbuf;\r
9827     int status;\r
9828     \r
9829     /* Any registered moves are unregistered if unregister is set, */\r
9830     /* i.e. invoked by the signal handler */\r
9831     if (unregister) {\r
9832         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9833             cmailMoveRegistered[i] = FALSE;\r
9834             if (cmailCommentList[i] != NULL) {\r
9835                 free(cmailCommentList[i]);\r
9836                 cmailCommentList[i] = NULL;\r
9837             }\r
9838         }\r
9839         nCmailMovesRegistered = 0;\r
9840     }\r
9841 \r
9842     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9843         cmailResult[i] = CMAIL_NOT_RESULT;\r
9844     }\r
9845     nCmailResults = 0;\r
9846 \r
9847     if (inFilename == NULL) {\r
9848         /* Because the filenames are static they only get malloced once  */\r
9849         /* and they never get freed                                      */\r
9850         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
9851         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
9852 \r
9853         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
9854         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
9855     }\r
9856     \r
9857     status = stat(outFilename, &outbuf);\r
9858     if (status < 0) {\r
9859         cmailMailedMove = FALSE;\r
9860     } else {\r
9861         status = stat(inFilename, &inbuf);\r
9862         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
9863     }\r
9864     \r
9865     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
9866        counts the games, notes how each one terminated, etc.\r
9867        \r
9868        It would be nice to remove this kludge and instead gather all\r
9869        the information while building the game list.  (And to keep it\r
9870        in the game list nodes instead of having a bunch of fixed-size\r
9871        parallel arrays.)  Note this will require getting each game's\r
9872        termination from the PGN tags, as the game list builder does\r
9873        not process the game moves.  --mann\r
9874        */\r
9875     cmailMsgLoaded = TRUE;\r
9876     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
9877     \r
9878     /* Load first game in the file or popup game menu */\r
9879     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
9880 \r
9881 #endif /* !WIN32 */\r
9882     return;\r
9883 }\r
9884 \r
9885 int\r
9886 RegisterMove()\r
9887 {\r
9888     FILE *f;\r
9889     char string[MSG_SIZ];\r
9890 \r
9891     if (   cmailMailedMove\r
9892         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9893         return TRUE;            /* Allow free viewing  */\r
9894     }\r
9895 \r
9896     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9897     /* with the move registered when the conditions for registering no   */\r
9898     /* longer hold                                                       */\r
9899     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9900         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9901         nCmailMovesRegistered --;\r
9902 \r
9903         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9904           {\r
9905               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9906               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9907           }\r
9908     }\r
9909 \r
9910     if (cmailOldMove == -1) {\r
9911         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);\r
9912         return FALSE;\r
9913     }\r
9914 \r
9915     if (currentMove > cmailOldMove + 1) {\r
9916         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);\r
9917         return FALSE;\r
9918     }\r
9919 \r
9920     if (currentMove < cmailOldMove) {\r
9921         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);\r
9922         return FALSE;\r
9923     }\r
9924 \r
9925     if (forwardMostMove > currentMove) {\r
9926         /* Silently truncate extra moves */\r
9927         TruncateGame();\r
9928     }\r
9929 \r
9930     if (   (currentMove == cmailOldMove + 1)\r
9931         || (   (currentMove == cmailOldMove)\r
9932             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9933                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9934         if (gameInfo.result != GameUnfinished) {\r
9935             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9936         }\r
9937 \r
9938         if (commentList[currentMove] != NULL) {\r
9939             cmailCommentList[lastLoadGameNumber - 1]\r
9940               = StrSave(commentList[currentMove]);\r
9941         }\r
9942         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9943 \r
9944         if (appData.debugMode)\r
9945           fprintf(debugFP, "Saving %s for game %d\n",\r
9946                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9947 \r
9948         sprintf(string,\r
9949                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
9950         \r
9951         f = fopen(string, "w");\r
9952         if (appData.oldSaveStyle) {\r
9953             SaveGameOldStyle(f); /* also closes the file */\r
9954             \r
9955             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
9956             f = fopen(string, "w");\r
9957             SavePosition(f, 0, NULL); /* also closes the file */\r
9958         } else {\r
9959             fprintf(f, "{--------------\n");\r
9960             PrintPosition(f, currentMove);\r
9961             fprintf(f, "--------------}\n\n");\r
9962             \r
9963             SaveGame(f, 0, NULL); /* also closes the file*/\r
9964         }\r
9965         \r
9966         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
9967         nCmailMovesRegistered ++;\r
9968     } else if (nCmailGames == 1) {\r
9969         DisplayError(_("You have not made a move yet"), 0);\r
9970         return FALSE;\r
9971     }\r
9972 \r
9973     return TRUE;\r
9974 }\r
9975 \r
9976 void\r
9977 MailMoveEvent()\r
9978 {\r
9979 #if !WIN32\r
9980     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
9981     FILE *commandOutput;\r
9982     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
9983     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
9984     int nBuffers;\r
9985     int i;\r
9986     int archived;\r
9987     char *arcDir;\r
9988 \r
9989     if (! cmailMsgLoaded) {\r
9990         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);\r
9991         return;\r
9992     }\r
9993 \r
9994     if (nCmailGames == nCmailResults) {\r
9995         DisplayError(_("No unfinished games"), 0);\r
9996         return;\r
9997     }\r
9998 \r
9999 #if CMAIL_PROHIBIT_REMAIL\r
10000     if (cmailMailedMove) {\r
10001         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
10002         DisplayError(msg, 0);\r
10003         return;\r
10004     }\r
10005 #endif\r
10006 \r
10007     if (! (cmailMailedMove || RegisterMove())) return;\r
10008     \r
10009     if (   cmailMailedMove\r
10010         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
10011         sprintf(string, partCommandString,\r
10012                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
10013         commandOutput = popen(string, "r");\r
10014 \r
10015         if (commandOutput == NULL) {\r
10016             DisplayError(_("Failed to invoke cmail"), 0);\r
10017         } else {\r
10018             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
10019                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
10020             }\r
10021             if (nBuffers > 1) {\r
10022                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
10023                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
10024                 nBytes = MSG_SIZ - 1;\r
10025             } else {\r
10026                 (void) memcpy(msg, buffer, nBytes);\r
10027             }\r
10028             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
10029 \r
10030             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
10031                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
10032 \r
10033                 archived = TRUE;\r
10034                 for (i = 0; i < nCmailGames; i ++) {\r
10035                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
10036                         archived = FALSE;\r
10037                     }\r
10038                 }\r
10039                 if (   archived\r
10040                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
10041                         != NULL)) {\r
10042                     sprintf(buffer, "%s/%s.%s.archive",\r
10043                             arcDir,\r
10044                             appData.cmailGameName,\r
10045                             gameInfo.date);\r
10046                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
10047                     cmailMsgLoaded = FALSE;\r
10048                 }\r
10049             }\r
10050 \r
10051             DisplayInformation(msg);\r
10052             pclose(commandOutput);\r
10053         }\r
10054     } else {\r
10055         if ((*cmailMsg) != '\0') {\r
10056             DisplayInformation(cmailMsg);\r
10057         }\r
10058     }\r
10059 \r
10060     return;\r
10061 #endif /* !WIN32 */\r
10062 }\r
10063 \r
10064 char *\r
10065 CmailMsg()\r
10066 {\r
10067 #if WIN32\r
10068     return NULL;\r
10069 #else\r
10070     int  prependComma = 0;\r
10071     char number[5];\r
10072     char string[MSG_SIZ];       /* Space for game-list */\r
10073     int  i;\r
10074     \r
10075     if (!cmailMsgLoaded) return "";\r
10076 \r
10077     if (cmailMailedMove) {\r
10078         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));\r
10079     } else {\r
10080         /* Create a list of games left */\r
10081         sprintf(string, "[");\r
10082         for (i = 0; i < nCmailGames; i ++) {\r
10083             if (! (   cmailMoveRegistered[i]\r
10084                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
10085                 if (prependComma) {\r
10086                     sprintf(number, ",%d", i + 1);\r
10087                 } else {\r
10088                     sprintf(number, "%d", i + 1);\r
10089                     prependComma = 1;\r
10090                 }\r
10091                 \r
10092                 strcat(string, number);\r
10093             }\r
10094         }\r
10095         strcat(string, "]");\r
10096 \r
10097         if (nCmailMovesRegistered + nCmailResults == 0) {\r
10098             switch (nCmailGames) {\r
10099               case 1:\r
10100                 sprintf(cmailMsg,\r
10101                         _("Still need to make move for game\n"));\r
10102                 break;\r
10103                 \r
10104               case 2:\r
10105                 sprintf(cmailMsg,\r
10106                         _("Still need to make moves for both games\n"));\r
10107                 break;\r
10108                 \r
10109               default:\r
10110                 sprintf(cmailMsg,\r
10111                         _("Still need to make moves for all %d games\n"),\r
10112                         nCmailGames);\r
10113                 break;\r
10114             }\r
10115         } else {\r
10116             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
10117               case 1:\r
10118                 sprintf(cmailMsg,\r
10119                         _("Still need to make a move for game %s\n"),\r
10120                         string);\r
10121                 break;\r
10122                 \r
10123               case 0:\r
10124                 if (nCmailResults == nCmailGames) {\r
10125                     sprintf(cmailMsg, _("No unfinished games\n"));\r
10126                 } else {\r
10127                     sprintf(cmailMsg, _("Ready to send mail\n"));\r
10128                 }\r
10129                 break;\r
10130                 \r
10131               default:\r
10132                 sprintf(cmailMsg,\r
10133                         _("Still need to make moves for games %s\n"),\r
10134                         string);\r
10135             }\r
10136         }\r
10137     }\r
10138     return cmailMsg;\r
10139 #endif /* WIN32 */\r
10140 }\r
10141 \r
10142 void\r
10143 ResetGameEvent()\r
10144 {\r
10145     if (gameMode == Training)\r
10146       SetTrainingModeOff();\r
10147 \r
10148     Reset(TRUE, TRUE);\r
10149     cmailMsgLoaded = FALSE;\r
10150     if (appData.icsActive) {\r
10151       SendToICS(ics_prefix);\r
10152       SendToICS("refresh\n");\r
10153     }\r
10154 }\r
10155 \r
10156 void\r
10157 ExitEvent(status)\r
10158      int status;\r
10159 {\r
10160     exiting++;\r
10161     if (exiting > 2) {\r
10162       /* Give up on clean exit */\r
10163       exit(status);\r
10164     }\r
10165     if (exiting > 1) {\r
10166       /* Keep trying for clean exit */\r
10167       return;\r
10168     }\r
10169 \r
10170     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
10171 \r
10172     if (telnetISR != NULL) {\r
10173       RemoveInputSource(telnetISR);\r
10174     }\r
10175     if (icsPR != NoProc) {\r
10176       DestroyChildProcess(icsPR, TRUE);\r
10177     }\r
10178 #if 0\r
10179     /* Save game if resource set and not already saved by GameEnds() */\r
10180     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
10181                              && forwardMostMove > 0) {\r
10182       if (*appData.saveGameFile != NULLCHAR) {\r
10183         SaveGameToFile(appData.saveGameFile, TRUE);\r
10184       } else if (appData.autoSaveGames) {\r
10185         AutoSaveGame();\r
10186       }\r
10187       if (*appData.savePositionFile != NULLCHAR) {\r
10188         SavePositionToFile(appData.savePositionFile);\r
10189       }\r
10190     }\r
10191     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10192 #else\r
10193     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
10194     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);\r
10195 #endif\r
10196     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
10197     /* make sure this other one finishes before killing it!                  */\r
10198     if(endingGame) { int count = 0;\r
10199         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
10200         while(endingGame && count++ < 10) DoSleep(1);\r
10201         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
10202     }\r
10203 \r
10204     /* Kill off chess programs */\r
10205     if (first.pr != NoProc) {\r
10206         ExitAnalyzeMode();\r
10207         \r
10208         DoSleep( appData.delayBeforeQuit );\r
10209         SendToProgram("quit\n", &first);\r
10210         DoSleep( appData.delayAfterQuit );\r
10211         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
10212     }\r
10213     if (second.pr != NoProc) {\r
10214         DoSleep( appData.delayBeforeQuit );\r
10215         SendToProgram("quit\n", &second);\r
10216         DoSleep( appData.delayAfterQuit );\r
10217         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
10218     }\r
10219     if (first.isr != NULL) {\r
10220         RemoveInputSource(first.isr);\r
10221     }\r
10222     if (second.isr != NULL) {\r
10223         RemoveInputSource(second.isr);\r
10224     }\r
10225 \r
10226     ShutDownFrontEnd();\r
10227     exit(status);\r
10228 }\r
10229 \r
10230 void\r
10231 PauseEvent()\r
10232 {\r
10233     if (appData.debugMode)\r
10234         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
10235     if (pausing) {\r
10236         pausing = FALSE;\r
10237         ModeHighlight();\r
10238         if (gameMode == MachinePlaysWhite ||\r
10239             gameMode == MachinePlaysBlack) {\r
10240             StartClocks();\r
10241         } else {\r
10242             DisplayBothClocks();\r
10243         }\r
10244         if (gameMode == PlayFromGameFile) {\r
10245             if (appData.timeDelay >= 0) \r
10246                 AutoPlayGameLoop();\r
10247         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
10248             Reset(FALSE, TRUE);\r
10249             SendToICS(ics_prefix);\r
10250             SendToICS("refresh\n");\r
10251         } else if (currentMove < forwardMostMove) {\r
10252             ForwardInner(forwardMostMove);\r
10253         }\r
10254         pauseExamInvalid = FALSE;\r
10255     } else {\r
10256         switch (gameMode) {\r
10257           default:\r
10258             return;\r
10259           case IcsExamining:\r
10260             pauseExamForwardMostMove = forwardMostMove;\r
10261             pauseExamInvalid = FALSE;\r
10262             /* fall through */\r
10263           case IcsObserving:\r
10264           case IcsPlayingWhite:\r
10265           case IcsPlayingBlack:\r
10266             pausing = TRUE;\r
10267             ModeHighlight();\r
10268             return;\r
10269           case PlayFromGameFile:\r
10270             (void) StopLoadGameTimer();\r
10271             pausing = TRUE;\r
10272             ModeHighlight();\r
10273             break;\r
10274           case BeginningOfGame:\r
10275             if (appData.icsActive) return;\r
10276             /* else fall through */\r
10277           case MachinePlaysWhite:\r
10278           case MachinePlaysBlack:\r
10279           case TwoMachinesPlay:\r
10280             if (forwardMostMove == 0)\r
10281               return;           /* don't pause if no one has moved */\r
10282             if ((gameMode == MachinePlaysWhite &&\r
10283                  !WhiteOnMove(forwardMostMove)) ||\r
10284                 (gameMode == MachinePlaysBlack &&\r
10285                  WhiteOnMove(forwardMostMove))) {\r
10286                 StopClocks();\r
10287             }\r
10288             pausing = TRUE;\r
10289             ModeHighlight();\r
10290             break;\r
10291         }\r
10292     }\r
10293 }\r
10294 \r
10295 void\r
10296 EditCommentEvent()\r
10297 {\r
10298     char title[MSG_SIZ];\r
10299 \r
10300     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
10301         strcpy(title, _("Edit comment"));\r
10302     } else {\r
10303         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,\r
10304                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
10305                 parseList[currentMove - 1]);\r
10306     }\r
10307 \r
10308     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
10309 }\r
10310 \r
10311 \r
10312 void\r
10313 EditTagsEvent()\r
10314 {\r
10315     char *tags = PGNTags(&gameInfo);\r
10316     EditTagsPopUp(tags);\r
10317     free(tags);\r
10318 }\r
10319 \r
10320 void\r
10321 AnalyzeModeEvent()\r
10322 {\r
10323     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
10324       return;\r
10325 \r
10326     if (gameMode != AnalyzeFile) {\r
10327         if (!appData.icsEngineAnalyze) {\r
10328                EditGameEvent();\r
10329                if (gameMode != EditGame) return;\r
10330         }\r
10331         ResurrectChessProgram();\r
10332         SendToProgram("analyze\n", &first);\r
10333         first.analyzing = TRUE;\r
10334         /*first.maybeThinking = TRUE;*/\r
10335         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10336         AnalysisPopUp(_("Analysis"),\r
10337                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10338     }\r
10339     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;\r
10340     pausing = FALSE;\r
10341     ModeHighlight();\r
10342     SetGameInfo();\r
10343 \r
10344     StartAnalysisClock();\r
10345     GetTimeMark(&lastNodeCountTime);\r
10346     lastNodeCount = 0;\r
10347 }\r
10348 \r
10349 void\r
10350 AnalyzeFileEvent()\r
10351 {\r
10352     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
10353       return;\r
10354 \r
10355     if (gameMode != AnalyzeMode) {\r
10356         EditGameEvent();\r
10357         if (gameMode != EditGame) return;\r
10358         ResurrectChessProgram();\r
10359         SendToProgram("analyze\n", &first);\r
10360         first.analyzing = TRUE;\r
10361         /*first.maybeThinking = TRUE;*/\r
10362         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10363         AnalysisPopUp(_("Analysis"),\r
10364                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10365     }\r
10366     gameMode = AnalyzeFile;\r
10367     pausing = FALSE;\r
10368     ModeHighlight();\r
10369     SetGameInfo();\r
10370 \r
10371     StartAnalysisClock();\r
10372     GetTimeMark(&lastNodeCountTime);\r
10373     lastNodeCount = 0;\r
10374 }\r
10375 \r
10376 void\r
10377 MachineWhiteEvent()\r
10378 {\r
10379     char buf[MSG_SIZ];\r
10380     char *bookHit = NULL;\r
10381 \r
10382     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
10383       return;\r
10384 \r
10385 \r
10386     if (gameMode == PlayFromGameFile || \r
10387         gameMode == TwoMachinesPlay  || \r
10388         gameMode == Training         || \r
10389         gameMode == AnalyzeMode      || \r
10390         gameMode == EndOfGame)\r
10391         EditGameEvent();\r
10392 \r
10393     if (gameMode == EditPosition) \r
10394         EditPositionDone();\r
10395 \r
10396     if (!WhiteOnMove(currentMove)) {\r
10397         DisplayError(_("It is not White's turn"), 0);\r
10398         return;\r
10399     }\r
10400   \r
10401     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10402       ExitAnalyzeMode();\r
10403 \r
10404     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10405         gameMode == AnalyzeFile)\r
10406         TruncateGame();\r
10407 \r
10408     ResurrectChessProgram();    /* in case it isn't running */\r
10409     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */\r
10410         gameMode = MachinePlaysWhite;\r
10411         ResetClocks();\r
10412     } else\r
10413     gameMode = MachinePlaysWhite;\r
10414     pausing = FALSE;\r
10415     ModeHighlight();\r
10416     SetGameInfo();\r
10417     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10418     DisplayTitle(buf);\r
10419     if (first.sendName) {\r
10420       sprintf(buf, "name %s\n", gameInfo.black);\r
10421       SendToProgram(buf, &first);\r
10422     }\r
10423     if (first.sendTime) {\r
10424       if (first.useColors) {\r
10425         SendToProgram("black\n", &first); /*gnu kludge*/\r
10426       }\r
10427       SendTimeRemaining(&first, TRUE);\r
10428     }\r
10429     if (first.useColors) {\r
10430       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately\r
10431     }\r
10432     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10433     SetMachineThinkingEnables();\r
10434     first.maybeThinking = TRUE;\r
10435     StartClocks();\r
10436 \r
10437     if (appData.autoFlipView && !flipView) {\r
10438       flipView = !flipView;\r
10439       DrawPosition(FALSE, NULL);\r
10440       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10441     }\r
10442 \r
10443     if(bookHit) { // [HGM] book: simulate book reply\r
10444         static char bookMove[MSG_SIZ]; // a bit generous?\r
10445 \r
10446         programStats.nodes = programStats.depth = programStats.time = \r
10447         programStats.score = programStats.got_only_move = 0;\r
10448         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10449 \r
10450         strcpy(bookMove, "move ");\r
10451         strcat(bookMove, bookHit);\r
10452         HandleMachineMove(bookMove, &first);\r
10453     }\r
10454 }\r
10455 \r
10456 void\r
10457 MachineBlackEvent()\r
10458 {\r
10459     char buf[MSG_SIZ];\r
10460    char *bookHit = NULL;\r
10461 \r
10462     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
10463         return;\r
10464 \r
10465 \r
10466     if (gameMode == PlayFromGameFile || \r
10467         gameMode == TwoMachinesPlay  || \r
10468         gameMode == Training         || \r
10469         gameMode == AnalyzeMode      || \r
10470         gameMode == EndOfGame)\r
10471         EditGameEvent();\r
10472 \r
10473     if (gameMode == EditPosition) \r
10474         EditPositionDone();\r
10475 \r
10476     if (WhiteOnMove(currentMove)) {\r
10477         DisplayError(_("It is not Black's turn"), 0);\r
10478         return;\r
10479     }\r
10480     \r
10481     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10482       ExitAnalyzeMode();\r
10483 \r
10484     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10485         gameMode == AnalyzeFile)\r
10486         TruncateGame();\r
10487 \r
10488     ResurrectChessProgram();    /* in case it isn't running */\r
10489     gameMode = MachinePlaysBlack;\r
10490     pausing = FALSE;\r
10491     ModeHighlight();\r
10492     SetGameInfo();\r
10493     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10494     DisplayTitle(buf);\r
10495     if (first.sendName) {\r
10496       sprintf(buf, "name %s\n", gameInfo.white);\r
10497       SendToProgram(buf, &first);\r
10498     }\r
10499     if (first.sendTime) {\r
10500       if (first.useColors) {\r
10501         SendToProgram("white\n", &first); /*gnu kludge*/\r
10502       }\r
10503       SendTimeRemaining(&first, FALSE);\r
10504     }\r
10505     if (first.useColors) {\r
10506       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately\r
10507     }\r
10508     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10509     SetMachineThinkingEnables();\r
10510     first.maybeThinking = TRUE;\r
10511     StartClocks();\r
10512 \r
10513     if (appData.autoFlipView && flipView) {\r
10514       flipView = !flipView;\r
10515       DrawPosition(FALSE, NULL);\r
10516       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10517     }\r
10518     if(bookHit) { // [HGM] book: simulate book reply\r
10519         static char bookMove[MSG_SIZ]; // a bit generous?\r
10520 \r
10521         programStats.nodes = programStats.depth = programStats.time = \r
10522         programStats.score = programStats.got_only_move = 0;\r
10523         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10524 \r
10525         strcpy(bookMove, "move ");\r
10526         strcat(bookMove, bookHit);\r
10527         HandleMachineMove(bookMove, &first);\r
10528     }\r
10529 }\r
10530 \r
10531 \r
10532 void\r
10533 DisplayTwoMachinesTitle()\r
10534 {\r
10535     char buf[MSG_SIZ];\r
10536     if (appData.matchGames > 0) {\r
10537         if (first.twoMachinesColor[0] == 'w') {\r
10538             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10539                     gameInfo.white, gameInfo.black,\r
10540                     first.matchWins, second.matchWins,\r
10541                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10542         } else {\r
10543             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10544                     gameInfo.white, gameInfo.black,\r
10545                     second.matchWins, first.matchWins,\r
10546                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10547         }\r
10548     } else {\r
10549         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10550     }\r
10551     DisplayTitle(buf);\r
10552 }\r
10553 \r
10554 void\r
10555 TwoMachinesEvent P((void))\r
10556 {\r
10557     int i;\r
10558     char buf[MSG_SIZ];\r
10559     ChessProgramState *onmove;\r
10560     char *bookHit = NULL;\r
10561     \r
10562     if (appData.noChessProgram) return;\r
10563 \r
10564     switch (gameMode) {\r
10565       case TwoMachinesPlay:\r
10566         return;\r
10567       case MachinePlaysWhite:\r
10568       case MachinePlaysBlack:\r
10569         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10570             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
10571             return;\r
10572         }\r
10573         /* fall through */\r
10574       case BeginningOfGame:\r
10575       case PlayFromGameFile:\r
10576       case EndOfGame:\r
10577         EditGameEvent();\r
10578         if (gameMode != EditGame) return;\r
10579         break;\r
10580       case EditPosition:\r
10581         EditPositionDone();\r
10582         break;\r
10583       case AnalyzeMode:\r
10584       case AnalyzeFile:\r
10585         ExitAnalyzeMode();\r
10586         break;\r
10587       case EditGame:\r
10588       default:\r
10589         break;\r
10590     }\r
10591 \r
10592     forwardMostMove = currentMove;\r
10593     ResurrectChessProgram();    /* in case first program isn't running */\r
10594 \r
10595     if (second.pr == NULL) {\r
10596         StartChessProgram(&second);\r
10597         if (second.protocolVersion == 1) {\r
10598           TwoMachinesEventIfReady();\r
10599         } else {\r
10600           /* kludge: allow timeout for initial "feature" command */\r
10601           FreezeUI();\r
10602           DisplayMessage("", _("Starting second chess program"));\r
10603           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
10604         }\r
10605         return;\r
10606     }\r
10607     DisplayMessage("", "");\r
10608     InitChessProgram(&second, FALSE);\r
10609     SendToProgram("force\n", &second);\r
10610     if (startedFromSetupPosition) {\r
10611         SendBoard(&second, backwardMostMove);\r
10612     if (appData.debugMode) {\r
10613         fprintf(debugFP, "Two Machines\n");\r
10614     }\r
10615     }\r
10616     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
10617         SendMoveToProgram(i, &second);\r
10618     }\r
10619 \r
10620     gameMode = TwoMachinesPlay;\r
10621     pausing = FALSE;\r
10622     ModeHighlight();\r
10623     SetGameInfo();\r
10624     DisplayTwoMachinesTitle();\r
10625     firstMove = TRUE;\r
10626     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
10627         onmove = &first;\r
10628     } else {\r
10629         onmove = &second;\r
10630     }\r
10631 \r
10632     SendToProgram(first.computerString, &first);\r
10633     if (first.sendName) {\r
10634       sprintf(buf, "name %s\n", second.tidy);\r
10635       SendToProgram(buf, &first);\r
10636     }\r
10637     SendToProgram(second.computerString, &second);\r
10638     if (second.sendName) {\r
10639       sprintf(buf, "name %s\n", first.tidy);\r
10640       SendToProgram(buf, &second);\r
10641     }\r
10642 \r
10643     ResetClocks();\r
10644     if (!first.sendTime || !second.sendTime) {\r
10645         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10646         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10647     }\r
10648     if (onmove->sendTime) {\r
10649       if (onmove->useColors) {\r
10650         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
10651       }\r
10652       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
10653     }\r
10654     if (onmove->useColors) {\r
10655       SendToProgram(onmove->twoMachinesColor, onmove);\r
10656     }\r
10657     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move\r
10658 //    SendToProgram("go\n", onmove);\r
10659     onmove->maybeThinking = TRUE;\r
10660     SetMachineThinkingEnables();\r
10661 \r
10662     StartClocks();\r
10663 \r
10664     if(bookHit) { // [HGM] book: simulate book reply\r
10665         static char bookMove[MSG_SIZ]; // a bit generous?\r
10666 \r
10667         programStats.nodes = programStats.depth = programStats.time = \r
10668         programStats.score = programStats.got_only_move = 0;\r
10669         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10670 \r
10671         strcpy(bookMove, "move ");\r
10672         strcat(bookMove, bookHit);\r
10673         HandleMachineMove(bookMove, &first);\r
10674     }\r
10675 }\r
10676 \r
10677 void\r
10678 TrainingEvent()\r
10679 {\r
10680     if (gameMode == Training) {\r
10681       SetTrainingModeOff();\r
10682       gameMode = PlayFromGameFile;\r
10683       DisplayMessage("", _("Training mode off"));\r
10684     } else {\r
10685       gameMode = Training;\r
10686       animateTraining = appData.animate;\r
10687 \r
10688       /* make sure we are not already at the end of the game */\r
10689       if (currentMove < forwardMostMove) {\r
10690         SetTrainingModeOn();\r
10691         DisplayMessage("", _("Training mode on"));\r
10692       } else {\r
10693         gameMode = PlayFromGameFile;\r
10694         DisplayError(_("Already at end of game"), 0);\r
10695       }\r
10696     }\r
10697     ModeHighlight();\r
10698 }\r
10699 \r
10700 void\r
10701 IcsClientEvent()\r
10702 {\r
10703     if (!appData.icsActive) return;\r
10704     switch (gameMode) {\r
10705       case IcsPlayingWhite:\r
10706       case IcsPlayingBlack:\r
10707       case IcsObserving:\r
10708       case IcsIdle:\r
10709       case BeginningOfGame:\r
10710       case IcsExamining:\r
10711         return;\r
10712 \r
10713       case EditGame:\r
10714         break;\r
10715 \r
10716       case EditPosition:\r
10717         EditPositionDone();\r
10718         break;\r
10719 \r
10720       case AnalyzeMode:\r
10721       case AnalyzeFile:\r
10722         ExitAnalyzeMode();\r
10723         break;\r
10724         \r
10725       default:\r
10726         EditGameEvent();\r
10727         break;\r
10728     }\r
10729 \r
10730     gameMode = IcsIdle;\r
10731     ModeHighlight();\r
10732     return;\r
10733 }\r
10734 \r
10735 \r
10736 void\r
10737 EditGameEvent()\r
10738 {\r
10739     int i;\r
10740 \r
10741     switch (gameMode) {\r
10742       case Training:\r
10743         SetTrainingModeOff();\r
10744         break;\r
10745       case MachinePlaysWhite:\r
10746       case MachinePlaysBlack:\r
10747       case BeginningOfGame:\r
10748         SendToProgram("force\n", &first);\r
10749         SetUserThinkingEnables();\r
10750         break;\r
10751       case PlayFromGameFile:\r
10752         (void) StopLoadGameTimer();\r
10753         if (gameFileFP != NULL) {\r
10754             gameFileFP = NULL;\r
10755         }\r
10756         break;\r
10757       case EditPosition:\r
10758         EditPositionDone();\r
10759         break;\r
10760       case AnalyzeMode:\r
10761       case AnalyzeFile:\r
10762         ExitAnalyzeMode();\r
10763         SendToProgram("force\n", &first);\r
10764         break;\r
10765       case TwoMachinesPlay:\r
10766         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10767         ResurrectChessProgram();\r
10768         SetUserThinkingEnables();\r
10769         break;\r
10770       case EndOfGame:\r
10771         ResurrectChessProgram();\r
10772         break;\r
10773       case IcsPlayingBlack:\r
10774       case IcsPlayingWhite:\r
10775         DisplayError(_("Warning: You are still playing a game"), 0);\r
10776         break;\r
10777       case IcsObserving:\r
10778         DisplayError(_("Warning: You are still observing a game"), 0);\r
10779         break;\r
10780       case IcsExamining:\r
10781         DisplayError(_("Warning: You are still examining a game"), 0);\r
10782         break;\r
10783       case IcsIdle:\r
10784         break;\r
10785       case EditGame:\r
10786       default:\r
10787         return;\r
10788     }\r
10789     \r
10790     pausing = FALSE;\r
10791     StopClocks();\r
10792     first.offeredDraw = second.offeredDraw = 0;\r
10793 \r
10794     if (gameMode == PlayFromGameFile) {\r
10795         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10796         blackTimeRemaining = timeRemaining[1][currentMove];\r
10797         DisplayTitle("");\r
10798     }\r
10799 \r
10800     if (gameMode == MachinePlaysWhite ||\r
10801         gameMode == MachinePlaysBlack ||\r
10802         gameMode == TwoMachinesPlay ||\r
10803         gameMode == EndOfGame) {\r
10804         i = forwardMostMove;\r
10805         while (i > currentMove) {\r
10806             SendToProgram("undo\n", &first);\r
10807             i--;\r
10808         }\r
10809         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10810         blackTimeRemaining = timeRemaining[1][currentMove];\r
10811         DisplayBothClocks();\r
10812         if (whiteFlag || blackFlag) {\r
10813             whiteFlag = blackFlag = 0;\r
10814         }\r
10815         DisplayTitle("");\r
10816     }           \r
10817     \r
10818     gameMode = EditGame;\r
10819     ModeHighlight();\r
10820     SetGameInfo();\r
10821 }\r
10822 \r
10823 \r
10824 void\r
10825 EditPositionEvent()\r
10826 {\r
10827     if (gameMode == EditPosition) {\r
10828         EditGameEvent();\r
10829         return;\r
10830     }\r
10831     \r
10832     EditGameEvent();\r
10833     if (gameMode != EditGame) return;\r
10834     \r
10835     gameMode = EditPosition;\r
10836     ModeHighlight();\r
10837     SetGameInfo();\r
10838     if (currentMove > 0)\r
10839       CopyBoard(boards[0], boards[currentMove]);\r
10840     \r
10841     blackPlaysFirst = !WhiteOnMove(currentMove);\r
10842     ResetClocks();\r
10843     currentMove = forwardMostMove = backwardMostMove = 0;\r
10844     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10845     DisplayMove(-1);\r
10846 }\r
10847 \r
10848 void\r
10849 ExitAnalyzeMode()\r
10850 {\r
10851     /* [DM] icsEngineAnalyze - possible call from other functions */\r
10852     if (appData.icsEngineAnalyze) {\r
10853         appData.icsEngineAnalyze = FALSE;\r
10854 \r
10855         DisplayMessage("",_("Close ICS engine analyze..."));\r
10856     }\r
10857     if (first.analysisSupport && first.analyzing) {\r
10858       SendToProgram("exit\n", &first);\r
10859       first.analyzing = FALSE;\r
10860     }\r
10861     AnalysisPopDown();\r
10862     thinkOutput[0] = NULLCHAR;\r
10863 }\r
10864 \r
10865 void\r
10866 EditPositionDone()\r
10867 {\r
10868     startedFromSetupPosition = TRUE;\r
10869     InitChessProgram(&first, FALSE);\r
10870     SendToProgram("force\n", &first);\r
10871     if (blackPlaysFirst) {\r
10872         strcpy(moveList[0], "");\r
10873         strcpy(parseList[0], "");\r
10874         currentMove = forwardMostMove = backwardMostMove = 1;\r
10875         CopyBoard(boards[1], boards[0]);\r
10876         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */\r
10877         { int i;\r
10878           epStatus[1] = epStatus[0];\r
10879           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];\r
10880         }\r
10881     } else {\r
10882         currentMove = forwardMostMove = backwardMostMove = 0;\r
10883     }\r
10884     SendBoard(&first, forwardMostMove);\r
10885     if (appData.debugMode) {\r
10886         fprintf(debugFP, "EditPosDone\n");\r
10887     }\r
10888     DisplayTitle("");\r
10889     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10890     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10891     gameMode = EditGame;\r
10892     ModeHighlight();\r
10893     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10894     ClearHighlights(); /* [AS] */\r
10895 }\r
10896 \r
10897 /* Pause for `ms' milliseconds */\r
10898 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10899 void\r
10900 TimeDelay(ms)\r
10901      long ms;\r
10902 {\r
10903     TimeMark m1, m2;\r
10904 \r
10905     GetTimeMark(&m1);\r
10906     do {\r
10907         GetTimeMark(&m2);\r
10908     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
10909 }\r
10910 \r
10911 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10912 void\r
10913 SendMultiLineToICS(buf)\r
10914      char *buf;\r
10915 {\r
10916     char temp[MSG_SIZ+1], *p;\r
10917     int len;\r
10918 \r
10919     len = strlen(buf);\r
10920     if (len > MSG_SIZ)\r
10921       len = MSG_SIZ;\r
10922   \r
10923     strncpy(temp, buf, len);\r
10924     temp[len] = 0;\r
10925 \r
10926     p = temp;\r
10927     while (*p) {\r
10928         if (*p == '\n' || *p == '\r')\r
10929           *p = ' ';\r
10930         ++p;\r
10931     }\r
10932 \r
10933     strcat(temp, "\n");\r
10934     SendToICS(temp);\r
10935     SendToPlayer(temp, strlen(temp));\r
10936 }\r
10937 \r
10938 void\r
10939 SetWhiteToPlayEvent()\r
10940 {\r
10941     if (gameMode == EditPosition) {\r
10942         blackPlaysFirst = FALSE;\r
10943         DisplayBothClocks();    /* works because currentMove is 0 */\r
10944     } else if (gameMode == IcsExamining) {\r
10945         SendToICS(ics_prefix);\r
10946         SendToICS("tomove white\n");\r
10947     }\r
10948 }\r
10949 \r
10950 void\r
10951 SetBlackToPlayEvent()\r
10952 {\r
10953     if (gameMode == EditPosition) {\r
10954         blackPlaysFirst = TRUE;\r
10955         currentMove = 1;        /* kludge */\r
10956         DisplayBothClocks();\r
10957         currentMove = 0;\r
10958     } else if (gameMode == IcsExamining) {\r
10959         SendToICS(ics_prefix);\r
10960         SendToICS("tomove black\n");\r
10961     }\r
10962 }\r
10963 \r
10964 void\r
10965 EditPositionMenuEvent(selection, x, y)\r
10966      ChessSquare selection;\r
10967      int x, y;\r
10968 {\r
10969     char buf[MSG_SIZ];\r
10970     ChessSquare piece = boards[0][y][x];\r
10971 \r
10972     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
10973 \r
10974     switch (selection) {\r
10975       case ClearBoard:\r
10976         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
10977             SendToICS(ics_prefix);\r
10978             SendToICS("bsetup clear\n");\r
10979         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
10980             SendToICS(ics_prefix);\r
10981             SendToICS("clearboard\n");\r
10982         } else {\r
10983             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;\r
10984                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */\r
10985                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
10986                     if (gameMode == IcsExamining) {\r
10987                         if (boards[currentMove][y][x] != EmptySquare) {\r
10988                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
10989                                     AAA + x, ONE + y);\r
10990                             SendToICS(buf);\r
10991                         }\r
10992                     } else {\r
10993                         boards[0][y][x] = p;\r
10994                     }\r
10995                 }\r
10996             }\r
10997         }\r
10998         if (gameMode == EditPosition) {\r
10999             DrawPosition(FALSE, boards[0]);\r
11000         }\r
11001         break;\r
11002 \r
11003       case WhitePlay:\r
11004         SetWhiteToPlayEvent();\r
11005         break;\r
11006 \r
11007       case BlackPlay:\r
11008         SetBlackToPlayEvent();\r
11009         break;\r
11010 \r
11011       case EmptySquare:\r
11012         if (gameMode == IcsExamining) {\r
11013             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
11014             SendToICS(buf);\r
11015         } else {\r
11016             boards[0][y][x] = EmptySquare;\r
11017             DrawPosition(FALSE, boards[0]);\r
11018         }\r
11019         break;\r
11020 \r
11021       case PromotePiece:\r
11022         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
11023            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
11024             selection = (ChessSquare) (PROMOTED piece);\r
11025         } else if(piece == EmptySquare) selection = WhiteSilver;\r
11026         else selection = (ChessSquare)((int)piece - 1);\r
11027         goto defaultlabel;\r
11028 \r
11029       case DemotePiece:\r
11030         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
11031            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
11032             selection = (ChessSquare) (DEMOTED piece);\r
11033         } else if(piece == EmptySquare) selection = BlackSilver;\r
11034         else selection = (ChessSquare)((int)piece + 1);       \r
11035         goto defaultlabel;\r
11036 \r
11037       case WhiteQueen:\r
11038       case BlackQueen:\r
11039         if(gameInfo.variant == VariantShatranj ||\r
11040            gameInfo.variant == VariantXiangqi  ||\r
11041            gameInfo.variant == VariantCourier    )\r
11042             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
11043         goto defaultlabel;\r
11044 \r
11045       case WhiteKing:\r
11046       case BlackKing:\r
11047         if(gameInfo.variant == VariantXiangqi)\r
11048             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
11049         if(gameInfo.variant == VariantKnightmate)\r
11050             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
11051       default:\r
11052         defaultlabel:\r
11053         if (gameMode == IcsExamining) {\r
11054             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
11055                     PieceToChar(selection), AAA + x, ONE + y);\r
11056             SendToICS(buf);\r
11057         } else {\r
11058             boards[0][y][x] = selection;\r
11059             DrawPosition(FALSE, boards[0]);\r
11060         }\r
11061         break;\r
11062     }\r
11063 }\r
11064 \r
11065 \r
11066 void\r
11067 DropMenuEvent(selection, x, y)\r
11068      ChessSquare selection;\r
11069      int x, y;\r
11070 {\r
11071     ChessMove moveType;\r
11072 \r
11073     switch (gameMode) {\r
11074       case IcsPlayingWhite:\r
11075       case MachinePlaysBlack:\r
11076         if (!WhiteOnMove(currentMove)) {\r
11077             DisplayMoveError(_("It is Black's turn"));\r
11078             return;\r
11079         }\r
11080         moveType = WhiteDrop;\r
11081         break;\r
11082       case IcsPlayingBlack:\r
11083       case MachinePlaysWhite:\r
11084         if (WhiteOnMove(currentMove)) {\r
11085             DisplayMoveError(_("It is White's turn"));\r
11086             return;\r
11087         }\r
11088         moveType = BlackDrop;\r
11089         break;\r
11090       case EditGame:\r
11091         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
11092         break;\r
11093       default:\r
11094         return;\r
11095     }\r
11096 \r
11097     if (moveType == BlackDrop && selection < BlackPawn) {\r
11098       selection = (ChessSquare) ((int) selection\r
11099                                  + (int) BlackPawn - (int) WhitePawn);\r
11100     }\r
11101     if (boards[currentMove][y][x] != EmptySquare) {\r
11102         DisplayMoveError(_("That square is occupied"));\r
11103         return;\r
11104     }\r
11105 \r
11106     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
11107 }\r
11108 \r
11109 void\r
11110 AcceptEvent()\r
11111 {\r
11112     /* Accept a pending offer of any kind from opponent */\r
11113     \r
11114     if (appData.icsActive) {\r
11115         SendToICS(ics_prefix);\r
11116         SendToICS("accept\n");\r
11117     } else if (cmailMsgLoaded) {\r
11118         if (currentMove == cmailOldMove &&\r
11119             commentList[cmailOldMove] != NULL &&\r
11120             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11121                    "Black offers a draw" : "White offers a draw")) {\r
11122             TruncateGame();\r
11123             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11124             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11125         } else {\r
11126             DisplayError(_("There is no pending offer on this move"), 0);\r
11127             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11128         }\r
11129     } else {\r
11130         /* Not used for offers from chess program */\r
11131     }\r
11132 }\r
11133 \r
11134 void\r
11135 DeclineEvent()\r
11136 {\r
11137     /* Decline a pending offer of any kind from opponent */\r
11138     \r
11139     if (appData.icsActive) {\r
11140         SendToICS(ics_prefix);\r
11141         SendToICS("decline\n");\r
11142     } else if (cmailMsgLoaded) {\r
11143         if (currentMove == cmailOldMove &&\r
11144             commentList[cmailOldMove] != NULL &&\r
11145             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11146                    "Black offers a draw" : "White offers a draw")) {\r
11147 #ifdef NOTDEF\r
11148             AppendComment(cmailOldMove, "Draw declined");\r
11149             DisplayComment(cmailOldMove - 1, "Draw declined");\r
11150 #endif /*NOTDEF*/\r
11151         } else {\r
11152             DisplayError(_("There is no pending offer on this move"), 0);\r
11153         }\r
11154     } else {\r
11155         /* Not used for offers from chess program */\r
11156     }\r
11157 }\r
11158 \r
11159 void\r
11160 RematchEvent()\r
11161 {\r
11162     /* Issue ICS rematch command */\r
11163     if (appData.icsActive) {\r
11164         SendToICS(ics_prefix);\r
11165         SendToICS("rematch\n");\r
11166     }\r
11167 }\r
11168 \r
11169 void\r
11170 CallFlagEvent()\r
11171 {\r
11172     /* Call your opponent's flag (claim a win on time) */\r
11173     if (appData.icsActive) {\r
11174         SendToICS(ics_prefix);\r
11175         SendToICS("flag\n");\r
11176     } else {\r
11177         switch (gameMode) {\r
11178           default:\r
11179             return;\r
11180           case MachinePlaysWhite:\r
11181             if (whiteFlag) {\r
11182                 if (blackFlag)\r
11183                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11184                            GE_PLAYER);\r
11185                 else\r
11186                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
11187             } else {\r
11188                 DisplayError(_("Your opponent is not out of time"), 0);\r
11189             }\r
11190             break;\r
11191           case MachinePlaysBlack:\r
11192             if (blackFlag) {\r
11193                 if (whiteFlag)\r
11194                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11195                            GE_PLAYER);\r
11196                 else\r
11197                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
11198             } else {\r
11199                 DisplayError(_("Your opponent is not out of time"), 0);\r
11200             }\r
11201             break;\r
11202         }\r
11203     }\r
11204 }\r
11205 \r
11206 void\r
11207 DrawEvent()\r
11208 {\r
11209     /* Offer draw or accept pending draw offer from opponent */\r
11210     \r
11211     if (appData.icsActive) {\r
11212         /* Note: tournament rules require draw offers to be\r
11213            made after you make your move but before you punch\r
11214            your clock.  Currently ICS doesn't let you do that;\r
11215            instead, you immediately punch your clock after making\r
11216            a move, but you can offer a draw at any time. */\r
11217         \r
11218         SendToICS(ics_prefix);\r
11219         SendToICS("draw\n");\r
11220     } else if (cmailMsgLoaded) {\r
11221         if (currentMove == cmailOldMove &&\r
11222             commentList[cmailOldMove] != NULL &&\r
11223             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11224                    "Black offers a draw" : "White offers a draw")) {\r
11225             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11226             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11227         } else if (currentMove == cmailOldMove + 1) {\r
11228             char *offer = WhiteOnMove(cmailOldMove) ?\r
11229               "White offers a draw" : "Black offers a draw";\r
11230             AppendComment(currentMove, offer);\r
11231             DisplayComment(currentMove - 1, offer);\r
11232             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
11233         } else {\r
11234             DisplayError(_("You must make your move before offering a draw"), 0);\r
11235             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11236         }\r
11237     } else if (first.offeredDraw) {\r
11238         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
11239     } else {\r
11240         if (first.sendDrawOffers) {\r
11241             SendToProgram("draw\n", &first);\r
11242             userOfferedDraw = TRUE;\r
11243         }\r
11244     }\r
11245 }\r
11246 \r
11247 void\r
11248 AdjournEvent()\r
11249 {\r
11250     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
11251     \r
11252     if (appData.icsActive) {\r
11253         SendToICS(ics_prefix);\r
11254         SendToICS("adjourn\n");\r
11255     } else {\r
11256         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
11257     }\r
11258 }\r
11259 \r
11260 \r
11261 void\r
11262 AbortEvent()\r
11263 {\r
11264     /* Offer Abort or accept pending Abort offer from opponent */\r
11265     \r
11266     if (appData.icsActive) {\r
11267         SendToICS(ics_prefix);\r
11268         SendToICS("abort\n");\r
11269     } else {\r
11270         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
11271     }\r
11272 }\r
11273 \r
11274 void\r
11275 ResignEvent()\r
11276 {\r
11277     /* Resign.  You can do this even if it's not your turn. */\r
11278     \r
11279     if (appData.icsActive) {\r
11280         SendToICS(ics_prefix);\r
11281         SendToICS("resign\n");\r
11282     } else {\r
11283         switch (gameMode) {\r
11284           case MachinePlaysWhite:\r
11285             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11286             break;\r
11287           case MachinePlaysBlack:\r
11288             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11289             break;\r
11290           case EditGame:\r
11291             if (cmailMsgLoaded) {\r
11292                 TruncateGame();\r
11293                 if (WhiteOnMove(cmailOldMove)) {\r
11294                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11295                 } else {\r
11296                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11297                 }\r
11298                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
11299             }\r
11300             break;\r
11301           default:\r
11302             break;\r
11303         }\r
11304     }\r
11305 }\r
11306 \r
11307 \r
11308 void\r
11309 StopObservingEvent()\r
11310 {\r
11311     /* Stop observing current games */\r
11312     SendToICS(ics_prefix);\r
11313     SendToICS("unobserve\n");\r
11314 }\r
11315 \r
11316 void\r
11317 StopExaminingEvent()\r
11318 {\r
11319     /* Stop observing current game */\r
11320     SendToICS(ics_prefix);\r
11321     SendToICS("unexamine\n");\r
11322 }\r
11323 \r
11324 void\r
11325 ForwardInner(target)\r
11326      int target;\r
11327 {\r
11328     int limit;\r
11329 \r
11330     if (appData.debugMode)\r
11331         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
11332                 target, currentMove, forwardMostMove);\r
11333 \r
11334     if (gameMode == EditPosition)\r
11335       return;\r
11336 \r
11337     if (gameMode == PlayFromGameFile && !pausing)\r
11338       PauseEvent();\r
11339     \r
11340     if (gameMode == IcsExamining && pausing)\r
11341       limit = pauseExamForwardMostMove;\r
11342     else\r
11343       limit = forwardMostMove;\r
11344     \r
11345     if (target > limit) target = limit;\r
11346 \r
11347     if (target > 0 && moveList[target - 1][0]) {\r
11348         int fromX, fromY, toX, toY;\r
11349         toX = moveList[target - 1][2] - AAA;\r
11350         toY = moveList[target - 1][3] - ONE;\r
11351         if (moveList[target - 1][1] == '@') {\r
11352             if (appData.highlightLastMove) {\r
11353                 SetHighlights(-1, -1, toX, toY);\r
11354             }\r
11355         } else {\r
11356             fromX = moveList[target - 1][0] - AAA;\r
11357             fromY = moveList[target - 1][1] - ONE;\r
11358             if (target == currentMove + 1) {\r
11359                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
11360             }\r
11361             if (appData.highlightLastMove) {\r
11362                 SetHighlights(fromX, fromY, toX, toY);\r
11363             }\r
11364         }\r
11365     }\r
11366     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
11367         gameMode == Training || gameMode == PlayFromGameFile || \r
11368         gameMode == AnalyzeFile) {\r
11369         while (currentMove < target) {\r
11370             SendMoveToProgram(currentMove++, &first);\r
11371         }\r
11372     } else {\r
11373         currentMove = target;\r
11374     }\r
11375     \r
11376     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11377         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11378         blackTimeRemaining = timeRemaining[1][currentMove];\r
11379     }\r
11380     DisplayBothClocks();\r
11381     DisplayMove(currentMove - 1);\r
11382     DrawPosition(FALSE, boards[currentMove]);\r
11383     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11384     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
11385         DisplayComment(currentMove - 1, commentList[currentMove]);\r
11386     }\r
11387 }\r
11388 \r
11389 \r
11390 void\r
11391 ForwardEvent()\r
11392 {\r
11393     if (gameMode == IcsExamining && !pausing) {\r
11394         SendToICS(ics_prefix);\r
11395         SendToICS("forward\n");\r
11396     } else {\r
11397         ForwardInner(currentMove + 1);\r
11398     }\r
11399 }\r
11400 \r
11401 void\r
11402 ToEndEvent()\r
11403 {\r
11404     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11405         /* to optimze, we temporarily turn off analysis mode while we feed\r
11406          * the remaining moves to the engine. Otherwise we get analysis output\r
11407          * after each move.\r
11408          */ \r
11409         if (first.analysisSupport) {\r
11410           SendToProgram("exit\nforce\n", &first);\r
11411           first.analyzing = FALSE;\r
11412         }\r
11413     }\r
11414         \r
11415     if (gameMode == IcsExamining && !pausing) {\r
11416         SendToICS(ics_prefix);\r
11417         SendToICS("forward 999999\n");\r
11418     } else {\r
11419         ForwardInner(forwardMostMove);\r
11420     }\r
11421 \r
11422     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11423         /* we have fed all the moves, so reactivate analysis mode */\r
11424         SendToProgram("analyze\n", &first);\r
11425         first.analyzing = TRUE;\r
11426         /*first.maybeThinking = TRUE;*/\r
11427         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11428     }\r
11429 }\r
11430 \r
11431 void\r
11432 BackwardInner(target)\r
11433      int target;\r
11434 {\r
11435     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
11436 \r
11437     if (appData.debugMode)\r
11438         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
11439                 target, currentMove, forwardMostMove);\r
11440 \r
11441     if (gameMode == EditPosition) return;\r
11442     if (currentMove <= backwardMostMove) {\r
11443         ClearHighlights();\r
11444         DrawPosition(full_redraw, boards[currentMove]);\r
11445         return;\r
11446     }\r
11447     if (gameMode == PlayFromGameFile && !pausing)\r
11448       PauseEvent();\r
11449     \r
11450     if (moveList[target][0]) {\r
11451         int fromX, fromY, toX, toY;\r
11452         toX = moveList[target][2] - AAA;\r
11453         toY = moveList[target][3] - ONE;\r
11454         if (moveList[target][1] == '@') {\r
11455             if (appData.highlightLastMove) {\r
11456                 SetHighlights(-1, -1, toX, toY);\r
11457             }\r
11458         } else {\r
11459             fromX = moveList[target][0] - AAA;\r
11460             fromY = moveList[target][1] - ONE;\r
11461             if (target == currentMove - 1) {\r
11462                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
11463             }\r
11464             if (appData.highlightLastMove) {\r
11465                 SetHighlights(fromX, fromY, toX, toY);\r
11466             }\r
11467         }\r
11468     }\r
11469     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
11470         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
11471         while (currentMove > target) {\r
11472             SendToProgram("undo\n", &first);\r
11473             currentMove--;\r
11474         }\r
11475     } else {\r
11476         currentMove = target;\r
11477     }\r
11478     \r
11479     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11480         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11481         blackTimeRemaining = timeRemaining[1][currentMove];\r
11482     }\r
11483     DisplayBothClocks();\r
11484     DisplayMove(currentMove - 1);\r
11485     DrawPosition(full_redraw, boards[currentMove]);\r
11486     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11487     // [HGM] PV info: routine tests if comment empty\r
11488     DisplayComment(currentMove - 1, commentList[currentMove]);\r
11489 }\r
11490 \r
11491 void\r
11492 BackwardEvent()\r
11493 {\r
11494     if (gameMode == IcsExamining && !pausing) {\r
11495         SendToICS(ics_prefix);\r
11496         SendToICS("backward\n");\r
11497     } else {\r
11498         BackwardInner(currentMove - 1);\r
11499     }\r
11500 }\r
11501 \r
11502 void\r
11503 ToStartEvent()\r
11504 {\r
11505     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11506         /* to optimze, we temporarily turn off analysis mode while we undo\r
11507          * all the moves. Otherwise we get analysis output after each undo.\r
11508          */ \r
11509         if (first.analysisSupport) {\r
11510           SendToProgram("exit\nforce\n", &first);\r
11511           first.analyzing = FALSE;\r
11512         }\r
11513     }\r
11514 \r
11515     if (gameMode == IcsExamining && !pausing) {\r
11516         SendToICS(ics_prefix);\r
11517         SendToICS("backward 999999\n");\r
11518     } else {\r
11519         BackwardInner(backwardMostMove);\r
11520     }\r
11521 \r
11522     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11523         /* we have fed all the moves, so reactivate analysis mode */\r
11524         SendToProgram("analyze\n", &first);\r
11525         first.analyzing = TRUE;\r
11526         /*first.maybeThinking = TRUE;*/\r
11527         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11528     }\r
11529 }\r
11530 \r
11531 void\r
11532 ToNrEvent(int to)\r
11533 {\r
11534   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
11535   if (to >= forwardMostMove) to = forwardMostMove;\r
11536   if (to <= backwardMostMove) to = backwardMostMove;\r
11537   if (to < currentMove) {\r
11538     BackwardInner(to);\r
11539   } else {\r
11540     ForwardInner(to);\r
11541   }\r
11542 }\r
11543 \r
11544 void\r
11545 RevertEvent()\r
11546 {\r
11547     if (gameMode != IcsExamining) {\r
11548         DisplayError(_("You are not examining a game"), 0);\r
11549         return;\r
11550     }\r
11551     if (pausing) {\r
11552         DisplayError(_("You can't revert while pausing"), 0);\r
11553         return;\r
11554     }\r
11555     SendToICS(ics_prefix);\r
11556     SendToICS("revert\n");\r
11557 }\r
11558 \r
11559 void\r
11560 RetractMoveEvent()\r
11561 {\r
11562     switch (gameMode) {\r
11563       case MachinePlaysWhite:\r
11564       case MachinePlaysBlack:\r
11565         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
11566             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
11567             return;\r
11568         }\r
11569         if (forwardMostMove < 2) return;\r
11570         currentMove = forwardMostMove = forwardMostMove - 2;\r
11571         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11572         blackTimeRemaining = timeRemaining[1][currentMove];\r
11573         DisplayBothClocks();\r
11574         DisplayMove(currentMove - 1);\r
11575         ClearHighlights();/*!! could figure this out*/\r
11576         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
11577         SendToProgram("remove\n", &first);\r
11578         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
11579         break;\r
11580 \r
11581       case BeginningOfGame:\r
11582       default:\r
11583         break;\r
11584 \r
11585       case IcsPlayingWhite:\r
11586       case IcsPlayingBlack:\r
11587         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
11588             SendToICS(ics_prefix);\r
11589             SendToICS("takeback 2\n");\r
11590         } else {\r
11591             SendToICS(ics_prefix);\r
11592             SendToICS("takeback 1\n");\r
11593         }\r
11594         break;\r
11595     }\r
11596 }\r
11597 \r
11598 void\r
11599 MoveNowEvent()\r
11600 {\r
11601     ChessProgramState *cps;\r
11602 \r
11603     switch (gameMode) {\r
11604       case MachinePlaysWhite:\r
11605         if (!WhiteOnMove(forwardMostMove)) {\r
11606             DisplayError(_("It is your turn"), 0);\r
11607             return;\r
11608         }\r
11609         cps = &first;\r
11610         break;\r
11611       case MachinePlaysBlack:\r
11612         if (WhiteOnMove(forwardMostMove)) {\r
11613             DisplayError(_("It is your turn"), 0);\r
11614             return;\r
11615         }\r
11616         cps = &first;\r
11617         break;\r
11618       case TwoMachinesPlay:\r
11619         if (WhiteOnMove(forwardMostMove) ==\r
11620             (first.twoMachinesColor[0] == 'w')) {\r
11621             cps = &first;\r
11622         } else {\r
11623             cps = &second;\r
11624         }\r
11625         break;\r
11626       case BeginningOfGame:\r
11627       default:\r
11628         return;\r
11629     }\r
11630     SendToProgram("?\n", cps);\r
11631 }\r
11632 \r
11633 void\r
11634 TruncateGameEvent()\r
11635 {\r
11636     EditGameEvent();\r
11637     if (gameMode != EditGame) return;\r
11638     TruncateGame();\r
11639 }\r
11640 \r
11641 void\r
11642 TruncateGame()\r
11643 {\r
11644     if (forwardMostMove > currentMove) {\r
11645         if (gameInfo.resultDetails != NULL) {\r
11646             free(gameInfo.resultDetails);\r
11647             gameInfo.resultDetails = NULL;\r
11648             gameInfo.result = GameUnfinished;\r
11649         }\r
11650         forwardMostMove = currentMove;\r
11651         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
11652                    currentMove-1);\r
11653     }\r
11654 }\r
11655 \r
11656 void\r
11657 HintEvent()\r
11658 {\r
11659     if (appData.noChessProgram) return;\r
11660     switch (gameMode) {\r
11661       case MachinePlaysWhite:\r
11662         if (WhiteOnMove(forwardMostMove)) {\r
11663             DisplayError(_("Wait until your turn"), 0);\r
11664             return;\r
11665         }\r
11666         break;\r
11667       case BeginningOfGame:\r
11668       case MachinePlaysBlack:\r
11669         if (!WhiteOnMove(forwardMostMove)) {\r
11670             DisplayError(_("Wait until your turn"), 0);\r
11671             return;\r
11672         }\r
11673         break;\r
11674       default:\r
11675         DisplayError(_("No hint available"), 0);\r
11676         return;\r
11677     }\r
11678     SendToProgram("hint\n", &first);\r
11679     hintRequested = TRUE;\r
11680 }\r
11681 \r
11682 void\r
11683 BookEvent()\r
11684 {\r
11685     if (appData.noChessProgram) return;\r
11686     switch (gameMode) {\r
11687       case MachinePlaysWhite:\r
11688         if (WhiteOnMove(forwardMostMove)) {\r
11689             DisplayError(_("Wait until your turn"), 0);\r
11690             return;\r
11691         }\r
11692         break;\r
11693       case BeginningOfGame:\r
11694       case MachinePlaysBlack:\r
11695         if (!WhiteOnMove(forwardMostMove)) {\r
11696             DisplayError(_("Wait until your turn"), 0);\r
11697             return;\r
11698         }\r
11699         break;\r
11700       case EditPosition:\r
11701         EditPositionDone();\r
11702         break;\r
11703       case TwoMachinesPlay:\r
11704         return;\r
11705       default:\r
11706         break;\r
11707     }\r
11708     SendToProgram("bk\n", &first);\r
11709     bookOutput[0] = NULLCHAR;\r
11710     bookRequested = TRUE;\r
11711 }\r
11712 \r
11713 void\r
11714 AboutGameEvent()\r
11715 {\r
11716     char *tags = PGNTags(&gameInfo);\r
11717     TagsPopUp(tags, CmailMsg());\r
11718     free(tags);\r
11719 }\r
11720 \r
11721 /* end button procedures */\r
11722 \r
11723 void\r
11724 PrintPosition(fp, move)\r
11725      FILE *fp;\r
11726      int move;\r
11727 {\r
11728     int i, j;\r
11729     \r
11730     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11731         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11732             char c = PieceToChar(boards[move][i][j]);\r
11733             fputc(c == 'x' ? '.' : c, fp);\r
11734             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
11735         }\r
11736     }\r
11737     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
11738       fprintf(fp, "white to play\n");\r
11739     else\r
11740       fprintf(fp, "black to play\n");\r
11741 }\r
11742 \r
11743 void\r
11744 PrintOpponents(fp)\r
11745      FILE *fp;\r
11746 {\r
11747     if (gameInfo.white != NULL) {\r
11748         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
11749     } else {\r
11750         fprintf(fp, "\n");\r
11751     }\r
11752 }\r
11753 \r
11754 /* Find last component of program's own name, using some heuristics */\r
11755 void\r
11756 TidyProgramName(prog, host, buf)\r
11757      char *prog, *host, buf[MSG_SIZ];\r
11758 {\r
11759     char *p, *q;\r
11760     int local = (strcmp(host, "localhost") == 0);\r
11761     while (!local && (p = strchr(prog, ';')) != NULL) {\r
11762         p++;\r
11763         while (*p == ' ') p++;\r
11764         prog = p;\r
11765     }\r
11766     if (*prog == '"' || *prog == '\'') {\r
11767         q = strchr(prog + 1, *prog);\r
11768     } else {\r
11769         q = strchr(prog, ' ');\r
11770     }\r
11771     if (q == NULL) q = prog + strlen(prog);\r
11772     p = q;\r
11773     while (p >= prog && *p != '/' && *p != '\\') p--;\r
11774     p++;\r
11775     if(p == prog && *p == '"') p++;\r
11776     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
11777     memcpy(buf, p, q - p);\r
11778     buf[q - p] = NULLCHAR;\r
11779     if (!local) {\r
11780         strcat(buf, "@");\r
11781         strcat(buf, host);\r
11782     }\r
11783 }\r
11784 \r
11785 char *\r
11786 TimeControlTagValue()\r
11787 {\r
11788     char buf[MSG_SIZ];\r
11789     if (!appData.clockMode) {\r
11790         strcpy(buf, "-");\r
11791     } else if (movesPerSession > 0) {\r
11792         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
11793     } else if (timeIncrement == 0) {\r
11794         sprintf(buf, "%ld", timeControl/1000);\r
11795     } else {\r
11796         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
11797     }\r
11798     return StrSave(buf);\r
11799 }\r
11800 \r
11801 void\r
11802 SetGameInfo()\r
11803 {\r
11804     /* This routine is used only for certain modes */\r
11805     VariantClass v = gameInfo.variant;\r
11806     ClearGameInfo(&gameInfo);\r
11807     gameInfo.variant = v;\r
11808 \r
11809     switch (gameMode) {\r
11810       case MachinePlaysWhite:\r
11811         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11812         gameInfo.site = StrSave(HostName());\r
11813         gameInfo.date = PGNDate();\r
11814         gameInfo.round = StrSave("-");\r
11815         gameInfo.white = StrSave(first.tidy);\r
11816         gameInfo.black = StrSave(UserName());\r
11817         gameInfo.timeControl = TimeControlTagValue();\r
11818         break;\r
11819 \r
11820       case MachinePlaysBlack:\r
11821         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11822         gameInfo.site = StrSave(HostName());\r
11823         gameInfo.date = PGNDate();\r
11824         gameInfo.round = StrSave("-");\r
11825         gameInfo.white = StrSave(UserName());\r
11826         gameInfo.black = StrSave(first.tidy);\r
11827         gameInfo.timeControl = TimeControlTagValue();\r
11828         break;\r
11829 \r
11830       case TwoMachinesPlay:\r
11831         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11832         gameInfo.site = StrSave(HostName());\r
11833         gameInfo.date = PGNDate();\r
11834         if (matchGame > 0) {\r
11835             char buf[MSG_SIZ];\r
11836             sprintf(buf, "%d", matchGame);\r
11837             gameInfo.round = StrSave(buf);\r
11838         } else {\r
11839             gameInfo.round = StrSave("-");\r
11840         }\r
11841         if (first.twoMachinesColor[0] == 'w') {\r
11842             gameInfo.white = StrSave(first.tidy);\r
11843             gameInfo.black = StrSave(second.tidy);\r
11844         } else {\r
11845             gameInfo.white = StrSave(second.tidy);\r
11846             gameInfo.black = StrSave(first.tidy);\r
11847         }\r
11848         gameInfo.timeControl = TimeControlTagValue();\r
11849         break;\r
11850 \r
11851       case EditGame:\r
11852         gameInfo.event = StrSave("Edited game");\r
11853         gameInfo.site = StrSave(HostName());\r
11854         gameInfo.date = PGNDate();\r
11855         gameInfo.round = StrSave("-");\r
11856         gameInfo.white = StrSave("-");\r
11857         gameInfo.black = StrSave("-");\r
11858         break;\r
11859 \r
11860       case EditPosition:\r
11861         gameInfo.event = StrSave("Edited position");\r
11862         gameInfo.site = StrSave(HostName());\r
11863         gameInfo.date = PGNDate();\r
11864         gameInfo.round = StrSave("-");\r
11865         gameInfo.white = StrSave("-");\r
11866         gameInfo.black = StrSave("-");\r
11867         break;\r
11868 \r
11869       case IcsPlayingWhite:\r
11870       case IcsPlayingBlack:\r
11871       case IcsObserving:\r
11872       case IcsExamining:\r
11873         break;\r
11874 \r
11875       case PlayFromGameFile:\r
11876         gameInfo.event = StrSave("Game from non-PGN file");\r
11877         gameInfo.site = StrSave(HostName());\r
11878         gameInfo.date = PGNDate();\r
11879         gameInfo.round = StrSave("-");\r
11880         gameInfo.white = StrSave("?");\r
11881         gameInfo.black = StrSave("?");\r
11882         break;\r
11883 \r
11884       default:\r
11885         break;\r
11886     }\r
11887 }\r
11888 \r
11889 void\r
11890 ReplaceComment(index, text)\r
11891      int index;\r
11892      char *text;\r
11893 {\r
11894     int len;\r
11895 \r
11896     while (*text == '\n') text++;\r
11897     len = strlen(text);\r
11898     while (len > 0 && text[len - 1] == '\n') len--;\r
11899 \r
11900     if (commentList[index] != NULL)\r
11901       free(commentList[index]);\r
11902 \r
11903     if (len == 0) {\r
11904         commentList[index] = NULL;\r
11905         return;\r
11906     }\r
11907     commentList[index] = (char *) malloc(len + 2);\r
11908     strncpy(commentList[index], text, len);\r
11909     commentList[index][len] = '\n';\r
11910     commentList[index][len + 1] = NULLCHAR;\r
11911 }\r
11912 \r
11913 void\r
11914 CrushCRs(text)\r
11915      char *text;\r
11916 {\r
11917   char *p = text;\r
11918   char *q = text;\r
11919   char ch;\r
11920 \r
11921   do {\r
11922     ch = *p++;\r
11923     if (ch == '\r') continue;\r
11924     *q++ = ch;\r
11925   } while (ch != '\0');\r
11926 }\r
11927 \r
11928 void\r
11929 AppendComment(index, text)\r
11930      int index;\r
11931      char *text;\r
11932 {\r
11933     int oldlen, len;\r
11934     char *old;\r
11935 \r
11936     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11937 \r
11938     CrushCRs(text);\r
11939     while (*text == '\n') text++;\r
11940     len = strlen(text);\r
11941     while (len > 0 && text[len - 1] == '\n') len--;\r
11942 \r
11943     if (len == 0) return;\r
11944 \r
11945     if (commentList[index] != NULL) {\r
11946         old = commentList[index];\r
11947         oldlen = strlen(old);\r
11948         commentList[index] = (char *) malloc(oldlen + len + 2);\r
11949         strcpy(commentList[index], old);\r
11950         free(old);\r
11951         strncpy(&commentList[index][oldlen], text, len);\r
11952         commentList[index][oldlen + len] = '\n';\r
11953         commentList[index][oldlen + len + 1] = NULLCHAR;\r
11954     } else {\r
11955         commentList[index] = (char *) malloc(len + 2);\r
11956         strncpy(commentList[index], text, len);\r
11957         commentList[index][len] = '\n';\r
11958         commentList[index][len + 1] = NULLCHAR;\r
11959     }\r
11960 }\r
11961 \r
11962 static char * FindStr( char * text, char * sub_text )\r
11963 {\r
11964     char * result = strstr( text, sub_text );\r
11965 \r
11966     if( result != NULL ) {\r
11967         result += strlen( sub_text );\r
11968     }\r
11969 \r
11970     return result;\r
11971 }\r
11972 \r
11973 /* [AS] Try to extract PV info from PGN comment */\r
11974 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
11975 char *GetInfoFromComment( int index, char * text )\r
11976 {\r
11977     char * sep = text;\r
11978 \r
11979     if( text != NULL && index > 0 ) {\r
11980         int score = 0;\r
11981         int depth = 0;\r
11982         int time = -1, sec = 0, deci;\r
11983         char * s_eval = FindStr( text, "[%eval " );\r
11984         char * s_emt = FindStr( text, "[%emt " );\r
11985 \r
11986         if( s_eval != NULL || s_emt != NULL ) {\r
11987             /* New style */\r
11988             char delim;\r
11989 \r
11990             if( s_eval != NULL ) {\r
11991                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
11992                     return text;\r
11993                 }\r
11994 \r
11995                 if( delim != ']' ) {\r
11996                     return text;\r
11997                 }\r
11998             }\r
11999 \r
12000             if( s_emt != NULL ) {\r
12001             }\r
12002         }\r
12003         else {\r
12004             /* We expect something like: [+|-]nnn.nn/dd */\r
12005             int score_lo = 0;\r
12006 \r
12007             sep = strchr( text, '/' );\r
12008             if( sep == NULL || sep < (text+4) ) {\r
12009                 return text;\r
12010             }\r
12011 \r
12012             time = -1; sec = -1; deci = -1;\r
12013             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
12014                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&\r
12015                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
12016                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
12017                 return text;\r
12018             }\r
12019 \r
12020             if( score_lo < 0 || score_lo >= 100 ) {\r
12021                 return text;\r
12022             }\r
12023 \r
12024             if(sec >= 0) time = 600*time + 10*sec; else\r
12025             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec\r
12026 \r
12027             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
12028 \r
12029             /* [HGM] PV time: now locate end of PV info */\r
12030             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
12031             if(time >= 0)\r
12032             while( *++sep >= '0' && *sep <= '9'); // strip time\r
12033             if(sec >= 0)\r
12034             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
12035             if(deci >= 0)\r
12036             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds\r
12037             while(*sep == ' ') sep++;\r
12038         }\r
12039 \r
12040         if( depth <= 0 ) {\r
12041             return text;\r
12042         }\r
12043 \r
12044         if( time < 0 ) {\r
12045             time = -1;\r
12046         }\r
12047 \r
12048         pvInfoList[index-1].depth = depth;\r
12049         pvInfoList[index-1].score = score;\r
12050         pvInfoList[index-1].time  = 10*time; // centi-sec\r
12051     }\r
12052     return sep;\r
12053 }\r
12054 \r
12055 void\r
12056 SendToProgram(message, cps)\r
12057      char *message;\r
12058      ChessProgramState *cps;\r
12059 {\r
12060     int count, outCount, error;\r
12061     char buf[MSG_SIZ];\r
12062 \r
12063     if (cps->pr == NULL) return;\r
12064     Attention(cps);\r
12065     \r
12066     if (appData.debugMode) {\r
12067         TimeMark now;\r
12068         GetTimeMark(&now);\r
12069         fprintf(debugFP, "%ld >%-6s: %s", \r
12070                 SubtractTimeMarks(&now, &programStartTime),\r
12071                 cps->which, message);\r
12072     }\r
12073     \r
12074     count = strlen(message);\r
12075     outCount = OutputToProcess(cps->pr, message, count, &error);\r
12076     if (outCount < count && !exiting \r
12077                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
12078         sprintf(buf, _("Error writing to %s chess program"), cps->which);\r
12079         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12080             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12081                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12082                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
12083             } else {\r
12084                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12085             }\r
12086             gameInfo.resultDetails = buf;\r
12087         }\r
12088         DisplayFatalError(buf, error, 1);\r
12089     }\r
12090 }\r
12091 \r
12092 void\r
12093 ReceiveFromProgram(isr, closure, message, count, error)\r
12094      InputSourceRef isr;\r
12095      VOIDSTAR closure;\r
12096      char *message;\r
12097      int count;\r
12098      int error;\r
12099 {\r
12100     char *end_str;\r
12101     char buf[MSG_SIZ];\r
12102     ChessProgramState *cps = (ChessProgramState *)closure;\r
12103 \r
12104     if (isr != cps->isr) return; /* Killed intentionally */\r
12105     if (count <= 0) {\r
12106         if (count == 0) {\r
12107             sprintf(buf,\r
12108                     _("Error: %s chess program (%s) exited unexpectedly"),\r
12109                     cps->which, cps->program);\r
12110         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12111                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12112                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12113                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);\r
12114                 } else {\r
12115                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12116                 }\r
12117                 gameInfo.resultDetails = buf;\r
12118             }\r
12119             RemoveInputSource(cps->isr);\r
12120             DisplayFatalError(buf, 0, 1);\r
12121         } else {\r
12122             sprintf(buf,\r
12123                     _("Error reading from %s chess program (%s)"),\r
12124                     cps->which, cps->program);\r
12125             RemoveInputSource(cps->isr);\r
12126 \r
12127             /* [AS] Program is misbehaving badly... kill it */\r
12128             if( count == -2 ) {\r
12129                 DestroyChildProcess( cps->pr, 9 );\r
12130                 cps->pr = NoProc;\r
12131             }\r
12132 \r
12133             DisplayFatalError(buf, error, 1);\r
12134         }\r
12135         return;\r
12136     }\r
12137     \r
12138     if ((end_str = strchr(message, '\r')) != NULL)\r
12139       *end_str = NULLCHAR;\r
12140     if ((end_str = strchr(message, '\n')) != NULL)\r
12141       *end_str = NULLCHAR;\r
12142     \r
12143     if (appData.debugMode) {\r
12144         TimeMark now; int print = 1;\r
12145         char *quote = ""; char c; int i;\r
12146 \r
12147         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
12148                 char start = message[0];\r
12149                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
12150                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
12151                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&\r
12152                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
12153                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
12154                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&\r
12155                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
12156                         { quote = "# "; print = (appData.engineComments == 2); }\r
12157                 message[0] = start; // restore original message\r
12158         }\r
12159         if(print) {\r
12160                 GetTimeMark(&now);\r
12161                 fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
12162                         SubtractTimeMarks(&now, &programStartTime), cps->which, \r
12163                         quote,\r
12164                         message);\r
12165         }\r
12166     }\r
12167 \r
12168     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */\r
12169     if (appData.icsEngineAnalyze) {\r
12170         if (strstr(message, "whisper") != NULL ||\r
12171              strstr(message, "kibitz") != NULL || \r
12172             strstr(message, "tellics") != NULL) return;\r
12173     }\r
12174 \r
12175     HandleMachineMove(message, cps);\r
12176 }\r
12177 \r
12178 \r
12179 void\r
12180 SendTimeControl(cps, mps, tc, inc, sd, st)\r
12181      ChessProgramState *cps;\r
12182      int mps, inc, sd, st;\r
12183      long tc;\r
12184 {\r
12185     char buf[MSG_SIZ];\r
12186     int seconds;\r
12187 \r
12188     if( timeControl_2 > 0 ) {\r
12189         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
12190             tc = timeControl_2;\r
12191         }\r
12192     }\r
12193     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
12194     inc /= cps->timeOdds;\r
12195     st  /= cps->timeOdds;\r
12196 \r
12197     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
12198 \r
12199     if (st > 0) {\r
12200       /* Set exact time per move, normally using st command */\r
12201       if (cps->stKludge) {\r
12202         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
12203         seconds = st % 60;\r
12204         if (seconds == 0) {\r
12205           sprintf(buf, "level 1 %d\n", st/60);\r
12206         } else {\r
12207           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
12208         }\r
12209       } else {\r
12210         sprintf(buf, "st %d\n", st);\r
12211       }\r
12212     } else {\r
12213       /* Set conventional or incremental time control, using level command */\r
12214       if (seconds == 0) {\r
12215         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
12216            Fixed in later versions, but still avoid :seconds\r
12217            when seconds is 0. */\r
12218         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
12219       } else {\r
12220         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
12221                 seconds, inc/1000);\r
12222       }\r
12223     }\r
12224     SendToProgram(buf, cps);\r
12225 \r
12226     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
12227     /* Orthogonally, limit search to given depth */\r
12228     if (sd > 0) {\r
12229       if (cps->sdKludge) {\r
12230         sprintf(buf, "depth\n%d\n", sd);\r
12231       } else {\r
12232         sprintf(buf, "sd %d\n", sd);\r
12233       }\r
12234       SendToProgram(buf, cps);\r
12235     }\r
12236 \r
12237     if(cps->nps > 0) { /* [HGM] nps */\r
12238         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
12239         else {\r
12240                 sprintf(buf, "nps %d\n", cps->nps);\r
12241               SendToProgram(buf, cps);\r
12242         }\r
12243     }\r
12244 }\r
12245 \r
12246 ChessProgramState *WhitePlayer()\r
12247 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
12248 {\r
12249     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || \r
12250        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)\r
12251         return &second;\r
12252     return &first;\r
12253 }\r
12254 \r
12255 void\r
12256 SendTimeRemaining(cps, machineWhite)\r
12257      ChessProgramState *cps;\r
12258      int /*boolean*/ machineWhite;\r
12259 {\r
12260     char message[MSG_SIZ];\r
12261     long time, otime;\r
12262 \r
12263     /* Note: this routine must be called when the clocks are stopped\r
12264        or when they have *just* been set or switched; otherwise\r
12265        it will be off by the time since the current tick started.\r
12266     */\r
12267     if (machineWhite) {\r
12268         time = whiteTimeRemaining / 10;\r
12269         otime = blackTimeRemaining / 10;\r
12270     } else {\r
12271         time = blackTimeRemaining / 10;\r
12272         otime = whiteTimeRemaining / 10;\r
12273     }\r
12274     /* [HGM] translate opponent's time by time-odds factor */\r
12275     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
12276     if (appData.debugMode) {\r
12277         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
12278     }\r
12279 \r
12280     if (time <= 0) time = 1;\r
12281     if (otime <= 0) otime = 1;\r
12282     \r
12283     sprintf(message, "time %ld\n", time);\r
12284     SendToProgram(message, cps);\r
12285 \r
12286     sprintf(message, "otim %ld\n", otime);\r
12287     SendToProgram(message, cps);\r
12288 }\r
12289 \r
12290 int\r
12291 BoolFeature(p, name, loc, cps)\r
12292      char **p;\r
12293      char *name;\r
12294      int *loc;\r
12295      ChessProgramState *cps;\r
12296 {\r
12297   char buf[MSG_SIZ];\r
12298   int len = strlen(name);\r
12299   int val;\r
12300   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12301     (*p) += len + 1;\r
12302     sscanf(*p, "%d", &val);\r
12303     *loc = (val != 0);\r
12304     while (**p && **p != ' ') (*p)++;\r
12305     sprintf(buf, "accepted %s\n", name);\r
12306     SendToProgram(buf, cps);\r
12307     return TRUE;\r
12308   }\r
12309   return FALSE;\r
12310 }\r
12311 \r
12312 int\r
12313 IntFeature(p, name, loc, cps)\r
12314      char **p;\r
12315      char *name;\r
12316      int *loc;\r
12317      ChessProgramState *cps;\r
12318 {\r
12319   char buf[MSG_SIZ];\r
12320   int len = strlen(name);\r
12321   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12322     (*p) += len + 1;\r
12323     sscanf(*p, "%d", loc);\r
12324     while (**p && **p != ' ') (*p)++;\r
12325     sprintf(buf, "accepted %s\n", name);\r
12326     SendToProgram(buf, cps);\r
12327     return TRUE;\r
12328   }\r
12329   return FALSE;\r
12330 }\r
12331 \r
12332 int\r
12333 StringFeature(p, name, loc, cps)\r
12334      char **p;\r
12335      char *name;\r
12336      char loc[];\r
12337      ChessProgramState *cps;\r
12338 {\r
12339   char buf[MSG_SIZ];\r
12340   int len = strlen(name);\r
12341   if (strncmp((*p), name, len) == 0\r
12342       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
12343     (*p) += len + 2;\r
12344     sscanf(*p, "%[^\"]", loc);\r
12345     while (**p && **p != '\"') (*p)++;\r
12346     if (**p == '\"') (*p)++;\r
12347     sprintf(buf, "accepted %s\n", name);\r
12348     SendToProgram(buf, cps);\r
12349     return TRUE;\r
12350   }\r
12351   return FALSE;\r
12352 }\r
12353 \r
12354 int \r
12355 ParseOption(Option *opt, ChessProgramState *cps)\r
12356 // [HGM] options: process the string that defines an engine option, and determine\r
12357 // name, type, default value, and allowed value range\r
12358 {\r
12359         char *p, *q, buf[MSG_SIZ];\r
12360         int n, min = (-1)<<31, max = 1<<31, def;\r
12361 \r
12362         if(p = strstr(opt->name, " -spin ")) {\r
12363             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;\r
12364             if(max < min) max = min; // enforce consistency\r
12365             if(def < min) def = min;\r
12366             if(def > max) def = max;\r
12367             opt->value = def;\r
12368             opt->min = min;\r
12369             opt->max = max;\r
12370             opt->type = Spin;\r
12371         } else if(p = strstr(opt->name, " -string ")) {\r
12372             opt->textValue = p+9;\r
12373             opt->type = TextBox;\r
12374         } else if(p = strstr(opt->name, " -check ")) {\r
12375             if(sscanf(p, " -check %d", &def) < 1) return FALSE;\r
12376             opt->value = (def != 0);\r
12377             opt->type = CheckBox;\r
12378         } else if(p = strstr(opt->name, " -combo ")) {\r
12379             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type\r
12380             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices\r
12381             opt->value = n = 0;\r
12382             while(q = StrStr(q, " /// ")) {\r
12383                 n++; *q = 0;    // count choices, and null-terminate each of them\r
12384                 q += 5;\r
12385                 if(*q == '*') { // remember default, which is marked with * prefix\r
12386                     q++;\r
12387                     opt->value = n;\r
12388                 }\r
12389                 cps->comboList[cps->comboCnt++] = q;\r
12390             }\r
12391             cps->comboList[cps->comboCnt++] = NULL;\r
12392             opt->max = n + 1;\r
12393             opt->type = ComboBox;\r
12394         } else if(p = strstr(opt->name, " -button")) {\r
12395             opt->type = Button;\r
12396         } else if(p = strstr(opt->name, " -save")) {\r
12397             opt->type = SaveButton;\r
12398         } else return FALSE;\r
12399         *p = 0; // terminate option name\r
12400         // now look if the command-line options define a setting for this engine option.\r
12401         if(cps->optionSettings && cps->optionSettings[0])\r
12402             p = strstr(cps->optionSettings, opt->name); else p = NULL;\r
12403         if(p && (p == cps->optionSettings || p[-1] == ',')) {\r
12404                 sprintf(buf, "option %s", p);\r
12405                 if(p = strstr(buf, ",")) *p = 0;\r
12406                 strcat(buf, "\n");\r
12407                 SendToProgram(buf, cps);\r
12408         }\r
12409         return TRUE;\r
12410 }\r
12411 \r
12412 void\r
12413 FeatureDone(cps, val)\r
12414      ChessProgramState* cps;\r
12415      int val;\r
12416 {\r
12417   DelayedEventCallback cb = GetDelayedEvent();\r
12418   if ((cb == InitBackEnd3 && cps == &first) ||\r
12419       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
12420     CancelDelayedEvent();\r
12421     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
12422   }\r
12423   cps->initDone = val;\r
12424 }\r
12425 \r
12426 /* Parse feature command from engine */\r
12427 void\r
12428 ParseFeatures(args, cps)\r
12429      char* args;\r
12430      ChessProgramState *cps;  \r
12431 {\r
12432   char *p = args;\r
12433   char *q;\r
12434   int val;\r
12435   char buf[MSG_SIZ];\r
12436 \r
12437   for (;;) {\r
12438     while (*p == ' ') p++;\r
12439     if (*p == NULLCHAR) return;\r
12440 \r
12441     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
12442     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
12443     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
12444     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
12445     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
12446     if (BoolFeature(&p, "reuse", &val, cps)) {\r
12447       /* Engine can disable reuse, but can't enable it if user said no */\r
12448       if (!val) cps->reuse = FALSE;\r
12449       continue;\r
12450     }\r
12451     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
12452     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
12453       if (gameMode == TwoMachinesPlay) {\r
12454         DisplayTwoMachinesTitle();\r
12455       } else {\r
12456         DisplayTitle("");\r
12457       }\r
12458       continue;\r
12459     }\r
12460     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
12461     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
12462     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
12463     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
12464     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
12465     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
12466     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
12467     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
12468     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
12469     if (IntFeature(&p, "done", &val, cps)) {\r
12470       FeatureDone(cps, val);\r
12471       continue;\r
12472     }\r
12473     /* Added by Tord: */\r
12474     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
12475     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
12476     /* End of additions by Tord */\r
12477 \r
12478     /* [HGM] added features: */\r
12479     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
12480     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
12481     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
12482     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
12483     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12484     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
12485     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {\r
12486         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature\r
12487         if(cps->nrOptions >= MAX_OPTIONS) {\r
12488             cps->nrOptions--;\r
12489             sprintf(buf, "%s engine has too many options\n", cps->which);\r
12490             DisplayError(buf, 0);\r
12491         }\r
12492         continue;\r
12493     }\r
12494     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12495     /* End of additions by HGM */\r
12496 \r
12497     /* unknown feature: complain and skip */\r
12498     q = p;\r
12499     while (*q && *q != '=') q++;\r
12500     sprintf(buf, "rejected %.*s\n", q-p, p);\r
12501     SendToProgram(buf, cps);\r
12502     p = q;\r
12503     if (*p == '=') {\r
12504       p++;\r
12505       if (*p == '\"') {\r
12506         p++;\r
12507         while (*p && *p != '\"') p++;\r
12508         if (*p == '\"') p++;\r
12509       } else {\r
12510         while (*p && *p != ' ') p++;\r
12511       }\r
12512     }\r
12513   }\r
12514 \r
12515 }\r
12516 \r
12517 void\r
12518 PeriodicUpdatesEvent(newState)\r
12519      int newState;\r
12520 {\r
12521     if (newState == appData.periodicUpdates)\r
12522       return;\r
12523 \r
12524     appData.periodicUpdates=newState;\r
12525 \r
12526     /* Display type changes, so update it now */\r
12527     DisplayAnalysis();\r
12528 \r
12529     /* Get the ball rolling again... */\r
12530     if (newState) {\r
12531         AnalysisPeriodicEvent(1);\r
12532         StartAnalysisClock();\r
12533     }\r
12534 }\r
12535 \r
12536 void\r
12537 PonderNextMoveEvent(newState)\r
12538      int newState;\r
12539 {\r
12540     if (newState == appData.ponderNextMove) return;\r
12541     if (gameMode == EditPosition) EditPositionDone();\r
12542     if (newState) {\r
12543         SendToProgram("hard\n", &first);\r
12544         if (gameMode == TwoMachinesPlay) {\r
12545             SendToProgram("hard\n", &second);\r
12546         }\r
12547     } else {\r
12548         SendToProgram("easy\n", &first);\r
12549         thinkOutput[0] = NULLCHAR;\r
12550         if (gameMode == TwoMachinesPlay) {\r
12551             SendToProgram("easy\n", &second);\r
12552         }\r
12553     }\r
12554     appData.ponderNextMove = newState;\r
12555 }\r
12556 \r
12557 void\r
12558 NewSettingEvent(option, command, value)\r
12559      char *command;\r
12560      int option, value;\r
12561 {\r
12562     char buf[MSG_SIZ];\r
12563 \r
12564     if (gameMode == EditPosition) EditPositionDone();\r
12565     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);\r
12566     SendToProgram(buf, &first);\r
12567     if (gameMode == TwoMachinesPlay) {\r
12568         SendToProgram(buf, &second);\r
12569     }\r
12570 }\r
12571 \r
12572 void\r
12573 ShowThinkingEvent()\r
12574 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup\r
12575 {\r
12576     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated\r
12577     int newState = appData.showThinking\r
12578         // [HGM] thinking: other features now need thinking output as well\r
12579         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();\r
12580     \r
12581     if (oldState == newState) return;\r
12582     oldState = newState;\r
12583     if (gameMode == EditPosition) EditPositionDone();\r
12584     if (oldState) {\r
12585         SendToProgram("post\n", &first);\r
12586         if (gameMode == TwoMachinesPlay) {\r
12587             SendToProgram("post\n", &second);\r
12588         }\r
12589     } else {\r
12590         SendToProgram("nopost\n", &first);\r
12591         thinkOutput[0] = NULLCHAR;\r
12592         if (gameMode == TwoMachinesPlay) {\r
12593             SendToProgram("nopost\n", &second);\r
12594         }\r
12595     }\r
12596 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!\r
12597 }\r
12598 \r
12599 void\r
12600 AskQuestionEvent(title, question, replyPrefix, which)\r
12601      char *title; char *question; char *replyPrefix; char *which;\r
12602 {\r
12603   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
12604   if (pr == NoProc) return;\r
12605   AskQuestion(title, question, replyPrefix, pr);\r
12606 }\r
12607 \r
12608 void\r
12609 DisplayMove(moveNumber)\r
12610      int moveNumber;\r
12611 {\r
12612     char message[MSG_SIZ];\r
12613     char res[MSG_SIZ];\r
12614     char cpThinkOutput[MSG_SIZ];\r
12615 \r
12616     if(appData.noGUI) return; // [HGM] fast: suppress display of moves\r
12617     \r
12618     if (moveNumber == forwardMostMove - 1 || \r
12619         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12620 \r
12621         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
12622 \r
12623         if (strchr(cpThinkOutput, '\n')) {\r
12624             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
12625         }\r
12626     } else {\r
12627         *cpThinkOutput = NULLCHAR;\r
12628     }\r
12629 \r
12630     /* [AS] Hide thinking from human user */\r
12631     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
12632         *cpThinkOutput = NULLCHAR;\r
12633         if( thinkOutput[0] != NULLCHAR ) {\r
12634             int i;\r
12635 \r
12636             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
12637                 cpThinkOutput[i] = '.';\r
12638             }\r
12639             cpThinkOutput[i] = NULLCHAR;\r
12640             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
12641         }\r
12642     }\r
12643 \r
12644     if (moveNumber == forwardMostMove - 1 &&\r
12645         gameInfo.resultDetails != NULL) {\r
12646         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
12647             sprintf(res, " %s", PGNResult(gameInfo.result));\r
12648         } else {\r
12649             sprintf(res, " {%s} %s",\r
12650                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
12651         }\r
12652     } else {\r
12653         res[0] = NULLCHAR;\r
12654     }\r
12655 \r
12656     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12657         DisplayMessage(res, cpThinkOutput);\r
12658     } else {\r
12659         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
12660                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
12661                 parseList[moveNumber], res);\r
12662         DisplayMessage(message, cpThinkOutput);\r
12663     }\r
12664 }\r
12665 \r
12666 void\r
12667 DisplayAnalysisText(text)\r
12668      char *text;\r
12669 {\r
12670     char buf[MSG_SIZ];\r
12671 \r
12672     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile \r
12673                || appData.icsEngineAnalyze) {\r
12674         sprintf(buf, "Analysis (%s)", first.tidy);\r
12675         AnalysisPopUp(buf, text);\r
12676     }\r
12677 }\r
12678 \r
12679 static int\r
12680 only_one_move(str)\r
12681      char *str;\r
12682 {\r
12683     while (*str && isspace(*str)) ++str;\r
12684     while (*str && !isspace(*str)) ++str;\r
12685     if (!*str) return 1;\r
12686     while (*str && isspace(*str)) ++str;\r
12687     if (!*str) return 1;\r
12688     return 0;\r
12689 }\r
12690 \r
12691 void\r
12692 DisplayAnalysis()\r
12693 {\r
12694     char buf[MSG_SIZ];\r
12695     char lst[MSG_SIZ / 2];\r
12696     double nps;\r
12697     static char *xtra[] = { "", " (--)", " (++)" };\r
12698     int h, m, s, cs;\r
12699   \r
12700     if (programStats.time == 0) {\r
12701         programStats.time = 1;\r
12702     }\r
12703   \r
12704     if (programStats.got_only_move) {\r
12705         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
12706     } else {\r
12707         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
12708 \r
12709         nps = (u64ToDouble(programStats.nodes) /\r
12710              ((double)programStats.time /100.0));\r
12711 \r
12712         cs = programStats.time % 100;\r
12713         s = programStats.time / 100;\r
12714         h = (s / (60*60));\r
12715         s = s - h*60*60;\r
12716         m = (s/60);\r
12717         s = s - m*60;\r
12718 \r
12719         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
12720           if (programStats.move_name[0] != NULLCHAR) {\r
12721             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12722                     programStats.depth,\r
12723                     programStats.nr_moves-programStats.moves_left,\r
12724                     programStats.nr_moves, programStats.move_name,\r
12725                     ((float)programStats.score)/100.0, lst,\r
12726                     only_one_move(lst)?\r
12727                     xtra[programStats.got_fail] : "",\r
12728                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12729           } else {\r
12730             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12731                     programStats.depth,\r
12732                     programStats.nr_moves-programStats.moves_left,\r
12733                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
12734                     lst,\r
12735                     only_one_move(lst)?\r
12736                     xtra[programStats.got_fail] : "",\r
12737                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12738           }\r
12739         } else {\r
12740             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12741                     programStats.depth,\r
12742                     ((float)programStats.score)/100.0,\r
12743                     lst,\r
12744                     only_one_move(lst)?\r
12745                     xtra[programStats.got_fail] : "",\r
12746                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12747         }\r
12748     }\r
12749     DisplayAnalysisText(buf);\r
12750 }\r
12751 \r
12752 void\r
12753 DisplayComment(moveNumber, text)\r
12754      int moveNumber;\r
12755      char *text;\r
12756 {\r
12757     char title[MSG_SIZ];\r
12758     char buf[8000]; // comment can be long!\r
12759     int score, depth;\r
12760 \r
12761     if( appData.autoDisplayComment ) {\r
12762         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12763             strcpy(title, "Comment");\r
12764         } else {\r
12765             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
12766                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
12767                     parseList[moveNumber]);\r
12768         }\r
12769     } else title[0] = 0;\r
12770 \r
12771     // [HGM] PV info: display PV info together with (or as) comment\r
12772     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
12773         if(text == NULL) text = "";                                           \r
12774         score = pvInfoList[moveNumber].score;\r
12775         sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
12776                               depth, (pvInfoList[moveNumber].time+50)/100, text);\r
12777         CommentPopUp(title, buf);\r
12778     } else\r
12779     if (text != NULL)\r
12780         CommentPopUp(title, text);\r
12781 }\r
12782 \r
12783 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
12784  * might be busy thinking or pondering.  It can be omitted if your\r
12785  * gnuchess is configured to stop thinking immediately on any user\r
12786  * input.  However, that gnuchess feature depends on the FIONREAD\r
12787  * ioctl, which does not work properly on some flavors of Unix.\r
12788  */\r
12789 void\r
12790 Attention(cps)\r
12791      ChessProgramState *cps;\r
12792 {\r
12793 #if ATTENTION\r
12794     if (!cps->useSigint) return;\r
12795     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
12796     switch (gameMode) {\r
12797       case MachinePlaysWhite:\r
12798       case MachinePlaysBlack:\r
12799       case TwoMachinesPlay:\r
12800       case IcsPlayingWhite:\r
12801       case IcsPlayingBlack:\r
12802       case AnalyzeMode:\r
12803       case AnalyzeFile:\r
12804         /* Skip if we know it isn't thinking */\r
12805         if (!cps->maybeThinking) return;\r
12806         if (appData.debugMode)\r
12807           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
12808         InterruptChildProcess(cps->pr);\r
12809         cps->maybeThinking = FALSE;\r
12810         break;\r
12811       default:\r
12812         break;\r
12813     }\r
12814 #endif /*ATTENTION*/\r
12815 }\r
12816 \r
12817 int\r
12818 CheckFlags()\r
12819 {\r
12820     if (whiteTimeRemaining <= 0) {\r
12821         if (!whiteFlag) {\r
12822             whiteFlag = TRUE;\r
12823             if (appData.icsActive) {\r
12824                 if (appData.autoCallFlag &&\r
12825                     gameMode == IcsPlayingBlack && !blackFlag) {\r
12826                   SendToICS(ics_prefix);\r
12827                   SendToICS("flag\n");\r
12828                 }\r
12829             } else {\r
12830                 if (blackFlag) {\r
12831                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12832                 } else {\r
12833                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));\r
12834                     if (appData.autoCallFlag) {\r
12835                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
12836                         return TRUE;\r
12837                     }\r
12838                 }\r
12839             }\r
12840         }\r
12841     }\r
12842     if (blackTimeRemaining <= 0) {\r
12843         if (!blackFlag) {\r
12844             blackFlag = TRUE;\r
12845             if (appData.icsActive) {\r
12846                 if (appData.autoCallFlag &&\r
12847                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
12848                   SendToICS(ics_prefix);\r
12849                   SendToICS("flag\n");\r
12850                 }\r
12851             } else {\r
12852                 if (whiteFlag) {\r
12853                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12854                 } else {\r
12855                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));\r
12856                     if (appData.autoCallFlag) {\r
12857                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
12858                         return TRUE;\r
12859                     }\r
12860                 }\r
12861             }\r
12862         }\r
12863     }\r
12864     return FALSE;\r
12865 }\r
12866 \r
12867 void\r
12868 CheckTimeControl()\r
12869 {\r
12870     if (!appData.clockMode || appData.icsActive ||\r
12871         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
12872 \r
12873     /*\r
12874      * add time to clocks when time control is achieved ([HGM] now also used for increment)\r
12875      */\r
12876     if ( !WhiteOnMove(forwardMostMove) )\r
12877         /* White made time control */\r
12878         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12879         /* [HGM] time odds: correct new time quota for time odds! */\r
12880                                             / WhitePlayer()->timeOdds;\r
12881       else\r
12882         /* Black made time control */\r
12883         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12884                                             / WhitePlayer()->other->timeOdds;\r
12885 }\r
12886 \r
12887 void\r
12888 DisplayBothClocks()\r
12889 {\r
12890     int wom = gameMode == EditPosition ?\r
12891       !blackPlaysFirst : WhiteOnMove(currentMove);\r
12892     DisplayWhiteClock(whiteTimeRemaining, wom);\r
12893     DisplayBlackClock(blackTimeRemaining, !wom);\r
12894 }\r
12895 \r
12896 \r
12897 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
12898    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
12899    to use other calls if you don't.  Clocks will be less accurate if\r
12900    you have neither ftime nor gettimeofday.\r
12901 */\r
12902 \r
12903 /* VS 2008 requires the #include outside of the function */\r
12904 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME\r
12905 #include <sys/timeb.h>\r
12906 #endif\r
12907 \r
12908 /* Get the current time as a TimeMark */\r
12909 void\r
12910 GetTimeMark(tm)\r
12911      TimeMark *tm;\r
12912 {\r
12913 #if HAVE_GETTIMEOFDAY\r
12914 \r
12915     struct timeval timeVal;\r
12916     struct timezone timeZone;\r
12917 \r
12918     gettimeofday(&timeVal, &timeZone);\r
12919     tm->sec = (long) timeVal.tv_sec; \r
12920     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
12921 \r
12922 #else /*!HAVE_GETTIMEOFDAY*/\r
12923 #if HAVE_FTIME\r
12924 \r
12925 // include <sys/timeb.h> / moved to just above start of function\r
12926     struct timeb timeB;\r
12927 \r
12928     ftime(&timeB);\r
12929     tm->sec = (long) timeB.time;\r
12930     tm->ms = (int) timeB.millitm;\r
12931 \r
12932 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
12933     tm->sec = (long) time(NULL);\r
12934     tm->ms = 0;\r
12935 #endif\r
12936 #endif\r
12937 }\r
12938 \r
12939 /* Return the difference in milliseconds between two\r
12940    time marks.  We assume the difference will fit in a long!\r
12941 */\r
12942 long\r
12943 SubtractTimeMarks(tm2, tm1)\r
12944      TimeMark *tm2, *tm1;\r
12945 {\r
12946     return 1000L*(tm2->sec - tm1->sec) +\r
12947            (long) (tm2->ms - tm1->ms);\r
12948 }\r
12949 \r
12950 \r
12951 /*\r
12952  * Code to manage the game clocks.\r
12953  *\r
12954  * In tournament play, black starts the clock and then white makes a move.\r
12955  * We give the human user a slight advantage if he is playing white---the\r
12956  * clocks don't run until he makes his first move, so it takes zero time.\r
12957  * Also, we don't account for network lag, so we could get out of sync\r
12958  * with GNU Chess's clock -- but then, referees are always right.  \r
12959  */\r
12960 \r
12961 static TimeMark tickStartTM;\r
12962 static long intendedTickLength;\r
12963 \r
12964 long\r
12965 NextTickLength(timeRemaining)\r
12966      long timeRemaining;\r
12967 {\r
12968     long nominalTickLength, nextTickLength;\r
12969 \r
12970     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
12971       nominalTickLength = 100L;\r
12972     else\r
12973       nominalTickLength = 1000L;\r
12974     nextTickLength = timeRemaining % nominalTickLength;\r
12975     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
12976 \r
12977     return nextTickLength;\r
12978 }\r
12979 \r
12980 /* Adjust clock one minute up or down */\r
12981 void\r
12982 AdjustClock(Boolean which, int dir)\r
12983 {\r
12984     if(which) blackTimeRemaining += 60000*dir;\r
12985     else      whiteTimeRemaining += 60000*dir;\r
12986     DisplayBothClocks();\r
12987 }\r
12988 \r
12989 /* Stop clocks and reset to a fresh time control */\r
12990 void\r
12991 ResetClocks() \r
12992 {\r
12993     (void) StopClockTimer();\r
12994     if (appData.icsActive) {\r
12995         whiteTimeRemaining = blackTimeRemaining = 0;\r
12996     } else { /* [HGM] correct new time quote for time odds */\r
12997         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
12998         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
12999     }\r
13000     if (whiteFlag || blackFlag) {\r
13001         DisplayTitle("");\r
13002         whiteFlag = blackFlag = FALSE;\r
13003     }\r
13004     DisplayBothClocks();\r
13005 }\r
13006 \r
13007 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
13008 \r
13009 /* Decrement running clock by amount of time that has passed */\r
13010 void\r
13011 DecrementClocks()\r
13012 {\r
13013     long timeRemaining;\r
13014     long lastTickLength, fudge;\r
13015     TimeMark now;\r
13016 \r
13017     if (!appData.clockMode) return;\r
13018     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
13019         \r
13020     GetTimeMark(&now);\r
13021 \r
13022     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13023 \r
13024     /* Fudge if we woke up a little too soon */\r
13025     fudge = intendedTickLength - lastTickLength;\r
13026     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
13027 \r
13028     if (WhiteOnMove(forwardMostMove)) {\r
13029         if(whiteNPS >= 0) lastTickLength = 0;\r
13030         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
13031         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
13032                           WhiteOnMove(currentMove));\r
13033     } else {\r
13034         if(blackNPS >= 0) lastTickLength = 0;\r
13035         timeRemaining = blackTimeRemaining -= lastTickLength;\r
13036         DisplayBlackClock(blackTimeRemaining - fudge,\r
13037                           !WhiteOnMove(currentMove));\r
13038     }\r
13039 \r
13040     if (CheckFlags()) return;\r
13041         \r
13042     tickStartTM = now;\r
13043     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
13044     StartClockTimer(intendedTickLength);\r
13045 \r
13046     /* if the time remaining has fallen below the alarm threshold, sound the\r
13047      * alarm. if the alarm has sounded and (due to a takeback or time control\r
13048      * with increment) the time remaining has increased to a level above the\r
13049      * threshold, reset the alarm so it can sound again. \r
13050      */\r
13051     \r
13052     if (appData.icsActive && appData.icsAlarm) {\r
13053 \r
13054         /* make sure we are dealing with the user's clock */\r
13055         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
13056                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
13057            )) return;\r
13058 \r
13059         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
13060             alarmSounded = FALSE;\r
13061         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
13062             PlayAlarmSound();\r
13063             alarmSounded = TRUE;\r
13064         }\r
13065     }\r
13066 }\r
13067 \r
13068 \r
13069 /* A player has just moved, so stop the previously running\r
13070    clock and (if in clock mode) start the other one.\r
13071    We redisplay both clocks in case we're in ICS mode, because\r
13072    ICS gives us an update to both clocks after every move.\r
13073    Note that this routine is called *after* forwardMostMove\r
13074    is updated, so the last fractional tick must be subtracted\r
13075    from the color that is *not* on move now.\r
13076 */\r
13077 void\r
13078 SwitchClocks()\r
13079 {\r
13080     long lastTickLength;\r
13081     TimeMark now;\r
13082     int flagged = FALSE;\r
13083 \r
13084     GetTimeMark(&now);\r
13085 \r
13086     if (StopClockTimer() && appData.clockMode) {\r
13087         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13088         if (WhiteOnMove(forwardMostMove)) {\r
13089             if(blackNPS >= 0) lastTickLength = 0;\r
13090             blackTimeRemaining -= lastTickLength;\r
13091            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13092 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13093                  pvInfoList[forwardMostMove-1].time =               // use GUI time\r
13094                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
13095         } else {\r
13096            if(whiteNPS >= 0) lastTickLength = 0;\r
13097            whiteTimeRemaining -= lastTickLength;\r
13098            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13099 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13100                  pvInfoList[forwardMostMove-1].time = \r
13101                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
13102         }\r
13103         flagged = CheckFlags();\r
13104     }\r
13105     CheckTimeControl();\r
13106 \r
13107     if (flagged || !appData.clockMode) return;\r
13108 \r
13109     switch (gameMode) {\r
13110       case MachinePlaysBlack:\r
13111       case MachinePlaysWhite:\r
13112       case BeginningOfGame:\r
13113         if (pausing) return;\r
13114         break;\r
13115 \r
13116       case EditGame:\r
13117       case PlayFromGameFile:\r
13118       case IcsExamining:\r
13119         return;\r
13120 \r
13121       default:\r
13122         break;\r
13123     }\r
13124 \r
13125     tickStartTM = now;\r
13126     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13127       whiteTimeRemaining : blackTimeRemaining);\r
13128     StartClockTimer(intendedTickLength);\r
13129 }\r
13130         \r
13131 \r
13132 /* Stop both clocks */\r
13133 void\r
13134 StopClocks()\r
13135 {       \r
13136     long lastTickLength;\r
13137     TimeMark now;\r
13138 \r
13139     if (!StopClockTimer()) return;\r
13140     if (!appData.clockMode) return;\r
13141 \r
13142     GetTimeMark(&now);\r
13143 \r
13144     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13145     if (WhiteOnMove(forwardMostMove)) {\r
13146         if(whiteNPS >= 0) lastTickLength = 0;\r
13147         whiteTimeRemaining -= lastTickLength;\r
13148         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
13149     } else {\r
13150         if(blackNPS >= 0) lastTickLength = 0;\r
13151         blackTimeRemaining -= lastTickLength;\r
13152         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
13153     }\r
13154     CheckFlags();\r
13155 }\r
13156         \r
13157 /* Start clock of player on move.  Time may have been reset, so\r
13158    if clock is already running, stop and restart it. */\r
13159 void\r
13160 StartClocks()\r
13161 {\r
13162     (void) StopClockTimer(); /* in case it was running already */\r
13163     DisplayBothClocks();\r
13164     if (CheckFlags()) return;\r
13165 \r
13166     if (!appData.clockMode) return;\r
13167     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
13168 \r
13169     GetTimeMark(&tickStartTM);\r
13170     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13171       whiteTimeRemaining : blackTimeRemaining);\r
13172 \r
13173    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
13174     whiteNPS = blackNPS = -1; \r
13175     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
13176        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
13177         whiteNPS = first.nps;\r
13178     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
13179        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
13180         blackNPS = first.nps;\r
13181     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
13182         whiteNPS = second.nps;\r
13183     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
13184         blackNPS = second.nps;\r
13185     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
13186 \r
13187     StartClockTimer(intendedTickLength);\r
13188 }\r
13189 \r
13190 char *\r
13191 TimeString(ms)\r
13192      long ms;\r
13193 {\r
13194     long second, minute, hour, day;\r
13195     char *sign = "";\r
13196     static char buf[32];\r
13197     \r
13198     if (ms > 0 && ms <= 9900) {\r
13199       /* convert milliseconds to tenths, rounding up */\r
13200       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
13201 \r
13202       sprintf(buf, " %03.1f ", tenths/10.0);\r
13203       return buf;\r
13204     }\r
13205 \r
13206     /* convert milliseconds to seconds, rounding up */\r
13207     /* use floating point to avoid strangeness of integer division\r
13208        with negative dividends on many machines */\r
13209     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
13210 \r
13211     if (second < 0) {\r
13212         sign = "-";\r
13213         second = -second;\r
13214     }\r
13215     \r
13216     day = second / (60 * 60 * 24);\r
13217     second = second % (60 * 60 * 24);\r
13218     hour = second / (60 * 60);\r
13219     second = second % (60 * 60);\r
13220     minute = second / 60;\r
13221     second = second % 60;\r
13222     \r
13223     if (day > 0)\r
13224       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
13225               sign, day, hour, minute, second);\r
13226     else if (hour > 0)\r
13227       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
13228     else\r
13229       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
13230     \r
13231     return buf;\r
13232 }\r
13233 \r
13234 \r
13235 /*\r
13236  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
13237  */\r
13238 char *\r
13239 StrStr(string, match)\r
13240      char *string, *match;\r
13241 {\r
13242     int i, length;\r
13243     \r
13244     length = strlen(match);\r
13245     \r
13246     for (i = strlen(string) - length; i >= 0; i--, string++)\r
13247       if (!strncmp(match, string, length))\r
13248         return string;\r
13249     \r
13250     return NULL;\r
13251 }\r
13252 \r
13253 char *\r
13254 StrCaseStr(string, match)\r
13255      char *string, *match;\r
13256 {\r
13257     int i, j, length;\r
13258     \r
13259     length = strlen(match);\r
13260     \r
13261     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
13262         for (j = 0; j < length; j++) {\r
13263             if (ToLower(match[j]) != ToLower(string[j]))\r
13264               break;\r
13265         }\r
13266         if (j == length) return string;\r
13267     }\r
13268 \r
13269     return NULL;\r
13270 }\r
13271 \r
13272 #ifndef _amigados\r
13273 int\r
13274 StrCaseCmp(s1, s2)\r
13275      char *s1, *s2;\r
13276 {\r
13277     char c1, c2;\r
13278     \r
13279     for (;;) {\r
13280         c1 = ToLower(*s1++);\r
13281         c2 = ToLower(*s2++);\r
13282         if (c1 > c2) return 1;\r
13283         if (c1 < c2) return -1;\r
13284         if (c1 == NULLCHAR) return 0;\r
13285     }\r
13286 }\r
13287 \r
13288 \r
13289 int\r
13290 ToLower(c)\r
13291      int c;\r
13292 {\r
13293     return isupper(c) ? tolower(c) : c;\r
13294 }\r
13295 \r
13296 \r
13297 int\r
13298 ToUpper(c)\r
13299      int c;\r
13300 {\r
13301     return islower(c) ? toupper(c) : c;\r
13302 }\r
13303 #endif /* !_amigados    */\r
13304 \r
13305 char *\r
13306 StrSave(s)\r
13307      char *s;\r
13308 {\r
13309     char *ret;\r
13310 \r
13311     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
13312         strcpy(ret, s);\r
13313     }\r
13314     return ret;\r
13315 }\r
13316 \r
13317 char *\r
13318 StrSavePtr(s, savePtr)\r
13319      char *s, **savePtr;\r
13320 {\r
13321     if (*savePtr) {\r
13322         free(*savePtr);\r
13323     }\r
13324     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
13325         strcpy(*savePtr, s);\r
13326     }\r
13327     return(*savePtr);\r
13328 }\r
13329 \r
13330 char *\r
13331 PGNDate()\r
13332 {\r
13333     time_t clock;\r
13334     struct tm *tm;\r
13335     char buf[MSG_SIZ];\r
13336 \r
13337     clock = time((time_t *)NULL);\r
13338     tm = localtime(&clock);\r
13339     sprintf(buf, "%04d.%02d.%02d",\r
13340             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
13341     return StrSave(buf);\r
13342 }\r
13343 \r
13344 \r
13345 char *\r
13346 PositionToFEN(move, useFEN960)\r
13347      int move;\r
13348      int useFEN960;\r
13349 {\r
13350     int i, j, fromX, fromY, toX, toY;\r
13351     int whiteToPlay;\r
13352     char buf[128];\r
13353     char *p, *q;\r
13354     int emptycount;\r
13355     ChessSquare piece;\r
13356 \r
13357     whiteToPlay = (gameMode == EditPosition) ?\r
13358       !blackPlaysFirst : (move % 2 == 0);\r
13359     p = buf;\r
13360 \r
13361     /* Piece placement data */\r
13362     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13363         emptycount = 0;\r
13364         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
13365             if (boards[move][i][j] == EmptySquare) {\r
13366                 emptycount++;\r
13367             } else { ChessSquare piece = boards[move][i][j];\r
13368                 if (emptycount > 0) {\r
13369                     if(emptycount<10) /* [HGM] can be >= 10 */\r
13370                         *p++ = '0' + emptycount;\r
13371                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13372                     emptycount = 0;\r
13373                 }\r
13374                 if(PieceToChar(piece) == '+') {\r
13375                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
13376                     *p++ = '+';\r
13377                     piece = (ChessSquare)(DEMOTED piece);\r
13378                 } \r
13379                 *p++ = PieceToChar(piece);\r
13380                 if(p[-1] == '~') {\r
13381                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
13382                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
13383                     *p++ = '~';\r
13384                 }\r
13385             }\r
13386         }\r
13387         if (emptycount > 0) {\r
13388             if(emptycount<10) /* [HGM] can be >= 10 */\r
13389                 *p++ = '0' + emptycount;\r
13390             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13391             emptycount = 0;\r
13392         }\r
13393         *p++ = '/';\r
13394     }\r
13395     *(p - 1) = ' ';\r
13396 \r
13397     /* [HGM] print Crazyhouse or Shogi holdings */\r
13398     if( gameInfo.holdingsWidth ) {\r
13399         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
13400         q = p;\r
13401         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
13402             piece = boards[move][i][BOARD_WIDTH-1];\r
13403             if( piece != EmptySquare )\r
13404               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
13405                   *p++ = PieceToChar(piece);\r
13406         }\r
13407         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
13408             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
13409             if( piece != EmptySquare )\r
13410               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
13411                   *p++ = PieceToChar(piece);\r
13412         }\r
13413 \r
13414         if( q == p ) *p++ = '-';\r
13415         *p++ = ']';\r
13416         *p++ = ' ';\r
13417     }\r
13418 \r
13419     /* Active color */\r
13420     *p++ = whiteToPlay ? 'w' : 'b';\r
13421     *p++ = ' ';\r
13422 \r
13423   if(nrCastlingRights) {\r
13424      q = p;\r
13425      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
13426        /* [HGM] write directly from rights */\r
13427            if(castlingRights[move][2] >= 0 &&\r
13428               castlingRights[move][0] >= 0   )\r
13429                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
13430            if(castlingRights[move][2] >= 0 &&\r
13431               castlingRights[move][1] >= 0   )\r
13432                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
13433            if(castlingRights[move][5] >= 0 &&\r
13434               castlingRights[move][3] >= 0   )\r
13435                 *p++ = castlingRights[move][3] + AAA;\r
13436            if(castlingRights[move][5] >= 0 &&\r
13437               castlingRights[move][4] >= 0   )\r
13438                 *p++ = castlingRights[move][4] + AAA;\r
13439      } else {\r
13440 \r
13441         /* [HGM] write true castling rights */\r
13442         if( nrCastlingRights == 6 ) {\r
13443             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
13444                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
13445             if(castlingRights[move][1] == BOARD_LEFT &&\r
13446                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
13447             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
13448                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
13449             if(castlingRights[move][4] == BOARD_LEFT &&\r
13450                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
13451         }\r
13452      }\r
13453      if (q == p) *p++ = '-'; /* No castling rights */\r
13454      *p++ = ' ';\r
13455   }\r
13456 \r
13457   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13458      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13459     /* En passant target square */\r
13460     if (move > backwardMostMove) {\r
13461         fromX = moveList[move - 1][0] - AAA;\r
13462         fromY = moveList[move - 1][1] - ONE;\r
13463         toX = moveList[move - 1][2] - AAA;\r
13464         toY = moveList[move - 1][3] - ONE;\r
13465         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
13466             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
13467             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
13468             fromX == toX) {\r
13469             /* 2-square pawn move just happened */\r
13470             *p++ = toX + AAA;\r
13471             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
13472         } else {\r
13473             *p++ = '-';\r
13474         }\r
13475     } else {\r
13476         *p++ = '-';\r
13477     }\r
13478     *p++ = ' ';\r
13479   }\r
13480 \r
13481     /* [HGM] find reversible plies */\r
13482     {   int i = 0, j=move;\r
13483 \r
13484         if (appData.debugMode) { int k;\r
13485             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
13486             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
13487                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
13488 \r
13489         }\r
13490 \r
13491         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
13492         if( j == backwardMostMove ) i += initialRulePlies;\r
13493         sprintf(p, "%d ", i);\r
13494         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
13495     }\r
13496     /* Fullmove number */\r
13497     sprintf(p, "%d", (move / 2) + 1);\r
13498     \r
13499     return StrSave(buf);\r
13500 }\r
13501 \r
13502 Boolean\r
13503 ParseFEN(board, blackPlaysFirst, fen)\r
13504     Board board;\r
13505      int *blackPlaysFirst;\r
13506      char *fen;\r
13507 {\r
13508     int i, j;\r
13509     char *p;\r
13510     int emptycount;\r
13511     ChessSquare piece;\r
13512 \r
13513     p = fen;\r
13514 \r
13515     /* [HGM] by default clear Crazyhouse holdings, if present */\r
13516     if(gameInfo.holdingsWidth) {\r
13517        for(i=0; i<BOARD_HEIGHT; i++) {\r
13518            board[i][0]             = EmptySquare; /* black holdings */\r
13519            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
13520            board[i][1]             = (ChessSquare) 0; /* black counts */\r
13521            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
13522        }\r
13523     }\r
13524 \r
13525     /* Piece placement data */\r
13526     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13527         j = 0;\r
13528         for (;;) {\r
13529             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
13530                 if (*p == '/') p++;\r
13531                 emptycount = gameInfo.boardWidth - j;\r
13532                 while (emptycount--)\r
13533                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13534                 break;\r
13535 #if(BOARD_SIZE >= 10)\r
13536             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
13537                 p++; emptycount=10;\r
13538                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13539                 while (emptycount--)\r
13540                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13541 #endif\r
13542             } else if (isdigit(*p)) {\r
13543                 emptycount = *p++ - '0';\r
13544                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
13545                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13546                 while (emptycount--)\r
13547                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13548             } else if (*p == '+' || isalpha(*p)) {\r
13549                 if (j >= gameInfo.boardWidth) return FALSE;\r
13550                 if(*p=='+') {\r
13551                     piece = CharToPiece(*++p);\r
13552                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
13553                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
13554                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
13555                 } else piece = CharToPiece(*p++);\r
13556 \r
13557                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
13558                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
13559                     piece = (ChessSquare) (PROMOTED piece);\r
13560                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
13561                     p++;\r
13562                 }\r
13563                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
13564             } else {\r
13565                 return FALSE;\r
13566             }\r
13567         }\r
13568     }\r
13569     while (*p == '/' || *p == ' ') p++;\r
13570 \r
13571     /* [HGM] look for Crazyhouse holdings here */\r
13572     while(*p==' ') p++;\r
13573     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
13574         if(*p == '[') p++;\r
13575         if(*p == '-' ) *p++; /* empty holdings */ else {\r
13576             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
13577             /* if we would allow FEN reading to set board size, we would   */\r
13578             /* have to add holdings and shift the board read so far here   */\r
13579             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
13580                 *p++;\r
13581                 if((int) piece >= (int) BlackPawn ) {\r
13582                     i = (int)piece - (int)BlackPawn;\r
13583                     i = PieceToNumber((ChessSquare)i);\r
13584                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13585                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
13586                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
13587                 } else {\r
13588                     i = (int)piece - (int)WhitePawn;\r
13589                     i = PieceToNumber((ChessSquare)i);\r
13590                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13591                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
13592                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
13593                 }\r
13594             }\r
13595         }\r
13596         if(*p == ']') *p++;\r
13597     }\r
13598 \r
13599     while(*p == ' ') p++;\r
13600 \r
13601     /* Active color */\r
13602     switch (*p++) {\r
13603       case 'w':\r
13604         *blackPlaysFirst = FALSE;\r
13605         break;\r
13606       case 'b': \r
13607         *blackPlaysFirst = TRUE;\r
13608         break;\r
13609       default:\r
13610         return FALSE;\r
13611     }\r
13612 \r
13613     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
13614     /* return the extra info in global variiables             */\r
13615 \r
13616     /* set defaults in case FEN is incomplete */\r
13617     FENepStatus = EP_UNKNOWN;\r
13618     for(i=0; i<nrCastlingRights; i++ ) {\r
13619         FENcastlingRights[i] =\r
13620             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
13621     }   /* assume possible unless obviously impossible */\r
13622     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
13623     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
13624     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
13625     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
13626     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
13627     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
13628     FENrulePlies = 0;\r
13629 \r
13630     while(*p==' ') p++;\r
13631     if(nrCastlingRights) {\r
13632       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
13633           /* castling indicator present, so default becomes no castlings */\r
13634           for(i=0; i<nrCastlingRights; i++ ) {\r
13635                  FENcastlingRights[i] = -1;\r
13636           }\r
13637       }\r
13638       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
13639              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
13640              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
13641              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
13642         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
13643 \r
13644         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
13645             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
13646             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
13647         }\r
13648         switch(c) {\r
13649           case'K':\r
13650               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
13651               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
13652               FENcastlingRights[2] = whiteKingFile;\r
13653               break;\r
13654           case'Q':\r
13655               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
13656               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
13657               FENcastlingRights[2] = whiteKingFile;\r
13658               break;\r
13659           case'k':\r
13660               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
13661               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
13662               FENcastlingRights[5] = blackKingFile;\r
13663               break;\r
13664           case'q':\r
13665               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
13666               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
13667               FENcastlingRights[5] = blackKingFile;\r
13668           case '-':\r
13669               break;\r
13670           default: /* FRC castlings */\r
13671               if(c >= 'a') { /* black rights */\r
13672                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13673                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
13674                   if(i == BOARD_RGHT) break;\r
13675                   FENcastlingRights[5] = i;\r
13676                   c -= AAA;\r
13677                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
13678                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
13679                   if(c > i)\r
13680                       FENcastlingRights[3] = c;\r
13681                   else\r
13682                       FENcastlingRights[4] = c;\r
13683               } else { /* white rights */\r
13684                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13685                     if(board[0][i] == WhiteKing) break;\r
13686                   if(i == BOARD_RGHT) break;\r
13687                   FENcastlingRights[2] = i;\r
13688                   c -= AAA - 'a' + 'A';\r
13689                   if(board[0][c] >= WhiteKing) break;\r
13690                   if(c > i)\r
13691                       FENcastlingRights[0] = c;\r
13692                   else\r
13693                       FENcastlingRights[1] = c;\r
13694               }\r
13695         }\r
13696       }\r
13697     if (appData.debugMode) {\r
13698         fprintf(debugFP, "FEN castling rights:");\r
13699         for(i=0; i<nrCastlingRights; i++)\r
13700         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
13701         fprintf(debugFP, "\n");\r
13702     }\r
13703 \r
13704       while(*p==' ') p++;\r
13705     }\r
13706 \r
13707     /* read e.p. field in games that know e.p. capture */\r
13708     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13709        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13710       if(*p=='-') {\r
13711         p++; FENepStatus = EP_NONE;\r
13712       } else {\r
13713          char c = *p++ - AAA;\r
13714 \r
13715          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
13716          if(*p >= '0' && *p <='9') *p++;\r
13717          FENepStatus = c;\r
13718       }\r
13719     }\r
13720 \r
13721 \r
13722     if(sscanf(p, "%d", &i) == 1) {\r
13723         FENrulePlies = i; /* 50-move ply counter */\r
13724         /* (The move number is still ignored)    */\r
13725     }\r
13726 \r
13727     return TRUE;\r
13728 }\r
13729       \r
13730 void\r
13731 EditPositionPasteFEN(char *fen)\r
13732 {\r
13733   if (fen != NULL) {\r
13734     Board initial_position;\r
13735 \r
13736     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
13737       DisplayError(_("Bad FEN position in clipboard"), 0);\r
13738       return ;\r
13739     } else {\r
13740       int savedBlackPlaysFirst = blackPlaysFirst;\r
13741       EditPositionEvent();\r
13742       blackPlaysFirst = savedBlackPlaysFirst;\r
13743       CopyBoard(boards[0], initial_position);\r
13744           /* [HGM] copy FEN attributes as well */\r
13745           {   int i;\r
13746               initialRulePlies = FENrulePlies;\r
13747               epStatus[0] = FENepStatus;\r
13748               for( i=0; i<nrCastlingRights; i++ )\r
13749                   castlingRights[0][i] = FENcastlingRights[i];\r
13750           }\r
13751       EditPositionDone();\r
13752       DisplayBothClocks();\r
13753       DrawPosition(FALSE, boards[currentMove]);\r
13754     }\r
13755   }\r
13756 }\r