fixed adjudicating probelm in Shantranj
[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       nrCastlingRights = 8;                 /* add rights for second King */\r
4631       castlingRights[0][6] = initialRights[2] = 5;\r
4632       castlingRights[0][7] = initialRights[5] = 5;\r
4633       castlingRank[6] = 0;\r
4634       castlingRank[7] = BOARD_HEIGHT-1;\r
4635       break;\r
4636     case VariantCapaRandom:\r
4637       shuffleOpenings = TRUE;\r
4638     case VariantCapablanca:\r
4639       pieces = CapablancaArray;\r
4640       gameInfo.boardWidth = 10;\r
4641       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4642       break;\r
4643     case VariantGothic:\r
4644       pieces = GothicArray;\r
4645       gameInfo.boardWidth = 10;\r
4646       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4647       break;\r
4648     case VariantJanus:\r
4649       pieces = JanusArray;\r
4650       gameInfo.boardWidth = 10;\r
4651       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
4652       nrCastlingRights = 6;\r
4653         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4654         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4655         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;\r
4656         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4657         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4658         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;\r
4659       break;\r
4660     case VariantFalcon:\r
4661       pieces = FalconArray;\r
4662       gameInfo.boardWidth = 10;\r
4663       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4664       break;\r
4665     case VariantXiangqi:\r
4666       pieces = XiangqiArray;\r
4667       gameInfo.boardWidth  = 9;\r
4668       gameInfo.boardHeight = 10;\r
4669       nrCastlingRights = 0;\r
4670       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4671       break;\r
4672     case VariantShogi:\r
4673       pieces = ShogiArray;\r
4674       gameInfo.boardWidth  = 9;\r
4675       gameInfo.boardHeight = 9;\r
4676       gameInfo.holdingsSize = 7;\r
4677       nrCastlingRights = 0;\r
4678       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4679       break;\r
4680     case VariantCourier:\r
4681       pieces = CourierArray;\r
4682       gameInfo.boardWidth  = 12;\r
4683       nrCastlingRights = 0;\r
4684       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4685       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4686       break;\r
4687     case VariantKnightmate:\r
4688       pieces = KnightmateArray;\r
4689       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4690       break;\r
4691     case VariantFairy:\r
4692       pieces = fairyArray;\r
4693       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4694       break;\r
4695     case VariantGreat:\r
4696       pieces = GreatArray;\r
4697       gameInfo.boardWidth = 10;\r
4698       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");\r
4699       gameInfo.holdingsSize = 8;\r
4700       break;\r
4701     case VariantSuper:\r
4702       pieces = FIDEArray;\r
4703       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
4704       gameInfo.holdingsSize = 8;\r
4705       startedFromSetupPosition = TRUE;\r
4706       break;\r
4707     case VariantCrazyhouse:\r
4708     case VariantBughouse:\r
4709       pieces = FIDEArray;\r
4710       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4711       gameInfo.holdingsSize = 5;\r
4712       break;\r
4713     case VariantWildCastle:\r
4714       pieces = FIDEArray;\r
4715       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4716       shuffleOpenings = 1;\r
4717       break;\r
4718     case VariantNoCastle:\r
4719       pieces = FIDEArray;\r
4720       nrCastlingRights = 0;\r
4721       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4722       /* !!?unconstrained back-rank shuffle */\r
4723       shuffleOpenings = 1;\r
4724       break;\r
4725     }\r
4726 \r
4727     overrule = 0;\r
4728     if(appData.NrFiles >= 0) {\r
4729         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4730         gameInfo.boardWidth = appData.NrFiles;\r
4731     }\r
4732     if(appData.NrRanks >= 0) {\r
4733         gameInfo.boardHeight = appData.NrRanks;\r
4734     }\r
4735     if(appData.holdingsSize >= 0) {\r
4736         i = appData.holdingsSize;\r
4737         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4738         gameInfo.holdingsSize = i;\r
4739     }\r
4740     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4741     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4742         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);\r
4743 \r
4744     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4745     if(pawnRow < 1) pawnRow = 1;\r
4746 \r
4747     /* User pieceToChar list overrules defaults */\r
4748     if(appData.pieceToCharTable != NULL)\r
4749         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4750 \r
4751     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4752 \r
4753         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4754             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4755         for( i=0; i<BOARD_HEIGHT; i++ )\r
4756             initialPosition[i][j] = s;\r
4757 \r
4758         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4759         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4760         initialPosition[pawnRow][j] = WhitePawn;\r
4761         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4762         if(gameInfo.variant == VariantXiangqi) {\r
4763             if(j&1) {\r
4764                 initialPosition[pawnRow][j] = \r
4765                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4766                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4767                    initialPosition[2][j] = WhiteCannon;\r
4768                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4769                 }\r
4770             }\r
4771         }\r
4772         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4773     }\r
4774     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4775 \r
4776             j=BOARD_LEFT+1;\r
4777             initialPosition[1][j] = WhiteBishop;\r
4778             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4779             j=BOARD_RGHT-2;\r
4780             initialPosition[1][j] = WhiteRook;\r
4781             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4782     }\r
4783 \r
4784     if( nrCastlingRights == -1) {\r
4785         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4786         /*       This sets default castling rights from none to normal corners   */\r
4787         /* Variants with other castling rights must set them themselves above    */\r
4788         nrCastlingRights = 6;\r
4789        \r
4790         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4791         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4792         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4793         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4794         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4795         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4796      }\r
4797 \r
4798      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
4799      if(gameInfo.variant == VariantGreat) { // promotion commoners\r
4800         initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-1] = WhiteMan;\r
4801         initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-2] = 9;\r
4802         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;\r
4803         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;\r
4804      }\r
4805 #if 0\r
4806     if(gameInfo.variant == VariantFischeRandom) {\r
4807       if( appData.defaultFrcPosition < 0 ) {\r
4808         ShuffleFRC( initialPosition );\r
4809       }\r
4810       else {\r
4811         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4812       }\r
4813       startedFromSetupPosition = TRUE;\r
4814     } else \r
4815 #else\r
4816   if (appData.debugMode) {\r
4817     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
4818   }\r
4819     if(shuffleOpenings) {\r
4820         SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
4821         startedFromSetupPosition = TRUE;\r
4822     }\r
4823 #endif\r
4824     if(startedFromPositionFile) {\r
4825       /* [HGM] loadPos: use PositionFile for every new game */\r
4826       CopyBoard(initialPosition, filePosition);\r
4827       for(i=0; i<nrCastlingRights; i++)\r
4828           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4829       startedFromSetupPosition = TRUE;\r
4830     }\r
4831 \r
4832     CopyBoard(boards[0], initialPosition);\r
4833 \r
4834     if(oldx != gameInfo.boardWidth ||\r
4835        oldy != gameInfo.boardHeight ||\r
4836        oldh != gameInfo.holdingsWidth\r
4837 #ifdef GOTHIC\r
4838        || oldv == VariantGothic ||        // For licensing popups\r
4839        gameInfo.variant == VariantGothic\r
4840 #endif\r
4841 #ifdef FALCON\r
4842        || oldv == VariantFalcon ||\r
4843        gameInfo.variant == VariantFalcon\r
4844 #endif\r
4845                                          )\r
4846             InitDrawingSizes(-2 ,0);\r
4847 \r
4848     if (redraw)\r
4849       DrawPosition(TRUE, boards[currentMove]);\r
4850 }\r
4851 \r
4852 void\r
4853 SendBoard(cps, moveNum)\r
4854      ChessProgramState *cps;\r
4855      int moveNum;\r
4856 {\r
4857     char message[MSG_SIZ];\r
4858     \r
4859     if (cps->useSetboard) {\r
4860       char* fen = PositionToFEN(moveNum, cps->useFEN960);\r
4861       sprintf(message, "setboard %s\n", fen);\r
4862       SendToProgram(message, cps);\r
4863       free(fen);\r
4864 \r
4865     } else {\r
4866       ChessSquare *bp;\r
4867       int i, j;\r
4868       /* Kludge to set black to move, avoiding the troublesome and now\r
4869        * deprecated "black" command.\r
4870        */\r
4871       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4872 \r
4873       SendToProgram("edit\n", cps);\r
4874       SendToProgram("#\n", cps);\r
4875       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4876         bp = &boards[moveNum][i][BOARD_LEFT];\r
4877         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4878           if ((int) *bp < (int) BlackPawn) {\r
4879             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4880                     AAA + j, ONE + i);\r
4881             if(message[0] == '+' || message[0] == '~') {\r
4882                 sprintf(message, "%c%c%c+\n",\r
4883                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4884                         AAA + j, ONE + i);\r
4885             }\r
4886             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4887                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4888                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4889             }\r
4890             SendToProgram(message, cps);\r
4891           }\r
4892         }\r
4893       }\r
4894     \r
4895       SendToProgram("c\n", cps);\r
4896       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4897         bp = &boards[moveNum][i][BOARD_LEFT];\r
4898         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4899           if (((int) *bp != (int) EmptySquare)\r
4900               && ((int) *bp >= (int) BlackPawn)) {\r
4901             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4902                     AAA + j, ONE + i);\r
4903             if(message[0] == '+' || message[0] == '~') {\r
4904                 sprintf(message, "%c%c%c+\n",\r
4905                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4906                         AAA + j, ONE + i);\r
4907             }\r
4908             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4909                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4910                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4911             }\r
4912             SendToProgram(message, cps);\r
4913           }\r
4914         }\r
4915       }\r
4916     \r
4917       SendToProgram(".\n", cps);\r
4918     }\r
4919     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4920 }\r
4921 \r
4922 int\r
4923 IsPromotion(fromX, fromY, toX, toY)\r
4924      int fromX, fromY, toX, toY;\r
4925 {\r
4926     /* [HGM] add Shogi promotions */\r
4927     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4928     ChessSquare piece;\r
4929 \r
4930     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4931       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4932    /* [HGM] Note to self: line above also weeds out drops */\r
4933     piece = boards[currentMove][fromY][fromX];\r
4934     if(gameInfo.variant == VariantShogi) {\r
4935         promotionZoneSize = 3;\r
4936         highestPromotingPiece = (int)WhiteKing;\r
4937         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4938            and if in normal chess we then allow promotion to King, why not\r
4939            allow promotion of other piece in Shogi?                         */\r
4940     }\r
4941     if((int)piece >= BlackPawn) {\r
4942         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4943              return FALSE;\r
4944         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4945     } else {\r
4946         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4947            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4948     }\r
4949     return ( (int)piece <= highestPromotingPiece );\r
4950 }\r
4951 \r
4952 int\r
4953 InPalace(row, column)\r
4954      int row, column;\r
4955 {   /* [HGM] for Xiangqi */\r
4956     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4957          column < (BOARD_WIDTH + 4)/2 &&\r
4958          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4959     return FALSE;\r
4960 }\r
4961 \r
4962 int\r
4963 PieceForSquare (x, y)\r
4964      int x;\r
4965      int y;\r
4966 {\r
4967   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
4968      return -1;\r
4969   else\r
4970      return boards[currentMove][y][x];\r
4971 }\r
4972 \r
4973 int\r
4974 OKToStartUserMove(x, y)\r
4975      int x, y;\r
4976 {\r
4977     ChessSquare from_piece;\r
4978     int white_piece;\r
4979 \r
4980     if (matchMode) return FALSE;\r
4981     if (gameMode == EditPosition) return TRUE;\r
4982 \r
4983     if (x >= 0 && y >= 0)\r
4984       from_piece = boards[currentMove][y][x];\r
4985     else\r
4986       from_piece = EmptySquare;\r
4987 \r
4988     if (from_piece == EmptySquare) return FALSE;\r
4989 \r
4990     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4991       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4992 \r
4993     switch (gameMode) {\r
4994       case PlayFromGameFile:\r
4995       case AnalyzeFile:\r
4996       case TwoMachinesPlay:\r
4997       case EndOfGame:\r
4998         return FALSE;\r
4999 \r
5000       case IcsObserving:\r
5001       case IcsIdle:\r
5002         return FALSE;\r
5003 \r
5004       case MachinePlaysWhite:\r
5005       case IcsPlayingBlack:\r
5006         if (appData.zippyPlay) return FALSE;\r
5007         if (white_piece) {\r
5008             DisplayMoveError(_("You are playing Black"));\r
5009             return FALSE;\r
5010         }\r
5011         break;\r
5012 \r
5013       case MachinePlaysBlack:\r
5014       case IcsPlayingWhite:\r
5015         if (appData.zippyPlay) return FALSE;\r
5016         if (!white_piece) {\r
5017             DisplayMoveError(_("You are playing White"));\r
5018             return FALSE;\r
5019         }\r
5020         break;\r
5021 \r
5022       case EditGame:\r
5023         if (!white_piece && WhiteOnMove(currentMove)) {\r
5024             DisplayMoveError(_("It is White's turn"));\r
5025             return FALSE;\r
5026         }           \r
5027         if (white_piece && !WhiteOnMove(currentMove)) {\r
5028             DisplayMoveError(_("It is Black's turn"));\r
5029             return FALSE;\r
5030         }           \r
5031         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
5032             /* Editing correspondence game history */\r
5033             /* Could disallow this or prompt for confirmation */\r
5034             cmailOldMove = -1;\r
5035         }\r
5036         if (currentMove < forwardMostMove) {\r
5037             /* Discarding moves */\r
5038             /* Could prompt for confirmation here,\r
5039                but I don't think that's such a good idea */\r
5040             forwardMostMove = currentMove;\r
5041         }\r
5042         break;\r
5043 \r
5044       case BeginningOfGame:\r
5045         if (appData.icsActive) return FALSE;\r
5046         if (!appData.noChessProgram) {\r
5047             if (!white_piece) {\r
5048                 DisplayMoveError(_("You are playing White"));\r
5049                 return FALSE;\r
5050             }\r
5051         }\r
5052         break;\r
5053         \r
5054       case Training:\r
5055         if (!white_piece && WhiteOnMove(currentMove)) {\r
5056             DisplayMoveError(_("It is White's turn"));\r
5057             return FALSE;\r
5058         }           \r
5059         if (white_piece && !WhiteOnMove(currentMove)) {\r
5060             DisplayMoveError(_("It is Black's turn"));\r
5061             return FALSE;\r
5062         }           \r
5063         break;\r
5064 \r
5065       default:\r
5066       case IcsExamining:\r
5067         break;\r
5068     }\r
5069     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
5070         && gameMode != AnalyzeFile && gameMode != Training) {\r
5071         DisplayMoveError(_("Displayed position is not current"));\r
5072         return FALSE;\r
5073     }\r
5074     return TRUE;\r
5075 }\r
5076 \r
5077 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
5078 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
5079 int lastLoadGameUseList = FALSE;\r
5080 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
5081 ChessMove lastLoadGameStart = (ChessMove) 0;\r
5082 \r
5083 \r
5084 ChessMove\r
5085 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
5086      int fromX, fromY, toX, toY;\r
5087      int promoChar;\r
5088 {\r
5089     ChessMove moveType;\r
5090     ChessSquare pdown, pup;\r
5091 \r
5092     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
5093     if ((fromX == toX) && (fromY == toY)) {\r
5094         return ImpossibleMove;\r
5095     }\r
5096 \r
5097     /* [HGM] suppress all moves into holdings area and guard band */\r
5098     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
5099             return ImpossibleMove;\r
5100 \r
5101     /* [HGM] <sameColor> moved to here from winboard.c */\r
5102     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
5103     pdown = boards[currentMove][fromY][fromX];\r
5104     pup = boards[currentMove][toY][toX];\r
5105     if (    gameMode != EditPosition &&\r
5106             (WhitePawn <= pdown && pdown < BlackPawn &&\r
5107              WhitePawn <= pup && pup < BlackPawn  ||\r
5108              BlackPawn <= pdown && pdown < EmptySquare &&\r
5109              BlackPawn <= pup && pup < EmptySquare \r
5110             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
5111                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
5112                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
5113         )           )\r
5114          return ImpossibleMove;\r
5115 \r
5116     /* Check if the user is playing in turn.  This is complicated because we\r
5117        let the user "pick up" a piece before it is his turn.  So the piece he\r
5118        tried to pick up may have been captured by the time he puts it down!\r
5119        Therefore we use the color the user is supposed to be playing in this\r
5120        test, not the color of the piece that is currently on the starting\r
5121        square---except in EditGame mode, where the user is playing both\r
5122        sides; fortunately there the capture race can't happen.  (It can\r
5123        now happen in IcsExamining mode, but that's just too bad.  The user\r
5124        will get a somewhat confusing message in that case.)\r
5125        */\r
5126 \r
5127     switch (gameMode) {\r
5128       case PlayFromGameFile:\r
5129       case AnalyzeFile:\r
5130       case TwoMachinesPlay:\r
5131       case EndOfGame:\r
5132       case IcsObserving:\r
5133       case IcsIdle:\r
5134         /* We switched into a game mode where moves are not accepted,\r
5135            perhaps while the mouse button was down. */\r
5136         return ImpossibleMove;\r
5137 \r
5138       case MachinePlaysWhite:\r
5139         /* User is moving for Black */\r
5140         if (WhiteOnMove(currentMove)) {\r
5141             DisplayMoveError(_("It is White's turn"));\r
5142             return ImpossibleMove;\r
5143         }\r
5144         break;\r
5145 \r
5146       case MachinePlaysBlack:\r
5147         /* User is moving for White */\r
5148         if (!WhiteOnMove(currentMove)) {\r
5149             DisplayMoveError(_("It is Black's turn"));\r
5150             return ImpossibleMove;\r
5151         }\r
5152         break;\r
5153 \r
5154       case EditGame:\r
5155       case IcsExamining:\r
5156       case BeginningOfGame:\r
5157       case AnalyzeMode:\r
5158       case Training:\r
5159         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
5160             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
5161             /* User is moving for Black */\r
5162             if (WhiteOnMove(currentMove)) {\r
5163                 DisplayMoveError(_("It is White's turn"));\r
5164                 return ImpossibleMove;\r
5165             }\r
5166         } else {\r
5167             /* User is moving for White */\r
5168             if (!WhiteOnMove(currentMove)) {\r
5169                 DisplayMoveError(_("It is Black's turn"));\r
5170                 return ImpossibleMove;\r
5171             }\r
5172         }\r
5173         break;\r
5174 \r
5175       case IcsPlayingBlack:\r
5176         /* User is moving for Black */\r
5177         if (WhiteOnMove(currentMove)) {\r
5178             if (!appData.premove) {\r
5179                 DisplayMoveError(_("It is White's turn"));\r
5180             } else if (toX >= 0 && toY >= 0) {\r
5181                 premoveToX = toX;\r
5182                 premoveToY = toY;\r
5183                 premoveFromX = fromX;\r
5184                 premoveFromY = fromY;\r
5185                 premovePromoChar = promoChar;\r
5186                 gotPremove = 1;\r
5187                 if (appData.debugMode) \r
5188                     fprintf(debugFP, "Got premove: fromX %d,"\r
5189                             "fromY %d, toX %d, toY %d\n",\r
5190                             fromX, fromY, toX, toY);\r
5191             }\r
5192             return ImpossibleMove;\r
5193         }\r
5194         break;\r
5195 \r
5196       case IcsPlayingWhite:\r
5197         /* User is moving for White */\r
5198         if (!WhiteOnMove(currentMove)) {\r
5199             if (!appData.premove) {\r
5200                 DisplayMoveError(_("It is Black's turn"));\r
5201             } else if (toX >= 0 && toY >= 0) {\r
5202                 premoveToX = toX;\r
5203                 premoveToY = toY;\r
5204                 premoveFromX = fromX;\r
5205                 premoveFromY = fromY;\r
5206                 premovePromoChar = promoChar;\r
5207                 gotPremove = 1;\r
5208                 if (appData.debugMode) \r
5209                     fprintf(debugFP, "Got premove: fromX %d,"\r
5210                             "fromY %d, toX %d, toY %d\n",\r
5211                             fromX, fromY, toX, toY);\r
5212             }\r
5213             return ImpossibleMove;\r
5214         }\r
5215         break;\r
5216 \r
5217       default:\r
5218         break;\r
5219 \r
5220       case EditPosition:\r
5221         /* EditPosition, empty square, or different color piece;\r
5222            click-click move is possible */\r
5223         if (toX == -2 || toY == -2) {\r
5224             boards[0][fromY][fromX] = EmptySquare;\r
5225             return AmbiguousMove;\r
5226         } else if (toX >= 0 && toY >= 0) {\r
5227             boards[0][toY][toX] = boards[0][fromY][fromX];\r
5228             boards[0][fromY][fromX] = EmptySquare;\r
5229             return AmbiguousMove;\r
5230         }\r
5231         return ImpossibleMove;\r
5232     }\r
5233 \r
5234     /* [HGM] If move started in holdings, it means a drop */\r
5235     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
5236          if( pup != EmptySquare ) return ImpossibleMove;\r
5237          if(appData.testLegality) {\r
5238              /* it would be more logical if LegalityTest() also figured out\r
5239               * which drops are legal. For now we forbid pawns on back rank.\r
5240               * Shogi is on its own here...\r
5241               */\r
5242              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
5243                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
5244                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
5245          }\r
5246          return WhiteDrop; /* Not needed to specify white or black yet */\r
5247     }\r
5248 \r
5249     userOfferedDraw = FALSE;\r
5250         \r
5251     /* [HGM] always test for legality, to get promotion info */\r
5252     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
5253                           epStatus[currentMove], castlingRights[currentMove],\r
5254                                          fromY, fromX, toY, toX, promoChar);\r
5255 \r
5256     /* [HGM] but possibly ignore an IllegalMove result */\r
5257     if (appData.testLegality) {\r
5258         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
5259             DisplayMoveError(_("Illegal move"));\r
5260             return ImpossibleMove;\r
5261         }\r
5262     }\r
5263 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);\r
5264     return moveType;\r
5265     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
5266        function is made into one that returns an OK move type if FinishMove\r
5267        should be called. This to give the calling driver routine the\r
5268        opportunity to finish the userMove input with a promotion popup,\r
5269        without bothering the user with this for invalid or illegal moves */\r
5270 \r
5271 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
5272 }\r
5273 \r
5274 /* Common tail of UserMoveEvent and DropMenuEvent */\r
5275 int\r
5276 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
5277      ChessMove moveType;\r
5278      int fromX, fromY, toX, toY;\r
5279      /*char*/int promoChar;\r
5280 {\r
5281     char *bookHit = 0;\r
5282 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);\r
5283     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { \r
5284         // [HGM] superchess: suppress promotions to non-available piece\r
5285         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5286         if(WhiteOnMove(currentMove)) {\r
5287             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;\r
5288         } else {\r
5289             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;\r
5290         }\r
5291     }\r
5292 \r
5293     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion\r
5294        move type in caller when we know the move is a legal promotion */\r
5295     if(moveType == NormalMove && promoChar)\r
5296         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
5297 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);\r
5298     /* [HGM] convert drag-and-drop piece drops to standard form */\r
5299     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
5300          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
5301          fromX = boards[currentMove][fromY][fromX];\r
5302          fromY = DROP_RANK;\r
5303     }\r
5304 \r
5305     /* [HGM] <popupFix> The following if has been moved here from\r
5306        UserMoveEvent(). Because it seemed to belon here (why not allow\r
5307        piece drops in training games?), and because it can only be\r
5308        performed after it is known to what we promote. */\r
5309     if (gameMode == Training) {\r
5310       /* compare the move played on the board to the next move in the\r
5311        * game. If they match, display the move and the opponent's response. \r
5312        * If they don't match, display an error message.\r
5313        */\r
5314       int saveAnimate;\r
5315       Board testBoard;\r
5316       CopyBoard(testBoard, boards[currentMove]);\r
5317       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
5318 \r
5319       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
5320         ForwardInner(currentMove+1);\r
5321 \r
5322         /* Autoplay the opponent's response.\r
5323          * if appData.animate was TRUE when Training mode was entered,\r
5324          * the response will be animated.\r
5325          */\r
5326         saveAnimate = appData.animate;\r
5327         appData.animate = animateTraining;\r
5328         ForwardInner(currentMove+1);\r
5329         appData.animate = saveAnimate;\r
5330 \r
5331         /* check for the end of the game */\r
5332         if (currentMove >= forwardMostMove) {\r
5333           gameMode = PlayFromGameFile;\r
5334           ModeHighlight();\r
5335           SetTrainingModeOff();\r
5336           DisplayInformation(_("End of game"));\r
5337         }\r
5338       } else {\r
5339         DisplayError(_("Incorrect move"), 0);\r
5340       }\r
5341       return 1;\r
5342     }\r
5343 \r
5344   /* Ok, now we know that the move is good, so we can kill\r
5345      the previous line in Analysis Mode */\r
5346   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
5347     forwardMostMove = currentMove;\r
5348   }\r
5349 \r
5350   /* If we need the chess program but it's dead, restart it */\r
5351   ResurrectChessProgram();\r
5352 \r
5353   /* A user move restarts a paused game*/\r
5354   if (pausing)\r
5355     PauseEvent();\r
5356 \r
5357   thinkOutput[0] = NULLCHAR;\r
5358 \r
5359   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
5360 \r
5361     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) \r
5362                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
5363         // [HGM] superchess: take promotion piece out of holdings\r
5364         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5365         if(WhiteOnMove(forwardMostMove-1)) {\r
5366             if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])\r
5367                 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;\r
5368         } else {\r
5369             if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])\r
5370                 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;\r
5371         }\r
5372     }\r
5373 \r
5374   if (gameMode == BeginningOfGame) {\r
5375     if (appData.noChessProgram) {\r
5376       gameMode = EditGame;\r
5377       SetGameInfo();\r
5378     } else {\r
5379       char buf[MSG_SIZ];\r
5380       gameMode = MachinePlaysBlack;\r
5381       StartClocks();\r
5382       SetGameInfo();\r
5383       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
5384       DisplayTitle(buf);\r
5385       if (first.sendName) {\r
5386         sprintf(buf, "name %s\n", gameInfo.white);\r
5387         SendToProgram(buf, &first);\r
5388       }\r
5389       StartClocks();\r
5390     }\r
5391     ModeHighlight();\r
5392   }\r
5393 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);\r
5394   /* Relay move to ICS or chess engine */\r
5395   if (appData.icsActive) {\r
5396     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
5397         gameMode == IcsExamining) {\r
5398       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5399       ics_user_moved = 1;\r
5400     }\r
5401   } else {\r
5402     if (first.sendTime && (gameMode == BeginningOfGame ||\r
5403                            gameMode == MachinePlaysWhite ||\r
5404                            gameMode == MachinePlaysBlack)) {\r
5405       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
5406     }\r
5407     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
5408          // [HGM] book: if program might be playing, let it use book\r
5409         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);\r
5410         first.maybeThinking = TRUE;\r
5411     } else SendMoveToProgram(forwardMostMove-1, &first);\r
5412     if (currentMove == cmailOldMove + 1) {\r
5413       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
5414     }\r
5415   }\r
5416 \r
5417   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5418 \r
5419   switch (gameMode) {\r
5420   case EditGame:\r
5421     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
5422                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
5423     case MT_NONE:\r
5424     case MT_CHECK:\r
5425       break;\r
5426     case MT_CHECKMATE:\r
5427       if (WhiteOnMove(currentMove)) {\r
5428         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
5429       } else {\r
5430         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
5431       }\r
5432       break;\r
5433     case MT_STALEMATE:\r
5434       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
5435       break;\r
5436     }\r
5437     break;\r
5438     \r
5439   case MachinePlaysBlack:\r
5440   case MachinePlaysWhite:\r
5441     /* disable certain menu options while machine is thinking */\r
5442     SetMachineThinkingEnables();\r
5443     break;\r
5444 \r
5445   default:\r
5446     break;\r
5447   }\r
5448 \r
5449   if(bookHit) { // [HGM] book: simulate book reply\r
5450         static char bookMove[MSG_SIZ]; // a bit generous?\r
5451 \r
5452         programStats.nodes = programStats.depth = programStats.time = \r
5453         programStats.score = programStats.got_only_move = 0;\r
5454         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
5455 \r
5456         strcpy(bookMove, "move ");\r
5457         strcat(bookMove, bookHit);\r
5458         HandleMachineMove(bookMove, &first);\r
5459   }\r
5460   return 1;\r
5461 }\r
5462 \r
5463 void\r
5464 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
5465      int fromX, fromY, toX, toY;\r
5466      int promoChar;\r
5467 {\r
5468     /* [HGM] This routine was added to allow calling of its two logical\r
5469        parts from other modules in the old way. Before, UserMoveEvent()\r
5470        automatically called FinishMove() if the move was OK, and returned\r
5471        otherwise. I separated the two, in order to make it possible to\r
5472        slip a promotion popup in between. But that it always needs two\r
5473        calls, to the first part, (now called UserMoveTest() ), and to\r
5474        FinishMove if the first part succeeded. Calls that do not need\r
5475        to do anything in between, can call this routine the old way. \r
5476     */\r
5477     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
5478 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);\r
5479     if(moveType != ImpossibleMove)\r
5480         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
5481 }\r
5482 \r
5483 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
5484 {\r
5485 //    char * hint = lastHint;\r
5486     FrontEndProgramStats stats;\r
5487 \r
5488     stats.which = cps == &first ? 0 : 1;\r
5489     stats.depth = cpstats->depth;\r
5490     stats.nodes = cpstats->nodes;\r
5491     stats.score = cpstats->score;\r
5492     stats.time = cpstats->time;\r
5493     stats.pv = cpstats->movelist;\r
5494     stats.hint = lastHint;\r
5495     stats.an_move_index = 0;\r
5496     stats.an_move_count = 0;\r
5497 \r
5498     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
5499         stats.hint = cpstats->move_name;\r
5500         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
5501         stats.an_move_count = cpstats->nr_moves;\r
5502     }\r
5503 \r
5504     SetProgramStats( &stats );\r
5505 }\r
5506 \r
5507 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)\r
5508 {   // [HGM] book: this routine intercepts moves to simulate book replies\r
5509     char *bookHit = NULL;\r
5510 \r
5511     //first determine if the incoming move brings opponent into his book\r
5512     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
5513         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
5514     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");\r
5515     if(bookHit != NULL && !cps->bookSuspend) {\r
5516         // make sure opponent is not going to reply after receiving move to book position\r
5517         SendToProgram("force\n", cps);\r
5518         cps->bookSuspend = TRUE; // flag indicating it has to be restarted\r
5519     }\r
5520     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move\r
5521     // now arrange restart after book miss\r
5522     if(bookHit) {\r
5523         // after a book hit we never send 'go', and the code after the call to this routine\r
5524         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').\r
5525         char buf[MSG_SIZ];\r
5526         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(\r
5527         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it\r
5528         SendToProgram(buf, cps);\r
5529         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'\r
5530     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine\r
5531         SendToProgram("go\n", cps);\r
5532         cps->bookSuspend = FALSE; // after a 'go' we are never suspended\r
5533     } else { // 'go' might be sent based on 'firstMove' after this routine returns\r
5534         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return\r
5535             SendToProgram("go\n", cps); \r
5536         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss\r
5537     }\r
5538     return bookHit; // notify caller of hit, so it can take action to send move to opponent\r
5539 }\r
5540 \r
5541 char *savedMessage;\r
5542 ChessProgramState *savedState;\r
5543 void DeferredBookMove(void)\r
5544 {\r
5545         if(savedState->lastPing != savedState->lastPong)\r
5546                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
5547         else\r
5548         HandleMachineMove(savedMessage, savedState);\r
5549 }\r
5550 \r
5551 void\r
5552 HandleMachineMove(message, cps)\r
5553      char *message;\r
5554      ChessProgramState *cps;\r
5555 {\r
5556     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
5557     char realname[MSG_SIZ];\r
5558     int fromX, fromY, toX, toY;\r
5559     ChessMove moveType;\r
5560     char promoChar;\r
5561     char *p;\r
5562     int machineWhite;\r
5563     char *bookHit;\r
5564 \r
5565 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit\r
5566     /*\r
5567      * Kludge to ignore BEL characters\r
5568      */\r
5569     while (*message == '\007') message++;\r
5570 \r
5571     /*\r
5572      * [HGM] engine debug message: ignore lines starting with '#' character\r
5573      */\r
5574     if(cps->debug && *message == '#') return;\r
5575 \r
5576     /*\r
5577      * Look for book output\r
5578      */\r
5579     if (cps == &first && bookRequested) {\r
5580         if (message[0] == '\t' || message[0] == ' ') {\r
5581             /* Part of the book output is here; append it */\r
5582             strcat(bookOutput, message);\r
5583             strcat(bookOutput, "  \n");\r
5584             return;\r
5585         } else if (bookOutput[0] != NULLCHAR) {\r
5586             /* All of book output has arrived; display it */\r
5587             char *p = bookOutput;\r
5588             while (*p != NULLCHAR) {\r
5589                 if (*p == '\t') *p = ' ';\r
5590                 p++;\r
5591             }\r
5592             DisplayInformation(bookOutput);\r
5593             bookRequested = FALSE;\r
5594             /* Fall through to parse the current output */\r
5595         }\r
5596     }\r
5597 \r
5598     /*\r
5599      * Look for machine move.\r
5600      */\r
5601     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
5602         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
5603     {\r
5604         /* This method is only useful on engines that support ping */\r
5605         if (cps->lastPing != cps->lastPong) {\r
5606           if (gameMode == BeginningOfGame) {\r
5607             /* Extra move from before last new; ignore */\r
5608             if (appData.debugMode) {\r
5609                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5610             }\r
5611           } else {\r
5612             if (appData.debugMode) {\r
5613                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5614                         cps->which, gameMode);\r
5615             }\r
5616 \r
5617             SendToProgram("undo\n", cps);\r
5618           }\r
5619           return;\r
5620         }\r
5621 \r
5622         switch (gameMode) {\r
5623           case BeginningOfGame:\r
5624             /* Extra move from before last reset; ignore */\r
5625             if (appData.debugMode) {\r
5626                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5627             }\r
5628             return;\r
5629 \r
5630           case EndOfGame:\r
5631           case IcsIdle:\r
5632           default:\r
5633             /* Extra move after we tried to stop.  The mode test is\r
5634                not a reliable way of detecting this problem, but it's\r
5635                the best we can do on engines that don't support ping.\r
5636             */\r
5637             if (appData.debugMode) {\r
5638                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5639                         cps->which, gameMode);\r
5640             }\r
5641             SendToProgram("undo\n", cps);\r
5642             return;\r
5643 \r
5644           case MachinePlaysWhite:\r
5645           case IcsPlayingWhite:\r
5646             machineWhite = TRUE;\r
5647             break;\r
5648 \r
5649           case MachinePlaysBlack:\r
5650           case IcsPlayingBlack:\r
5651             machineWhite = FALSE;\r
5652             break;\r
5653 \r
5654           case TwoMachinesPlay:\r
5655             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5656             break;\r
5657         }\r
5658         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5659             if (appData.debugMode) {\r
5660                 fprintf(debugFP,\r
5661                         "Ignoring move out of turn by %s, gameMode %d"\r
5662                         ", forwardMost %d\n",\r
5663                         cps->which, gameMode, forwardMostMove);\r
5664             }\r
5665             return;\r
5666         }\r
5667 \r
5668     if (appData.debugMode) { int f = forwardMostMove;\r
5669         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5670                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5671     }\r
5672         if(cps->alphaRank) AlphaRank(machineMove, 4);\r
5673         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5674                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5675             /* Machine move could not be parsed; ignore it. */\r
5676             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),\r
5677                     machineMove, cps->which);\r
5678             DisplayError(buf1, 0);\r
5679             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",\r
5680                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);\r
5681             if (gameMode == TwoMachinesPlay) {\r
5682               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5683                        buf1, GE_XBOARD);\r
5684             }\r
5685             return;\r
5686         }\r
5687 \r
5688         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5689         /* So we have to redo legality test with true e.p. status here,  */\r
5690         /* to make sure an illegal e.p. capture does not slip through,   */\r
5691         /* to cause a forfeit on a justified illegal-move complaint      */\r
5692         /* of the opponent.                                              */\r
5693         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5694             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5695                                                               ) {\r
5696            ChessMove moveType;\r
5697            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5698                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5699                              fromY, fromX, toY, toX, promoChar);\r
5700             if (appData.debugMode) {\r
5701                 int i;\r
5702                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5703                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5704                 fprintf(debugFP, "castling rights\n");\r
5705             }\r
5706             if(moveType == IllegalMove) {\r
5707                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5708                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5709                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5710                            buf1, GE_XBOARD);\r
5711            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
5712            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5713               when they shouldn't (like TSCP-Gothic) */\r
5714            switch(moveType) {\r
5715              case WhiteASideCastleFR:\r
5716              case BlackASideCastleFR:\r
5717                toX+=2;\r
5718                currentMoveString[2]++;\r
5719                break;\r
5720              case WhiteHSideCastleFR:\r
5721              case BlackHSideCastleFR:\r
5722                toX--;\r
5723                currentMoveString[2]--;\r
5724                break;\r
5725              default: ; // nothing to do, but suppresses warning of pedantic compilers\r
5726            }\r
5727         }\r
5728         hintRequested = FALSE;\r
5729         lastHint[0] = NULLCHAR;\r
5730         bookRequested = FALSE;\r
5731         /* Program may be pondering now */\r
5732         cps->maybeThinking = TRUE;\r
5733         if (cps->sendTime == 2) cps->sendTime = 1;\r
5734         if (cps->offeredDraw) cps->offeredDraw--;\r
5735 \r
5736 #if ZIPPY\r
5737         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5738             first.initDone) {\r
5739           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5740           ics_user_moved = 1;\r
5741           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
5742                 char buf[3*MSG_SIZ];\r
5743 \r
5744                 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",\r
5745                         programStats.depth,\r
5746                         programStats.score / 100.,\r
5747                         programStats.time / 100.,\r
5748                         u64ToDouble(programStats.nodes),\r
5749                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),\r
5750                         programStats.movelist);\r
5751                 SendToICS(buf);\r
5752           }\r
5753         }\r
5754 #endif\r
5755         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5756         strcpy(machineMove, currentMoveString);\r
5757         strcat(machineMove, "\n");\r
5758         strcpy(moveList[forwardMostMove], machineMove);\r
5759 \r
5760         /* [AS] Save move info and clear stats for next move */\r
5761         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5762         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5763         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5764         ClearProgramStats();\r
5765         thinkOutput[0] = NULLCHAR;\r
5766         hiddenThinkOutputState = 0;\r
5767 \r
5768         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5769 \r
5770         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5771         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5772             int count = 0;\r
5773 \r
5774             while( count < adjudicateLossPlies ) {\r
5775                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5776 \r
5777                 if( count & 1 ) {\r
5778                     score = -score; /* Flip score for winning side */\r
5779                 }\r
5780 \r
5781                 if( score > adjudicateLossThreshold ) {\r
5782                     break;\r
5783                 }\r
5784 \r
5785                 count++;\r
5786             }\r
5787 \r
5788             if( count >= adjudicateLossPlies ) {\r
5789                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5790 \r
5791                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5792                     "Xboard adjudication", \r
5793                     GE_XBOARD );\r
5794 \r
5795                 return;\r
5796             }\r
5797         }\r
5798 \r
5799         if( gameMode == TwoMachinesPlay ) {\r
5800           // [HGM] some adjudications useful with buggy engines\r
5801             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
5802           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {\r
5803 \r
5804             if(appData.testLegality)\r
5805             // don't wait for engine to announce game end if we can judge ourselves\r
5806             switch (MateTest(boards[forwardMostMove],\r
5807                                  PosFlags(forwardMostMove), epFile,\r
5808                                        castlingRights[forwardMostMove]) ) {\r
5809               case MT_NONE:\r
5810               case MT_CHECK:\r
5811               default:\r
5812                 break;\r
5813               case MT_STALEMATE:\r
5814                 epStatus[forwardMostMove] = EP_STALEMATE;\r
5815                 if(appData.checkMates) {\r
5816                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5817                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5818                     GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",\r
5819                         GE_XBOARD );\r
5820                 }\r
5821                 break;\r
5822               case MT_CHECKMATE:\r
5823                 epStatus[forwardMostMove] = EP_CHECKMATE;\r
5824                 if(appData.checkMates) {\r
5825                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5826                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5827                     GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5828                     "Xboard adjudication: Checkmate", \r
5829                     GE_XBOARD );\r
5830                 }\r
5831                 break;\r
5832             }\r
5833 \r
5834             if( appData.testLegality )\r
5835             {   /* [HGM] Some more adjudications for obstinate engines */\r
5836                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5837                     NrWQ=0, NrBQ=0, NrW=0, bishopsColor = 0,\r
5838                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;\r
5839                 static int moveCount = 6;\r
5840 \r
5841                 /* First absolutely insufficient mating material. Count what is on board. */\r
5842                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5843                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5844                     int m=i;\r
5845 \r
5846                     switch((int) p)\r
5847                     {   /* count B,N,R and other of each side */\r
5848                         case WhiteKnight:\r
5849                              NrWN++; break;\r
5850                         case WhiteBishop:\r
5851                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5852                              bishopsColor |= 1 << ((i^j)&1);\r
5853                              NrWB++; break;\r
5854                         case BlackKnight:\r
5855                              NrBN++; break;\r
5856                         case BlackBishop:\r
5857                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5858                              bishopsColor |= 1 << ((i^j)&1);\r
5859                              NrBB++; break;\r
5860                         case WhiteRook:\r
5861                              NrWR++; break;\r
5862                         case BlackRook:\r
5863                              NrBR++; break;\r
5864                         case WhiteQueen:\r
5865                              NrWQ++; break;\r
5866                         case BlackQueen:\r
5867                              NrBQ++; break;\r
5868                         case EmptySquare: \r
5869                              break;\r
5870                         case BlackPawn:\r
5871                              m = 7-i;\r
5872                         case WhitePawn:\r
5873                              PawnAdvance += m; NrPawns++;\r
5874                     }\r
5875                     NrPieces += (p != EmptySquare);\r
5876                     NrW += ((int)p < (int)BlackPawn);\r
5877                     if(gameInfo.variant == VariantXiangqi && \r
5878                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {\r
5879                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces\r
5880                         NrW -= ((int)p < (int)BlackPawn);\r
5881                     }\r
5882                 }\r
5883 \r
5884                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && \r
5885                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible\r
5886                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||\r
5887                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color\r
5888                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5889 \r
5890                      /* always flag draws, for judging claims */\r
5891                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5892 \r
5893                      if(appData.materialDraws) {\r
5894                          /* but only adjudicate them if adjudication enabled */\r
5895                          SendToProgram("force\n", cps->other); // suppress reply\r
5896                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */\r
5897                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5898                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5899                          return;\r
5900                      }\r
5901                 }\r
5902 \r
5903                 /* Shatranj baring rule */\r
5904                 if( gameInfo.variant == VariantShatranj && (NrW == 1 || NrPieces - NrW == 1) )\r
5905                 {    /* bare King */\r
5906 \r
5907                      if(--bare < 0 && appData.checkMates) {\r
5908                          /* but only adjudicate them if adjudication enabled */\r
5909                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5910                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5911                          GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
5912                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5913                          return;\r
5914                      }\r
5915                 } else bare = 1;\r
5916 \r
5917                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5918                 if(NrPieces == 4 && \r
5919                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5920                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5921                    || NrWN==2 || NrBN==2     /* KNNK */\r
5922                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5923                   ) ) {\r
5924                      if(--moveCount < 0 && appData.trivialDraws)\r
5925                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5926                           SendToProgram("force\n", cps->other); // suppress reply\r
5927                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5928                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5929                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5930                           return;\r
5931                      }\r
5932                 } else moveCount = 6;\r
5933             }\r
5934           }\r
5935 #if 1\r
5936     if (appData.debugMode) { int i;\r
5937       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5938               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5939               appData.drawRepeats);\r
5940       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5941            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5942 \r
5943     }\r
5944 #endif\r
5945                 /* Check for rep-draws */\r
5946                 count = 0;\r
5947                 for(k = forwardMostMove-2;\r
5948                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5949                         epStatus[k] < EP_UNKNOWN &&\r
5950                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5951                     k-=2)\r
5952                 {   int rights=0;\r
5953 #if 0\r
5954     if (appData.debugMode) {\r
5955       fprintf(debugFP, " loop\n");\r
5956     }\r
5957 #endif\r
5958                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5959 #if 0\r
5960     if (appData.debugMode) {\r
5961       fprintf(debugFP, "match\n");\r
5962     }\r
5963 #endif\r
5964                         /* compare castling rights */\r
5965                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5966                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5967                                 rights++; /* King lost rights, while rook still had them */\r
5968                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5969                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5970                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5971                                    rights++; /* but at least one rook lost them */\r
5972                         }\r
5973                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5974                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5975                                 rights++; \r
5976                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5977                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5978                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5979                                    rights++;\r
5980                         }\r
5981 #if 0\r
5982     if (appData.debugMode) {\r
5983       for(i=0; i<nrCastlingRights; i++)\r
5984       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5985     }\r
5986 \r
5987     if (appData.debugMode) {\r
5988       fprintf(debugFP, " %d %d\n", rights, k);\r
5989     }\r
5990 #endif\r
5991                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5992                             && appData.drawRepeats > 1) {\r
5993                              /* adjudicate after user-specified nr of repeats */\r
5994                              SendToProgram("force\n", cps->other); // suppress reply\r
5995                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5996                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5997                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { \r
5998                                 // [HGM] xiangqi: check for forbidden perpetuals\r
5999                                 int m, ourPerpetual = 1, hisPerpetual = 1;\r
6000                                 for(m=forwardMostMove; m>k; m-=2) {\r
6001                                     if(MateTest(boards[m], PosFlags(m), \r
6002                                                         EP_NONE, castlingRights[m]) != MT_CHECK)\r
6003                                         ourPerpetual = 0; // the current mover did not always check\r
6004                                     if(MateTest(boards[m-1], PosFlags(m-1), \r
6005                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)\r
6006                                         hisPerpetual = 0; // the opponent did not always check\r
6007                                 }\r
6008                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",\r
6009                                                                         ourPerpetual, hisPerpetual);\r
6010                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
6011                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
6012                                            "Xboard adjudication: perpetual checking", GE_XBOARD );\r
6013                                     return;\r
6014                                 }\r
6015                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet\r
6016                                     break; // (or we would have caught him before). Abort repetition-checking loop.\r
6017                                 // Now check for perpetual chases\r
6018                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase\r
6019                                     hisPerpetual = PerpetualChase(k, forwardMostMove);\r
6020                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);\r
6021                                     if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
6022                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
6023                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );\r
6024                                         return;\r
6025                                     }\r
6026                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet\r
6027                                         break; // Abort repetition-checking loop.\r
6028                                 }\r
6029                                 // if neither of us is checking or chasing all the time, or both are, it is draw\r
6030                              }\r
6031                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
6032                              return;\r
6033                         }\r
6034                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
6035                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
6036                     }\r
6037                 }\r
6038 \r
6039                 /* Now we test for 50-move draws. Determine ply count */\r
6040                 count = forwardMostMove;\r
6041                 /* look for last irreversble move */\r
6042                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
6043                     count--;\r
6044                 /* if we hit starting position, add initial plies */\r
6045                 if( count == backwardMostMove )\r
6046                     count -= initialRulePlies;\r
6047                 count = forwardMostMove - count; \r
6048                 if( count >= 100)\r
6049                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
6050                          /* this is used to judge if draw claims are legal */\r
6051                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
6052                          SendToProgram("force\n", cps->other); // suppress reply\r
6053                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6054                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6055                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
6056                          return;\r
6057                 }\r
6058 \r
6059                 /* if draw offer is pending, treat it as a draw claim\r
6060                  * when draw condition present, to allow engines a way to\r
6061                  * claim draws before making their move to avoid a race\r
6062                  * condition occurring after their move\r
6063                  */\r
6064                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
6065                          char *p = NULL;\r
6066                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
6067                              p = "Draw claim: 50-move rule";\r
6068                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
6069                              p = "Draw claim: 3-fold repetition";\r
6070                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
6071                              p = "Draw claim: insufficient mating material";\r
6072                          if( p != NULL ) {\r
6073                              SendToProgram("force\n", cps->other); // suppress reply\r
6074                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6075                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
6076                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6077                              return;\r
6078                          }\r
6079                 }\r
6080 \r
6081 \r
6082                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
6083                     SendToProgram("force\n", cps->other); // suppress reply\r
6084                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6085                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6086 \r
6087                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
6088 \r
6089                     return;\r
6090                 }\r
6091         }\r
6092 \r
6093         bookHit = NULL;\r
6094         if (gameMode == TwoMachinesPlay) {\r
6095             /* [HGM] relaying draw offers moved to after reception of move */\r
6096             /* and interpreting offer as claim if it brings draw condition */\r
6097             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
6098                 SendToProgram("draw\n", cps->other);\r
6099             }\r
6100             if (cps->other->sendTime) {\r
6101                 SendTimeRemaining(cps->other,\r
6102                                   cps->other->twoMachinesColor[0] == 'w');\r
6103             }\r
6104             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);\r
6105             if (firstMove && !bookHit) {\r
6106                 firstMove = FALSE;\r
6107                 if (cps->other->useColors) {\r
6108                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
6109                 }\r
6110                 SendToProgram("go\n", cps->other);\r
6111             }\r
6112             cps->other->maybeThinking = TRUE;\r
6113         }\r
6114 \r
6115         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6116         \r
6117         if (!pausing && appData.ringBellAfterMoves) {\r
6118             RingBell();\r
6119         }\r
6120 \r
6121         /* \r
6122          * Reenable menu items that were disabled while\r
6123          * machine was thinking\r
6124          */\r
6125         if (gameMode != TwoMachinesPlay)\r
6126             SetUserThinkingEnables();\r
6127 \r
6128         // [HGM] book: after book hit opponent has received move and is now in force mode\r
6129         // force the book reply into it, and then fake that it outputted this move by jumping\r
6130         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move\r
6131         if(bookHit) {\r
6132                 static char bookMove[MSG_SIZ]; // a bit generous?\r
6133 \r
6134                 strcpy(bookMove, "move ");\r
6135                 strcat(bookMove, bookHit);\r
6136                 message = bookMove;\r
6137                 cps = cps->other;\r
6138                 programStats.nodes = programStats.depth = programStats.time = \r
6139                 programStats.score = programStats.got_only_move = 0;\r
6140                 sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
6141 \r
6142                 if(cps->lastPing != cps->lastPong) {\r
6143                     savedMessage = message; // args for deferred call\r
6144                     savedState = cps;\r
6145                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
6146                     return;\r
6147                 }\r
6148                 goto FakeBookMove;\r
6149         }\r
6150 \r
6151         return;\r
6152     }\r
6153 \r
6154     /* Set special modes for chess engines.  Later something general\r
6155      *  could be added here; for now there is just one kludge feature,\r
6156      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
6157      *  when "xboard" is given as an interactive command.\r
6158      */\r
6159     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
6160         cps->useSigint = FALSE;\r
6161         cps->useSigterm = FALSE;\r
6162     }\r
6163 \r
6164     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
6165      * want this, I was asked to put it in, and obliged.\r
6166      */\r
6167     if (!strncmp(message, "setboard ", 9)) {\r
6168         Board initial_position; int i;\r
6169 \r
6170         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
6171 \r
6172         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
6173             DisplayError(_("Bad FEN received from engine"), 0);\r
6174             return ;\r
6175         } else {\r
6176            Reset(FALSE, FALSE);\r
6177            CopyBoard(boards[0], initial_position);\r
6178            initialRulePlies = FENrulePlies;\r
6179            epStatus[0] = FENepStatus;\r
6180            for( i=0; i<nrCastlingRights; i++ )\r
6181                 castlingRights[0][i] = FENcastlingRights[i];\r
6182            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
6183            else gameMode = MachinePlaysBlack;                 \r
6184            DrawPosition(FALSE, boards[currentMove]);\r
6185         }\r
6186         return;\r
6187     }\r
6188 \r
6189     /*\r
6190      * Look for communication commands\r
6191      */\r
6192     if (!strncmp(message, "telluser ", 9)) {\r
6193         DisplayNote(message + 9);\r
6194         return;\r
6195     }\r
6196     if (!strncmp(message, "tellusererror ", 14)) {\r
6197         DisplayError(message + 14, 0);\r
6198         return;\r
6199     }\r
6200     if (!strncmp(message, "tellopponent ", 13)) {\r
6201       if (appData.icsActive) {\r
6202         if (loggedOn) {\r
6203           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
6204           SendToICS(buf1);\r
6205         }\r
6206       } else {\r
6207         DisplayNote(message + 13);\r
6208       }\r
6209       return;\r
6210     }\r
6211     if (!strncmp(message, "tellothers ", 11)) {\r
6212       if (appData.icsActive) {\r
6213         if (loggedOn) {\r
6214           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
6215           SendToICS(buf1);\r
6216         }\r
6217       }\r
6218       return;\r
6219     }\r
6220     if (!strncmp(message, "tellall ", 8)) {\r
6221       if (appData.icsActive) {\r
6222         if (loggedOn) {\r
6223           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
6224           SendToICS(buf1);\r
6225         }\r
6226       } else {\r
6227         DisplayNote(message + 8);\r
6228       }\r
6229       return;\r
6230     }\r
6231     if (strncmp(message, "warning", 7) == 0) {\r
6232         /* Undocumented feature, use tellusererror in new code */\r
6233         DisplayError(message, 0);\r
6234         return;\r
6235     }\r
6236     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
6237         strcpy(realname, cps->tidy);\r
6238         strcat(realname, " query");\r
6239         AskQuestion(realname, buf2, buf1, cps->pr);\r
6240         return;\r
6241     }\r
6242     /* Commands from the engine directly to ICS.  We don't allow these to be \r
6243      *  sent until we are logged on. Crafty kibitzes have been known to \r
6244      *  interfere with the login process.\r
6245      */\r
6246     if (loggedOn) {\r
6247         if (!strncmp(message, "tellics ", 8)) {\r
6248             SendToICS(message + 8);\r
6249             SendToICS("\n");\r
6250             return;\r
6251         }\r
6252         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
6253             SendToICS(ics_prefix);\r
6254             SendToICS(message + 15);\r
6255             SendToICS("\n");\r
6256             return;\r
6257         }\r
6258         /* The following are for backward compatibility only */\r
6259         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
6260             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
6261             SendToICS(ics_prefix);\r
6262             SendToICS(message);\r
6263             SendToICS("\n");\r
6264             return;\r
6265         }\r
6266     }\r
6267     if (strncmp(message, "feature ", 8) == 0) {\r
6268       ParseFeatures(message+8, cps);\r
6269     }\r
6270     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
6271         return;\r
6272     }\r
6273     /*\r
6274      * If the move is illegal, cancel it and redraw the board.\r
6275      * Also deal with other error cases.  Matching is rather loose\r
6276      * here to accommodate engines written before the spec.\r
6277      */\r
6278     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
6279         strncmp(message, "Error", 5) == 0) {\r
6280         if (StrStr(message, "name") || \r
6281             StrStr(message, "rating") || StrStr(message, "?") ||\r
6282             StrStr(message, "result") || StrStr(message, "board") ||\r
6283             StrStr(message, "bk") || StrStr(message, "computer") ||\r
6284             StrStr(message, "variant") || StrStr(message, "hint") ||\r
6285             StrStr(message, "random") || StrStr(message, "depth") ||\r
6286             StrStr(message, "accepted")) {\r
6287             return;\r
6288         }\r
6289         if (StrStr(message, "protover")) {\r
6290           /* Program is responding to input, so it's apparently done\r
6291              initializing, and this error message indicates it is\r
6292              protocol version 1.  So we don't need to wait any longer\r
6293              for it to initialize and send feature commands. */\r
6294           FeatureDone(cps, 1);\r
6295           cps->protocolVersion = 1;\r
6296           return;\r
6297         }\r
6298         cps->maybeThinking = FALSE;\r
6299 \r
6300         if (StrStr(message, "draw")) {\r
6301             /* Program doesn't have "draw" command */\r
6302             cps->sendDrawOffers = 0;\r
6303             return;\r
6304         }\r
6305         if (cps->sendTime != 1 &&\r
6306             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
6307           /* Program apparently doesn't have "time" or "otim" command */\r
6308           cps->sendTime = 0;\r
6309           return;\r
6310         }\r
6311         if (StrStr(message, "analyze")) {\r
6312             cps->analysisSupport = FALSE;\r
6313             cps->analyzing = FALSE;\r
6314             Reset(FALSE, TRUE);\r
6315             sprintf(buf2, _("%s does not support analysis"), cps->tidy);\r
6316             DisplayError(buf2, 0);\r
6317             return;\r
6318         }\r
6319         if (StrStr(message, "(no matching move)st")) {\r
6320           /* Special kludge for GNU Chess 4 only */\r
6321           cps->stKludge = TRUE;\r
6322           SendTimeControl(cps, movesPerSession, timeControl,\r
6323                           timeIncrement, appData.searchDepth,\r
6324                           searchTime);\r
6325           return;\r
6326         }\r
6327         if (StrStr(message, "(no matching move)sd")) {\r
6328           /* Special kludge for GNU Chess 4 only */\r
6329           cps->sdKludge = TRUE;\r
6330           SendTimeControl(cps, movesPerSession, timeControl,\r
6331                           timeIncrement, appData.searchDepth,\r
6332                           searchTime);\r
6333           return;\r
6334         }\r
6335         if (!StrStr(message, "llegal")) {\r
6336             return;\r
6337         }\r
6338         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6339             gameMode == IcsIdle) return;\r
6340         if (forwardMostMove <= backwardMostMove) return;\r
6341 #if 0\r
6342         /* Following removed: it caused a bug where a real illegal move\r
6343            message in analyze mored would be ignored. */\r
6344         if (cps == &first && programStats.ok_to_send == 0) {\r
6345             /* Bogus message from Crafty responding to "."  This filtering\r
6346                can miss some of the bad messages, but fortunately the bug \r
6347                is fixed in current Crafty versions, so it doesn't matter. */\r
6348             return;\r
6349         }\r
6350 #endif\r
6351         if (pausing) PauseEvent();\r
6352         if (gameMode == PlayFromGameFile) {\r
6353             /* Stop reading this game file */\r
6354             gameMode = EditGame;\r
6355             ModeHighlight();\r
6356         }\r
6357         currentMove = --forwardMostMove;\r
6358         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
6359         SwitchClocks();\r
6360         DisplayBothClocks();\r
6361         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),\r
6362                 parseList[currentMove], cps->which);\r
6363         DisplayMoveError(buf1);\r
6364         DrawPosition(FALSE, boards[currentMove]);\r
6365 \r
6366         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
6367         /* only passes fully legal moves                            */\r
6368         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
6369             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
6370                                 "False illegal-move claim", GE_XBOARD );\r
6371         }\r
6372         return;\r
6373     }\r
6374     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
6375         /* Program has a broken "time" command that\r
6376            outputs a string not ending in newline.\r
6377            Don't use it. */\r
6378         cps->sendTime = 0;\r
6379     }\r
6380     \r
6381     /*\r
6382      * If chess program startup fails, exit with an error message.\r
6383      * Attempts to recover here are futile.\r
6384      */\r
6385     if ((StrStr(message, "unknown host") != NULL)\r
6386         || (StrStr(message, "No remote directory") != NULL)\r
6387         || (StrStr(message, "not found") != NULL)\r
6388         || (StrStr(message, "No such file") != NULL)\r
6389         || (StrStr(message, "can't alloc") != NULL)\r
6390         || (StrStr(message, "Permission denied") != NULL)) {\r
6391 \r
6392         cps->maybeThinking = FALSE;\r
6393         sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),\r
6394                 cps->which, cps->program, cps->host, message);\r
6395         RemoveInputSource(cps->isr);\r
6396         DisplayFatalError(buf1, 0, 1);\r
6397         return;\r
6398     }\r
6399     \r
6400     /* \r
6401      * Look for hint output\r
6402      */\r
6403     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
6404         if (cps == &first && hintRequested) {\r
6405             hintRequested = FALSE;\r
6406             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
6407                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
6408                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
6409                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
6410                                     fromY, fromX, toY, toX, promoChar, buf1);\r
6411                 sprintf(buf2, _("Hint: %s"), buf1);\r
6412                 DisplayInformation(buf2);\r
6413             } else {\r
6414                 /* Hint move could not be parsed!? */\r
6415                 sprintf(buf2,\r
6416                         _("Illegal hint move \"%s\"\nfrom %s chess program"),\r
6417                         buf1, cps->which);\r
6418                 DisplayError(buf2, 0);\r
6419             }\r
6420         } else {\r
6421             strcpy(lastHint, buf1);\r
6422         }\r
6423         return;\r
6424     }\r
6425 \r
6426     /*\r
6427      * Ignore other messages if game is not in progress\r
6428      */\r
6429     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6430         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
6431 \r
6432     /*\r
6433      * look for win, lose, draw, or draw offer\r
6434      */\r
6435     if (strncmp(message, "1-0", 3) == 0) {\r
6436         char *p, *q, *r = "";\r
6437         p = strchr(message, '{');\r
6438         if (p) {\r
6439             q = strchr(p, '}');\r
6440             if (q) {\r
6441                 *q = NULLCHAR;\r
6442                 r = p + 1;\r
6443             }\r
6444         }\r
6445         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
6446         return;\r
6447     } else if (strncmp(message, "0-1", 3) == 0) {\r
6448         char *p, *q, *r = "";\r
6449         p = strchr(message, '{');\r
6450         if (p) {\r
6451             q = strchr(p, '}');\r
6452             if (q) {\r
6453                 *q = NULLCHAR;\r
6454                 r = p + 1;\r
6455             }\r
6456         }\r
6457         /* Kludge for Arasan 4.1 bug */\r
6458         if (strcmp(r, "Black resigns") == 0) {\r
6459             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
6460             return;\r
6461         }\r
6462         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
6463         return;\r
6464     } else if (strncmp(message, "1/2", 3) == 0) {\r
6465         char *p, *q, *r = "";\r
6466         p = strchr(message, '{');\r
6467         if (p) {\r
6468             q = strchr(p, '}');\r
6469             if (q) {\r
6470                 *q = NULLCHAR;\r
6471                 r = p + 1;\r
6472             }\r
6473         }\r
6474             \r
6475         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
6476         return;\r
6477 \r
6478     } else if (strncmp(message, "White resign", 12) == 0) {\r
6479         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6480         return;\r
6481     } else if (strncmp(message, "Black resign", 12) == 0) {\r
6482         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6483         return;\r
6484     } else if (strncmp(message, "White matches", 13) == 0 ||\r
6485                strncmp(message, "Black matches", 13) == 0   ) {\r
6486         /* [HGM] ignore GNUShogi noises */\r
6487         return;\r
6488     } else if (strncmp(message, "White", 5) == 0 &&\r
6489                message[5] != '(' &&\r
6490                StrStr(message, "Black") == NULL) {\r
6491         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6492         return;\r
6493     } else if (strncmp(message, "Black", 5) == 0 &&\r
6494                message[5] != '(') {\r
6495         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6496         return;\r
6497     } else if (strcmp(message, "resign") == 0 ||\r
6498                strcmp(message, "computer resigns") == 0) {\r
6499         switch (gameMode) {\r
6500           case MachinePlaysBlack:\r
6501           case IcsPlayingBlack:\r
6502             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
6503             break;\r
6504           case MachinePlaysWhite:\r
6505           case IcsPlayingWhite:\r
6506             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
6507             break;\r
6508           case TwoMachinesPlay:\r
6509             if (cps->twoMachinesColor[0] == 'w')\r
6510               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6511             else\r
6512               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6513             break;\r
6514           default:\r
6515             /* can't happen */\r
6516             break;\r
6517         }\r
6518         return;\r
6519     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
6520         switch (gameMode) {\r
6521           case MachinePlaysBlack:\r
6522           case IcsPlayingBlack:\r
6523             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6524             break;\r
6525           case MachinePlaysWhite:\r
6526           case IcsPlayingWhite:\r
6527             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
6528             break;\r
6529           case TwoMachinesPlay:\r
6530             if (cps->twoMachinesColor[0] == 'w')\r
6531               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6532             else\r
6533               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6534             break;\r
6535           default:\r
6536             /* can't happen */\r
6537             break;\r
6538         }\r
6539         return;\r
6540     } else if (strncmp(message, "computer mates", 14) == 0) {\r
6541         switch (gameMode) {\r
6542           case MachinePlaysBlack:\r
6543           case IcsPlayingBlack:\r
6544             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
6545             break;\r
6546           case MachinePlaysWhite:\r
6547           case IcsPlayingWhite:\r
6548             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6549             break;\r
6550           case TwoMachinesPlay:\r
6551             if (cps->twoMachinesColor[0] == 'w')\r
6552               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6553             else\r
6554               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6555             break;\r
6556           default:\r
6557             /* can't happen */\r
6558             break;\r
6559         }\r
6560         return;\r
6561     } else if (strncmp(message, "checkmate", 9) == 0) {\r
6562         if (WhiteOnMove(forwardMostMove)) {\r
6563             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6564         } else {\r
6565             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6566         }\r
6567         return;\r
6568     } else if (strstr(message, "Draw") != NULL ||\r
6569                strstr(message, "game is a draw") != NULL) {\r
6570         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
6571         return;\r
6572     } else if (strstr(message, "offer") != NULL &&\r
6573                strstr(message, "draw") != NULL) {\r
6574 #if ZIPPY\r
6575         if (appData.zippyPlay && first.initDone) {\r
6576             /* Relay offer to ICS */\r
6577             SendToICS(ics_prefix);\r
6578             SendToICS("draw\n");\r
6579         }\r
6580 #endif\r
6581         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
6582         if (gameMode == TwoMachinesPlay) {\r
6583             if (cps->other->offeredDraw) {\r
6584                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6585             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
6586             /* until after we also have move, to see if it is really claim */\r
6587             }\r
6588 #if 0\r
6589               else {\r
6590                 if (cps->other->sendDrawOffers) {\r
6591                     SendToProgram("draw\n", cps->other);\r
6592                 }\r
6593             }\r
6594 #endif\r
6595         } else if (gameMode == MachinePlaysWhite ||\r
6596                    gameMode == MachinePlaysBlack) {\r
6597           if (userOfferedDraw) {\r
6598             DisplayInformation(_("Machine accepts your draw offer"));\r
6599             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6600           } else {\r
6601             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));\r
6602           }\r
6603         }\r
6604     }\r
6605 \r
6606     \r
6607     /*\r
6608      * Look for thinking output\r
6609      */\r
6610     if ( appData.showThinking // [HGM] thinking: test all options that cause this output\r
6611           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
6612                                 ) {\r
6613         int plylev, mvleft, mvtot, curscore, time;\r
6614         char mvname[MOVE_LEN];\r
6615         u64 nodes; // [DM]\r
6616         char plyext;\r
6617         int ignore = FALSE;\r
6618         int prefixHint = FALSE;\r
6619         mvname[0] = NULLCHAR;\r
6620 \r
6621         switch (gameMode) {\r
6622           case MachinePlaysBlack:\r
6623           case IcsPlayingBlack:\r
6624             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6625             break;\r
6626           case MachinePlaysWhite:\r
6627           case IcsPlayingWhite:\r
6628             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6629             break;\r
6630           case AnalyzeMode:\r
6631           case AnalyzeFile:\r
6632             break;\r
6633           case IcsObserving: /* [DM] icsEngineAnalyze */\r
6634             if (!appData.icsEngineAnalyze) ignore = TRUE;\r
6635             break;\r
6636           case TwoMachinesPlay:\r
6637             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
6638                 ignore = TRUE;\r
6639             }\r
6640             break;\r
6641           default:\r
6642             ignore = TRUE;\r
6643             break;\r
6644         }\r
6645 \r
6646         if (!ignore) {\r
6647             buf1[0] = NULLCHAR;\r
6648             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6649                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
6650 \r
6651                 if (plyext != ' ' && plyext != '\t') {\r
6652                     time *= 100;\r
6653                 }\r
6654 \r
6655                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6656                 if( cps->scoreIsAbsolute && \r
6657                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
6658                 {\r
6659                     curscore = -curscore;\r
6660                 }\r
6661 \r
6662 \r
6663                 programStats.depth = plylev;\r
6664                 programStats.nodes = nodes;\r
6665                 programStats.time = time;\r
6666                 programStats.score = curscore;\r
6667                 programStats.got_only_move = 0;\r
6668 \r
6669                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
6670                         int ticklen;\r
6671 \r
6672                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time\r
6673                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time\r
6674                         if(WhiteOnMove(forwardMostMove)) \r
6675                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
6676                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
6677                 }\r
6678 \r
6679                 /* Buffer overflow protection */\r
6680                 if (buf1[0] != NULLCHAR) {\r
6681                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
6682                         && appData.debugMode) {\r
6683                         fprintf(debugFP,\r
6684                                 "PV is too long; using the first %d bytes.\n",\r
6685                                 sizeof(programStats.movelist) - 1);\r
6686                     }\r
6687 \r
6688                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
6689                 } else {\r
6690                     sprintf(programStats.movelist, " no PV\n");\r
6691                 }\r
6692 \r
6693                 if (programStats.seen_stat) {\r
6694                     programStats.ok_to_send = 1;\r
6695                 }\r
6696 \r
6697                 if (strchr(programStats.movelist, '(') != NULL) {\r
6698                     programStats.line_is_book = 1;\r
6699                     programStats.nr_moves = 0;\r
6700                     programStats.moves_left = 0;\r
6701                 } else {\r
6702                     programStats.line_is_book = 0;\r
6703                 }\r
6704 \r
6705                 SendProgramStatsToFrontend( cps, &programStats );\r
6706 \r
6707                 /* \r
6708                     [AS] Protect the thinkOutput buffer from overflow... this\r
6709                     is only useful if buf1 hasn't overflowed first!\r
6710                 */\r
6711                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
6712                         plylev, \r
6713                         (gameMode == TwoMachinesPlay ?\r
6714                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
6715                         ((double) curscore) / 100.0,\r
6716                         prefixHint ? lastHint : "",\r
6717                         prefixHint ? " " : "" );\r
6718 \r
6719                 if( buf1[0] != NULLCHAR ) {\r
6720                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
6721 \r
6722                     if( strlen(buf1) > max_len ) {\r
6723                         if( appData.debugMode) {\r
6724                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
6725                         }\r
6726                         buf1[max_len+1] = '\0';\r
6727                     }\r
6728 \r
6729                     strcat( thinkOutput, buf1 );\r
6730                 }\r
6731 \r
6732                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode\r
6733                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6734                     DisplayMove(currentMove - 1);\r
6735                     DisplayAnalysis();\r
6736                 }\r
6737                 return;\r
6738 \r
6739             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6740                 /* crafty (9.25+) says "(only move) <move>"\r
6741                  * if there is only 1 legal move\r
6742                  */\r
6743                 sscanf(p, "(only move) %s", buf1);\r
6744                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6745                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6746                 programStats.depth = 1;\r
6747                 programStats.nr_moves = 1;\r
6748                 programStats.moves_left = 1;\r
6749                 programStats.nodes = 1;\r
6750                 programStats.time = 1;\r
6751                 programStats.got_only_move = 1;\r
6752 \r
6753                 /* Not really, but we also use this member to\r
6754                    mean "line isn't going to change" (Crafty\r
6755                    isn't searching, so stats won't change) */\r
6756                 programStats.line_is_book = 1;\r
6757 \r
6758                 SendProgramStatsToFrontend( cps, &programStats );\r
6759                 \r
6760                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || \r
6761                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6762                     DisplayMove(currentMove - 1);\r
6763                     DisplayAnalysis();\r
6764                 }\r
6765                 return;\r
6766             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",\r
6767                               &time, &nodes, &plylev, &mvleft,\r
6768                               &mvtot, mvname) >= 5) {\r
6769                 /* The stat01: line is from Crafty (9.29+) in response\r
6770                    to the "." command */\r
6771                 programStats.seen_stat = 1;\r
6772                 cps->maybeThinking = TRUE;\r
6773 \r
6774                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6775                   return;\r
6776 \r
6777                 programStats.depth = plylev;\r
6778                 programStats.time = time;\r
6779                 programStats.nodes = nodes;\r
6780                 programStats.moves_left = mvleft;\r
6781                 programStats.nr_moves = mvtot;\r
6782                 strcpy(programStats.move_name, mvname);\r
6783                 programStats.ok_to_send = 1;\r
6784                 programStats.movelist[0] = '\0';\r
6785 \r
6786                 SendProgramStatsToFrontend( cps, &programStats );\r
6787 \r
6788                 DisplayAnalysis();\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 = 2;\r
6794                 return;\r
6795 \r
6796             } else if (strncmp(message,"--",2) == 0) {\r
6797                 /* Crafty 9.29+ outputs this */\r
6798                 programStats.got_fail = 1;\r
6799                 return;\r
6800 \r
6801             } else if (thinkOutput[0] != NULLCHAR &&\r
6802                        strncmp(message, "    ", 4) == 0) {\r
6803                 unsigned message_len;\r
6804 \r
6805                 p = message;\r
6806                 while (*p && *p == ' ') p++;\r
6807 \r
6808                 message_len = strlen( p );\r
6809 \r
6810                 /* [AS] Avoid buffer overflow */\r
6811                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6812                     strcat(thinkOutput, " ");\r
6813                     strcat(thinkOutput, p);\r
6814                 }\r
6815 \r
6816                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6817                     strcat(programStats.movelist, " ");\r
6818                     strcat(programStats.movelist, p);\r
6819                 }\r
6820 \r
6821                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||\r
6822                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6823                     DisplayMove(currentMove - 1);\r
6824                     DisplayAnalysis();\r
6825                 }\r
6826                 return;\r
6827             }\r
6828         }\r
6829         else {\r
6830             buf1[0] = NULLCHAR;\r
6831 \r
6832             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6833                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6834             {\r
6835                 ChessProgramStats cpstats;\r
6836 \r
6837                 if (plyext != ' ' && plyext != '\t') {\r
6838                     time *= 100;\r
6839                 }\r
6840 \r
6841                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6842                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6843                     curscore = -curscore;\r
6844                 }\r
6845 \r
6846                 cpstats.depth = plylev;\r
6847                 cpstats.nodes = nodes;\r
6848                 cpstats.time = time;\r
6849                 cpstats.score = curscore;\r
6850                 cpstats.got_only_move = 0;\r
6851                 cpstats.movelist[0] = '\0';\r
6852 \r
6853                 if (buf1[0] != NULLCHAR) {\r
6854                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6855                 }\r
6856 \r
6857                 cpstats.ok_to_send = 0;\r
6858                 cpstats.line_is_book = 0;\r
6859                 cpstats.nr_moves = 0;\r
6860                 cpstats.moves_left = 0;\r
6861 \r
6862                 SendProgramStatsToFrontend( cps, &cpstats );\r
6863             }\r
6864         }\r
6865     }\r
6866 }\r
6867 \r
6868 \r
6869 /* Parse a game score from the character string "game", and\r
6870    record it as the history of the current game.  The game\r
6871    score is NOT assumed to start from the standard position. \r
6872    The display is not updated in any way.\r
6873    */\r
6874 void\r
6875 ParseGameHistory(game)\r
6876      char *game;\r
6877 {\r
6878     ChessMove moveType;\r
6879     int fromX, fromY, toX, toY, boardIndex;\r
6880     char promoChar;\r
6881     char *p, *q;\r
6882     char buf[MSG_SIZ];\r
6883 \r
6884     if (appData.debugMode)\r
6885       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6886 \r
6887     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6888     gameInfo.site = StrSave(appData.icsHost);\r
6889     gameInfo.date = PGNDate();\r
6890     gameInfo.round = StrSave("-");\r
6891 \r
6892     /* Parse out names of players */\r
6893     while (*game == ' ') game++;\r
6894     p = buf;\r
6895     while (*game != ' ') *p++ = *game++;\r
6896     *p = NULLCHAR;\r
6897     gameInfo.white = StrSave(buf);\r
6898     while (*game == ' ') game++;\r
6899     p = buf;\r
6900     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6901     *p = NULLCHAR;\r
6902     gameInfo.black = StrSave(buf);\r
6903 \r
6904     /* Parse moves */\r
6905     boardIndex = blackPlaysFirst ? 1 : 0;\r
6906     yynewstr(game);\r
6907     for (;;) {\r
6908         yyboardindex = boardIndex;\r
6909         moveType = (ChessMove) yylex();\r
6910         switch (moveType) {\r
6911           case IllegalMove:             /* maybe suicide chess, etc. */\r
6912   if (appData.debugMode) {\r
6913     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6914     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6915     setbuf(debugFP, NULL);\r
6916   }\r
6917           case WhitePromotionChancellor:\r
6918           case BlackPromotionChancellor:\r
6919           case WhitePromotionArchbishop:\r
6920           case BlackPromotionArchbishop:\r
6921           case WhitePromotionQueen:\r
6922           case BlackPromotionQueen:\r
6923           case WhitePromotionRook:\r
6924           case BlackPromotionRook:\r
6925           case WhitePromotionBishop:\r
6926           case BlackPromotionBishop:\r
6927           case WhitePromotionKnight:\r
6928           case BlackPromotionKnight:\r
6929           case WhitePromotionKing:\r
6930           case BlackPromotionKing:\r
6931           case NormalMove:\r
6932           case WhiteCapturesEnPassant:\r
6933           case BlackCapturesEnPassant:\r
6934           case WhiteKingSideCastle:\r
6935           case WhiteQueenSideCastle:\r
6936           case BlackKingSideCastle:\r
6937           case BlackQueenSideCastle:\r
6938           case WhiteKingSideCastleWild:\r
6939           case WhiteQueenSideCastleWild:\r
6940           case BlackKingSideCastleWild:\r
6941           case BlackQueenSideCastleWild:\r
6942           /* PUSH Fabien */\r
6943           case WhiteHSideCastleFR:\r
6944           case WhiteASideCastleFR:\r
6945           case BlackHSideCastleFR:\r
6946           case BlackASideCastleFR:\r
6947           /* POP Fabien */\r
6948             fromX = currentMoveString[0] - AAA;\r
6949             fromY = currentMoveString[1] - ONE;\r
6950             toX = currentMoveString[2] - AAA;\r
6951             toY = currentMoveString[3] - ONE;\r
6952             promoChar = currentMoveString[4];\r
6953             break;\r
6954           case WhiteDrop:\r
6955           case BlackDrop:\r
6956             fromX = moveType == WhiteDrop ?\r
6957               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6958             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6959             fromY = DROP_RANK;\r
6960             toX = currentMoveString[2] - AAA;\r
6961             toY = currentMoveString[3] - ONE;\r
6962             promoChar = NULLCHAR;\r
6963             break;\r
6964           case AmbiguousMove:\r
6965             /* bug? */\r
6966             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);\r
6967   if (appData.debugMode) {\r
6968     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
6969     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6970     setbuf(debugFP, NULL);\r
6971   }\r
6972             DisplayError(buf, 0);\r
6973             return;\r
6974           case ImpossibleMove:\r
6975             /* bug? */\r
6976             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);\r
6977   if (appData.debugMode) {\r
6978     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
6979     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6980     setbuf(debugFP, NULL);\r
6981   }\r
6982             DisplayError(buf, 0);\r
6983             return;\r
6984           case (ChessMove) 0:   /* end of file */\r
6985             if (boardIndex < backwardMostMove) {\r
6986                 /* Oops, gap.  How did that happen? */\r
6987                 DisplayError(_("Gap in move list"), 0);\r
6988                 return;\r
6989             }\r
6990             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
6991             if (boardIndex > forwardMostMove) {\r
6992                 forwardMostMove = boardIndex;\r
6993             }\r
6994             return;\r
6995           case ElapsedTime:\r
6996             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
6997                 strcat(parseList[boardIndex-1], " ");\r
6998                 strcat(parseList[boardIndex-1], yy_text);\r
6999             }\r
7000             continue;\r
7001           case Comment:\r
7002           case PGNTag:\r
7003           case NAG:\r
7004           default:\r
7005             /* ignore */\r
7006             continue;\r
7007           case WhiteWins:\r
7008           case BlackWins:\r
7009           case GameIsDrawn:\r
7010           case GameUnfinished:\r
7011             if (gameMode == IcsExamining) {\r
7012                 if (boardIndex < backwardMostMove) {\r
7013                     /* Oops, gap.  How did that happen? */\r
7014                     return;\r
7015                 }\r
7016                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
7017                 return;\r
7018             }\r
7019             gameInfo.result = moveType;\r
7020             p = strchr(yy_text, '{');\r
7021             if (p == NULL) p = strchr(yy_text, '(');\r
7022             if (p == NULL) {\r
7023                 p = yy_text;\r
7024                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
7025             } else {\r
7026                 q = strchr(p, *p == '{' ? '}' : ')');\r
7027                 if (q != NULL) *q = NULLCHAR;\r
7028                 p++;\r
7029             }\r
7030             gameInfo.resultDetails = StrSave(p);\r
7031             continue;\r
7032         }\r
7033         if (boardIndex >= forwardMostMove &&\r
7034             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
7035             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
7036             return;\r
7037         }\r
7038         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
7039                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
7040                                  parseList[boardIndex]);\r
7041         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
7042         /* currentMoveString is set as a side-effect of yylex */\r
7043         strcpy(moveList[boardIndex], currentMoveString);\r
7044         strcat(moveList[boardIndex], "\n");\r
7045         boardIndex++;\r
7046         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
7047         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
7048                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
7049           case MT_NONE:\r
7050           case MT_STALEMATE:\r
7051           default:\r
7052             break;\r
7053           case MT_CHECK:\r
7054             if(gameInfo.variant != VariantShogi)\r
7055                 strcat(parseList[boardIndex - 1], "+");\r
7056             break;\r
7057           case MT_CHECKMATE:\r
7058             strcat(parseList[boardIndex - 1], "#");\r
7059             break;\r
7060         }\r
7061     }\r
7062 }\r
7063 \r
7064 \r
7065 /* Apply a move to the given board  */\r
7066 void\r
7067 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
7068      int fromX, fromY, toX, toY;\r
7069      int promoChar;\r
7070      Board board;\r
7071 {\r
7072   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
7073 \r
7074     /* [HGM] compute & store e.p. status and castling rights for new position */\r
7075     /* if we are updating a board for which those exist (i.e. in boards[])    */\r
7076     if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)\r
7077     { int i;\r
7078 \r
7079       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
7080       oldEP = epStatus[p-1];\r
7081       epStatus[p] = EP_NONE;\r
7082 \r
7083       if( board[toY][toX] != EmptySquare ) \r
7084            epStatus[p] = EP_CAPTURE;  \r
7085 \r
7086       if( board[fromY][fromX] == WhitePawn ) {\r
7087            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7088                epStatus[p] = EP_PAWN_MOVE;\r
7089            if( toY-fromY==2) {\r
7090                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
7091                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7092                       epStatus[p] = toX | berolina;\r
7093                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
7094                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7095                       epStatus[p] = toX;\r
7096            }\r
7097       } else \r
7098       if( board[fromY][fromX] == BlackPawn ) {\r
7099            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7100                epStatus[p] = EP_PAWN_MOVE; \r
7101            if( toY-fromY== -2) {\r
7102                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
7103                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7104                       epStatus[p] = toX | berolina;\r
7105                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
7106                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7107                       epStatus[p] = toX;\r
7108            }\r
7109        }\r
7110 \r
7111        for(i=0; i<nrCastlingRights; i++) {\r
7112            castlingRights[p][i] = castlingRights[p-1][i];\r
7113            if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||\r
7114               castlingRights[p][i] == toX   && castlingRank[i] == toY   \r
7115              ) castlingRights[p][i] = -1; // revoke for moved or captured piece\r
7116        }\r
7117 \r
7118     }\r
7119 \r
7120   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
7121   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
7122        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
7123          \r
7124   if (fromX == toX && fromY == toY) return;\r
7125 \r
7126   if (fromY == DROP_RANK) {\r
7127         /* must be first */\r
7128         piece = board[toY][toX] = (ChessSquare) fromX;\r
7129   } else {\r
7130      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
7131      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
7132      if(gameInfo.variant == VariantKnightmate)\r
7133          king += (int) WhiteUnicorn - (int) WhiteKing;\r
7134 \r
7135     /* Code added by Tord: */\r
7136     /* FRC castling assumed when king captures friendly rook. */\r
7137     if (board[fromY][fromX] == WhiteKing &&\r
7138              board[toY][toX] == WhiteRook) {\r
7139       board[fromY][fromX] = EmptySquare;\r
7140       board[toY][toX] = EmptySquare;\r
7141       if(toX > fromX) {\r
7142         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
7143       } else {\r
7144         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
7145       }\r
7146     } else if (board[fromY][fromX] == BlackKing &&\r
7147                board[toY][toX] == BlackRook) {\r
7148       board[fromY][fromX] = EmptySquare;\r
7149       board[toY][toX] = EmptySquare;\r
7150       if(toX > fromX) {\r
7151         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
7152       } else {\r
7153         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
7154       }\r
7155     /* End of code added by Tord */\r
7156 \r
7157     } else if (board[fromY][fromX] == king\r
7158         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7159         && toY == fromY && toX > fromX+1) {\r
7160         board[fromY][fromX] = EmptySquare;\r
7161         board[toY][toX] = king;\r
7162         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7163         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7164     } else if (board[fromY][fromX] == king\r
7165         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7166                && toY == fromY && toX < fromX-1) {\r
7167         board[fromY][fromX] = EmptySquare;\r
7168         board[toY][toX] = king;\r
7169         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7170         board[fromY][BOARD_LEFT] = EmptySquare;\r
7171     } else if (board[fromY][fromX] == WhitePawn\r
7172                && toY == BOARD_HEIGHT-1\r
7173                && gameInfo.variant != VariantXiangqi\r
7174                ) {\r
7175         /* white pawn promotion */\r
7176         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
7177         if (board[toY][toX] == EmptySquare) {\r
7178             board[toY][toX] = WhiteQueen;\r
7179         }\r
7180         if(gameInfo.variant==VariantBughouse ||\r
7181            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7182             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7183         board[fromY][fromX] = EmptySquare;\r
7184     } else if ((fromY == BOARD_HEIGHT-4)\r
7185                && (toX != fromX)\r
7186                && gameInfo.variant != VariantXiangqi\r
7187                && gameInfo.variant != VariantBerolina\r
7188                && (board[fromY][fromX] == WhitePawn)\r
7189                && (board[toY][toX] == EmptySquare)) {\r
7190         board[fromY][fromX] = EmptySquare;\r
7191         board[toY][toX] = WhitePawn;\r
7192         captured = board[toY - 1][toX];\r
7193         board[toY - 1][toX] = EmptySquare;\r
7194     } else if ((fromY == BOARD_HEIGHT-4)\r
7195                && (toX == fromX)\r
7196                && gameInfo.variant == VariantBerolina\r
7197                && (board[fromY][fromX] == WhitePawn)\r
7198                && (board[toY][toX] == EmptySquare)) {\r
7199         board[fromY][fromX] = EmptySquare;\r
7200         board[toY][toX] = WhitePawn;\r
7201         if(oldEP & EP_BEROLIN_A) {\r
7202                 captured = board[fromY][fromX-1];\r
7203                 board[fromY][fromX-1] = EmptySquare;\r
7204         }else{  captured = board[fromY][fromX+1];\r
7205                 board[fromY][fromX+1] = EmptySquare;\r
7206         }\r
7207     } else if (board[fromY][fromX] == king\r
7208         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7209                && toY == fromY && toX > fromX+1) {\r
7210         board[fromY][fromX] = EmptySquare;\r
7211         board[toY][toX] = king;\r
7212         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7213         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7214     } else if (board[fromY][fromX] == king\r
7215         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7216                && toY == fromY && toX < fromX-1) {\r
7217         board[fromY][fromX] = EmptySquare;\r
7218         board[toY][toX] = king;\r
7219         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7220         board[fromY][BOARD_LEFT] = EmptySquare;\r
7221     } else if (fromY == 7 && fromX == 3\r
7222                && board[fromY][fromX] == BlackKing\r
7223                && toY == 7 && toX == 5) {\r
7224         board[fromY][fromX] = EmptySquare;\r
7225         board[toY][toX] = BlackKing;\r
7226         board[fromY][7] = EmptySquare;\r
7227         board[toY][4] = BlackRook;\r
7228     } else if (fromY == 7 && fromX == 3\r
7229                && board[fromY][fromX] == BlackKing\r
7230                && toY == 7 && toX == 1) {\r
7231         board[fromY][fromX] = EmptySquare;\r
7232         board[toY][toX] = BlackKing;\r
7233         board[fromY][0] = EmptySquare;\r
7234         board[toY][2] = BlackRook;\r
7235     } else if (board[fromY][fromX] == BlackPawn\r
7236                && toY == 0\r
7237                && gameInfo.variant != VariantXiangqi\r
7238                ) {\r
7239         /* black pawn promotion */\r
7240         board[0][toX] = CharToPiece(ToLower(promoChar));\r
7241         if (board[0][toX] == EmptySquare) {\r
7242             board[0][toX] = BlackQueen;\r
7243         }\r
7244         if(gameInfo.variant==VariantBughouse ||\r
7245            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7246             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7247         board[fromY][fromX] = EmptySquare;\r
7248     } else if ((fromY == 3)\r
7249                && (toX != fromX)\r
7250                && gameInfo.variant != VariantXiangqi\r
7251                && gameInfo.variant != VariantBerolina\r
7252                && (board[fromY][fromX] == BlackPawn)\r
7253                && (board[toY][toX] == EmptySquare)) {\r
7254         board[fromY][fromX] = EmptySquare;\r
7255         board[toY][toX] = BlackPawn;\r
7256         captured = board[toY + 1][toX];\r
7257         board[toY + 1][toX] = EmptySquare;\r
7258     } else if ((fromY == 3)\r
7259                && (toX == fromX)\r
7260                && gameInfo.variant == VariantBerolina\r
7261                && (board[fromY][fromX] == BlackPawn)\r
7262                && (board[toY][toX] == EmptySquare)) {\r
7263         board[fromY][fromX] = EmptySquare;\r
7264         board[toY][toX] = BlackPawn;\r
7265         if(oldEP & EP_BEROLIN_A) {\r
7266                 captured = board[fromY][fromX-1];\r
7267                 board[fromY][fromX-1] = EmptySquare;\r
7268         }else{  captured = board[fromY][fromX+1];\r
7269                 board[fromY][fromX+1] = EmptySquare;\r
7270         }\r
7271     } else {\r
7272         board[toY][toX] = board[fromY][fromX];\r
7273         board[fromY][fromX] = EmptySquare;\r
7274     }\r
7275 \r
7276     /* [HGM] now we promote for Shogi, if needed */\r
7277     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
7278         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7279   }\r
7280 \r
7281     if (gameInfo.holdingsWidth != 0) {\r
7282 \r
7283       /* !!A lot more code needs to be written to support holdings  */\r
7284       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
7285       /* penultimate board files, so they are automaticlly stored   */\r
7286       /* in the game history.                                       */\r
7287       if (fromY == DROP_RANK) {\r
7288         /* Delete from holdings, by decreasing count */\r
7289         /* and erasing image if necessary            */\r
7290         p = (int) fromX;\r
7291         if(p < (int) BlackPawn) { /* white drop */\r
7292              p -= (int)WhitePawn;\r
7293              if(p >= gameInfo.holdingsSize) p = 0;\r
7294              if(--board[p][BOARD_WIDTH-2] == 0)\r
7295                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
7296         } else {                  /* black drop */\r
7297              p -= (int)BlackPawn;\r
7298              if(p >= gameInfo.holdingsSize) p = 0;\r
7299              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
7300                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
7301         }\r
7302       }\r
7303       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
7304           && gameInfo.variant != VariantBughouse        ) {\r
7305         /* [HGM] holdings: Add to holdings, if holdings exist */\r
7306         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { \r
7307                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
7308                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
7309         }\r
7310         p = (int) captured;\r
7311         if (p >= (int) BlackPawn) {\r
7312           p -= (int)BlackPawn;\r
7313           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7314                   /* in Shogi restore piece to its original  first */\r
7315                   captured = (ChessSquare) (DEMOTED captured);\r
7316                   p = DEMOTED p;\r
7317           }\r
7318           p = PieceToNumber((ChessSquare)p);\r
7319           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
7320           board[p][BOARD_WIDTH-2]++;\r
7321           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;\r
7322         } else {\r
7323           p -= (int)WhitePawn;\r
7324           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7325                   captured = (ChessSquare) (DEMOTED captured);\r
7326                   p = DEMOTED p;\r
7327           }\r
7328           p = PieceToNumber((ChessSquare)p);\r
7329           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
7330           board[BOARD_HEIGHT-1-p][1]++;\r
7331           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;\r
7332         }\r
7333       }\r
7334 \r
7335     } else if (gameInfo.variant == VariantAtomic) {\r
7336       if (captured != EmptySquare) {\r
7337         int y, x;\r
7338         for (y = toY-1; y <= toY+1; y++) {\r
7339           for (x = toX-1; x <= toX+1; x++) {\r
7340             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
7341                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
7342               board[y][x] = EmptySquare;\r
7343             }\r
7344           }\r
7345         }\r
7346         board[toY][toX] = EmptySquare;\r
7347       }\r
7348     }\r
7349     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
7350         /* [HGM] Shogi promotions */\r
7351         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7352     }\r
7353 \r
7354 }\r
7355 \r
7356 /* Updates forwardMostMove */\r
7357 void\r
7358 MakeMove(fromX, fromY, toX, toY, promoChar)\r
7359      int fromX, fromY, toX, toY;\r
7360      int promoChar;\r
7361 {\r
7362 //    forwardMostMove++; // [HGM] bare: moved downstream\r
7363 \r
7364     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */\r
7365         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
7366         piece = boards[forwardMostMove][fromY][fromX];\r
7367         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
7368         if(gameInfo.variant == VariantKnightmate)\r
7369             king += (int) WhiteUnicorn - (int) WhiteKing;\r
7370         if(forwardMostMove == 0) {\r
7371             if(blackPlaysFirst) \r
7372                 fprintf(serverMoves, "%s;", second.tidy);\r
7373             fprintf(serverMoves, "%s;", first.tidy);\r
7374             if(!blackPlaysFirst) \r
7375                 fprintf(serverMoves, "%s;", second.tidy);\r
7376         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
7377         lastLoadFlag = loadFlag;\r
7378         // print base move\r
7379         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
7380         // print castling suffix\r
7381         if( toY == fromY && piece == king ) {\r
7382             if(toX-fromX > 1)\r
7383                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
7384             if(fromX-toX >1)\r
7385                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
7386         }\r
7387         // e.p. suffix\r
7388         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||\r
7389              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&\r
7390              boards[forwardMostMove][toY][toX] == EmptySquare\r
7391              && fromX != toX )\r
7392                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
7393         // promotion suffix\r
7394         if(promoChar != NULLCHAR)\r
7395                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
7396         if(!loadFlag) {\r
7397             fprintf(serverMoves, "/%d/%d",\r
7398                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);\r
7399             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;\r
7400             else                      timeLeft = blackTimeRemaining/1000;\r
7401             fprintf(serverMoves, "/%d", timeLeft);\r
7402         }\r
7403         fflush(serverMoves);\r
7404     }\r
7405 \r
7406     if (forwardMostMove+1 >= MAX_MOVES) {\r
7407       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
7408                         0, 1);\r
7409       return;\r
7410     }\r
7411     SwitchClocks();\r
7412     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;\r
7413     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;\r
7414     if (commentList[forwardMostMove+1] != NULL) {\r
7415         free(commentList[forwardMostMove+1]);\r
7416         commentList[forwardMostMove+1] = NULL;\r
7417     }\r
7418     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);\r
7419     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);\r
7420     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board\r
7421     gameInfo.result = GameUnfinished;\r
7422     if (gameInfo.resultDetails != NULL) {\r
7423         free(gameInfo.resultDetails);\r
7424         gameInfo.resultDetails = NULL;\r
7425     }\r
7426     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
7427                               moveList[forwardMostMove - 1]);\r
7428     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
7429                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
7430                              fromY, fromX, toY, toX, promoChar,\r
7431                              parseList[forwardMostMove - 1]);\r
7432     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
7433                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
7434                             castlingRights[forwardMostMove]) ) {\r
7435       case MT_NONE:\r
7436       case MT_STALEMATE:\r
7437       default:\r
7438         break;\r
7439       case MT_CHECK:\r
7440         if(gameInfo.variant != VariantShogi)\r
7441             strcat(parseList[forwardMostMove - 1], "+");\r
7442         break;\r
7443       case MT_CHECKMATE:\r
7444         strcat(parseList[forwardMostMove - 1], "#");\r
7445         break;\r
7446     }\r
7447     if (appData.debugMode) {\r
7448         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
7449     }\r
7450 \r
7451 }\r
7452 \r
7453 /* Updates currentMove if not pausing */\r
7454 void\r
7455 ShowMove(fromX, fromY, toX, toY)\r
7456 {\r
7457     int instant = (gameMode == PlayFromGameFile) ?\r
7458         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
7459     if(appData.noGUI) return;\r
7460     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
7461         if (!instant) {\r
7462             if (forwardMostMove == currentMove + 1) {\r
7463                 AnimateMove(boards[forwardMostMove - 1],\r
7464                             fromX, fromY, toX, toY);\r
7465             }\r
7466             if (appData.highlightLastMove) {\r
7467                 SetHighlights(fromX, fromY, toX, toY);\r
7468             }\r
7469         }\r
7470         currentMove = forwardMostMove;\r
7471     }\r
7472 \r
7473     if (instant) return;\r
7474 \r
7475     DisplayMove(currentMove - 1);\r
7476     DrawPosition(FALSE, boards[currentMove]);\r
7477     DisplayBothClocks();\r
7478     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
7479 }\r
7480 \r
7481 void SendEgtPath(ChessProgramState *cps)\r
7482 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */\r
7483         char buf[MSG_SIZ], name[MSG_SIZ], *p;\r
7484 \r
7485         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;\r
7486 \r
7487         while(*p) {\r
7488             char c, *q = name+1, *r, *s;\r
7489 \r
7490             name[0] = ','; // extract next format name from feature and copy with prefixed ','\r
7491             while(*p && *p != ',') *q++ = *p++;\r
7492             *q++ = ':'; *q = 0;\r
7493             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && \r
7494                 strcmp(name, ",nalimov:") == 0 ) {\r
7495                 // take nalimov path from the menu-changeable option first, if it is defined\r
7496                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);\r
7497                 SendToProgram(buf,cps);     // send egtbpath command for nalimov\r
7498             } else\r
7499             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||\r
7500                 (s = StrStr(appData.egtFormats, name)) != NULL) {\r
7501                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma\r
7502                 s = r = StrStr(s, ":") + 1; // beginning of path info\r
7503                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string\r
7504                 c = *r; *r = 0;             // temporarily null-terminate path info\r
7505                     *--q = 0;               // strip of trailig ':' from name\r
7506                     sprintf(buf, "egtbpath %s %s\n", name+1, s);\r
7507                 *r = c;\r
7508                 SendToProgram(buf,cps);     // send egtbpath command for this format\r
7509             }\r
7510             if(*p == ',') p++; // read away comma to position for next format name\r
7511         }\r
7512 }\r
7513 \r
7514 void\r
7515 InitChessProgram(cps, setup)\r
7516      ChessProgramState *cps;\r
7517      int setup; /* [HGM] needed to setup FRC opening position */\r
7518 {\r
7519     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
7520     if (appData.noChessProgram) return;\r
7521     hintRequested = FALSE;\r
7522     bookRequested = FALSE;\r
7523 \r
7524     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */\r
7525     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */\r
7526     if(cps->memSize) { /* [HGM] memory */\r
7527         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);\r
7528         SendToProgram(buf, cps);\r
7529     }\r
7530     SendEgtPath(cps); /* [HGM] EGT */\r
7531     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */\r
7532         sprintf(buf, "cores %d\n", appData.smpCores);\r
7533         SendToProgram(buf, cps);\r
7534     }\r
7535 \r
7536     SendToProgram(cps->initString, cps);\r
7537     if (gameInfo.variant != VariantNormal &&\r
7538         gameInfo.variant != VariantLoadable\r
7539         /* [HGM] also send variant if board size non-standard */\r
7540         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
7541                                             ) {\r
7542       char *v = VariantName(gameInfo.variant);\r
7543       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
7544         /* [HGM] in protocol 1 we have to assume all variants valid */\r
7545         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);\r
7546         DisplayFatalError(buf, 0, 1);\r
7547         return;\r
7548       }\r
7549 \r
7550       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
7551       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7552       if( gameInfo.variant == VariantXiangqi )\r
7553            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
7554       if( gameInfo.variant == VariantShogi )\r
7555            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
7556       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
7557            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
7558       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
7559                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
7560            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7561       if( gameInfo.variant == VariantCourier )\r
7562            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7563       if( gameInfo.variant == VariantSuper )\r
7564            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7565       if( gameInfo.variant == VariantGreat )\r
7566            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7567 \r
7568       if(overruled) {\r
7569            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
7570                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
7571            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
7572            if(StrStr(cps->variants, b) == NULL) { \r
7573                // specific sized variant not known, check if general sizing allowed\r
7574                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
7575                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
7576                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
7577                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
7578                        DisplayFatalError(buf, 0, 1);\r
7579                        return;\r
7580                    }\r
7581                    /* [HGM] here we really should compare with the maximum supported board size */\r
7582                }\r
7583            }\r
7584       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
7585       sprintf(buf, "variant %s\n", b);\r
7586       SendToProgram(buf, cps);\r
7587     }\r
7588     currentlyInitializedVariant = gameInfo.variant;\r
7589 \r
7590     /* [HGM] send opening position in FRC to first engine */\r
7591     if(setup) {\r
7592           SendToProgram("force\n", cps);\r
7593           SendBoard(cps, 0);\r
7594           /* engine is now in force mode! Set flag to wake it up after first move. */\r
7595           setboardSpoiledMachineBlack = 1;\r
7596     }\r
7597 \r
7598     if (cps->sendICS) {\r
7599       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
7600       SendToProgram(buf, cps);\r
7601     }\r
7602     cps->maybeThinking = FALSE;\r
7603     cps->offeredDraw = 0;\r
7604     if (!appData.icsActive) {\r
7605         SendTimeControl(cps, movesPerSession, timeControl,\r
7606                         timeIncrement, appData.searchDepth,\r
7607                         searchTime);\r
7608     }\r
7609     if (appData.showThinking \r
7610         // [HGM] thinking: four options require thinking output to be sent\r
7611         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
7612                                 ) {\r
7613         SendToProgram("post\n", cps);\r
7614     }\r
7615     SendToProgram("hard\n", cps);\r
7616     if (!appData.ponderNextMove) {\r
7617         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
7618            it without being sure what state we are in first.  "hard"\r
7619            is not a toggle, so that one is OK.\r
7620          */\r
7621         SendToProgram("easy\n", cps);\r
7622     }\r
7623     if (cps->usePing) {\r
7624       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
7625       SendToProgram(buf, cps);\r
7626     }\r
7627     cps->initDone = TRUE;\r
7628 }   \r
7629 \r
7630 \r
7631 void\r
7632 StartChessProgram(cps)\r
7633      ChessProgramState *cps;\r
7634 {\r
7635     char buf[MSG_SIZ];\r
7636     int err;\r
7637 \r
7638     if (appData.noChessProgram) return;\r
7639     cps->initDone = FALSE;\r
7640 \r
7641     if (strcmp(cps->host, "localhost") == 0) {\r
7642         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
7643     } else if (*appData.remoteShell == NULLCHAR) {\r
7644         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
7645     } else {\r
7646         if (*appData.remoteUser == NULLCHAR) {\r
7647             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
7648                     cps->program);\r
7649         } else {\r
7650             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
7651                     cps->host, appData.remoteUser, cps->program);\r
7652         }\r
7653         err = StartChildProcess(buf, "", &cps->pr);\r
7654     }\r
7655     \r
7656     if (err != 0) {\r
7657         sprintf(buf, _("Startup failure on '%s'"), cps->program);\r
7658         DisplayFatalError(buf, err, 1);\r
7659         cps->pr = NoProc;\r
7660         cps->isr = NULL;\r
7661         return;\r
7662     }\r
7663     \r
7664     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
7665     if (cps->protocolVersion > 1) {\r
7666       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
7667       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options\r
7668       cps->comboCnt = 0;  //                and values of combo boxes\r
7669       SendToProgram(buf, cps);\r
7670     } else {\r
7671       SendToProgram("xboard\n", cps);\r
7672     }\r
7673 }\r
7674 \r
7675 \r
7676 void\r
7677 TwoMachinesEventIfReady P((void))\r
7678 {\r
7679   if (first.lastPing != first.lastPong) {\r
7680     DisplayMessage("", _("Waiting for first chess program"));\r
7681     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7682     return;\r
7683   }\r
7684   if (second.lastPing != second.lastPong) {\r
7685     DisplayMessage("", _("Waiting for second chess program"));\r
7686     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7687     return;\r
7688   }\r
7689   ThawUI();\r
7690   TwoMachinesEvent();\r
7691 }\r
7692 \r
7693 void\r
7694 NextMatchGame P((void))\r
7695 {\r
7696     int index; /* [HGM] autoinc: step lod index during match */\r
7697     Reset(FALSE, TRUE);\r
7698     if (*appData.loadGameFile != NULLCHAR) {\r
7699         index = appData.loadGameIndex;\r
7700         if(index < 0) { // [HGM] autoinc\r
7701             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7702             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7703         } \r
7704         LoadGameFromFile(appData.loadGameFile,\r
7705                          index,\r
7706                          appData.loadGameFile, FALSE);\r
7707     } else if (*appData.loadPositionFile != NULLCHAR) {\r
7708         index = appData.loadPositionIndex;\r
7709         if(index < 0) { // [HGM] autoinc\r
7710             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7711             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7712         } \r
7713         LoadPositionFromFile(appData.loadPositionFile,\r
7714                              index,\r
7715                              appData.loadPositionFile);\r
7716     }\r
7717     TwoMachinesEventIfReady();\r
7718 }\r
7719 \r
7720 void UserAdjudicationEvent( int result )\r
7721 {\r
7722     ChessMove gameResult = GameIsDrawn;\r
7723 \r
7724     if( result > 0 ) {\r
7725         gameResult = WhiteWins;\r
7726     }\r
7727     else if( result < 0 ) {\r
7728         gameResult = BlackWins;\r
7729     }\r
7730 \r
7731     if( gameMode == TwoMachinesPlay ) {\r
7732         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
7733     }\r
7734 }\r
7735 \r
7736 \r
7737 void\r
7738 GameEnds(result, resultDetails, whosays)\r
7739      ChessMove result;\r
7740      char *resultDetails;\r
7741      int whosays;\r
7742 {\r
7743     GameMode nextGameMode;\r
7744     int isIcsGame;\r
7745     char buf[MSG_SIZ];\r
7746 \r
7747     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
7748     endingGame = 1;\r
7749 \r
7750     if (appData.debugMode) {\r
7751       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
7752               result, resultDetails ? resultDetails : "(null)", whosays);\r
7753     }\r
7754 \r
7755     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
7756         /* If we are playing on ICS, the server decides when the\r
7757            game is over, but the engine can offer to draw, claim \r
7758            a draw, or resign. \r
7759          */\r
7760 #if ZIPPY\r
7761         if (appData.zippyPlay && first.initDone) {\r
7762             if (result == GameIsDrawn) {\r
7763                 /* In case draw still needs to be claimed */\r
7764                 SendToICS(ics_prefix);\r
7765                 SendToICS("draw\n");\r
7766             } else if (StrCaseStr(resultDetails, "resign")) {\r
7767                 SendToICS(ics_prefix);\r
7768                 SendToICS("resign\n");\r
7769             }\r
7770         }\r
7771 #endif\r
7772         endingGame = 0; /* [HGM] crash */\r
7773         return;\r
7774     }\r
7775 \r
7776     /* If we're loading the game from a file, stop */\r
7777     if (whosays == GE_FILE) {\r
7778       (void) StopLoadGameTimer();\r
7779       gameFileFP = NULL;\r
7780     }\r
7781 \r
7782     /* Cancel draw offers */\r
7783     first.offeredDraw = second.offeredDraw = 0;\r
7784 \r
7785     /* If this is an ICS game, only ICS can really say it's done;\r
7786        if not, anyone can. */\r
7787     isIcsGame = (gameMode == IcsPlayingWhite || \r
7788                  gameMode == IcsPlayingBlack || \r
7789                  gameMode == IcsObserving    || \r
7790                  gameMode == IcsExamining);\r
7791 \r
7792     if (!isIcsGame || whosays == GE_ICS) {\r
7793         /* OK -- not an ICS game, or ICS said it was done */\r
7794         StopClocks();\r
7795         if (!isIcsGame && !appData.noChessProgram) \r
7796           SetUserThinkingEnables();\r
7797     \r
7798         /* [HGM] if a machine claims the game end we verify this claim */\r
7799         if(gameMode == TwoMachinesPlay && appData.testClaims) {\r
7800             if(appData.testLegality && whosays >= GE_ENGINE1 ) {\r
7801                 char claimer;\r
7802 \r
7803                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
7804                                             first.twoMachinesColor[0] :\r
7805                                             second.twoMachinesColor[0] ;\r
7806                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) &&\r
7807                     (result == WhiteWins && claimer == 'w' ||\r
7808                      result == BlackWins && claimer == 'b'   ) ) {\r
7809                 if (appData.debugMode) {\r
7810                      fprintf(debugFP, "result=%d sp=%d move=%d\n",\r
7811                         result, epStatus[forwardMostMove], forwardMostMove);\r
7812                 }\r
7813                       /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7814                       if(epStatus[forwardMostMove] != EP_CHECKMATE &&\r
7815                          result != (WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins)) {\r
7816                               sprintf(buf, "False win claim: '%s'", resultDetails);\r
7817                               result = claimer == 'w' ? BlackWins : WhiteWins;\r
7818                               resultDetails = buf;\r
7819                       }\r
7820                 } else\r
7821                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
7822                     && (forwardMostMove <= backwardMostMove ||\r
7823                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
7824                         (claimer=='b')==(forwardMostMove&1))\r
7825                                                                                   ) {\r
7826                       /* [HGM] verify: draws that were not flagged are false claims */\r
7827                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
7828                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7829                       resultDetails = buf;\r
7830                 }\r
7831                 /* (Claiming a loss is accepted no questions asked!) */\r
7832             }\r
7833             /* [HGM] bare: don't allow bare King to win */\r
7834             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)\r
7835                          && result != GameIsDrawn)\r
7836             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
7837                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
7838                         int p = (int)boards[forwardMostMove][i][j] - color;\r
7839                         if(p >= 0 && p <= (int)WhiteKing) k++;\r
7840                 }\r
7841                 if (appData.debugMode) {\r
7842                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",\r
7843                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);\r
7844                 }\r
7845                 if(k <= 1) {\r
7846                         result = GameIsDrawn;\r
7847                         sprintf(buf, "%s but bare king", resultDetails);\r
7848                         resultDetails = buf;\r
7849                 }\r
7850             }\r
7851         }\r
7852 \r
7853 \r
7854         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
7855             if(result==WhiteWins) c = '+';\r
7856             if(result==BlackWins) c = '-';\r
7857             if(resultDetails != NULL)\r
7858                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
7859         }\r
7860         if (resultDetails != NULL) {\r
7861             gameInfo.result = result;\r
7862             gameInfo.resultDetails = StrSave(resultDetails);\r
7863 \r
7864             /* display last move only if game was not loaded from file */\r
7865             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7866                 DisplayMove(currentMove - 1);\r
7867     \r
7868             if (forwardMostMove != 0) {\r
7869                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7870                     if (*appData.saveGameFile != NULLCHAR) {\r
7871                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7872                     } else if (appData.autoSaveGames) {\r
7873                         AutoSaveGame();\r
7874                     }\r
7875                     if (*appData.savePositionFile != NULLCHAR) {\r
7876                         SavePositionToFile(appData.savePositionFile);\r
7877                     }\r
7878                 }\r
7879             }\r
7880 \r
7881             /* Tell program how game ended in case it is learning */\r
7882             /* [HGM] Moved this to after saving the PGN, just in case */\r
7883             /* engine died and we got here through time loss. In that */\r
7884             /* case we will get a fatal error writing the pipe, which */\r
7885             /* would otherwise lose us the PGN.                       */\r
7886             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7887             /* output during GameEnds should never be fatal anymore   */\r
7888             if (gameMode == MachinePlaysWhite ||\r
7889                 gameMode == MachinePlaysBlack ||\r
7890                 gameMode == TwoMachinesPlay ||\r
7891                 gameMode == IcsPlayingWhite ||\r
7892                 gameMode == IcsPlayingBlack ||\r
7893                 gameMode == BeginningOfGame) {\r
7894                 char buf[MSG_SIZ];\r
7895                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7896                         resultDetails);\r
7897                 if (first.pr != NoProc) {\r
7898                     SendToProgram(buf, &first);\r
7899                 }\r
7900                 if (second.pr != NoProc &&\r
7901                     gameMode == TwoMachinesPlay) {\r
7902                     SendToProgram(buf, &second);\r
7903                 }\r
7904             }\r
7905         }\r
7906 \r
7907         if (appData.icsActive) {\r
7908             if (appData.quietPlay &&\r
7909                 (gameMode == IcsPlayingWhite ||\r
7910                  gameMode == IcsPlayingBlack)) {\r
7911                 SendToICS(ics_prefix);\r
7912                 SendToICS("set shout 1\n");\r
7913             }\r
7914             nextGameMode = IcsIdle;\r
7915             ics_user_moved = FALSE;\r
7916             /* clean up premove.  It's ugly when the game has ended and the\r
7917              * premove highlights are still on the board.\r
7918              */\r
7919             if (gotPremove) {\r
7920               gotPremove = FALSE;\r
7921               ClearPremoveHighlights();\r
7922               DrawPosition(FALSE, boards[currentMove]);\r
7923             }\r
7924             if (whosays == GE_ICS) {\r
7925                 switch (result) {\r
7926                 case WhiteWins:\r
7927                     if (gameMode == IcsPlayingWhite)\r
7928                         PlayIcsWinSound();\r
7929                     else if(gameMode == IcsPlayingBlack)\r
7930                         PlayIcsLossSound();\r
7931                     break;\r
7932                 case BlackWins:\r
7933                     if (gameMode == IcsPlayingBlack)\r
7934                         PlayIcsWinSound();\r
7935                     else if(gameMode == IcsPlayingWhite)\r
7936                         PlayIcsLossSound();\r
7937                     break;\r
7938                 case GameIsDrawn:\r
7939                     PlayIcsDrawSound();\r
7940                     break;\r
7941                 default:\r
7942                     PlayIcsUnfinishedSound();\r
7943                 }\r
7944             }\r
7945         } else if (gameMode == EditGame ||\r
7946                    gameMode == PlayFromGameFile || \r
7947                    gameMode == AnalyzeMode || \r
7948                    gameMode == AnalyzeFile) {\r
7949             nextGameMode = gameMode;\r
7950         } else {\r
7951             nextGameMode = EndOfGame;\r
7952         }\r
7953         pausing = FALSE;\r
7954         ModeHighlight();\r
7955     } else {\r
7956         nextGameMode = gameMode;\r
7957     }\r
7958 \r
7959     if (appData.noChessProgram) {\r
7960         gameMode = nextGameMode;\r
7961         ModeHighlight();\r
7962         endingGame = 0; /* [HGM] crash */\r
7963         return;\r
7964     }\r
7965 \r
7966     if (first.reuse) {\r
7967         /* Put first chess program into idle state */\r
7968         if (first.pr != NoProc &&\r
7969             (gameMode == MachinePlaysWhite ||\r
7970              gameMode == MachinePlaysBlack ||\r
7971              gameMode == TwoMachinesPlay ||\r
7972              gameMode == IcsPlayingWhite ||\r
7973              gameMode == IcsPlayingBlack ||\r
7974              gameMode == BeginningOfGame)) {\r
7975             SendToProgram("force\n", &first);\r
7976             if (first.usePing) {\r
7977               char buf[MSG_SIZ];\r
7978               sprintf(buf, "ping %d\n", ++first.lastPing);\r
7979               SendToProgram(buf, &first);\r
7980             }\r
7981         }\r
7982     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7983         /* Kill off first chess program */\r
7984         if (first.isr != NULL)\r
7985           RemoveInputSource(first.isr);\r
7986         first.isr = NULL;\r
7987     \r
7988         if (first.pr != NoProc) {\r
7989             ExitAnalyzeMode();\r
7990             DoSleep( appData.delayBeforeQuit );\r
7991             SendToProgram("quit\n", &first);\r
7992             DoSleep( appData.delayAfterQuit );\r
7993             DestroyChildProcess(first.pr, first.useSigterm);\r
7994         }\r
7995         first.pr = NoProc;\r
7996     }\r
7997     if (second.reuse) {\r
7998         /* Put second chess program into idle state */\r
7999         if (second.pr != NoProc &&\r
8000             gameMode == TwoMachinesPlay) {\r
8001             SendToProgram("force\n", &second);\r
8002             if (second.usePing) {\r
8003               char buf[MSG_SIZ];\r
8004               sprintf(buf, "ping %d\n", ++second.lastPing);\r
8005               SendToProgram(buf, &second);\r
8006             }\r
8007         }\r
8008     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
8009         /* Kill off second chess program */\r
8010         if (second.isr != NULL)\r
8011           RemoveInputSource(second.isr);\r
8012         second.isr = NULL;\r
8013     \r
8014         if (second.pr != NoProc) {\r
8015             DoSleep( appData.delayBeforeQuit );\r
8016             SendToProgram("quit\n", &second);\r
8017             DoSleep( appData.delayAfterQuit );\r
8018             DestroyChildProcess(second.pr, second.useSigterm);\r
8019         }\r
8020         second.pr = NoProc;\r
8021     }\r
8022 \r
8023     if (matchMode && gameMode == TwoMachinesPlay) {\r
8024         switch (result) {\r
8025         case WhiteWins:\r
8026           if (first.twoMachinesColor[0] == 'w') {\r
8027             first.matchWins++;\r
8028           } else {\r
8029             second.matchWins++;\r
8030           }\r
8031           break;\r
8032         case BlackWins:\r
8033           if (first.twoMachinesColor[0] == 'b') {\r
8034             first.matchWins++;\r
8035           } else {\r
8036             second.matchWins++;\r
8037           }\r
8038           break;\r
8039         default:\r
8040           break;\r
8041         }\r
8042         if (matchGame < appData.matchGames) {\r
8043             char *tmp;\r
8044             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */\r
8045                 tmp = first.twoMachinesColor;\r
8046                 first.twoMachinesColor = second.twoMachinesColor;\r
8047                 second.twoMachinesColor = tmp;\r
8048             }\r
8049             gameMode = nextGameMode;\r
8050             matchGame++;\r
8051             if(appData.matchPause>10000 || appData.matchPause<10)\r
8052                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
8053             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
8054             endingGame = 0; /* [HGM] crash */\r
8055             return;\r
8056         } else {\r
8057             char buf[MSG_SIZ];\r
8058             gameMode = nextGameMode;\r
8059             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),\r
8060                     first.tidy, second.tidy,\r
8061                     first.matchWins, second.matchWins,\r
8062                     appData.matchGames - (first.matchWins + second.matchWins));\r
8063             DisplayFatalError(buf, 0, 0);\r
8064         }\r
8065     }\r
8066     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
8067         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
8068       ExitAnalyzeMode();\r
8069     gameMode = nextGameMode;\r
8070     ModeHighlight();\r
8071     endingGame = 0;  /* [HGM] crash */\r
8072 }\r
8073 \r
8074 /* Assumes program was just initialized (initString sent).\r
8075    Leaves program in force mode. */\r
8076 void\r
8077 FeedMovesToProgram(cps, upto) \r
8078      ChessProgramState *cps;\r
8079      int upto;\r
8080 {\r
8081     int i;\r
8082     \r
8083     if (appData.debugMode)\r
8084       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
8085               startedFromSetupPosition ? "position and " : "",\r
8086               backwardMostMove, upto, cps->which);\r
8087     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
8088         // [HGM] variantswitch: make engine aware of new variant\r
8089         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
8090                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
8091         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
8092         SendToProgram(buf, cps);\r
8093         currentlyInitializedVariant = gameInfo.variant;\r
8094     }\r
8095     SendToProgram("force\n", cps);\r
8096     if (startedFromSetupPosition) {\r
8097         SendBoard(cps, backwardMostMove);\r
8098     if (appData.debugMode) {\r
8099         fprintf(debugFP, "feedMoves\n");\r
8100     }\r
8101     }\r
8102     for (i = backwardMostMove; i < upto; i++) {\r
8103         SendMoveToProgram(i, cps);\r
8104     }\r
8105 }\r
8106 \r
8107 \r
8108 void\r
8109 ResurrectChessProgram()\r
8110 {\r
8111      /* The chess program may have exited.\r
8112         If so, restart it and feed it all the moves made so far. */\r
8113 \r
8114     if (appData.noChessProgram || first.pr != NoProc) return;\r
8115     \r
8116     StartChessProgram(&first);\r
8117     InitChessProgram(&first, FALSE);\r
8118     FeedMovesToProgram(&first, currentMove);\r
8119 \r
8120     if (!first.sendTime) {\r
8121         /* can't tell gnuchess what its clock should read,\r
8122            so we bow to its notion. */\r
8123         ResetClocks();\r
8124         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
8125         timeRemaining[1][currentMove] = blackTimeRemaining;\r
8126     }\r
8127 \r
8128     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||\r
8129                 appData.icsEngineAnalyze) && first.analysisSupport) {\r
8130       SendToProgram("analyze\n", &first);\r
8131       first.analyzing = TRUE;\r
8132     }\r
8133 }\r
8134 \r
8135 /*\r
8136  * Button procedures\r
8137  */\r
8138 void\r
8139 Reset(redraw, init)\r
8140      int redraw, init;\r
8141 {\r
8142     int i;\r
8143 \r
8144     if (appData.debugMode) {\r
8145         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
8146                 redraw, init, gameMode);\r
8147     }\r
8148     pausing = pauseExamInvalid = FALSE;\r
8149     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
8150     firstMove = TRUE;\r
8151     whiteFlag = blackFlag = FALSE;\r
8152     userOfferedDraw = FALSE;\r
8153     hintRequested = bookRequested = FALSE;\r
8154     first.maybeThinking = FALSE;\r
8155     second.maybeThinking = FALSE;\r
8156     first.bookSuspend = FALSE; // [HGM] book\r
8157     second.bookSuspend = FALSE;\r
8158     thinkOutput[0] = NULLCHAR;\r
8159     lastHint[0] = NULLCHAR;\r
8160     ClearGameInfo(&gameInfo);\r
8161     gameInfo.variant = StringToVariant(appData.variant);\r
8162     ics_user_moved = ics_clock_paused = FALSE;\r
8163     ics_getting_history = H_FALSE;\r
8164     ics_gamenum = -1;\r
8165     white_holding[0] = black_holding[0] = NULLCHAR;\r
8166     ClearProgramStats();\r
8167     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode\r
8168     \r
8169     ResetFrontEnd();\r
8170     ClearHighlights();\r
8171     flipView = appData.flipView;\r
8172     ClearPremoveHighlights();\r
8173     gotPremove = FALSE;\r
8174     alarmSounded = FALSE;\r
8175 \r
8176     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8177     if(appData.serverMovesName != NULL) {\r
8178         /* [HGM] prepare to make moves file for broadcasting */\r
8179         clock_t t = clock();\r
8180         if(serverMoves != NULL) fclose(serverMoves);\r
8181         serverMoves = fopen(appData.serverMovesName, "r");\r
8182         if(serverMoves != NULL) {\r
8183             fclose(serverMoves);\r
8184             /* delay 15 sec before overwriting, so all clients can see end */\r
8185             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
8186         }\r
8187         serverMoves = fopen(appData.serverMovesName, "w");\r
8188     }\r
8189 \r
8190     ExitAnalyzeMode();\r
8191     gameMode = BeginningOfGame;\r
8192     ModeHighlight();\r
8193     if(appData.icsActive) gameInfo.variant = VariantNormal;\r
8194     InitPosition(redraw);\r
8195     for (i = 0; i < MAX_MOVES; i++) {\r
8196         if (commentList[i] != NULL) {\r
8197             free(commentList[i]);\r
8198             commentList[i] = NULL;\r
8199         }\r
8200     }\r
8201     ResetClocks();\r
8202     timeRemaining[0][0] = whiteTimeRemaining;\r
8203     timeRemaining[1][0] = blackTimeRemaining;\r
8204     if (first.pr == NULL) {\r
8205         StartChessProgram(&first);\r
8206     }\r
8207     if (init) {\r
8208             InitChessProgram(&first, startedFromSetupPosition);\r
8209     }\r
8210     DisplayTitle("");\r
8211     DisplayMessage("", "");\r
8212     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8213 }\r
8214 \r
8215 void\r
8216 AutoPlayGameLoop()\r
8217 {\r
8218     for (;;) {\r
8219         if (!AutoPlayOneMove())\r
8220           return;\r
8221         if (matchMode || appData.timeDelay == 0)\r
8222           continue;\r
8223         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
8224           return;\r
8225         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
8226         break;\r
8227     }\r
8228 }\r
8229 \r
8230 \r
8231 int\r
8232 AutoPlayOneMove()\r
8233 {\r
8234     int fromX, fromY, toX, toY;\r
8235 \r
8236     if (appData.debugMode) {\r
8237       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
8238     }\r
8239 \r
8240     if (gameMode != PlayFromGameFile)\r
8241       return FALSE;\r
8242 \r
8243     if (currentMove >= forwardMostMove) {\r
8244       gameMode = EditGame;\r
8245       ModeHighlight();\r
8246 \r
8247       /* [AS] Clear current move marker at the end of a game */\r
8248       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
8249 \r
8250       return FALSE;\r
8251     }\r
8252     \r
8253     toX = moveList[currentMove][2] - AAA;\r
8254     toY = moveList[currentMove][3] - ONE;\r
8255 \r
8256     if (moveList[currentMove][1] == '@') {\r
8257         if (appData.highlightLastMove) {\r
8258             SetHighlights(-1, -1, toX, toY);\r
8259         }\r
8260     } else {\r
8261         fromX = moveList[currentMove][0] - AAA;\r
8262         fromY = moveList[currentMove][1] - ONE;\r
8263 \r
8264         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
8265 \r
8266         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
8267 \r
8268         if (appData.highlightLastMove) {\r
8269             SetHighlights(fromX, fromY, toX, toY);\r
8270         }\r
8271     }\r
8272     DisplayMove(currentMove);\r
8273     SendMoveToProgram(currentMove++, &first);\r
8274     DisplayBothClocks();\r
8275     DrawPosition(FALSE, boards[currentMove]);\r
8276     // [HGM] PV info: always display, routine tests if empty\r
8277     DisplayComment(currentMove - 1, commentList[currentMove]);\r
8278     return TRUE;\r
8279 }\r
8280 \r
8281 \r
8282 int\r
8283 LoadGameOneMove(readAhead)\r
8284      ChessMove readAhead;\r
8285 {\r
8286     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
8287     char promoChar = NULLCHAR;\r
8288     ChessMove moveType;\r
8289     char move[MSG_SIZ];\r
8290     char *p, *q;\r
8291     \r
8292     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
8293         gameMode != AnalyzeMode && gameMode != Training) {\r
8294         gameFileFP = NULL;\r
8295         return FALSE;\r
8296     }\r
8297     \r
8298     yyboardindex = forwardMostMove;\r
8299     if (readAhead != (ChessMove)0) {\r
8300       moveType = readAhead;\r
8301     } else {\r
8302       if (gameFileFP == NULL)\r
8303           return FALSE;\r
8304       moveType = (ChessMove) yylex();\r
8305     }\r
8306     \r
8307     done = FALSE;\r
8308     switch (moveType) {\r
8309       case Comment:\r
8310         if (appData.debugMode) \r
8311           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8312         p = yy_text;\r
8313         if (*p == '{' || *p == '[' || *p == '(') {\r
8314             p[strlen(p) - 1] = NULLCHAR;\r
8315             p++;\r
8316         }\r
8317 \r
8318         /* append the comment but don't display it */\r
8319         while (*p == '\n') p++;\r
8320         AppendComment(currentMove, p);\r
8321         return TRUE;\r
8322 \r
8323       case WhiteCapturesEnPassant:\r
8324       case BlackCapturesEnPassant:\r
8325       case WhitePromotionChancellor:\r
8326       case BlackPromotionChancellor:\r
8327       case WhitePromotionArchbishop:\r
8328       case BlackPromotionArchbishop:\r
8329       case WhitePromotionCentaur:\r
8330       case BlackPromotionCentaur:\r
8331       case WhitePromotionQueen:\r
8332       case BlackPromotionQueen:\r
8333       case WhitePromotionRook:\r
8334       case BlackPromotionRook:\r
8335       case WhitePromotionBishop:\r
8336       case BlackPromotionBishop:\r
8337       case WhitePromotionKnight:\r
8338       case BlackPromotionKnight:\r
8339       case WhitePromotionKing:\r
8340       case BlackPromotionKing:\r
8341       case NormalMove:\r
8342       case WhiteKingSideCastle:\r
8343       case WhiteQueenSideCastle:\r
8344       case BlackKingSideCastle:\r
8345       case BlackQueenSideCastle:\r
8346       case WhiteKingSideCastleWild:\r
8347       case WhiteQueenSideCastleWild:\r
8348       case BlackKingSideCastleWild:\r
8349       case BlackQueenSideCastleWild:\r
8350       /* PUSH Fabien */\r
8351       case WhiteHSideCastleFR:\r
8352       case WhiteASideCastleFR:\r
8353       case BlackHSideCastleFR:\r
8354       case BlackASideCastleFR:\r
8355       /* POP Fabien */\r
8356         if (appData.debugMode)\r
8357           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8358         fromX = currentMoveString[0] - AAA;\r
8359         fromY = currentMoveString[1] - ONE;\r
8360         toX = currentMoveString[2] - AAA;\r
8361         toY = currentMoveString[3] - ONE;\r
8362         promoChar = currentMoveString[4];\r
8363         break;\r
8364 \r
8365       case WhiteDrop:\r
8366       case BlackDrop:\r
8367         if (appData.debugMode)\r
8368           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8369         fromX = moveType == WhiteDrop ?\r
8370           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
8371         (int) CharToPiece(ToLower(currentMoveString[0]));\r
8372         fromY = DROP_RANK;\r
8373         toX = currentMoveString[2] - AAA;\r
8374         toY = currentMoveString[3] - ONE;\r
8375         break;\r
8376 \r
8377       case WhiteWins:\r
8378       case BlackWins:\r
8379       case GameIsDrawn:\r
8380       case GameUnfinished:\r
8381         if (appData.debugMode)\r
8382           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
8383         p = strchr(yy_text, '{');\r
8384         if (p == NULL) p = strchr(yy_text, '(');\r
8385         if (p == NULL) {\r
8386             p = yy_text;\r
8387             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
8388         } else {\r
8389             q = strchr(p, *p == '{' ? '}' : ')');\r
8390             if (q != NULL) *q = NULLCHAR;\r
8391             p++;\r
8392         }\r
8393         GameEnds(moveType, p, GE_FILE);\r
8394         done = TRUE;\r
8395         if (cmailMsgLoaded) {\r
8396             ClearHighlights();\r
8397             flipView = WhiteOnMove(currentMove);\r
8398             if (moveType == GameUnfinished) flipView = !flipView;\r
8399             if (appData.debugMode)\r
8400               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
8401         }\r
8402         break;\r
8403 \r
8404       case (ChessMove) 0:       /* end of file */\r
8405         if (appData.debugMode)\r
8406           fprintf(debugFP, "Parser hit end of file\n");\r
8407         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8408                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8409           case MT_NONE:\r
8410           case MT_CHECK:\r
8411             break;\r
8412           case MT_CHECKMATE:\r
8413             if (WhiteOnMove(currentMove)) {\r
8414                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8415             } else {\r
8416                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8417             }\r
8418             break;\r
8419           case MT_STALEMATE:\r
8420             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8421             break;\r
8422         }\r
8423         done = TRUE;\r
8424         break;\r
8425 \r
8426       case MoveNumberOne:\r
8427         if (lastLoadGameStart == GNUChessGame) {\r
8428             /* GNUChessGames have numbers, but they aren't move numbers */\r
8429             if (appData.debugMode)\r
8430               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8431                       yy_text, (int) moveType);\r
8432             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8433         }\r
8434         /* else fall thru */\r
8435 \r
8436       case XBoardGame:\r
8437       case GNUChessGame:\r
8438       case PGNTag:\r
8439         /* Reached start of next game in file */\r
8440         if (appData.debugMode)\r
8441           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
8442         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8443                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8444           case MT_NONE:\r
8445           case MT_CHECK:\r
8446             break;\r
8447           case MT_CHECKMATE:\r
8448             if (WhiteOnMove(currentMove)) {\r
8449                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8450             } else {\r
8451                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8452             }\r
8453             break;\r
8454           case MT_STALEMATE:\r
8455             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8456             break;\r
8457         }\r
8458         done = TRUE;\r
8459         break;\r
8460 \r
8461       case PositionDiagram:     /* should not happen; ignore */\r
8462       case ElapsedTime:         /* ignore */\r
8463       case NAG:                 /* ignore */\r
8464         if (appData.debugMode)\r
8465           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8466                   yy_text, (int) moveType);\r
8467         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8468 \r
8469       case IllegalMove:\r
8470         if (appData.testLegality) {\r
8471             if (appData.debugMode)\r
8472               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
8473             sprintf(move, _("Illegal move: %d.%s%s"),\r
8474                     (forwardMostMove / 2) + 1,\r
8475                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8476             DisplayError(move, 0);\r
8477             done = TRUE;\r
8478         } else {\r
8479             if (appData.debugMode)\r
8480               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
8481                       yy_text, currentMoveString);\r
8482             fromX = currentMoveString[0] - AAA;\r
8483             fromY = currentMoveString[1] - ONE;\r
8484             toX = currentMoveString[2] - AAA;\r
8485             toY = currentMoveString[3] - ONE;\r
8486             promoChar = currentMoveString[4];\r
8487         }\r
8488         break;\r
8489 \r
8490       case AmbiguousMove:\r
8491         if (appData.debugMode)\r
8492           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
8493         sprintf(move, _("Ambiguous move: %d.%s%s"),\r
8494                 (forwardMostMove / 2) + 1,\r
8495                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8496         DisplayError(move, 0);\r
8497         done = TRUE;\r
8498         break;\r
8499 \r
8500       default:\r
8501       case ImpossibleMove:\r
8502         if (appData.debugMode)\r
8503           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);\r
8504         sprintf(move, _("Illegal move: %d.%s%s"),\r
8505                 (forwardMostMove / 2) + 1,\r
8506                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8507         DisplayError(move, 0);\r
8508         done = TRUE;\r
8509         break;\r
8510     }\r
8511 \r
8512     if (done) {\r
8513         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
8514             DrawPosition(FALSE, boards[currentMove]);\r
8515             DisplayBothClocks();\r
8516             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
8517               DisplayComment(currentMove - 1, commentList[currentMove]);\r
8518         }\r
8519         (void) StopLoadGameTimer();\r
8520         gameFileFP = NULL;\r
8521         cmailOldMove = forwardMostMove;\r
8522         return FALSE;\r
8523     } else {\r
8524         /* currentMoveString is set as a side-effect of yylex */\r
8525         strcat(currentMoveString, "\n");\r
8526         strcpy(moveList[forwardMostMove], currentMoveString);\r
8527         \r
8528         thinkOutput[0] = NULLCHAR;\r
8529         MakeMove(fromX, fromY, toX, toY, promoChar);\r
8530         currentMove = forwardMostMove;\r
8531         return TRUE;\r
8532     }\r
8533 }\r
8534 \r
8535 /* Load the nth game from the given file */\r
8536 int\r
8537 LoadGameFromFile(filename, n, title, useList)\r
8538      char *filename;\r
8539      int n;\r
8540      char *title;\r
8541      /*Boolean*/ int useList;\r
8542 {\r
8543     FILE *f;\r
8544     char buf[MSG_SIZ];\r
8545 \r
8546     if (strcmp(filename, "-") == 0) {\r
8547         f = stdin;\r
8548         title = "stdin";\r
8549     } else {\r
8550         f = fopen(filename, "rb");\r
8551         if (f == NULL) {\r
8552             sprintf(buf, _("Can't open \"%s\""), filename);\r
8553             DisplayError(buf, errno);\r
8554             return FALSE;\r
8555         }\r
8556     }\r
8557     if (fseek(f, 0, 0) == -1) {\r
8558         /* f is not seekable; probably a pipe */\r
8559         useList = FALSE;\r
8560     }\r
8561     if (useList && n == 0) {\r
8562         int error = GameListBuild(f);\r
8563         if (error) {\r
8564             DisplayError(_("Cannot build game list"), error);\r
8565         } else if (!ListEmpty(&gameList) &&\r
8566                    ((ListGame *) gameList.tailPred)->number > 1) {\r
8567             GameListPopUp(f, title);\r
8568             return TRUE;\r
8569         }\r
8570         GameListDestroy();\r
8571         n = 1;\r
8572     }\r
8573     if (n == 0) n = 1;\r
8574     return LoadGame(f, n, title, FALSE);\r
8575 }\r
8576 \r
8577 \r
8578 void\r
8579 MakeRegisteredMove()\r
8580 {\r
8581     int fromX, fromY, toX, toY;\r
8582     char promoChar;\r
8583     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8584         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
8585           case CMAIL_MOVE:\r
8586           case CMAIL_DRAW:\r
8587             if (appData.debugMode)\r
8588               fprintf(debugFP, "Restoring %s for game %d\n",\r
8589                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8590     \r
8591             thinkOutput[0] = NULLCHAR;\r
8592             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
8593             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
8594             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
8595             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
8596             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
8597             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
8598             MakeMove(fromX, fromY, toX, toY, promoChar);\r
8599             ShowMove(fromX, fromY, toX, toY);\r
8600               \r
8601             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8602                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8603               case MT_NONE:\r
8604               case MT_CHECK:\r
8605                 break;\r
8606                 \r
8607               case MT_CHECKMATE:\r
8608                 if (WhiteOnMove(currentMove)) {\r
8609                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
8610                 } else {\r
8611                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
8612                 }\r
8613                 break;\r
8614                 \r
8615               case MT_STALEMATE:\r
8616                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
8617                 break;\r
8618             }\r
8619 \r
8620             break;\r
8621             \r
8622           case CMAIL_RESIGN:\r
8623             if (WhiteOnMove(currentMove)) {\r
8624                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
8625             } else {\r
8626                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
8627             }\r
8628             break;\r
8629             \r
8630           case CMAIL_ACCEPT:\r
8631             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
8632             break;\r
8633               \r
8634           default:\r
8635             break;\r
8636         }\r
8637     }\r
8638 \r
8639     return;\r
8640 }\r
8641 \r
8642 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
8643 int\r
8644 CmailLoadGame(f, gameNumber, title, useList)\r
8645      FILE *f;\r
8646      int gameNumber;\r
8647      char *title;\r
8648      int useList;\r
8649 {\r
8650     int retVal;\r
8651 \r
8652     if (gameNumber > nCmailGames) {\r
8653         DisplayError(_("No more games in this message"), 0);\r
8654         return FALSE;\r
8655     }\r
8656     if (f == lastLoadGameFP) {\r
8657         int offset = gameNumber - lastLoadGameNumber;\r
8658         if (offset == 0) {\r
8659             cmailMsg[0] = NULLCHAR;\r
8660             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8661                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8662                 nCmailMovesRegistered--;\r
8663             }\r
8664             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
8665             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
8666                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
8667             }\r
8668         } else {\r
8669             if (! RegisterMove()) return FALSE;\r
8670         }\r
8671     }\r
8672 \r
8673     retVal = LoadGame(f, gameNumber, title, useList);\r
8674 \r
8675     /* Make move registered during previous look at this game, if any */\r
8676     MakeRegisteredMove();\r
8677 \r
8678     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
8679         commentList[currentMove]\r
8680           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
8681         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8682     }\r
8683 \r
8684     return retVal;\r
8685 }\r
8686 \r
8687 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
8688 int\r
8689 ReloadGame(offset)\r
8690      int offset;\r
8691 {\r
8692     int gameNumber = lastLoadGameNumber + offset;\r
8693     if (lastLoadGameFP == NULL) {\r
8694         DisplayError(_("No game has been loaded yet"), 0);\r
8695         return FALSE;\r
8696     }\r
8697     if (gameNumber <= 0) {\r
8698         DisplayError(_("Can't back up any further"), 0);\r
8699         return FALSE;\r
8700     }\r
8701     if (cmailMsgLoaded) {\r
8702         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
8703                              lastLoadGameTitle, lastLoadGameUseList);\r
8704     } else {\r
8705         return LoadGame(lastLoadGameFP, gameNumber,\r
8706                         lastLoadGameTitle, lastLoadGameUseList);\r
8707     }\r
8708 }\r
8709 \r
8710 \r
8711 \r
8712 /* Load the nth game from open file f */\r
8713 int\r
8714 LoadGame(f, gameNumber, title, useList)\r
8715      FILE *f;\r
8716      int gameNumber;\r
8717      char *title;\r
8718      int useList;\r
8719 {\r
8720     ChessMove cm;\r
8721     char buf[MSG_SIZ];\r
8722     int gn = gameNumber;\r
8723     ListGame *lg = NULL;\r
8724     int numPGNTags = 0;\r
8725     int err;\r
8726     GameMode oldGameMode;\r
8727     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
8728 \r
8729     if (appData.debugMode) \r
8730         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
8731 \r
8732     if (gameMode == Training )\r
8733         SetTrainingModeOff();\r
8734 \r
8735     oldGameMode = gameMode;\r
8736     if (gameMode != BeginningOfGame) {\r
8737       Reset(FALSE, TRUE);\r
8738     }\r
8739 \r
8740     gameFileFP = f;\r
8741     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
8742         fclose(lastLoadGameFP);\r
8743     }\r
8744 \r
8745     if (useList) {\r
8746         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
8747         \r
8748         if (lg) {\r
8749             fseek(f, lg->offset, 0);\r
8750             GameListHighlight(gameNumber);\r
8751             gn = 1;\r
8752         }\r
8753         else {\r
8754             DisplayError(_("Game number out of range"), 0);\r
8755             return FALSE;\r
8756         }\r
8757     } else {\r
8758         GameListDestroy();\r
8759         if (fseek(f, 0, 0) == -1) {\r
8760             if (f == lastLoadGameFP ?\r
8761                 gameNumber == lastLoadGameNumber + 1 :\r
8762                 gameNumber == 1) {\r
8763                 gn = 1;\r
8764             } else {\r
8765                 DisplayError(_("Can't seek on game file"), 0);\r
8766                 return FALSE;\r
8767             }\r
8768         }\r
8769     }\r
8770     lastLoadGameFP = f;\r
8771     lastLoadGameNumber = gameNumber;\r
8772     strcpy(lastLoadGameTitle, title);\r
8773     lastLoadGameUseList = useList;\r
8774 \r
8775     yynewfile(f);\r
8776 \r
8777     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
8778         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
8779                 lg->gameInfo.black);\r
8780             DisplayTitle(buf);\r
8781     } else if (*title != NULLCHAR) {\r
8782         if (gameNumber > 1) {\r
8783             sprintf(buf, "%s %d", title, gameNumber);\r
8784             DisplayTitle(buf);\r
8785         } else {\r
8786             DisplayTitle(title);\r
8787         }\r
8788     }\r
8789 \r
8790     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
8791         gameMode = PlayFromGameFile;\r
8792         ModeHighlight();\r
8793     }\r
8794 \r
8795     currentMove = forwardMostMove = backwardMostMove = 0;\r
8796     CopyBoard(boards[0], initialPosition);\r
8797     StopClocks();\r
8798 \r
8799     /*\r
8800      * Skip the first gn-1 games in the file.\r
8801      * Also skip over anything that precedes an identifiable \r
8802      * start of game marker, to avoid being confused by \r
8803      * garbage at the start of the file.  Currently \r
8804      * recognized start of game markers are the move number "1",\r
8805      * the pattern "gnuchess .* game", the pattern\r
8806      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
8807      * A game that starts with one of the latter two patterns\r
8808      * will also have a move number 1, possibly\r
8809      * following a position diagram.\r
8810      * 5-4-02: Let's try being more lenient and allowing a game to\r
8811      * start with an unnumbered move.  Does that break anything?\r
8812      */\r
8813     cm = lastLoadGameStart = (ChessMove) 0;\r
8814     while (gn > 0) {\r
8815         yyboardindex = forwardMostMove;\r
8816         cm = (ChessMove) yylex();\r
8817         switch (cm) {\r
8818           case (ChessMove) 0:\r
8819             if (cmailMsgLoaded) {\r
8820                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
8821             } else {\r
8822                 Reset(TRUE, TRUE);\r
8823                 DisplayError(_("Game not found in file"), 0);\r
8824             }\r
8825             return FALSE;\r
8826 \r
8827           case GNUChessGame:\r
8828           case XBoardGame:\r
8829             gn--;\r
8830             lastLoadGameStart = cm;\r
8831             break;\r
8832             \r
8833           case MoveNumberOne:\r
8834             switch (lastLoadGameStart) {\r
8835               case GNUChessGame:\r
8836               case XBoardGame:\r
8837               case PGNTag:\r
8838                 break;\r
8839               case MoveNumberOne:\r
8840               case (ChessMove) 0:\r
8841                 gn--;           /* count this game */\r
8842                 lastLoadGameStart = cm;\r
8843                 break;\r
8844               default:\r
8845                 /* impossible */\r
8846                 break;\r
8847             }\r
8848             break;\r
8849 \r
8850           case PGNTag:\r
8851             switch (lastLoadGameStart) {\r
8852               case GNUChessGame:\r
8853               case PGNTag:\r
8854               case MoveNumberOne:\r
8855               case (ChessMove) 0:\r
8856                 gn--;           /* count this game */\r
8857                 lastLoadGameStart = cm;\r
8858                 break;\r
8859               case XBoardGame:\r
8860                 lastLoadGameStart = cm; /* game counted already */\r
8861                 break;\r
8862               default:\r
8863                 /* impossible */\r
8864                 break;\r
8865             }\r
8866             if (gn > 0) {\r
8867                 do {\r
8868                     yyboardindex = forwardMostMove;\r
8869                     cm = (ChessMove) yylex();\r
8870                 } while (cm == PGNTag || cm == Comment);\r
8871             }\r
8872             break;\r
8873 \r
8874           case WhiteWins:\r
8875           case BlackWins:\r
8876           case GameIsDrawn:\r
8877             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8878                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8879                     != CMAIL_OLD_RESULT) {\r
8880                     nCmailResults ++ ;\r
8881                     cmailResult[  CMAIL_MAX_GAMES\r
8882                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8883                 }\r
8884             }\r
8885             break;\r
8886 \r
8887           case NormalMove:\r
8888             /* Only a NormalMove can be at the start of a game\r
8889              * without a position diagram. */\r
8890             if (lastLoadGameStart == (ChessMove) 0) {\r
8891               gn--;\r
8892               lastLoadGameStart = MoveNumberOne;\r
8893             }\r
8894             break;\r
8895 \r
8896           default:\r
8897             break;\r
8898         }\r
8899     }\r
8900     \r
8901     if (appData.debugMode)\r
8902       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8903 \r
8904     if (cm == XBoardGame) {\r
8905         /* Skip any header junk before position diagram and/or move 1 */\r
8906         for (;;) {\r
8907             yyboardindex = forwardMostMove;\r
8908             cm = (ChessMove) yylex();\r
8909 \r
8910             if (cm == (ChessMove) 0 ||\r
8911                 cm == GNUChessGame || cm == XBoardGame) {\r
8912                 /* Empty game; pretend end-of-file and handle later */\r
8913                 cm = (ChessMove) 0;\r
8914                 break;\r
8915             }\r
8916 \r
8917             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8918                 cm == PGNTag || cm == Comment)\r
8919               break;\r
8920         }\r
8921     } else if (cm == GNUChessGame) {\r
8922         if (gameInfo.event != NULL) {\r
8923             free(gameInfo.event);\r
8924         }\r
8925         gameInfo.event = StrSave(yy_text);\r
8926     }   \r
8927 \r
8928     startedFromSetupPosition = FALSE;\r
8929     while (cm == PGNTag) {\r
8930         if (appData.debugMode) \r
8931           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8932         err = ParsePGNTag(yy_text, &gameInfo);\r
8933         if (!err) numPGNTags++;\r
8934 \r
8935         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8936         if(gameInfo.variant != oldVariant) {\r
8937             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8938             InitPosition(TRUE);\r
8939             oldVariant = gameInfo.variant;\r
8940             if (appData.debugMode) \r
8941               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8942         }\r
8943 \r
8944 \r
8945         if (gameInfo.fen != NULL) {\r
8946           Board initial_position;\r
8947           startedFromSetupPosition = TRUE;\r
8948           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8949             Reset(TRUE, TRUE);\r
8950             DisplayError(_("Bad FEN position in file"), 0);\r
8951             return FALSE;\r
8952           }\r
8953           CopyBoard(boards[0], initial_position);\r
8954           if (blackPlaysFirst) {\r
8955             currentMove = forwardMostMove = backwardMostMove = 1;\r
8956             CopyBoard(boards[1], initial_position);\r
8957             strcpy(moveList[0], "");\r
8958             strcpy(parseList[0], "");\r
8959             timeRemaining[0][1] = whiteTimeRemaining;\r
8960             timeRemaining[1][1] = blackTimeRemaining;\r
8961             if (commentList[0] != NULL) {\r
8962               commentList[1] = commentList[0];\r
8963               commentList[0] = NULL;\r
8964             }\r
8965           } else {\r
8966             currentMove = forwardMostMove = backwardMostMove = 0;\r
8967           }\r
8968           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */\r
8969           {   int i;\r
8970               initialRulePlies = FENrulePlies;\r
8971               epStatus[forwardMostMove] = FENepStatus;\r
8972               for( i=0; i< nrCastlingRights; i++ )\r
8973                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
8974           }\r
8975           yyboardindex = forwardMostMove;\r
8976           free(gameInfo.fen);\r
8977           gameInfo.fen = NULL;\r
8978         }\r
8979 \r
8980         yyboardindex = forwardMostMove;\r
8981         cm = (ChessMove) yylex();\r
8982 \r
8983         /* Handle comments interspersed among the tags */\r
8984         while (cm == Comment) {\r
8985             char *p;\r
8986             if (appData.debugMode) \r
8987               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8988             p = yy_text;\r
8989             if (*p == '{' || *p == '[' || *p == '(') {\r
8990                 p[strlen(p) - 1] = NULLCHAR;\r
8991                 p++;\r
8992             }\r
8993             while (*p == '\n') p++;\r
8994             AppendComment(currentMove, p);\r
8995             yyboardindex = forwardMostMove;\r
8996             cm = (ChessMove) yylex();\r
8997         }\r
8998     }\r
8999 \r
9000     /* don't rely on existence of Event tag since if game was\r
9001      * pasted from clipboard the Event tag may not exist\r
9002      */\r
9003     if (numPGNTags > 0){\r
9004         char *tags;\r
9005         if (gameInfo.variant == VariantNormal) {\r
9006           gameInfo.variant = StringToVariant(gameInfo.event);\r
9007         }\r
9008         if (!matchMode) {\r
9009           if( appData.autoDisplayTags ) {\r
9010             tags = PGNTags(&gameInfo);\r
9011             TagsPopUp(tags, CmailMsg());\r
9012             free(tags);\r
9013           }\r
9014         }\r
9015     } else {\r
9016         /* Make something up, but don't display it now */\r
9017         SetGameInfo();\r
9018         TagsPopDown();\r
9019     }\r
9020 \r
9021     if (cm == PositionDiagram) {\r
9022         int i, j;\r
9023         char *p;\r
9024         Board initial_position;\r
9025 \r
9026         if (appData.debugMode)\r
9027           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
9028 \r
9029         if (!startedFromSetupPosition) {\r
9030             p = yy_text;\r
9031             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
9032               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
9033                 switch (*p) {\r
9034                   case '[':\r
9035                   case '-':\r
9036                   case ' ':\r
9037                   case '\t':\r
9038                   case '\n':\r
9039                   case '\r':\r
9040                     break;\r
9041                   default:\r
9042                     initial_position[i][j++] = CharToPiece(*p);\r
9043                     break;\r
9044                 }\r
9045             while (*p == ' ' || *p == '\t' ||\r
9046                    *p == '\n' || *p == '\r') p++;\r
9047         \r
9048             if (strncmp(p, "black", strlen("black"))==0)\r
9049               blackPlaysFirst = TRUE;\r
9050             else\r
9051               blackPlaysFirst = FALSE;\r
9052             startedFromSetupPosition = TRUE;\r
9053         \r
9054             CopyBoard(boards[0], initial_position);\r
9055             if (blackPlaysFirst) {\r
9056                 currentMove = forwardMostMove = backwardMostMove = 1;\r
9057                 CopyBoard(boards[1], initial_position);\r
9058                 strcpy(moveList[0], "");\r
9059                 strcpy(parseList[0], "");\r
9060                 timeRemaining[0][1] = whiteTimeRemaining;\r
9061                 timeRemaining[1][1] = blackTimeRemaining;\r
9062                 if (commentList[0] != NULL) {\r
9063                     commentList[1] = commentList[0];\r
9064                     commentList[0] = NULL;\r
9065                 }\r
9066             } else {\r
9067                 currentMove = forwardMostMove = backwardMostMove = 0;\r
9068             }\r
9069         }\r
9070         yyboardindex = forwardMostMove;\r
9071         cm = (ChessMove) yylex();\r
9072     }\r
9073 \r
9074     if (first.pr == NoProc) {\r
9075         StartChessProgram(&first);\r
9076     }\r
9077     InitChessProgram(&first, FALSE);\r
9078     SendToProgram("force\n", &first);\r
9079     if (startedFromSetupPosition) {\r
9080         SendBoard(&first, forwardMostMove);\r
9081     if (appData.debugMode) {\r
9082         fprintf(debugFP, "Load Game\n");\r
9083     }\r
9084         DisplayBothClocks();\r
9085     }      \r
9086 \r
9087     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
9088     loadFlag = appData.suppressLoadMoves;\r
9089 \r
9090     while (cm == Comment) {\r
9091         char *p;\r
9092         if (appData.debugMode) \r
9093           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
9094         p = yy_text;\r
9095         if (*p == '{' || *p == '[' || *p == '(') {\r
9096             p[strlen(p) - 1] = NULLCHAR;\r
9097             p++;\r
9098         }\r
9099         while (*p == '\n') p++;\r
9100         AppendComment(currentMove, p);\r
9101         yyboardindex = forwardMostMove;\r
9102         cm = (ChessMove) yylex();\r
9103     }\r
9104 \r
9105     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
9106         cm == WhiteWins || cm == BlackWins ||\r
9107         cm == GameIsDrawn || cm == GameUnfinished) {\r
9108         DisplayMessage("", _("No moves in game"));\r
9109         if (cmailMsgLoaded) {\r
9110             if (appData.debugMode)\r
9111               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
9112             ClearHighlights();\r
9113             flipView = FALSE;\r
9114         }\r
9115         DrawPosition(FALSE, boards[currentMove]);\r
9116         DisplayBothClocks();\r
9117         gameMode = EditGame;\r
9118         ModeHighlight();\r
9119         gameFileFP = NULL;\r
9120         cmailOldMove = 0;\r
9121         return TRUE;\r
9122     }\r
9123 \r
9124     // [HGM] PV info: routine tests if comment empty\r
9125     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
9126         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9127     }\r
9128     if (!matchMode && appData.timeDelay != 0) \r
9129       DrawPosition(FALSE, boards[currentMove]);\r
9130 \r
9131     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
9132       programStats.ok_to_send = 1;\r
9133     }\r
9134 \r
9135     /* if the first token after the PGN tags is a move\r
9136      * and not move number 1, retrieve it from the parser \r
9137      */\r
9138     if (cm != MoveNumberOne)\r
9139         LoadGameOneMove(cm);\r
9140 \r
9141     /* load the remaining moves from the file */\r
9142     while (LoadGameOneMove((ChessMove)0)) {\r
9143       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9144       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9145     }\r
9146 \r
9147     /* rewind to the start of the game */\r
9148     currentMove = backwardMostMove;\r
9149 \r
9150     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9151 \r
9152     if (oldGameMode == AnalyzeFile ||\r
9153         oldGameMode == AnalyzeMode) {\r
9154       AnalyzeFileEvent();\r
9155     }\r
9156 \r
9157     if (matchMode || appData.timeDelay == 0) {\r
9158       ToEndEvent();\r
9159       gameMode = EditGame;\r
9160       ModeHighlight();\r
9161     } else if (appData.timeDelay > 0) {\r
9162       AutoPlayGameLoop();\r
9163     }\r
9164 \r
9165     if (appData.debugMode) \r
9166         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
9167 \r
9168     loadFlag = 0; /* [HGM] true game starts */\r
9169     return TRUE;\r
9170 }\r
9171 \r
9172 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
9173 int\r
9174 ReloadPosition(offset)\r
9175      int offset;\r
9176 {\r
9177     int positionNumber = lastLoadPositionNumber + offset;\r
9178     if (lastLoadPositionFP == NULL) {\r
9179         DisplayError(_("No position has been loaded yet"), 0);\r
9180         return FALSE;\r
9181     }\r
9182     if (positionNumber <= 0) {\r
9183         DisplayError(_("Can't back up any further"), 0);\r
9184         return FALSE;\r
9185     }\r
9186     return LoadPosition(lastLoadPositionFP, positionNumber,\r
9187                         lastLoadPositionTitle);\r
9188 }\r
9189 \r
9190 /* Load the nth position from the given file */\r
9191 int\r
9192 LoadPositionFromFile(filename, n, title)\r
9193      char *filename;\r
9194      int n;\r
9195      char *title;\r
9196 {\r
9197     FILE *f;\r
9198     char buf[MSG_SIZ];\r
9199 \r
9200     if (strcmp(filename, "-") == 0) {\r
9201         return LoadPosition(stdin, n, "stdin");\r
9202     } else {\r
9203         f = fopen(filename, "rb");\r
9204         if (f == NULL) {\r
9205             sprintf(buf, _("Can't open \"%s\""), filename);\r
9206             DisplayError(buf, errno);\r
9207             return FALSE;\r
9208         } else {\r
9209             return LoadPosition(f, n, title);\r
9210         }\r
9211     }\r
9212 }\r
9213 \r
9214 /* Load the nth position from the given open file, and close it */\r
9215 int\r
9216 LoadPosition(f, positionNumber, title)\r
9217      FILE *f;\r
9218      int positionNumber;\r
9219      char *title;\r
9220 {\r
9221     char *p, line[MSG_SIZ];\r
9222     Board initial_position;\r
9223     int i, j, fenMode, pn;\r
9224     \r
9225     if (gameMode == Training )\r
9226         SetTrainingModeOff();\r
9227 \r
9228     if (gameMode != BeginningOfGame) {\r
9229         Reset(FALSE, TRUE);\r
9230     }\r
9231     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
9232         fclose(lastLoadPositionFP);\r
9233     }\r
9234     if (positionNumber == 0) positionNumber = 1;\r
9235     lastLoadPositionFP = f;\r
9236     lastLoadPositionNumber = positionNumber;\r
9237     strcpy(lastLoadPositionTitle, title);\r
9238     if (first.pr == NoProc) {\r
9239       StartChessProgram(&first);\r
9240       InitChessProgram(&first, FALSE);\r
9241     }    \r
9242     pn = positionNumber;\r
9243     if (positionNumber < 0) {\r
9244         /* Negative position number means to seek to that byte offset */\r
9245         if (fseek(f, -positionNumber, 0) == -1) {\r
9246             DisplayError(_("Can't seek on position file"), 0);\r
9247             return FALSE;\r
9248         };\r
9249         pn = 1;\r
9250     } else {\r
9251         if (fseek(f, 0, 0) == -1) {\r
9252             if (f == lastLoadPositionFP ?\r
9253                 positionNumber == lastLoadPositionNumber + 1 :\r
9254                 positionNumber == 1) {\r
9255                 pn = 1;\r
9256             } else {\r
9257                 DisplayError(_("Can't seek on position file"), 0);\r
9258                 return FALSE;\r
9259             }\r
9260         }\r
9261     }\r
9262     /* See if this file is FEN or old-style xboard */\r
9263     if (fgets(line, MSG_SIZ, f) == NULL) {\r
9264         DisplayError(_("Position not found in file"), 0);\r
9265         return FALSE;\r
9266     }\r
9267 #if 0\r
9268     switch (line[0]) {\r
9269       case '#':  case 'x':\r
9270       default:\r
9271         fenMode = FALSE;\r
9272         break;\r
9273       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
9274       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
9275       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
9276       case '7':  case '8':  case '9':\r
9277       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
9278       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
9279       case 'C':  case 'W':             case 'c':  case 'w': \r
9280         fenMode = TRUE;\r
9281         break;\r
9282     }\r
9283 #else\r
9284     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
9285     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
9286 #endif\r
9287 \r
9288     if (pn >= 2) {\r
9289         if (fenMode || line[0] == '#') pn--;\r
9290         while (pn > 0) {\r
9291             /* skip positions before number pn */\r
9292             if (fgets(line, MSG_SIZ, f) == NULL) {\r
9293                 Reset(TRUE, TRUE);\r
9294                 DisplayError(_("Position not found in file"), 0);\r
9295                 return FALSE;\r
9296             }\r
9297             if (fenMode || line[0] == '#') pn--;\r
9298         }\r
9299     }\r
9300 \r
9301     if (fenMode) {\r
9302         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
9303             DisplayError(_("Bad FEN position in file"), 0);\r
9304             return FALSE;\r
9305         }\r
9306     } else {\r
9307         (void) fgets(line, MSG_SIZ, f);\r
9308         (void) fgets(line, MSG_SIZ, f);\r
9309     \r
9310         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
9311             (void) fgets(line, MSG_SIZ, f);\r
9312             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
9313                 if (*p == ' ')\r
9314                   continue;\r
9315                 initial_position[i][j++] = CharToPiece(*p);\r
9316             }\r
9317         }\r
9318     \r
9319         blackPlaysFirst = FALSE;\r
9320         if (!feof(f)) {\r
9321             (void) fgets(line, MSG_SIZ, f);\r
9322             if (strncmp(line, "black", strlen("black"))==0)\r
9323               blackPlaysFirst = TRUE;\r
9324         }\r
9325     }\r
9326     startedFromSetupPosition = TRUE;\r
9327     \r
9328     SendToProgram("force\n", &first);\r
9329     CopyBoard(boards[0], initial_position);\r
9330     if (blackPlaysFirst) {\r
9331         currentMove = forwardMostMove = backwardMostMove = 1;\r
9332         strcpy(moveList[0], "");\r
9333         strcpy(parseList[0], "");\r
9334         CopyBoard(boards[1], initial_position);\r
9335         DisplayMessage("", _("Black to play"));\r
9336     } else {\r
9337         currentMove = forwardMostMove = backwardMostMove = 0;\r
9338         DisplayMessage("", _("White to play"));\r
9339     }\r
9340           /* [HGM] copy FEN attributes as well */\r
9341           {   int i;\r
9342               initialRulePlies = FENrulePlies;\r
9343               epStatus[forwardMostMove] = FENepStatus;\r
9344               for( i=0; i< nrCastlingRights; i++ )\r
9345                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
9346           }\r
9347     SendBoard(&first, forwardMostMove);\r
9348     if (appData.debugMode) {\r
9349 int i, j;\r
9350   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}\r
9351   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");\r
9352         fprintf(debugFP, "Load Position\n");\r
9353     }\r
9354 \r
9355     if (positionNumber > 1) {\r
9356         sprintf(line, "%s %d", title, positionNumber);\r
9357         DisplayTitle(line);\r
9358     } else {\r
9359         DisplayTitle(title);\r
9360     }\r
9361     gameMode = EditGame;\r
9362     ModeHighlight();\r
9363     ResetClocks();\r
9364     timeRemaining[0][1] = whiteTimeRemaining;\r
9365     timeRemaining[1][1] = blackTimeRemaining;\r
9366     DrawPosition(FALSE, boards[currentMove]);\r
9367    \r
9368     return TRUE;\r
9369 }\r
9370 \r
9371 \r
9372 void\r
9373 CopyPlayerNameIntoFileName(dest, src)\r
9374      char **dest, *src;\r
9375 {\r
9376     while (*src != NULLCHAR && *src != ',') {\r
9377         if (*src == ' ') {\r
9378             *(*dest)++ = '_';\r
9379             src++;\r
9380         } else {\r
9381             *(*dest)++ = *src++;\r
9382         }\r
9383     }\r
9384 }\r
9385 \r
9386 char *DefaultFileName(ext)\r
9387      char *ext;\r
9388 {\r
9389     static char def[MSG_SIZ];\r
9390     char *p;\r
9391 \r
9392     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
9393         p = def;\r
9394         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
9395         *p++ = '-';\r
9396         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
9397         *p++ = '.';\r
9398         strcpy(p, ext);\r
9399     } else {\r
9400         def[0] = NULLCHAR;\r
9401     }\r
9402     return def;\r
9403 }\r
9404 \r
9405 /* Save the current game to the given file */\r
9406 int\r
9407 SaveGameToFile(filename, append)\r
9408      char *filename;\r
9409      int append;\r
9410 {\r
9411     FILE *f;\r
9412     char buf[MSG_SIZ];\r
9413 \r
9414     if (strcmp(filename, "-") == 0) {\r
9415         return SaveGame(stdout, 0, NULL);\r
9416     } else {\r
9417         f = fopen(filename, append ? "a" : "w");\r
9418         if (f == NULL) {\r
9419             sprintf(buf, _("Can't open \"%s\""), filename);\r
9420             DisplayError(buf, errno);\r
9421             return FALSE;\r
9422         } else {\r
9423             return SaveGame(f, 0, NULL);\r
9424         }\r
9425     }\r
9426 }\r
9427 \r
9428 char *\r
9429 SavePart(str)\r
9430      char *str;\r
9431 {\r
9432     static char buf[MSG_SIZ];\r
9433     char *p;\r
9434     \r
9435     p = strchr(str, ' ');\r
9436     if (p == NULL) return str;\r
9437     strncpy(buf, str, p - str);\r
9438     buf[p - str] = NULLCHAR;\r
9439     return buf;\r
9440 }\r
9441 \r
9442 #define PGN_MAX_LINE 75\r
9443 \r
9444 #define PGN_SIDE_WHITE  0\r
9445 #define PGN_SIDE_BLACK  1\r
9446 \r
9447 /* [AS] */\r
9448 static int FindFirstMoveOutOfBook( int side )\r
9449 {\r
9450     int result = -1;\r
9451 \r
9452     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
9453         int index = backwardMostMove;\r
9454         int has_book_hit = 0;\r
9455 \r
9456         if( (index % 2) != side ) {\r
9457             index++;\r
9458         }\r
9459 \r
9460         while( index < forwardMostMove ) {\r
9461             /* Check to see if engine is in book */\r
9462             int depth = pvInfoList[index].depth;\r
9463             int score = pvInfoList[index].score;\r
9464             int in_book = 0;\r
9465 \r
9466             if( depth <= 2 ) {\r
9467                 in_book = 1;\r
9468             }\r
9469             else if( score == 0 && depth == 63 ) {\r
9470                 in_book = 1; /* Zappa */\r
9471             }\r
9472             else if( score == 2 && depth == 99 ) {\r
9473                 in_book = 1; /* Abrok */\r
9474             }\r
9475 \r
9476             has_book_hit += in_book;\r
9477 \r
9478             if( ! in_book ) {\r
9479                 result = index;\r
9480 \r
9481                 break;\r
9482             }\r
9483 \r
9484             index += 2;\r
9485         }\r
9486     }\r
9487 \r
9488     return result;\r
9489 }\r
9490 \r
9491 /* [AS] */\r
9492 void GetOutOfBookInfo( char * buf )\r
9493 {\r
9494     int oob[2];\r
9495     int i;\r
9496     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9497 \r
9498     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
9499     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
9500 \r
9501     *buf = '\0';\r
9502 \r
9503     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
9504         for( i=0; i<2; i++ ) {\r
9505             int idx = oob[i];\r
9506 \r
9507             if( idx >= 0 ) {\r
9508                 if( i > 0 && oob[0] >= 0 ) {\r
9509                     strcat( buf, "   " );\r
9510                 }\r
9511 \r
9512                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
9513                 sprintf( buf+strlen(buf), "%s%.2f", \r
9514                     pvInfoList[idx].score >= 0 ? "+" : "",\r
9515                     pvInfoList[idx].score / 100.0 );\r
9516             }\r
9517         }\r
9518     }\r
9519 }\r
9520 \r
9521 /* Save game in PGN style and close the file */\r
9522 int\r
9523 SaveGamePGN(f)\r
9524      FILE *f;\r
9525 {\r
9526     int i, offset, linelen, newblock;\r
9527     time_t tm;\r
9528 //    char *movetext;\r
9529     char numtext[32];\r
9530     int movelen, numlen, blank;\r
9531     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
9532 \r
9533     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9534     \r
9535     tm = time((time_t *) NULL);\r
9536     \r
9537     PrintPGNTags(f, &gameInfo);\r
9538     \r
9539     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9540         char *fen = PositionToFEN(backwardMostMove, 1);\r
9541         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
9542         fprintf(f, "\n{--------------\n");\r
9543         PrintPosition(f, backwardMostMove);\r
9544         fprintf(f, "--------------}\n");\r
9545         free(fen);\r
9546     }\r
9547     else {\r
9548         /* [AS] Out of book annotation */\r
9549         if( appData.saveOutOfBookInfo ) {\r
9550             char buf[64];\r
9551 \r
9552             GetOutOfBookInfo( buf );\r
9553 \r
9554             if( buf[0] != '\0' ) {\r
9555                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
9556             }\r
9557         }\r
9558 \r
9559         fprintf(f, "\n");\r
9560     }\r
9561 \r
9562     i = backwardMostMove;\r
9563     linelen = 0;\r
9564     newblock = TRUE;\r
9565 \r
9566     while (i < forwardMostMove) {\r
9567         /* Print comments preceding this move */\r
9568         if (commentList[i] != NULL) {\r
9569             if (linelen > 0) fprintf(f, "\n");\r
9570             fprintf(f, "{\n%s}\n", commentList[i]);\r
9571             linelen = 0;\r
9572             newblock = TRUE;\r
9573         }\r
9574 \r
9575         /* Format move number */\r
9576         if ((i % 2) == 0) {\r
9577             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
9578         } else {\r
9579             if (newblock) {\r
9580                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
9581             } else {\r
9582                 numtext[0] = NULLCHAR;\r
9583             }\r
9584         }\r
9585         numlen = strlen(numtext);\r
9586         newblock = FALSE;\r
9587 \r
9588         /* Print move number */\r
9589         blank = linelen > 0 && numlen > 0;\r
9590         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
9591             fprintf(f, "\n");\r
9592             linelen = 0;\r
9593             blank = 0;\r
9594         }\r
9595         if (blank) {\r
9596             fprintf(f, " ");\r
9597             linelen++;\r
9598         }\r
9599         fprintf(f, numtext);\r
9600         linelen += numlen;\r
9601 \r
9602         /* Get move */\r
9603         movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */\r
9604 \r
9605         /* Print move */\r
9606         blank = linelen > 0 && movelen > 0;\r
9607         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9608             fprintf(f, "\n");\r
9609             linelen = 0;\r
9610             blank = 0;\r
9611         }\r
9612         if (blank) {\r
9613             fprintf(f, " ");\r
9614             linelen++;\r
9615         }\r
9616         fprintf(f, parseList[i]);\r
9617         linelen += movelen;\r
9618 \r
9619         /* [AS] Add PV info if present */\r
9620         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9621             /* [HGM] add time */\r
9622             char buf[MSG_SIZ]; int seconds = 0;\r
9623 \r
9624 #if 0\r
9625             if(i >= backwardMostMove) {\r
9626                 if(WhiteOnMove(i))\r
9627                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]\r
9628                                   + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;\r
9629                 else\r
9630                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]\r
9631                                   + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;\r
9632             }\r
9633             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest\r
9634 #else\r
9635             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time\r
9636 #endif\r
9637 \r
9638             if( seconds <= 0) buf[0] = 0; else\r
9639             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {\r
9640                 seconds = (seconds + 4)/10; // round to full seconds\r
9641                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else\r
9642                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
9643             }\r
9644 \r
9645             sprintf( move_buffer, "{%s%.2f/%d%s}", \r
9646                 pvInfoList[i].score >= 0 ? "+" : "",\r
9647                 pvInfoList[i].score / 100.0,\r
9648                 pvInfoList[i].depth,\r
9649                 buf );\r
9650 \r
9651             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */\r
9652 \r
9653             /* Print score/depth */\r
9654             blank = linelen > 0 && movelen > 0;\r
9655             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9656                 fprintf(f, "\n");\r
9657                 linelen = 0;\r
9658                 blank = 0;\r
9659             }\r
9660             if (blank) {\r
9661                 fprintf(f, " ");\r
9662                 linelen++;\r
9663             }\r
9664             fprintf(f, move_buffer);\r
9665             linelen += movelen;\r
9666         }\r
9667 \r
9668         i++;\r
9669     }\r
9670     \r
9671     /* Start a new line */\r
9672     if (linelen > 0) fprintf(f, "\n");\r
9673 \r
9674     /* Print comments after last move */\r
9675     if (commentList[i] != NULL) {\r
9676         fprintf(f, "{\n%s}\n", commentList[i]);\r
9677     }\r
9678 \r
9679     /* Print result */\r
9680     if (gameInfo.resultDetails != NULL &&\r
9681         gameInfo.resultDetails[0] != NULLCHAR) {\r
9682         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
9683                 PGNResult(gameInfo.result));\r
9684     } else {\r
9685         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9686     }\r
9687 \r
9688     fclose(f);\r
9689     return TRUE;\r
9690 }\r
9691 \r
9692 /* Save game in old style and close the file */\r
9693 int\r
9694 SaveGameOldStyle(f)\r
9695      FILE *f;\r
9696 {\r
9697     int i, offset;\r
9698     time_t tm;\r
9699     \r
9700     tm = time((time_t *) NULL);\r
9701     \r
9702     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
9703     PrintOpponents(f);\r
9704     \r
9705     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9706         fprintf(f, "\n[--------------\n");\r
9707         PrintPosition(f, backwardMostMove);\r
9708         fprintf(f, "--------------]\n");\r
9709     } else {\r
9710         fprintf(f, "\n");\r
9711     }\r
9712 \r
9713     i = backwardMostMove;\r
9714     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9715 \r
9716     while (i < forwardMostMove) {\r
9717         if (commentList[i] != NULL) {\r
9718             fprintf(f, "[%s]\n", commentList[i]);\r
9719         }\r
9720 \r
9721         if ((i % 2) == 1) {\r
9722             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
9723             i++;\r
9724         } else {\r
9725             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
9726             i++;\r
9727             if (commentList[i] != NULL) {\r
9728                 fprintf(f, "\n");\r
9729                 continue;\r
9730             }\r
9731             if (i >= forwardMostMove) {\r
9732                 fprintf(f, "\n");\r
9733                 break;\r
9734             }\r
9735             fprintf(f, "%s\n", parseList[i]);\r
9736             i++;\r
9737         }\r
9738     }\r
9739     \r
9740     if (commentList[i] != NULL) {\r
9741         fprintf(f, "[%s]\n", commentList[i]);\r
9742     }\r
9743 \r
9744     /* This isn't really the old style, but it's close enough */\r
9745     if (gameInfo.resultDetails != NULL &&\r
9746         gameInfo.resultDetails[0] != NULLCHAR) {\r
9747         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
9748                 gameInfo.resultDetails);\r
9749     } else {\r
9750         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9751     }\r
9752 \r
9753     fclose(f);\r
9754     return TRUE;\r
9755 }\r
9756 \r
9757 /* Save the current game to open file f and close the file */\r
9758 int\r
9759 SaveGame(f, dummy, dummy2)\r
9760      FILE *f;\r
9761      int dummy;\r
9762      char *dummy2;\r
9763 {\r
9764     if (gameMode == EditPosition) EditPositionDone();\r
9765     if (appData.oldSaveStyle)\r
9766       return SaveGameOldStyle(f);\r
9767     else\r
9768       return SaveGamePGN(f);\r
9769 }\r
9770 \r
9771 /* Save the current position to the given file */\r
9772 int\r
9773 SavePositionToFile(filename)\r
9774      char *filename;\r
9775 {\r
9776     FILE *f;\r
9777     char buf[MSG_SIZ];\r
9778 \r
9779     if (strcmp(filename, "-") == 0) {\r
9780         return SavePosition(stdout, 0, NULL);\r
9781     } else {\r
9782         f = fopen(filename, "a");\r
9783         if (f == NULL) {\r
9784             sprintf(buf, _("Can't open \"%s\""), filename);\r
9785             DisplayError(buf, errno);\r
9786             return FALSE;\r
9787         } else {\r
9788             SavePosition(f, 0, NULL);\r
9789             return TRUE;\r
9790         }\r
9791     }\r
9792 }\r
9793 \r
9794 /* Save the current position to the given open file and close the file */\r
9795 int\r
9796 SavePosition(f, dummy, dummy2)\r
9797      FILE *f;\r
9798      int dummy;\r
9799      char *dummy2;\r
9800 {\r
9801     time_t tm;\r
9802     char *fen;\r
9803     \r
9804     if (appData.oldSaveStyle) {\r
9805         tm = time((time_t *) NULL);\r
9806     \r
9807         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
9808         PrintOpponents(f);\r
9809         fprintf(f, "[--------------\n");\r
9810         PrintPosition(f, currentMove);\r
9811         fprintf(f, "--------------]\n");\r
9812     } else {\r
9813         fen = PositionToFEN(currentMove, 1);\r
9814         fprintf(f, "%s\n", fen);\r
9815         free(fen);\r
9816     }\r
9817     fclose(f);\r
9818     return TRUE;\r
9819 }\r
9820 \r
9821 void\r
9822 ReloadCmailMsgEvent(unregister)\r
9823      int unregister;\r
9824 {\r
9825 #if !WIN32\r
9826     static char *inFilename = NULL;\r
9827     static char *outFilename;\r
9828     int i;\r
9829     struct stat inbuf, outbuf;\r
9830     int status;\r
9831     \r
9832     /* Any registered moves are unregistered if unregister is set, */\r
9833     /* i.e. invoked by the signal handler */\r
9834     if (unregister) {\r
9835         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9836             cmailMoveRegistered[i] = FALSE;\r
9837             if (cmailCommentList[i] != NULL) {\r
9838                 free(cmailCommentList[i]);\r
9839                 cmailCommentList[i] = NULL;\r
9840             }\r
9841         }\r
9842         nCmailMovesRegistered = 0;\r
9843     }\r
9844 \r
9845     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9846         cmailResult[i] = CMAIL_NOT_RESULT;\r
9847     }\r
9848     nCmailResults = 0;\r
9849 \r
9850     if (inFilename == NULL) {\r
9851         /* Because the filenames are static they only get malloced once  */\r
9852         /* and they never get freed                                      */\r
9853         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
9854         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
9855 \r
9856         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
9857         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
9858     }\r
9859     \r
9860     status = stat(outFilename, &outbuf);\r
9861     if (status < 0) {\r
9862         cmailMailedMove = FALSE;\r
9863     } else {\r
9864         status = stat(inFilename, &inbuf);\r
9865         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
9866     }\r
9867     \r
9868     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
9869        counts the games, notes how each one terminated, etc.\r
9870        \r
9871        It would be nice to remove this kludge and instead gather all\r
9872        the information while building the game list.  (And to keep it\r
9873        in the game list nodes instead of having a bunch of fixed-size\r
9874        parallel arrays.)  Note this will require getting each game's\r
9875        termination from the PGN tags, as the game list builder does\r
9876        not process the game moves.  --mann\r
9877        */\r
9878     cmailMsgLoaded = TRUE;\r
9879     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
9880     \r
9881     /* Load first game in the file or popup game menu */\r
9882     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
9883 \r
9884 #endif /* !WIN32 */\r
9885     return;\r
9886 }\r
9887 \r
9888 int\r
9889 RegisterMove()\r
9890 {\r
9891     FILE *f;\r
9892     char string[MSG_SIZ];\r
9893 \r
9894     if (   cmailMailedMove\r
9895         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9896         return TRUE;            /* Allow free viewing  */\r
9897     }\r
9898 \r
9899     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9900     /* with the move registered when the conditions for registering no   */\r
9901     /* longer hold                                                       */\r
9902     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9903         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9904         nCmailMovesRegistered --;\r
9905 \r
9906         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9907           {\r
9908               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9909               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9910           }\r
9911     }\r
9912 \r
9913     if (cmailOldMove == -1) {\r
9914         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);\r
9915         return FALSE;\r
9916     }\r
9917 \r
9918     if (currentMove > cmailOldMove + 1) {\r
9919         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);\r
9920         return FALSE;\r
9921     }\r
9922 \r
9923     if (currentMove < cmailOldMove) {\r
9924         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);\r
9925         return FALSE;\r
9926     }\r
9927 \r
9928     if (forwardMostMove > currentMove) {\r
9929         /* Silently truncate extra moves */\r
9930         TruncateGame();\r
9931     }\r
9932 \r
9933     if (   (currentMove == cmailOldMove + 1)\r
9934         || (   (currentMove == cmailOldMove)\r
9935             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9936                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9937         if (gameInfo.result != GameUnfinished) {\r
9938             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9939         }\r
9940 \r
9941         if (commentList[currentMove] != NULL) {\r
9942             cmailCommentList[lastLoadGameNumber - 1]\r
9943               = StrSave(commentList[currentMove]);\r
9944         }\r
9945         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9946 \r
9947         if (appData.debugMode)\r
9948           fprintf(debugFP, "Saving %s for game %d\n",\r
9949                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9950 \r
9951         sprintf(string,\r
9952                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
9953         \r
9954         f = fopen(string, "w");\r
9955         if (appData.oldSaveStyle) {\r
9956             SaveGameOldStyle(f); /* also closes the file */\r
9957             \r
9958             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
9959             f = fopen(string, "w");\r
9960             SavePosition(f, 0, NULL); /* also closes the file */\r
9961         } else {\r
9962             fprintf(f, "{--------------\n");\r
9963             PrintPosition(f, currentMove);\r
9964             fprintf(f, "--------------}\n\n");\r
9965             \r
9966             SaveGame(f, 0, NULL); /* also closes the file*/\r
9967         }\r
9968         \r
9969         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
9970         nCmailMovesRegistered ++;\r
9971     } else if (nCmailGames == 1) {\r
9972         DisplayError(_("You have not made a move yet"), 0);\r
9973         return FALSE;\r
9974     }\r
9975 \r
9976     return TRUE;\r
9977 }\r
9978 \r
9979 void\r
9980 MailMoveEvent()\r
9981 {\r
9982 #if !WIN32\r
9983     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
9984     FILE *commandOutput;\r
9985     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
9986     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
9987     int nBuffers;\r
9988     int i;\r
9989     int archived;\r
9990     char *arcDir;\r
9991 \r
9992     if (! cmailMsgLoaded) {\r
9993         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);\r
9994         return;\r
9995     }\r
9996 \r
9997     if (nCmailGames == nCmailResults) {\r
9998         DisplayError(_("No unfinished games"), 0);\r
9999         return;\r
10000     }\r
10001 \r
10002 #if CMAIL_PROHIBIT_REMAIL\r
10003     if (cmailMailedMove) {\r
10004         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
10005         DisplayError(msg, 0);\r
10006         return;\r
10007     }\r
10008 #endif\r
10009 \r
10010     if (! (cmailMailedMove || RegisterMove())) return;\r
10011     \r
10012     if (   cmailMailedMove\r
10013         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
10014         sprintf(string, partCommandString,\r
10015                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
10016         commandOutput = popen(string, "r");\r
10017 \r
10018         if (commandOutput == NULL) {\r
10019             DisplayError(_("Failed to invoke cmail"), 0);\r
10020         } else {\r
10021             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
10022                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
10023             }\r
10024             if (nBuffers > 1) {\r
10025                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
10026                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
10027                 nBytes = MSG_SIZ - 1;\r
10028             } else {\r
10029                 (void) memcpy(msg, buffer, nBytes);\r
10030             }\r
10031             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
10032 \r
10033             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
10034                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
10035 \r
10036                 archived = TRUE;\r
10037                 for (i = 0; i < nCmailGames; i ++) {\r
10038                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
10039                         archived = FALSE;\r
10040                     }\r
10041                 }\r
10042                 if (   archived\r
10043                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
10044                         != NULL)) {\r
10045                     sprintf(buffer, "%s/%s.%s.archive",\r
10046                             arcDir,\r
10047                             appData.cmailGameName,\r
10048                             gameInfo.date);\r
10049                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
10050                     cmailMsgLoaded = FALSE;\r
10051                 }\r
10052             }\r
10053 \r
10054             DisplayInformation(msg);\r
10055             pclose(commandOutput);\r
10056         }\r
10057     } else {\r
10058         if ((*cmailMsg) != '\0') {\r
10059             DisplayInformation(cmailMsg);\r
10060         }\r
10061     }\r
10062 \r
10063     return;\r
10064 #endif /* !WIN32 */\r
10065 }\r
10066 \r
10067 char *\r
10068 CmailMsg()\r
10069 {\r
10070 #if WIN32\r
10071     return NULL;\r
10072 #else\r
10073     int  prependComma = 0;\r
10074     char number[5];\r
10075     char string[MSG_SIZ];       /* Space for game-list */\r
10076     int  i;\r
10077     \r
10078     if (!cmailMsgLoaded) return "";\r
10079 \r
10080     if (cmailMailedMove) {\r
10081         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));\r
10082     } else {\r
10083         /* Create a list of games left */\r
10084         sprintf(string, "[");\r
10085         for (i = 0; i < nCmailGames; i ++) {\r
10086             if (! (   cmailMoveRegistered[i]\r
10087                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
10088                 if (prependComma) {\r
10089                     sprintf(number, ",%d", i + 1);\r
10090                 } else {\r
10091                     sprintf(number, "%d", i + 1);\r
10092                     prependComma = 1;\r
10093                 }\r
10094                 \r
10095                 strcat(string, number);\r
10096             }\r
10097         }\r
10098         strcat(string, "]");\r
10099 \r
10100         if (nCmailMovesRegistered + nCmailResults == 0) {\r
10101             switch (nCmailGames) {\r
10102               case 1:\r
10103                 sprintf(cmailMsg,\r
10104                         _("Still need to make move for game\n"));\r
10105                 break;\r
10106                 \r
10107               case 2:\r
10108                 sprintf(cmailMsg,\r
10109                         _("Still need to make moves for both games\n"));\r
10110                 break;\r
10111                 \r
10112               default:\r
10113                 sprintf(cmailMsg,\r
10114                         _("Still need to make moves for all %d games\n"),\r
10115                         nCmailGames);\r
10116                 break;\r
10117             }\r
10118         } else {\r
10119             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
10120               case 1:\r
10121                 sprintf(cmailMsg,\r
10122                         _("Still need to make a move for game %s\n"),\r
10123                         string);\r
10124                 break;\r
10125                 \r
10126               case 0:\r
10127                 if (nCmailResults == nCmailGames) {\r
10128                     sprintf(cmailMsg, _("No unfinished games\n"));\r
10129                 } else {\r
10130                     sprintf(cmailMsg, _("Ready to send mail\n"));\r
10131                 }\r
10132                 break;\r
10133                 \r
10134               default:\r
10135                 sprintf(cmailMsg,\r
10136                         _("Still need to make moves for games %s\n"),\r
10137                         string);\r
10138             }\r
10139         }\r
10140     }\r
10141     return cmailMsg;\r
10142 #endif /* WIN32 */\r
10143 }\r
10144 \r
10145 void\r
10146 ResetGameEvent()\r
10147 {\r
10148     if (gameMode == Training)\r
10149       SetTrainingModeOff();\r
10150 \r
10151     Reset(TRUE, TRUE);\r
10152     cmailMsgLoaded = FALSE;\r
10153     if (appData.icsActive) {\r
10154       SendToICS(ics_prefix);\r
10155       SendToICS("refresh\n");\r
10156     }\r
10157 }\r
10158 \r
10159 void\r
10160 ExitEvent(status)\r
10161      int status;\r
10162 {\r
10163     exiting++;\r
10164     if (exiting > 2) {\r
10165       /* Give up on clean exit */\r
10166       exit(status);\r
10167     }\r
10168     if (exiting > 1) {\r
10169       /* Keep trying for clean exit */\r
10170       return;\r
10171     }\r
10172 \r
10173     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
10174 \r
10175     if (telnetISR != NULL) {\r
10176       RemoveInputSource(telnetISR);\r
10177     }\r
10178     if (icsPR != NoProc) {\r
10179       DestroyChildProcess(icsPR, TRUE);\r
10180     }\r
10181 #if 0\r
10182     /* Save game if resource set and not already saved by GameEnds() */\r
10183     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
10184                              && forwardMostMove > 0) {\r
10185       if (*appData.saveGameFile != NULLCHAR) {\r
10186         SaveGameToFile(appData.saveGameFile, TRUE);\r
10187       } else if (appData.autoSaveGames) {\r
10188         AutoSaveGame();\r
10189       }\r
10190       if (*appData.savePositionFile != NULLCHAR) {\r
10191         SavePositionToFile(appData.savePositionFile);\r
10192       }\r
10193     }\r
10194     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10195 #else\r
10196     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
10197     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);\r
10198 #endif\r
10199     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
10200     /* make sure this other one finishes before killing it!                  */\r
10201     if(endingGame) { int count = 0;\r
10202         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
10203         while(endingGame && count++ < 10) DoSleep(1);\r
10204         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
10205     }\r
10206 \r
10207     /* Kill off chess programs */\r
10208     if (first.pr != NoProc) {\r
10209         ExitAnalyzeMode();\r
10210         \r
10211         DoSleep( appData.delayBeforeQuit );\r
10212         SendToProgram("quit\n", &first);\r
10213         DoSleep( appData.delayAfterQuit );\r
10214         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
10215     }\r
10216     if (second.pr != NoProc) {\r
10217         DoSleep( appData.delayBeforeQuit );\r
10218         SendToProgram("quit\n", &second);\r
10219         DoSleep( appData.delayAfterQuit );\r
10220         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
10221     }\r
10222     if (first.isr != NULL) {\r
10223         RemoveInputSource(first.isr);\r
10224     }\r
10225     if (second.isr != NULL) {\r
10226         RemoveInputSource(second.isr);\r
10227     }\r
10228 \r
10229     ShutDownFrontEnd();\r
10230     exit(status);\r
10231 }\r
10232 \r
10233 void\r
10234 PauseEvent()\r
10235 {\r
10236     if (appData.debugMode)\r
10237         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
10238     if (pausing) {\r
10239         pausing = FALSE;\r
10240         ModeHighlight();\r
10241         if (gameMode == MachinePlaysWhite ||\r
10242             gameMode == MachinePlaysBlack) {\r
10243             StartClocks();\r
10244         } else {\r
10245             DisplayBothClocks();\r
10246         }\r
10247         if (gameMode == PlayFromGameFile) {\r
10248             if (appData.timeDelay >= 0) \r
10249                 AutoPlayGameLoop();\r
10250         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
10251             Reset(FALSE, TRUE);\r
10252             SendToICS(ics_prefix);\r
10253             SendToICS("refresh\n");\r
10254         } else if (currentMove < forwardMostMove) {\r
10255             ForwardInner(forwardMostMove);\r
10256         }\r
10257         pauseExamInvalid = FALSE;\r
10258     } else {\r
10259         switch (gameMode) {\r
10260           default:\r
10261             return;\r
10262           case IcsExamining:\r
10263             pauseExamForwardMostMove = forwardMostMove;\r
10264             pauseExamInvalid = FALSE;\r
10265             /* fall through */\r
10266           case IcsObserving:\r
10267           case IcsPlayingWhite:\r
10268           case IcsPlayingBlack:\r
10269             pausing = TRUE;\r
10270             ModeHighlight();\r
10271             return;\r
10272           case PlayFromGameFile:\r
10273             (void) StopLoadGameTimer();\r
10274             pausing = TRUE;\r
10275             ModeHighlight();\r
10276             break;\r
10277           case BeginningOfGame:\r
10278             if (appData.icsActive) return;\r
10279             /* else fall through */\r
10280           case MachinePlaysWhite:\r
10281           case MachinePlaysBlack:\r
10282           case TwoMachinesPlay:\r
10283             if (forwardMostMove == 0)\r
10284               return;           /* don't pause if no one has moved */\r
10285             if ((gameMode == MachinePlaysWhite &&\r
10286                  !WhiteOnMove(forwardMostMove)) ||\r
10287                 (gameMode == MachinePlaysBlack &&\r
10288                  WhiteOnMove(forwardMostMove))) {\r
10289                 StopClocks();\r
10290             }\r
10291             pausing = TRUE;\r
10292             ModeHighlight();\r
10293             break;\r
10294         }\r
10295     }\r
10296 }\r
10297 \r
10298 void\r
10299 EditCommentEvent()\r
10300 {\r
10301     char title[MSG_SIZ];\r
10302 \r
10303     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
10304         strcpy(title, _("Edit comment"));\r
10305     } else {\r
10306         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,\r
10307                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
10308                 parseList[currentMove - 1]);\r
10309     }\r
10310 \r
10311     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
10312 }\r
10313 \r
10314 \r
10315 void\r
10316 EditTagsEvent()\r
10317 {\r
10318     char *tags = PGNTags(&gameInfo);\r
10319     EditTagsPopUp(tags);\r
10320     free(tags);\r
10321 }\r
10322 \r
10323 void\r
10324 AnalyzeModeEvent()\r
10325 {\r
10326     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
10327       return;\r
10328 \r
10329     if (gameMode != AnalyzeFile) {\r
10330         if (!appData.icsEngineAnalyze) {\r
10331                EditGameEvent();\r
10332                if (gameMode != EditGame) return;\r
10333         }\r
10334         ResurrectChessProgram();\r
10335         SendToProgram("analyze\n", &first);\r
10336         first.analyzing = TRUE;\r
10337         /*first.maybeThinking = TRUE;*/\r
10338         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10339         AnalysisPopUp(_("Analysis"),\r
10340                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10341     }\r
10342     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;\r
10343     pausing = FALSE;\r
10344     ModeHighlight();\r
10345     SetGameInfo();\r
10346 \r
10347     StartAnalysisClock();\r
10348     GetTimeMark(&lastNodeCountTime);\r
10349     lastNodeCount = 0;\r
10350 }\r
10351 \r
10352 void\r
10353 AnalyzeFileEvent()\r
10354 {\r
10355     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
10356       return;\r
10357 \r
10358     if (gameMode != AnalyzeMode) {\r
10359         EditGameEvent();\r
10360         if (gameMode != EditGame) return;\r
10361         ResurrectChessProgram();\r
10362         SendToProgram("analyze\n", &first);\r
10363         first.analyzing = TRUE;\r
10364         /*first.maybeThinking = TRUE;*/\r
10365         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10366         AnalysisPopUp(_("Analysis"),\r
10367                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10368     }\r
10369     gameMode = AnalyzeFile;\r
10370     pausing = FALSE;\r
10371     ModeHighlight();\r
10372     SetGameInfo();\r
10373 \r
10374     StartAnalysisClock();\r
10375     GetTimeMark(&lastNodeCountTime);\r
10376     lastNodeCount = 0;\r
10377 }\r
10378 \r
10379 void\r
10380 MachineWhiteEvent()\r
10381 {\r
10382     char buf[MSG_SIZ];\r
10383     char *bookHit = NULL;\r
10384 \r
10385     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
10386       return;\r
10387 \r
10388 \r
10389     if (gameMode == PlayFromGameFile || \r
10390         gameMode == TwoMachinesPlay  || \r
10391         gameMode == Training         || \r
10392         gameMode == AnalyzeMode      || \r
10393         gameMode == EndOfGame)\r
10394         EditGameEvent();\r
10395 \r
10396     if (gameMode == EditPosition) \r
10397         EditPositionDone();\r
10398 \r
10399     if (!WhiteOnMove(currentMove)) {\r
10400         DisplayError(_("It is not White's turn"), 0);\r
10401         return;\r
10402     }\r
10403   \r
10404     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10405       ExitAnalyzeMode();\r
10406 \r
10407     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10408         gameMode == AnalyzeFile)\r
10409         TruncateGame();\r
10410 \r
10411     ResurrectChessProgram();    /* in case it isn't running */\r
10412     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */\r
10413         gameMode = MachinePlaysWhite;\r
10414         ResetClocks();\r
10415     } else\r
10416     gameMode = MachinePlaysWhite;\r
10417     pausing = FALSE;\r
10418     ModeHighlight();\r
10419     SetGameInfo();\r
10420     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10421     DisplayTitle(buf);\r
10422     if (first.sendName) {\r
10423       sprintf(buf, "name %s\n", gameInfo.black);\r
10424       SendToProgram(buf, &first);\r
10425     }\r
10426     if (first.sendTime) {\r
10427       if (first.useColors) {\r
10428         SendToProgram("black\n", &first); /*gnu kludge*/\r
10429       }\r
10430       SendTimeRemaining(&first, TRUE);\r
10431     }\r
10432     if (first.useColors) {\r
10433       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately\r
10434     }\r
10435     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10436     SetMachineThinkingEnables();\r
10437     first.maybeThinking = TRUE;\r
10438     StartClocks();\r
10439 \r
10440     if (appData.autoFlipView && !flipView) {\r
10441       flipView = !flipView;\r
10442       DrawPosition(FALSE, NULL);\r
10443       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10444     }\r
10445 \r
10446     if(bookHit) { // [HGM] book: simulate book reply\r
10447         static char bookMove[MSG_SIZ]; // a bit generous?\r
10448 \r
10449         programStats.nodes = programStats.depth = programStats.time = \r
10450         programStats.score = programStats.got_only_move = 0;\r
10451         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10452 \r
10453         strcpy(bookMove, "move ");\r
10454         strcat(bookMove, bookHit);\r
10455         HandleMachineMove(bookMove, &first);\r
10456     }\r
10457 }\r
10458 \r
10459 void\r
10460 MachineBlackEvent()\r
10461 {\r
10462     char buf[MSG_SIZ];\r
10463    char *bookHit = NULL;\r
10464 \r
10465     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
10466         return;\r
10467 \r
10468 \r
10469     if (gameMode == PlayFromGameFile || \r
10470         gameMode == TwoMachinesPlay  || \r
10471         gameMode == Training         || \r
10472         gameMode == AnalyzeMode      || \r
10473         gameMode == EndOfGame)\r
10474         EditGameEvent();\r
10475 \r
10476     if (gameMode == EditPosition) \r
10477         EditPositionDone();\r
10478 \r
10479     if (WhiteOnMove(currentMove)) {\r
10480         DisplayError(_("It is not Black's turn"), 0);\r
10481         return;\r
10482     }\r
10483     \r
10484     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10485       ExitAnalyzeMode();\r
10486 \r
10487     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10488         gameMode == AnalyzeFile)\r
10489         TruncateGame();\r
10490 \r
10491     ResurrectChessProgram();    /* in case it isn't running */\r
10492     gameMode = MachinePlaysBlack;\r
10493     pausing = FALSE;\r
10494     ModeHighlight();\r
10495     SetGameInfo();\r
10496     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10497     DisplayTitle(buf);\r
10498     if (first.sendName) {\r
10499       sprintf(buf, "name %s\n", gameInfo.white);\r
10500       SendToProgram(buf, &first);\r
10501     }\r
10502     if (first.sendTime) {\r
10503       if (first.useColors) {\r
10504         SendToProgram("white\n", &first); /*gnu kludge*/\r
10505       }\r
10506       SendTimeRemaining(&first, FALSE);\r
10507     }\r
10508     if (first.useColors) {\r
10509       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately\r
10510     }\r
10511     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10512     SetMachineThinkingEnables();\r
10513     first.maybeThinking = TRUE;\r
10514     StartClocks();\r
10515 \r
10516     if (appData.autoFlipView && flipView) {\r
10517       flipView = !flipView;\r
10518       DrawPosition(FALSE, NULL);\r
10519       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10520     }\r
10521     if(bookHit) { // [HGM] book: simulate book reply\r
10522         static char bookMove[MSG_SIZ]; // a bit generous?\r
10523 \r
10524         programStats.nodes = programStats.depth = programStats.time = \r
10525         programStats.score = programStats.got_only_move = 0;\r
10526         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10527 \r
10528         strcpy(bookMove, "move ");\r
10529         strcat(bookMove, bookHit);\r
10530         HandleMachineMove(bookMove, &first);\r
10531     }\r
10532 }\r
10533 \r
10534 \r
10535 void\r
10536 DisplayTwoMachinesTitle()\r
10537 {\r
10538     char buf[MSG_SIZ];\r
10539     if (appData.matchGames > 0) {\r
10540         if (first.twoMachinesColor[0] == 'w') {\r
10541             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10542                     gameInfo.white, gameInfo.black,\r
10543                     first.matchWins, second.matchWins,\r
10544                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10545         } else {\r
10546             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10547                     gameInfo.white, gameInfo.black,\r
10548                     second.matchWins, first.matchWins,\r
10549                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10550         }\r
10551     } else {\r
10552         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10553     }\r
10554     DisplayTitle(buf);\r
10555 }\r
10556 \r
10557 void\r
10558 TwoMachinesEvent P((void))\r
10559 {\r
10560     int i;\r
10561     char buf[MSG_SIZ];\r
10562     ChessProgramState *onmove;\r
10563     char *bookHit = NULL;\r
10564     \r
10565     if (appData.noChessProgram) return;\r
10566 \r
10567     switch (gameMode) {\r
10568       case TwoMachinesPlay:\r
10569         return;\r
10570       case MachinePlaysWhite:\r
10571       case MachinePlaysBlack:\r
10572         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10573             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
10574             return;\r
10575         }\r
10576         /* fall through */\r
10577       case BeginningOfGame:\r
10578       case PlayFromGameFile:\r
10579       case EndOfGame:\r
10580         EditGameEvent();\r
10581         if (gameMode != EditGame) return;\r
10582         break;\r
10583       case EditPosition:\r
10584         EditPositionDone();\r
10585         break;\r
10586       case AnalyzeMode:\r
10587       case AnalyzeFile:\r
10588         ExitAnalyzeMode();\r
10589         break;\r
10590       case EditGame:\r
10591       default:\r
10592         break;\r
10593     }\r
10594 \r
10595     forwardMostMove = currentMove;\r
10596     ResurrectChessProgram();    /* in case first program isn't running */\r
10597 \r
10598     if (second.pr == NULL) {\r
10599         StartChessProgram(&second);\r
10600         if (second.protocolVersion == 1) {\r
10601           TwoMachinesEventIfReady();\r
10602         } else {\r
10603           /* kludge: allow timeout for initial "feature" command */\r
10604           FreezeUI();\r
10605           DisplayMessage("", _("Starting second chess program"));\r
10606           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
10607         }\r
10608         return;\r
10609     }\r
10610     DisplayMessage("", "");\r
10611     InitChessProgram(&second, FALSE);\r
10612     SendToProgram("force\n", &second);\r
10613     if (startedFromSetupPosition) {\r
10614         SendBoard(&second, backwardMostMove);\r
10615     if (appData.debugMode) {\r
10616         fprintf(debugFP, "Two Machines\n");\r
10617     }\r
10618     }\r
10619     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
10620         SendMoveToProgram(i, &second);\r
10621     }\r
10622 \r
10623     gameMode = TwoMachinesPlay;\r
10624     pausing = FALSE;\r
10625     ModeHighlight();\r
10626     SetGameInfo();\r
10627     DisplayTwoMachinesTitle();\r
10628     firstMove = TRUE;\r
10629     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
10630         onmove = &first;\r
10631     } else {\r
10632         onmove = &second;\r
10633     }\r
10634 \r
10635     SendToProgram(first.computerString, &first);\r
10636     if (first.sendName) {\r
10637       sprintf(buf, "name %s\n", second.tidy);\r
10638       SendToProgram(buf, &first);\r
10639     }\r
10640     SendToProgram(second.computerString, &second);\r
10641     if (second.sendName) {\r
10642       sprintf(buf, "name %s\n", first.tidy);\r
10643       SendToProgram(buf, &second);\r
10644     }\r
10645 \r
10646     ResetClocks();\r
10647     if (!first.sendTime || !second.sendTime) {\r
10648         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10649         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10650     }\r
10651     if (onmove->sendTime) {\r
10652       if (onmove->useColors) {\r
10653         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
10654       }\r
10655       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
10656     }\r
10657     if (onmove->useColors) {\r
10658       SendToProgram(onmove->twoMachinesColor, onmove);\r
10659     }\r
10660     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move\r
10661 //    SendToProgram("go\n", onmove);\r
10662     onmove->maybeThinking = TRUE;\r
10663     SetMachineThinkingEnables();\r
10664 \r
10665     StartClocks();\r
10666 \r
10667     if(bookHit) { // [HGM] book: simulate book reply\r
10668         static char bookMove[MSG_SIZ]; // a bit generous?\r
10669 \r
10670         programStats.nodes = programStats.depth = programStats.time = \r
10671         programStats.score = programStats.got_only_move = 0;\r
10672         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10673 \r
10674         strcpy(bookMove, "move ");\r
10675         strcat(bookMove, bookHit);\r
10676         HandleMachineMove(bookMove, &first);\r
10677     }\r
10678 }\r
10679 \r
10680 void\r
10681 TrainingEvent()\r
10682 {\r
10683     if (gameMode == Training) {\r
10684       SetTrainingModeOff();\r
10685       gameMode = PlayFromGameFile;\r
10686       DisplayMessage("", _("Training mode off"));\r
10687     } else {\r
10688       gameMode = Training;\r
10689       animateTraining = appData.animate;\r
10690 \r
10691       /* make sure we are not already at the end of the game */\r
10692       if (currentMove < forwardMostMove) {\r
10693         SetTrainingModeOn();\r
10694         DisplayMessage("", _("Training mode on"));\r
10695       } else {\r
10696         gameMode = PlayFromGameFile;\r
10697         DisplayError(_("Already at end of game"), 0);\r
10698       }\r
10699     }\r
10700     ModeHighlight();\r
10701 }\r
10702 \r
10703 void\r
10704 IcsClientEvent()\r
10705 {\r
10706     if (!appData.icsActive) return;\r
10707     switch (gameMode) {\r
10708       case IcsPlayingWhite:\r
10709       case IcsPlayingBlack:\r
10710       case IcsObserving:\r
10711       case IcsIdle:\r
10712       case BeginningOfGame:\r
10713       case IcsExamining:\r
10714         return;\r
10715 \r
10716       case EditGame:\r
10717         break;\r
10718 \r
10719       case EditPosition:\r
10720         EditPositionDone();\r
10721         break;\r
10722 \r
10723       case AnalyzeMode:\r
10724       case AnalyzeFile:\r
10725         ExitAnalyzeMode();\r
10726         break;\r
10727         \r
10728       default:\r
10729         EditGameEvent();\r
10730         break;\r
10731     }\r
10732 \r
10733     gameMode = IcsIdle;\r
10734     ModeHighlight();\r
10735     return;\r
10736 }\r
10737 \r
10738 \r
10739 void\r
10740 EditGameEvent()\r
10741 {\r
10742     int i;\r
10743 \r
10744     switch (gameMode) {\r
10745       case Training:\r
10746         SetTrainingModeOff();\r
10747         break;\r
10748       case MachinePlaysWhite:\r
10749       case MachinePlaysBlack:\r
10750       case BeginningOfGame:\r
10751         SendToProgram("force\n", &first);\r
10752         SetUserThinkingEnables();\r
10753         break;\r
10754       case PlayFromGameFile:\r
10755         (void) StopLoadGameTimer();\r
10756         if (gameFileFP != NULL) {\r
10757             gameFileFP = NULL;\r
10758         }\r
10759         break;\r
10760       case EditPosition:\r
10761         EditPositionDone();\r
10762         break;\r
10763       case AnalyzeMode:\r
10764       case AnalyzeFile:\r
10765         ExitAnalyzeMode();\r
10766         SendToProgram("force\n", &first);\r
10767         break;\r
10768       case TwoMachinesPlay:\r
10769         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10770         ResurrectChessProgram();\r
10771         SetUserThinkingEnables();\r
10772         break;\r
10773       case EndOfGame:\r
10774         ResurrectChessProgram();\r
10775         break;\r
10776       case IcsPlayingBlack:\r
10777       case IcsPlayingWhite:\r
10778         DisplayError(_("Warning: You are still playing a game"), 0);\r
10779         break;\r
10780       case IcsObserving:\r
10781         DisplayError(_("Warning: You are still observing a game"), 0);\r
10782         break;\r
10783       case IcsExamining:\r
10784         DisplayError(_("Warning: You are still examining a game"), 0);\r
10785         break;\r
10786       case IcsIdle:\r
10787         break;\r
10788       case EditGame:\r
10789       default:\r
10790         return;\r
10791     }\r
10792     \r
10793     pausing = FALSE;\r
10794     StopClocks();\r
10795     first.offeredDraw = second.offeredDraw = 0;\r
10796 \r
10797     if (gameMode == PlayFromGameFile) {\r
10798         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10799         blackTimeRemaining = timeRemaining[1][currentMove];\r
10800         DisplayTitle("");\r
10801     }\r
10802 \r
10803     if (gameMode == MachinePlaysWhite ||\r
10804         gameMode == MachinePlaysBlack ||\r
10805         gameMode == TwoMachinesPlay ||\r
10806         gameMode == EndOfGame) {\r
10807         i = forwardMostMove;\r
10808         while (i > currentMove) {\r
10809             SendToProgram("undo\n", &first);\r
10810             i--;\r
10811         }\r
10812         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10813         blackTimeRemaining = timeRemaining[1][currentMove];\r
10814         DisplayBothClocks();\r
10815         if (whiteFlag || blackFlag) {\r
10816             whiteFlag = blackFlag = 0;\r
10817         }\r
10818         DisplayTitle("");\r
10819     }           \r
10820     \r
10821     gameMode = EditGame;\r
10822     ModeHighlight();\r
10823     SetGameInfo();\r
10824 }\r
10825 \r
10826 \r
10827 void\r
10828 EditPositionEvent()\r
10829 {\r
10830     if (gameMode == EditPosition) {\r
10831         EditGameEvent();\r
10832         return;\r
10833     }\r
10834     \r
10835     EditGameEvent();\r
10836     if (gameMode != EditGame) return;\r
10837     \r
10838     gameMode = EditPosition;\r
10839     ModeHighlight();\r
10840     SetGameInfo();\r
10841     if (currentMove > 0)\r
10842       CopyBoard(boards[0], boards[currentMove]);\r
10843     \r
10844     blackPlaysFirst = !WhiteOnMove(currentMove);\r
10845     ResetClocks();\r
10846     currentMove = forwardMostMove = backwardMostMove = 0;\r
10847     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10848     DisplayMove(-1);\r
10849 }\r
10850 \r
10851 void\r
10852 ExitAnalyzeMode()\r
10853 {\r
10854     /* [DM] icsEngineAnalyze - possible call from other functions */\r
10855     if (appData.icsEngineAnalyze) {\r
10856         appData.icsEngineAnalyze = FALSE;\r
10857 \r
10858         DisplayMessage("",_("Close ICS engine analyze..."));\r
10859     }\r
10860     if (first.analysisSupport && first.analyzing) {\r
10861       SendToProgram("exit\n", &first);\r
10862       first.analyzing = FALSE;\r
10863     }\r
10864     AnalysisPopDown();\r
10865     thinkOutput[0] = NULLCHAR;\r
10866 }\r
10867 \r
10868 void\r
10869 EditPositionDone()\r
10870 {\r
10871     startedFromSetupPosition = TRUE;\r
10872     InitChessProgram(&first, FALSE);\r
10873     SendToProgram("force\n", &first);\r
10874     if (blackPlaysFirst) {\r
10875         strcpy(moveList[0], "");\r
10876         strcpy(parseList[0], "");\r
10877         currentMove = forwardMostMove = backwardMostMove = 1;\r
10878         CopyBoard(boards[1], boards[0]);\r
10879         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */\r
10880         { int i;\r
10881           epStatus[1] = epStatus[0];\r
10882           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];\r
10883         }\r
10884     } else {\r
10885         currentMove = forwardMostMove = backwardMostMove = 0;\r
10886     }\r
10887     SendBoard(&first, forwardMostMove);\r
10888     if (appData.debugMode) {\r
10889         fprintf(debugFP, "EditPosDone\n");\r
10890     }\r
10891     DisplayTitle("");\r
10892     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10893     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10894     gameMode = EditGame;\r
10895     ModeHighlight();\r
10896     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10897     ClearHighlights(); /* [AS] */\r
10898 }\r
10899 \r
10900 /* Pause for `ms' milliseconds */\r
10901 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10902 void\r
10903 TimeDelay(ms)\r
10904      long ms;\r
10905 {\r
10906     TimeMark m1, m2;\r
10907 \r
10908     GetTimeMark(&m1);\r
10909     do {\r
10910         GetTimeMark(&m2);\r
10911     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
10912 }\r
10913 \r
10914 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10915 void\r
10916 SendMultiLineToICS(buf)\r
10917      char *buf;\r
10918 {\r
10919     char temp[MSG_SIZ+1], *p;\r
10920     int len;\r
10921 \r
10922     len = strlen(buf);\r
10923     if (len > MSG_SIZ)\r
10924       len = MSG_SIZ;\r
10925   \r
10926     strncpy(temp, buf, len);\r
10927     temp[len] = 0;\r
10928 \r
10929     p = temp;\r
10930     while (*p) {\r
10931         if (*p == '\n' || *p == '\r')\r
10932           *p = ' ';\r
10933         ++p;\r
10934     }\r
10935 \r
10936     strcat(temp, "\n");\r
10937     SendToICS(temp);\r
10938     SendToPlayer(temp, strlen(temp));\r
10939 }\r
10940 \r
10941 void\r
10942 SetWhiteToPlayEvent()\r
10943 {\r
10944     if (gameMode == EditPosition) {\r
10945         blackPlaysFirst = FALSE;\r
10946         DisplayBothClocks();    /* works because currentMove is 0 */\r
10947     } else if (gameMode == IcsExamining) {\r
10948         SendToICS(ics_prefix);\r
10949         SendToICS("tomove white\n");\r
10950     }\r
10951 }\r
10952 \r
10953 void\r
10954 SetBlackToPlayEvent()\r
10955 {\r
10956     if (gameMode == EditPosition) {\r
10957         blackPlaysFirst = TRUE;\r
10958         currentMove = 1;        /* kludge */\r
10959         DisplayBothClocks();\r
10960         currentMove = 0;\r
10961     } else if (gameMode == IcsExamining) {\r
10962         SendToICS(ics_prefix);\r
10963         SendToICS("tomove black\n");\r
10964     }\r
10965 }\r
10966 \r
10967 void\r
10968 EditPositionMenuEvent(selection, x, y)\r
10969      ChessSquare selection;\r
10970      int x, y;\r
10971 {\r
10972     char buf[MSG_SIZ];\r
10973     ChessSquare piece = boards[0][y][x];\r
10974 \r
10975     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
10976 \r
10977     switch (selection) {\r
10978       case ClearBoard:\r
10979         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
10980             SendToICS(ics_prefix);\r
10981             SendToICS("bsetup clear\n");\r
10982         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
10983             SendToICS(ics_prefix);\r
10984             SendToICS("clearboard\n");\r
10985         } else {\r
10986             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;\r
10987                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */\r
10988                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
10989                     if (gameMode == IcsExamining) {\r
10990                         if (boards[currentMove][y][x] != EmptySquare) {\r
10991                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
10992                                     AAA + x, ONE + y);\r
10993                             SendToICS(buf);\r
10994                         }\r
10995                     } else {\r
10996                         boards[0][y][x] = p;\r
10997                     }\r
10998                 }\r
10999             }\r
11000         }\r
11001         if (gameMode == EditPosition) {\r
11002             DrawPosition(FALSE, boards[0]);\r
11003         }\r
11004         break;\r
11005 \r
11006       case WhitePlay:\r
11007         SetWhiteToPlayEvent();\r
11008         break;\r
11009 \r
11010       case BlackPlay:\r
11011         SetBlackToPlayEvent();\r
11012         break;\r
11013 \r
11014       case EmptySquare:\r
11015         if (gameMode == IcsExamining) {\r
11016             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
11017             SendToICS(buf);\r
11018         } else {\r
11019             boards[0][y][x] = EmptySquare;\r
11020             DrawPosition(FALSE, boards[0]);\r
11021         }\r
11022         break;\r
11023 \r
11024       case PromotePiece:\r
11025         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
11026            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
11027             selection = (ChessSquare) (PROMOTED piece);\r
11028         } else if(piece == EmptySquare) selection = WhiteSilver;\r
11029         else selection = (ChessSquare)((int)piece - 1);\r
11030         goto defaultlabel;\r
11031 \r
11032       case DemotePiece:\r
11033         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
11034            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
11035             selection = (ChessSquare) (DEMOTED piece);\r
11036         } else if(piece == EmptySquare) selection = BlackSilver;\r
11037         else selection = (ChessSquare)((int)piece + 1);       \r
11038         goto defaultlabel;\r
11039 \r
11040       case WhiteQueen:\r
11041       case BlackQueen:\r
11042         if(gameInfo.variant == VariantShatranj ||\r
11043            gameInfo.variant == VariantXiangqi  ||\r
11044            gameInfo.variant == VariantCourier    )\r
11045             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
11046         goto defaultlabel;\r
11047 \r
11048       case WhiteKing:\r
11049       case BlackKing:\r
11050         if(gameInfo.variant == VariantXiangqi)\r
11051             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
11052         if(gameInfo.variant == VariantKnightmate)\r
11053             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
11054       default:\r
11055         defaultlabel:\r
11056         if (gameMode == IcsExamining) {\r
11057             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
11058                     PieceToChar(selection), AAA + x, ONE + y);\r
11059             SendToICS(buf);\r
11060         } else {\r
11061             boards[0][y][x] = selection;\r
11062             DrawPosition(FALSE, boards[0]);\r
11063         }\r
11064         break;\r
11065     }\r
11066 }\r
11067 \r
11068 \r
11069 void\r
11070 DropMenuEvent(selection, x, y)\r
11071      ChessSquare selection;\r
11072      int x, y;\r
11073 {\r
11074     ChessMove moveType;\r
11075 \r
11076     switch (gameMode) {\r
11077       case IcsPlayingWhite:\r
11078       case MachinePlaysBlack:\r
11079         if (!WhiteOnMove(currentMove)) {\r
11080             DisplayMoveError(_("It is Black's turn"));\r
11081             return;\r
11082         }\r
11083         moveType = WhiteDrop;\r
11084         break;\r
11085       case IcsPlayingBlack:\r
11086       case MachinePlaysWhite:\r
11087         if (WhiteOnMove(currentMove)) {\r
11088             DisplayMoveError(_("It is White's turn"));\r
11089             return;\r
11090         }\r
11091         moveType = BlackDrop;\r
11092         break;\r
11093       case EditGame:\r
11094         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
11095         break;\r
11096       default:\r
11097         return;\r
11098     }\r
11099 \r
11100     if (moveType == BlackDrop && selection < BlackPawn) {\r
11101       selection = (ChessSquare) ((int) selection\r
11102                                  + (int) BlackPawn - (int) WhitePawn);\r
11103     }\r
11104     if (boards[currentMove][y][x] != EmptySquare) {\r
11105         DisplayMoveError(_("That square is occupied"));\r
11106         return;\r
11107     }\r
11108 \r
11109     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
11110 }\r
11111 \r
11112 void\r
11113 AcceptEvent()\r
11114 {\r
11115     /* Accept a pending offer of any kind from opponent */\r
11116     \r
11117     if (appData.icsActive) {\r
11118         SendToICS(ics_prefix);\r
11119         SendToICS("accept\n");\r
11120     } else if (cmailMsgLoaded) {\r
11121         if (currentMove == cmailOldMove &&\r
11122             commentList[cmailOldMove] != NULL &&\r
11123             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11124                    "Black offers a draw" : "White offers a draw")) {\r
11125             TruncateGame();\r
11126             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11127             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11128         } else {\r
11129             DisplayError(_("There is no pending offer on this move"), 0);\r
11130             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11131         }\r
11132     } else {\r
11133         /* Not used for offers from chess program */\r
11134     }\r
11135 }\r
11136 \r
11137 void\r
11138 DeclineEvent()\r
11139 {\r
11140     /* Decline a pending offer of any kind from opponent */\r
11141     \r
11142     if (appData.icsActive) {\r
11143         SendToICS(ics_prefix);\r
11144         SendToICS("decline\n");\r
11145     } else if (cmailMsgLoaded) {\r
11146         if (currentMove == cmailOldMove &&\r
11147             commentList[cmailOldMove] != NULL &&\r
11148             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11149                    "Black offers a draw" : "White offers a draw")) {\r
11150 #ifdef NOTDEF\r
11151             AppendComment(cmailOldMove, "Draw declined");\r
11152             DisplayComment(cmailOldMove - 1, "Draw declined");\r
11153 #endif /*NOTDEF*/\r
11154         } else {\r
11155             DisplayError(_("There is no pending offer on this move"), 0);\r
11156         }\r
11157     } else {\r
11158         /* Not used for offers from chess program */\r
11159     }\r
11160 }\r
11161 \r
11162 void\r
11163 RematchEvent()\r
11164 {\r
11165     /* Issue ICS rematch command */\r
11166     if (appData.icsActive) {\r
11167         SendToICS(ics_prefix);\r
11168         SendToICS("rematch\n");\r
11169     }\r
11170 }\r
11171 \r
11172 void\r
11173 CallFlagEvent()\r
11174 {\r
11175     /* Call your opponent's flag (claim a win on time) */\r
11176     if (appData.icsActive) {\r
11177         SendToICS(ics_prefix);\r
11178         SendToICS("flag\n");\r
11179     } else {\r
11180         switch (gameMode) {\r
11181           default:\r
11182             return;\r
11183           case MachinePlaysWhite:\r
11184             if (whiteFlag) {\r
11185                 if (blackFlag)\r
11186                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11187                            GE_PLAYER);\r
11188                 else\r
11189                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
11190             } else {\r
11191                 DisplayError(_("Your opponent is not out of time"), 0);\r
11192             }\r
11193             break;\r
11194           case MachinePlaysBlack:\r
11195             if (blackFlag) {\r
11196                 if (whiteFlag)\r
11197                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11198                            GE_PLAYER);\r
11199                 else\r
11200                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
11201             } else {\r
11202                 DisplayError(_("Your opponent is not out of time"), 0);\r
11203             }\r
11204             break;\r
11205         }\r
11206     }\r
11207 }\r
11208 \r
11209 void\r
11210 DrawEvent()\r
11211 {\r
11212     /* Offer draw or accept pending draw offer from opponent */\r
11213     \r
11214     if (appData.icsActive) {\r
11215         /* Note: tournament rules require draw offers to be\r
11216            made after you make your move but before you punch\r
11217            your clock.  Currently ICS doesn't let you do that;\r
11218            instead, you immediately punch your clock after making\r
11219            a move, but you can offer a draw at any time. */\r
11220         \r
11221         SendToICS(ics_prefix);\r
11222         SendToICS("draw\n");\r
11223     } else if (cmailMsgLoaded) {\r
11224         if (currentMove == cmailOldMove &&\r
11225             commentList[cmailOldMove] != NULL &&\r
11226             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11227                    "Black offers a draw" : "White offers a draw")) {\r
11228             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11229             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11230         } else if (currentMove == cmailOldMove + 1) {\r
11231             char *offer = WhiteOnMove(cmailOldMove) ?\r
11232               "White offers a draw" : "Black offers a draw";\r
11233             AppendComment(currentMove, offer);\r
11234             DisplayComment(currentMove - 1, offer);\r
11235             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
11236         } else {\r
11237             DisplayError(_("You must make your move before offering a draw"), 0);\r
11238             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11239         }\r
11240     } else if (first.offeredDraw) {\r
11241         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
11242     } else {\r
11243         if (first.sendDrawOffers) {\r
11244             SendToProgram("draw\n", &first);\r
11245             userOfferedDraw = TRUE;\r
11246         }\r
11247     }\r
11248 }\r
11249 \r
11250 void\r
11251 AdjournEvent()\r
11252 {\r
11253     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
11254     \r
11255     if (appData.icsActive) {\r
11256         SendToICS(ics_prefix);\r
11257         SendToICS("adjourn\n");\r
11258     } else {\r
11259         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
11260     }\r
11261 }\r
11262 \r
11263 \r
11264 void\r
11265 AbortEvent()\r
11266 {\r
11267     /* Offer Abort or accept pending Abort offer from opponent */\r
11268     \r
11269     if (appData.icsActive) {\r
11270         SendToICS(ics_prefix);\r
11271         SendToICS("abort\n");\r
11272     } else {\r
11273         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
11274     }\r
11275 }\r
11276 \r
11277 void\r
11278 ResignEvent()\r
11279 {\r
11280     /* Resign.  You can do this even if it's not your turn. */\r
11281     \r
11282     if (appData.icsActive) {\r
11283         SendToICS(ics_prefix);\r
11284         SendToICS("resign\n");\r
11285     } else {\r
11286         switch (gameMode) {\r
11287           case MachinePlaysWhite:\r
11288             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11289             break;\r
11290           case MachinePlaysBlack:\r
11291             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11292             break;\r
11293           case EditGame:\r
11294             if (cmailMsgLoaded) {\r
11295                 TruncateGame();\r
11296                 if (WhiteOnMove(cmailOldMove)) {\r
11297                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11298                 } else {\r
11299                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11300                 }\r
11301                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
11302             }\r
11303             break;\r
11304           default:\r
11305             break;\r
11306         }\r
11307     }\r
11308 }\r
11309 \r
11310 \r
11311 void\r
11312 StopObservingEvent()\r
11313 {\r
11314     /* Stop observing current games */\r
11315     SendToICS(ics_prefix);\r
11316     SendToICS("unobserve\n");\r
11317 }\r
11318 \r
11319 void\r
11320 StopExaminingEvent()\r
11321 {\r
11322     /* Stop observing current game */\r
11323     SendToICS(ics_prefix);\r
11324     SendToICS("unexamine\n");\r
11325 }\r
11326 \r
11327 void\r
11328 ForwardInner(target)\r
11329      int target;\r
11330 {\r
11331     int limit;\r
11332 \r
11333     if (appData.debugMode)\r
11334         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
11335                 target, currentMove, forwardMostMove);\r
11336 \r
11337     if (gameMode == EditPosition)\r
11338       return;\r
11339 \r
11340     if (gameMode == PlayFromGameFile && !pausing)\r
11341       PauseEvent();\r
11342     \r
11343     if (gameMode == IcsExamining && pausing)\r
11344       limit = pauseExamForwardMostMove;\r
11345     else\r
11346       limit = forwardMostMove;\r
11347     \r
11348     if (target > limit) target = limit;\r
11349 \r
11350     if (target > 0 && moveList[target - 1][0]) {\r
11351         int fromX, fromY, toX, toY;\r
11352         toX = moveList[target - 1][2] - AAA;\r
11353         toY = moveList[target - 1][3] - ONE;\r
11354         if (moveList[target - 1][1] == '@') {\r
11355             if (appData.highlightLastMove) {\r
11356                 SetHighlights(-1, -1, toX, toY);\r
11357             }\r
11358         } else {\r
11359             fromX = moveList[target - 1][0] - AAA;\r
11360             fromY = moveList[target - 1][1] - ONE;\r
11361             if (target == currentMove + 1) {\r
11362                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
11363             }\r
11364             if (appData.highlightLastMove) {\r
11365                 SetHighlights(fromX, fromY, toX, toY);\r
11366             }\r
11367         }\r
11368     }\r
11369     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
11370         gameMode == Training || gameMode == PlayFromGameFile || \r
11371         gameMode == AnalyzeFile) {\r
11372         while (currentMove < target) {\r
11373             SendMoveToProgram(currentMove++, &first);\r
11374         }\r
11375     } else {\r
11376         currentMove = target;\r
11377     }\r
11378     \r
11379     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11380         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11381         blackTimeRemaining = timeRemaining[1][currentMove];\r
11382     }\r
11383     DisplayBothClocks();\r
11384     DisplayMove(currentMove - 1);\r
11385     DrawPosition(FALSE, boards[currentMove]);\r
11386     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11387     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
11388         DisplayComment(currentMove - 1, commentList[currentMove]);\r
11389     }\r
11390 }\r
11391 \r
11392 \r
11393 void\r
11394 ForwardEvent()\r
11395 {\r
11396     if (gameMode == IcsExamining && !pausing) {\r
11397         SendToICS(ics_prefix);\r
11398         SendToICS("forward\n");\r
11399     } else {\r
11400         ForwardInner(currentMove + 1);\r
11401     }\r
11402 }\r
11403 \r
11404 void\r
11405 ToEndEvent()\r
11406 {\r
11407     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11408         /* to optimze, we temporarily turn off analysis mode while we feed\r
11409          * the remaining moves to the engine. Otherwise we get analysis output\r
11410          * after each move.\r
11411          */ \r
11412         if (first.analysisSupport) {\r
11413           SendToProgram("exit\nforce\n", &first);\r
11414           first.analyzing = FALSE;\r
11415         }\r
11416     }\r
11417         \r
11418     if (gameMode == IcsExamining && !pausing) {\r
11419         SendToICS(ics_prefix);\r
11420         SendToICS("forward 999999\n");\r
11421     } else {\r
11422         ForwardInner(forwardMostMove);\r
11423     }\r
11424 \r
11425     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11426         /* we have fed all the moves, so reactivate analysis mode */\r
11427         SendToProgram("analyze\n", &first);\r
11428         first.analyzing = TRUE;\r
11429         /*first.maybeThinking = TRUE;*/\r
11430         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11431     }\r
11432 }\r
11433 \r
11434 void\r
11435 BackwardInner(target)\r
11436      int target;\r
11437 {\r
11438     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
11439 \r
11440     if (appData.debugMode)\r
11441         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
11442                 target, currentMove, forwardMostMove);\r
11443 \r
11444     if (gameMode == EditPosition) return;\r
11445     if (currentMove <= backwardMostMove) {\r
11446         ClearHighlights();\r
11447         DrawPosition(full_redraw, boards[currentMove]);\r
11448         return;\r
11449     }\r
11450     if (gameMode == PlayFromGameFile && !pausing)\r
11451       PauseEvent();\r
11452     \r
11453     if (moveList[target][0]) {\r
11454         int fromX, fromY, toX, toY;\r
11455         toX = moveList[target][2] - AAA;\r
11456         toY = moveList[target][3] - ONE;\r
11457         if (moveList[target][1] == '@') {\r
11458             if (appData.highlightLastMove) {\r
11459                 SetHighlights(-1, -1, toX, toY);\r
11460             }\r
11461         } else {\r
11462             fromX = moveList[target][0] - AAA;\r
11463             fromY = moveList[target][1] - ONE;\r
11464             if (target == currentMove - 1) {\r
11465                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
11466             }\r
11467             if (appData.highlightLastMove) {\r
11468                 SetHighlights(fromX, fromY, toX, toY);\r
11469             }\r
11470         }\r
11471     }\r
11472     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
11473         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
11474         while (currentMove > target) {\r
11475             SendToProgram("undo\n", &first);\r
11476             currentMove--;\r
11477         }\r
11478     } else {\r
11479         currentMove = target;\r
11480     }\r
11481     \r
11482     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11483         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11484         blackTimeRemaining = timeRemaining[1][currentMove];\r
11485     }\r
11486     DisplayBothClocks();\r
11487     DisplayMove(currentMove - 1);\r
11488     DrawPosition(full_redraw, boards[currentMove]);\r
11489     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11490     // [HGM] PV info: routine tests if comment empty\r
11491     DisplayComment(currentMove - 1, commentList[currentMove]);\r
11492 }\r
11493 \r
11494 void\r
11495 BackwardEvent()\r
11496 {\r
11497     if (gameMode == IcsExamining && !pausing) {\r
11498         SendToICS(ics_prefix);\r
11499         SendToICS("backward\n");\r
11500     } else {\r
11501         BackwardInner(currentMove - 1);\r
11502     }\r
11503 }\r
11504 \r
11505 void\r
11506 ToStartEvent()\r
11507 {\r
11508     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11509         /* to optimze, we temporarily turn off analysis mode while we undo\r
11510          * all the moves. Otherwise we get analysis output after each undo.\r
11511          */ \r
11512         if (first.analysisSupport) {\r
11513           SendToProgram("exit\nforce\n", &first);\r
11514           first.analyzing = FALSE;\r
11515         }\r
11516     }\r
11517 \r
11518     if (gameMode == IcsExamining && !pausing) {\r
11519         SendToICS(ics_prefix);\r
11520         SendToICS("backward 999999\n");\r
11521     } else {\r
11522         BackwardInner(backwardMostMove);\r
11523     }\r
11524 \r
11525     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11526         /* we have fed all the moves, so reactivate analysis mode */\r
11527         SendToProgram("analyze\n", &first);\r
11528         first.analyzing = TRUE;\r
11529         /*first.maybeThinking = TRUE;*/\r
11530         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11531     }\r
11532 }\r
11533 \r
11534 void\r
11535 ToNrEvent(int to)\r
11536 {\r
11537   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
11538   if (to >= forwardMostMove) to = forwardMostMove;\r
11539   if (to <= backwardMostMove) to = backwardMostMove;\r
11540   if (to < currentMove) {\r
11541     BackwardInner(to);\r
11542   } else {\r
11543     ForwardInner(to);\r
11544   }\r
11545 }\r
11546 \r
11547 void\r
11548 RevertEvent()\r
11549 {\r
11550     if (gameMode != IcsExamining) {\r
11551         DisplayError(_("You are not examining a game"), 0);\r
11552         return;\r
11553     }\r
11554     if (pausing) {\r
11555         DisplayError(_("You can't revert while pausing"), 0);\r
11556         return;\r
11557     }\r
11558     SendToICS(ics_prefix);\r
11559     SendToICS("revert\n");\r
11560 }\r
11561 \r
11562 void\r
11563 RetractMoveEvent()\r
11564 {\r
11565     switch (gameMode) {\r
11566       case MachinePlaysWhite:\r
11567       case MachinePlaysBlack:\r
11568         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
11569             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
11570             return;\r
11571         }\r
11572         if (forwardMostMove < 2) return;\r
11573         currentMove = forwardMostMove = forwardMostMove - 2;\r
11574         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11575         blackTimeRemaining = timeRemaining[1][currentMove];\r
11576         DisplayBothClocks();\r
11577         DisplayMove(currentMove - 1);\r
11578         ClearHighlights();/*!! could figure this out*/\r
11579         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
11580         SendToProgram("remove\n", &first);\r
11581         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
11582         break;\r
11583 \r
11584       case BeginningOfGame:\r
11585       default:\r
11586         break;\r
11587 \r
11588       case IcsPlayingWhite:\r
11589       case IcsPlayingBlack:\r
11590         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
11591             SendToICS(ics_prefix);\r
11592             SendToICS("takeback 2\n");\r
11593         } else {\r
11594             SendToICS(ics_prefix);\r
11595             SendToICS("takeback 1\n");\r
11596         }\r
11597         break;\r
11598     }\r
11599 }\r
11600 \r
11601 void\r
11602 MoveNowEvent()\r
11603 {\r
11604     ChessProgramState *cps;\r
11605 \r
11606     switch (gameMode) {\r
11607       case MachinePlaysWhite:\r
11608         if (!WhiteOnMove(forwardMostMove)) {\r
11609             DisplayError(_("It is your turn"), 0);\r
11610             return;\r
11611         }\r
11612         cps = &first;\r
11613         break;\r
11614       case MachinePlaysBlack:\r
11615         if (WhiteOnMove(forwardMostMove)) {\r
11616             DisplayError(_("It is your turn"), 0);\r
11617             return;\r
11618         }\r
11619         cps = &first;\r
11620         break;\r
11621       case TwoMachinesPlay:\r
11622         if (WhiteOnMove(forwardMostMove) ==\r
11623             (first.twoMachinesColor[0] == 'w')) {\r
11624             cps = &first;\r
11625         } else {\r
11626             cps = &second;\r
11627         }\r
11628         break;\r
11629       case BeginningOfGame:\r
11630       default:\r
11631         return;\r
11632     }\r
11633     SendToProgram("?\n", cps);\r
11634 }\r
11635 \r
11636 void\r
11637 TruncateGameEvent()\r
11638 {\r
11639     EditGameEvent();\r
11640     if (gameMode != EditGame) return;\r
11641     TruncateGame();\r
11642 }\r
11643 \r
11644 void\r
11645 TruncateGame()\r
11646 {\r
11647     if (forwardMostMove > currentMove) {\r
11648         if (gameInfo.resultDetails != NULL) {\r
11649             free(gameInfo.resultDetails);\r
11650             gameInfo.resultDetails = NULL;\r
11651             gameInfo.result = GameUnfinished;\r
11652         }\r
11653         forwardMostMove = currentMove;\r
11654         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
11655                    currentMove-1);\r
11656     }\r
11657 }\r
11658 \r
11659 void\r
11660 HintEvent()\r
11661 {\r
11662     if (appData.noChessProgram) return;\r
11663     switch (gameMode) {\r
11664       case MachinePlaysWhite:\r
11665         if (WhiteOnMove(forwardMostMove)) {\r
11666             DisplayError(_("Wait until your turn"), 0);\r
11667             return;\r
11668         }\r
11669         break;\r
11670       case BeginningOfGame:\r
11671       case MachinePlaysBlack:\r
11672         if (!WhiteOnMove(forwardMostMove)) {\r
11673             DisplayError(_("Wait until your turn"), 0);\r
11674             return;\r
11675         }\r
11676         break;\r
11677       default:\r
11678         DisplayError(_("No hint available"), 0);\r
11679         return;\r
11680     }\r
11681     SendToProgram("hint\n", &first);\r
11682     hintRequested = TRUE;\r
11683 }\r
11684 \r
11685 void\r
11686 BookEvent()\r
11687 {\r
11688     if (appData.noChessProgram) return;\r
11689     switch (gameMode) {\r
11690       case MachinePlaysWhite:\r
11691         if (WhiteOnMove(forwardMostMove)) {\r
11692             DisplayError(_("Wait until your turn"), 0);\r
11693             return;\r
11694         }\r
11695         break;\r
11696       case BeginningOfGame:\r
11697       case MachinePlaysBlack:\r
11698         if (!WhiteOnMove(forwardMostMove)) {\r
11699             DisplayError(_("Wait until your turn"), 0);\r
11700             return;\r
11701         }\r
11702         break;\r
11703       case EditPosition:\r
11704         EditPositionDone();\r
11705         break;\r
11706       case TwoMachinesPlay:\r
11707         return;\r
11708       default:\r
11709         break;\r
11710     }\r
11711     SendToProgram("bk\n", &first);\r
11712     bookOutput[0] = NULLCHAR;\r
11713     bookRequested = TRUE;\r
11714 }\r
11715 \r
11716 void\r
11717 AboutGameEvent()\r
11718 {\r
11719     char *tags = PGNTags(&gameInfo);\r
11720     TagsPopUp(tags, CmailMsg());\r
11721     free(tags);\r
11722 }\r
11723 \r
11724 /* end button procedures */\r
11725 \r
11726 void\r
11727 PrintPosition(fp, move)\r
11728      FILE *fp;\r
11729      int move;\r
11730 {\r
11731     int i, j;\r
11732     \r
11733     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11734         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11735             char c = PieceToChar(boards[move][i][j]);\r
11736             fputc(c == 'x' ? '.' : c, fp);\r
11737             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
11738         }\r
11739     }\r
11740     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
11741       fprintf(fp, "white to play\n");\r
11742     else\r
11743       fprintf(fp, "black to play\n");\r
11744 }\r
11745 \r
11746 void\r
11747 PrintOpponents(fp)\r
11748      FILE *fp;\r
11749 {\r
11750     if (gameInfo.white != NULL) {\r
11751         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
11752     } else {\r
11753         fprintf(fp, "\n");\r
11754     }\r
11755 }\r
11756 \r
11757 /* Find last component of program's own name, using some heuristics */\r
11758 void\r
11759 TidyProgramName(prog, host, buf)\r
11760      char *prog, *host, buf[MSG_SIZ];\r
11761 {\r
11762     char *p, *q;\r
11763     int local = (strcmp(host, "localhost") == 0);\r
11764     while (!local && (p = strchr(prog, ';')) != NULL) {\r
11765         p++;\r
11766         while (*p == ' ') p++;\r
11767         prog = p;\r
11768     }\r
11769     if (*prog == '"' || *prog == '\'') {\r
11770         q = strchr(prog + 1, *prog);\r
11771     } else {\r
11772         q = strchr(prog, ' ');\r
11773     }\r
11774     if (q == NULL) q = prog + strlen(prog);\r
11775     p = q;\r
11776     while (p >= prog && *p != '/' && *p != '\\') p--;\r
11777     p++;\r
11778     if(p == prog && *p == '"') p++;\r
11779     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
11780     memcpy(buf, p, q - p);\r
11781     buf[q - p] = NULLCHAR;\r
11782     if (!local) {\r
11783         strcat(buf, "@");\r
11784         strcat(buf, host);\r
11785     }\r
11786 }\r
11787 \r
11788 char *\r
11789 TimeControlTagValue()\r
11790 {\r
11791     char buf[MSG_SIZ];\r
11792     if (!appData.clockMode) {\r
11793         strcpy(buf, "-");\r
11794     } else if (movesPerSession > 0) {\r
11795         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
11796     } else if (timeIncrement == 0) {\r
11797         sprintf(buf, "%ld", timeControl/1000);\r
11798     } else {\r
11799         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
11800     }\r
11801     return StrSave(buf);\r
11802 }\r
11803 \r
11804 void\r
11805 SetGameInfo()\r
11806 {\r
11807     /* This routine is used only for certain modes */\r
11808     VariantClass v = gameInfo.variant;\r
11809     ClearGameInfo(&gameInfo);\r
11810     gameInfo.variant = v;\r
11811 \r
11812     switch (gameMode) {\r
11813       case MachinePlaysWhite:\r
11814         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11815         gameInfo.site = StrSave(HostName());\r
11816         gameInfo.date = PGNDate();\r
11817         gameInfo.round = StrSave("-");\r
11818         gameInfo.white = StrSave(first.tidy);\r
11819         gameInfo.black = StrSave(UserName());\r
11820         gameInfo.timeControl = TimeControlTagValue();\r
11821         break;\r
11822 \r
11823       case MachinePlaysBlack:\r
11824         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11825         gameInfo.site = StrSave(HostName());\r
11826         gameInfo.date = PGNDate();\r
11827         gameInfo.round = StrSave("-");\r
11828         gameInfo.white = StrSave(UserName());\r
11829         gameInfo.black = StrSave(first.tidy);\r
11830         gameInfo.timeControl = TimeControlTagValue();\r
11831         break;\r
11832 \r
11833       case TwoMachinesPlay:\r
11834         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11835         gameInfo.site = StrSave(HostName());\r
11836         gameInfo.date = PGNDate();\r
11837         if (matchGame > 0) {\r
11838             char buf[MSG_SIZ];\r
11839             sprintf(buf, "%d", matchGame);\r
11840             gameInfo.round = StrSave(buf);\r
11841         } else {\r
11842             gameInfo.round = StrSave("-");\r
11843         }\r
11844         if (first.twoMachinesColor[0] == 'w') {\r
11845             gameInfo.white = StrSave(first.tidy);\r
11846             gameInfo.black = StrSave(second.tidy);\r
11847         } else {\r
11848             gameInfo.white = StrSave(second.tidy);\r
11849             gameInfo.black = StrSave(first.tidy);\r
11850         }\r
11851         gameInfo.timeControl = TimeControlTagValue();\r
11852         break;\r
11853 \r
11854       case EditGame:\r
11855         gameInfo.event = StrSave("Edited game");\r
11856         gameInfo.site = StrSave(HostName());\r
11857         gameInfo.date = PGNDate();\r
11858         gameInfo.round = StrSave("-");\r
11859         gameInfo.white = StrSave("-");\r
11860         gameInfo.black = StrSave("-");\r
11861         break;\r
11862 \r
11863       case EditPosition:\r
11864         gameInfo.event = StrSave("Edited position");\r
11865         gameInfo.site = StrSave(HostName());\r
11866         gameInfo.date = PGNDate();\r
11867         gameInfo.round = StrSave("-");\r
11868         gameInfo.white = StrSave("-");\r
11869         gameInfo.black = StrSave("-");\r
11870         break;\r
11871 \r
11872       case IcsPlayingWhite:\r
11873       case IcsPlayingBlack:\r
11874       case IcsObserving:\r
11875       case IcsExamining:\r
11876         break;\r
11877 \r
11878       case PlayFromGameFile:\r
11879         gameInfo.event = StrSave("Game from non-PGN file");\r
11880         gameInfo.site = StrSave(HostName());\r
11881         gameInfo.date = PGNDate();\r
11882         gameInfo.round = StrSave("-");\r
11883         gameInfo.white = StrSave("?");\r
11884         gameInfo.black = StrSave("?");\r
11885         break;\r
11886 \r
11887       default:\r
11888         break;\r
11889     }\r
11890 }\r
11891 \r
11892 void\r
11893 ReplaceComment(index, text)\r
11894      int index;\r
11895      char *text;\r
11896 {\r
11897     int len;\r
11898 \r
11899     while (*text == '\n') text++;\r
11900     len = strlen(text);\r
11901     while (len > 0 && text[len - 1] == '\n') len--;\r
11902 \r
11903     if (commentList[index] != NULL)\r
11904       free(commentList[index]);\r
11905 \r
11906     if (len == 0) {\r
11907         commentList[index] = NULL;\r
11908         return;\r
11909     }\r
11910     commentList[index] = (char *) malloc(len + 2);\r
11911     strncpy(commentList[index], text, len);\r
11912     commentList[index][len] = '\n';\r
11913     commentList[index][len + 1] = NULLCHAR;\r
11914 }\r
11915 \r
11916 void\r
11917 CrushCRs(text)\r
11918      char *text;\r
11919 {\r
11920   char *p = text;\r
11921   char *q = text;\r
11922   char ch;\r
11923 \r
11924   do {\r
11925     ch = *p++;\r
11926     if (ch == '\r') continue;\r
11927     *q++ = ch;\r
11928   } while (ch != '\0');\r
11929 }\r
11930 \r
11931 void\r
11932 AppendComment(index, text)\r
11933      int index;\r
11934      char *text;\r
11935 {\r
11936     int oldlen, len;\r
11937     char *old;\r
11938 \r
11939     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11940 \r
11941     CrushCRs(text);\r
11942     while (*text == '\n') text++;\r
11943     len = strlen(text);\r
11944     while (len > 0 && text[len - 1] == '\n') len--;\r
11945 \r
11946     if (len == 0) return;\r
11947 \r
11948     if (commentList[index] != NULL) {\r
11949         old = commentList[index];\r
11950         oldlen = strlen(old);\r
11951         commentList[index] = (char *) malloc(oldlen + len + 2);\r
11952         strcpy(commentList[index], old);\r
11953         free(old);\r
11954         strncpy(&commentList[index][oldlen], text, len);\r
11955         commentList[index][oldlen + len] = '\n';\r
11956         commentList[index][oldlen + len + 1] = NULLCHAR;\r
11957     } else {\r
11958         commentList[index] = (char *) malloc(len + 2);\r
11959         strncpy(commentList[index], text, len);\r
11960         commentList[index][len] = '\n';\r
11961         commentList[index][len + 1] = NULLCHAR;\r
11962     }\r
11963 }\r
11964 \r
11965 static char * FindStr( char * text, char * sub_text )\r
11966 {\r
11967     char * result = strstr( text, sub_text );\r
11968 \r
11969     if( result != NULL ) {\r
11970         result += strlen( sub_text );\r
11971     }\r
11972 \r
11973     return result;\r
11974 }\r
11975 \r
11976 /* [AS] Try to extract PV info from PGN comment */\r
11977 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
11978 char *GetInfoFromComment( int index, char * text )\r
11979 {\r
11980     char * sep = text;\r
11981 \r
11982     if( text != NULL && index > 0 ) {\r
11983         int score = 0;\r
11984         int depth = 0;\r
11985         int time = -1, sec = 0, deci;\r
11986         char * s_eval = FindStr( text, "[%eval " );\r
11987         char * s_emt = FindStr( text, "[%emt " );\r
11988 \r
11989         if( s_eval != NULL || s_emt != NULL ) {\r
11990             /* New style */\r
11991             char delim;\r
11992 \r
11993             if( s_eval != NULL ) {\r
11994                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
11995                     return text;\r
11996                 }\r
11997 \r
11998                 if( delim != ']' ) {\r
11999                     return text;\r
12000                 }\r
12001             }\r
12002 \r
12003             if( s_emt != NULL ) {\r
12004             }\r
12005         }\r
12006         else {\r
12007             /* We expect something like: [+|-]nnn.nn/dd */\r
12008             int score_lo = 0;\r
12009 \r
12010             sep = strchr( text, '/' );\r
12011             if( sep == NULL || sep < (text+4) ) {\r
12012                 return text;\r
12013             }\r
12014 \r
12015             time = -1; sec = -1; deci = -1;\r
12016             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
12017                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&\r
12018                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
12019                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
12020                 return text;\r
12021             }\r
12022 \r
12023             if( score_lo < 0 || score_lo >= 100 ) {\r
12024                 return text;\r
12025             }\r
12026 \r
12027             if(sec >= 0) time = 600*time + 10*sec; else\r
12028             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec\r
12029 \r
12030             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
12031 \r
12032             /* [HGM] PV time: now locate end of PV info */\r
12033             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
12034             if(time >= 0)\r
12035             while( *++sep >= '0' && *sep <= '9'); // strip time\r
12036             if(sec >= 0)\r
12037             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
12038             if(deci >= 0)\r
12039             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds\r
12040             while(*sep == ' ') sep++;\r
12041         }\r
12042 \r
12043         if( depth <= 0 ) {\r
12044             return text;\r
12045         }\r
12046 \r
12047         if( time < 0 ) {\r
12048             time = -1;\r
12049         }\r
12050 \r
12051         pvInfoList[index-1].depth = depth;\r
12052         pvInfoList[index-1].score = score;\r
12053         pvInfoList[index-1].time  = 10*time; // centi-sec\r
12054     }\r
12055     return sep;\r
12056 }\r
12057 \r
12058 void\r
12059 SendToProgram(message, cps)\r
12060      char *message;\r
12061      ChessProgramState *cps;\r
12062 {\r
12063     int count, outCount, error;\r
12064     char buf[MSG_SIZ];\r
12065 \r
12066     if (cps->pr == NULL) return;\r
12067     Attention(cps);\r
12068     \r
12069     if (appData.debugMode) {\r
12070         TimeMark now;\r
12071         GetTimeMark(&now);\r
12072         fprintf(debugFP, "%ld >%-6s: %s", \r
12073                 SubtractTimeMarks(&now, &programStartTime),\r
12074                 cps->which, message);\r
12075     }\r
12076     \r
12077     count = strlen(message);\r
12078     outCount = OutputToProcess(cps->pr, message, count, &error);\r
12079     if (outCount < count && !exiting \r
12080                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
12081         sprintf(buf, _("Error writing to %s chess program"), cps->which);\r
12082         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12083             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12084                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12085                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
12086             } else {\r
12087                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12088             }\r
12089             gameInfo.resultDetails = buf;\r
12090         }\r
12091         DisplayFatalError(buf, error, 1);\r
12092     }\r
12093 }\r
12094 \r
12095 void\r
12096 ReceiveFromProgram(isr, closure, message, count, error)\r
12097      InputSourceRef isr;\r
12098      VOIDSTAR closure;\r
12099      char *message;\r
12100      int count;\r
12101      int error;\r
12102 {\r
12103     char *end_str;\r
12104     char buf[MSG_SIZ];\r
12105     ChessProgramState *cps = (ChessProgramState *)closure;\r
12106 \r
12107     if (isr != cps->isr) return; /* Killed intentionally */\r
12108     if (count <= 0) {\r
12109         if (count == 0) {\r
12110             sprintf(buf,\r
12111                     _("Error: %s chess program (%s) exited unexpectedly"),\r
12112                     cps->which, cps->program);\r
12113         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12114                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12115                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12116                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);\r
12117                 } else {\r
12118                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12119                 }\r
12120                 gameInfo.resultDetails = buf;\r
12121             }\r
12122             RemoveInputSource(cps->isr);\r
12123             DisplayFatalError(buf, 0, 1);\r
12124         } else {\r
12125             sprintf(buf,\r
12126                     _("Error reading from %s chess program (%s)"),\r
12127                     cps->which, cps->program);\r
12128             RemoveInputSource(cps->isr);\r
12129 \r
12130             /* [AS] Program is misbehaving badly... kill it */\r
12131             if( count == -2 ) {\r
12132                 DestroyChildProcess( cps->pr, 9 );\r
12133                 cps->pr = NoProc;\r
12134             }\r
12135 \r
12136             DisplayFatalError(buf, error, 1);\r
12137         }\r
12138         return;\r
12139     }\r
12140     \r
12141     if ((end_str = strchr(message, '\r')) != NULL)\r
12142       *end_str = NULLCHAR;\r
12143     if ((end_str = strchr(message, '\n')) != NULL)\r
12144       *end_str = NULLCHAR;\r
12145     \r
12146     if (appData.debugMode) {\r
12147         TimeMark now; int print = 1;\r
12148         char *quote = ""; char c; int i;\r
12149 \r
12150         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
12151                 char start = message[0];\r
12152                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
12153                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
12154                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&\r
12155                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
12156                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
12157                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&\r
12158                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
12159                         { quote = "# "; print = (appData.engineComments == 2); }\r
12160                 message[0] = start; // restore original message\r
12161         }\r
12162         if(print) {\r
12163                 GetTimeMark(&now);\r
12164                 fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
12165                         SubtractTimeMarks(&now, &programStartTime), cps->which, \r
12166                         quote,\r
12167                         message);\r
12168         }\r
12169     }\r
12170 \r
12171     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */\r
12172     if (appData.icsEngineAnalyze) {\r
12173         if (strstr(message, "whisper") != NULL ||\r
12174              strstr(message, "kibitz") != NULL || \r
12175             strstr(message, "tellics") != NULL) return;\r
12176     }\r
12177 \r
12178     HandleMachineMove(message, cps);\r
12179 }\r
12180 \r
12181 \r
12182 void\r
12183 SendTimeControl(cps, mps, tc, inc, sd, st)\r
12184      ChessProgramState *cps;\r
12185      int mps, inc, sd, st;\r
12186      long tc;\r
12187 {\r
12188     char buf[MSG_SIZ];\r
12189     int seconds;\r
12190 \r
12191     if( timeControl_2 > 0 ) {\r
12192         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
12193             tc = timeControl_2;\r
12194         }\r
12195     }\r
12196     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
12197     inc /= cps->timeOdds;\r
12198     st  /= cps->timeOdds;\r
12199 \r
12200     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
12201 \r
12202     if (st > 0) {\r
12203       /* Set exact time per move, normally using st command */\r
12204       if (cps->stKludge) {\r
12205         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
12206         seconds = st % 60;\r
12207         if (seconds == 0) {\r
12208           sprintf(buf, "level 1 %d\n", st/60);\r
12209         } else {\r
12210           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
12211         }\r
12212       } else {\r
12213         sprintf(buf, "st %d\n", st);\r
12214       }\r
12215     } else {\r
12216       /* Set conventional or incremental time control, using level command */\r
12217       if (seconds == 0) {\r
12218         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
12219            Fixed in later versions, but still avoid :seconds\r
12220            when seconds is 0. */\r
12221         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
12222       } else {\r
12223         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
12224                 seconds, inc/1000);\r
12225       }\r
12226     }\r
12227     SendToProgram(buf, cps);\r
12228 \r
12229     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
12230     /* Orthogonally, limit search to given depth */\r
12231     if (sd > 0) {\r
12232       if (cps->sdKludge) {\r
12233         sprintf(buf, "depth\n%d\n", sd);\r
12234       } else {\r
12235         sprintf(buf, "sd %d\n", sd);\r
12236       }\r
12237       SendToProgram(buf, cps);\r
12238     }\r
12239 \r
12240     if(cps->nps > 0) { /* [HGM] nps */\r
12241         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
12242         else {\r
12243                 sprintf(buf, "nps %d\n", cps->nps);\r
12244               SendToProgram(buf, cps);\r
12245         }\r
12246     }\r
12247 }\r
12248 \r
12249 ChessProgramState *WhitePlayer()\r
12250 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
12251 {\r
12252     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || \r
12253        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)\r
12254         return &second;\r
12255     return &first;\r
12256 }\r
12257 \r
12258 void\r
12259 SendTimeRemaining(cps, machineWhite)\r
12260      ChessProgramState *cps;\r
12261      int /*boolean*/ machineWhite;\r
12262 {\r
12263     char message[MSG_SIZ];\r
12264     long time, otime;\r
12265 \r
12266     /* Note: this routine must be called when the clocks are stopped\r
12267        or when they have *just* been set or switched; otherwise\r
12268        it will be off by the time since the current tick started.\r
12269     */\r
12270     if (machineWhite) {\r
12271         time = whiteTimeRemaining / 10;\r
12272         otime = blackTimeRemaining / 10;\r
12273     } else {\r
12274         time = blackTimeRemaining / 10;\r
12275         otime = whiteTimeRemaining / 10;\r
12276     }\r
12277     /* [HGM] translate opponent's time by time-odds factor */\r
12278     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
12279     if (appData.debugMode) {\r
12280         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
12281     }\r
12282 \r
12283     if (time <= 0) time = 1;\r
12284     if (otime <= 0) otime = 1;\r
12285     \r
12286     sprintf(message, "time %ld\n", time);\r
12287     SendToProgram(message, cps);\r
12288 \r
12289     sprintf(message, "otim %ld\n", otime);\r
12290     SendToProgram(message, cps);\r
12291 }\r
12292 \r
12293 int\r
12294 BoolFeature(p, name, loc, cps)\r
12295      char **p;\r
12296      char *name;\r
12297      int *loc;\r
12298      ChessProgramState *cps;\r
12299 {\r
12300   char buf[MSG_SIZ];\r
12301   int len = strlen(name);\r
12302   int val;\r
12303   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12304     (*p) += len + 1;\r
12305     sscanf(*p, "%d", &val);\r
12306     *loc = (val != 0);\r
12307     while (**p && **p != ' ') (*p)++;\r
12308     sprintf(buf, "accepted %s\n", name);\r
12309     SendToProgram(buf, cps);\r
12310     return TRUE;\r
12311   }\r
12312   return FALSE;\r
12313 }\r
12314 \r
12315 int\r
12316 IntFeature(p, name, loc, cps)\r
12317      char **p;\r
12318      char *name;\r
12319      int *loc;\r
12320      ChessProgramState *cps;\r
12321 {\r
12322   char buf[MSG_SIZ];\r
12323   int len = strlen(name);\r
12324   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12325     (*p) += len + 1;\r
12326     sscanf(*p, "%d", loc);\r
12327     while (**p && **p != ' ') (*p)++;\r
12328     sprintf(buf, "accepted %s\n", name);\r
12329     SendToProgram(buf, cps);\r
12330     return TRUE;\r
12331   }\r
12332   return FALSE;\r
12333 }\r
12334 \r
12335 int\r
12336 StringFeature(p, name, loc, cps)\r
12337      char **p;\r
12338      char *name;\r
12339      char loc[];\r
12340      ChessProgramState *cps;\r
12341 {\r
12342   char buf[MSG_SIZ];\r
12343   int len = strlen(name);\r
12344   if (strncmp((*p), name, len) == 0\r
12345       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
12346     (*p) += len + 2;\r
12347     sscanf(*p, "%[^\"]", loc);\r
12348     while (**p && **p != '\"') (*p)++;\r
12349     if (**p == '\"') (*p)++;\r
12350     sprintf(buf, "accepted %s\n", name);\r
12351     SendToProgram(buf, cps);\r
12352     return TRUE;\r
12353   }\r
12354   return FALSE;\r
12355 }\r
12356 \r
12357 int \r
12358 ParseOption(Option *opt, ChessProgramState *cps)\r
12359 // [HGM] options: process the string that defines an engine option, and determine\r
12360 // name, type, default value, and allowed value range\r
12361 {\r
12362         char *p, *q, buf[MSG_SIZ];\r
12363         int n, min = (-1)<<31, max = 1<<31, def;\r
12364 \r
12365         if(p = strstr(opt->name, " -spin ")) {\r
12366             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;\r
12367             if(max < min) max = min; // enforce consistency\r
12368             if(def < min) def = min;\r
12369             if(def > max) def = max;\r
12370             opt->value = def;\r
12371             opt->min = min;\r
12372             opt->max = max;\r
12373             opt->type = Spin;\r
12374         } else if(p = strstr(opt->name, " -string ")) {\r
12375             opt->textValue = p+9;\r
12376             opt->type = TextBox;\r
12377         } else if(p = strstr(opt->name, " -check ")) {\r
12378             if(sscanf(p, " -check %d", &def) < 1) return FALSE;\r
12379             opt->value = (def != 0);\r
12380             opt->type = CheckBox;\r
12381         } else if(p = strstr(opt->name, " -combo ")) {\r
12382             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type\r
12383             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices\r
12384             opt->value = n = 0;\r
12385             while(q = StrStr(q, " /// ")) {\r
12386                 n++; *q = 0;    // count choices, and null-terminate each of them\r
12387                 q += 5;\r
12388                 if(*q == '*') { // remember default, which is marked with * prefix\r
12389                     q++;\r
12390                     opt->value = n;\r
12391                 }\r
12392                 cps->comboList[cps->comboCnt++] = q;\r
12393             }\r
12394             cps->comboList[cps->comboCnt++] = NULL;\r
12395             opt->max = n + 1;\r
12396             opt->type = ComboBox;\r
12397         } else if(p = strstr(opt->name, " -button")) {\r
12398             opt->type = Button;\r
12399         } else if(p = strstr(opt->name, " -save")) {\r
12400             opt->type = SaveButton;\r
12401         } else return FALSE;\r
12402         *p = 0; // terminate option name\r
12403         // now look if the command-line options define a setting for this engine option.\r
12404         p = strstr(cps->optionSettings, opt->name);\r
12405         if(p == cps->optionSettings || p[-1] == ',') {\r
12406                 sprintf(buf, "option %s", p);\r
12407                 if(p = strstr(buf, ",")) *p = 0;\r
12408                 strcat(buf, "\n");\r
12409                 SendToProgram(buf, cps);\r
12410         }\r
12411         return TRUE;\r
12412 }\r
12413 \r
12414 void\r
12415 FeatureDone(cps, val)\r
12416      ChessProgramState* cps;\r
12417      int val;\r
12418 {\r
12419   DelayedEventCallback cb = GetDelayedEvent();\r
12420   if ((cb == InitBackEnd3 && cps == &first) ||\r
12421       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
12422     CancelDelayedEvent();\r
12423     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
12424   }\r
12425   cps->initDone = val;\r
12426 }\r
12427 \r
12428 /* Parse feature command from engine */\r
12429 void\r
12430 ParseFeatures(args, cps)\r
12431      char* args;\r
12432      ChessProgramState *cps;  \r
12433 {\r
12434   char *p = args;\r
12435   char *q;\r
12436   int val;\r
12437   char buf[MSG_SIZ];\r
12438 \r
12439   for (;;) {\r
12440     while (*p == ' ') p++;\r
12441     if (*p == NULLCHAR) return;\r
12442 \r
12443     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
12444     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
12445     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
12446     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
12447     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
12448     if (BoolFeature(&p, "reuse", &val, cps)) {\r
12449       /* Engine can disable reuse, but can't enable it if user said no */\r
12450       if (!val) cps->reuse = FALSE;\r
12451       continue;\r
12452     }\r
12453     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
12454     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
12455       if (gameMode == TwoMachinesPlay) {\r
12456         DisplayTwoMachinesTitle();\r
12457       } else {\r
12458         DisplayTitle("");\r
12459       }\r
12460       continue;\r
12461     }\r
12462     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
12463     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
12464     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
12465     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
12466     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
12467     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
12468     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
12469     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
12470     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
12471     if (IntFeature(&p, "done", &val, cps)) {\r
12472       FeatureDone(cps, val);\r
12473       continue;\r
12474     }\r
12475     /* Added by Tord: */\r
12476     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
12477     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
12478     /* End of additions by Tord */\r
12479 \r
12480     /* [HGM] added features: */\r
12481     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
12482     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
12483     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
12484     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
12485     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12486     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
12487     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {\r
12488         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature\r
12489         if(cps->nrOptions >= MAX_OPTIONS) {\r
12490             cps->nrOptions--;\r
12491             sprintf(buf, "%s engine has too many options\n", cps->which);\r
12492             DisplayError(buf, 0);\r
12493         }\r
12494         continue;\r
12495     }\r
12496     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12497     /* End of additions by HGM */\r
12498 \r
12499     /* unknown feature: complain and skip */\r
12500     q = p;\r
12501     while (*q && *q != '=') q++;\r
12502     sprintf(buf, "rejected %.*s\n", q-p, p);\r
12503     SendToProgram(buf, cps);\r
12504     p = q;\r
12505     if (*p == '=') {\r
12506       p++;\r
12507       if (*p == '\"') {\r
12508         p++;\r
12509         while (*p && *p != '\"') p++;\r
12510         if (*p == '\"') p++;\r
12511       } else {\r
12512         while (*p && *p != ' ') p++;\r
12513       }\r
12514     }\r
12515   }\r
12516 \r
12517 }\r
12518 \r
12519 void\r
12520 PeriodicUpdatesEvent(newState)\r
12521      int newState;\r
12522 {\r
12523     if (newState == appData.periodicUpdates)\r
12524       return;\r
12525 \r
12526     appData.periodicUpdates=newState;\r
12527 \r
12528     /* Display type changes, so update it now */\r
12529     DisplayAnalysis();\r
12530 \r
12531     /* Get the ball rolling again... */\r
12532     if (newState) {\r
12533         AnalysisPeriodicEvent(1);\r
12534         StartAnalysisClock();\r
12535     }\r
12536 }\r
12537 \r
12538 void\r
12539 PonderNextMoveEvent(newState)\r
12540      int newState;\r
12541 {\r
12542     if (newState == appData.ponderNextMove) return;\r
12543     if (gameMode == EditPosition) EditPositionDone();\r
12544     if (newState) {\r
12545         SendToProgram("hard\n", &first);\r
12546         if (gameMode == TwoMachinesPlay) {\r
12547             SendToProgram("hard\n", &second);\r
12548         }\r
12549     } else {\r
12550         SendToProgram("easy\n", &first);\r
12551         thinkOutput[0] = NULLCHAR;\r
12552         if (gameMode == TwoMachinesPlay) {\r
12553             SendToProgram("easy\n", &second);\r
12554         }\r
12555     }\r
12556     appData.ponderNextMove = newState;\r
12557 }\r
12558 \r
12559 void\r
12560 NewSettingEvent(option, command, value)\r
12561      char *command;\r
12562      int option, value;\r
12563 {\r
12564     char buf[MSG_SIZ];\r
12565 \r
12566     if (gameMode == EditPosition) EditPositionDone();\r
12567     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);\r
12568     SendToProgram(buf, &first);\r
12569     if (gameMode == TwoMachinesPlay) {\r
12570         SendToProgram(buf, &second);\r
12571     }\r
12572 }\r
12573 \r
12574 void\r
12575 ShowThinkingEvent()\r
12576 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup\r
12577 {\r
12578     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated\r
12579     int newState = appData.showThinking\r
12580         // [HGM] thinking: other features now need thinking output as well\r
12581         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();\r
12582     \r
12583     if (oldState == newState) return;\r
12584     oldState = newState;\r
12585     if (gameMode == EditPosition) EditPositionDone();\r
12586     if (oldState) {\r
12587         SendToProgram("post\n", &first);\r
12588         if (gameMode == TwoMachinesPlay) {\r
12589             SendToProgram("post\n", &second);\r
12590         }\r
12591     } else {\r
12592         SendToProgram("nopost\n", &first);\r
12593         thinkOutput[0] = NULLCHAR;\r
12594         if (gameMode == TwoMachinesPlay) {\r
12595             SendToProgram("nopost\n", &second);\r
12596         }\r
12597     }\r
12598 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!\r
12599 }\r
12600 \r
12601 void\r
12602 AskQuestionEvent(title, question, replyPrefix, which)\r
12603      char *title; char *question; char *replyPrefix; char *which;\r
12604 {\r
12605   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
12606   if (pr == NoProc) return;\r
12607   AskQuestion(title, question, replyPrefix, pr);\r
12608 }\r
12609 \r
12610 void\r
12611 DisplayMove(moveNumber)\r
12612      int moveNumber;\r
12613 {\r
12614     char message[MSG_SIZ];\r
12615     char res[MSG_SIZ];\r
12616     char cpThinkOutput[MSG_SIZ];\r
12617 \r
12618     if(appData.noGUI) return; // [HGM] fast: suppress display of moves\r
12619     \r
12620     if (moveNumber == forwardMostMove - 1 || \r
12621         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12622 \r
12623         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
12624 \r
12625         if (strchr(cpThinkOutput, '\n')) {\r
12626             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
12627         }\r
12628     } else {\r
12629         *cpThinkOutput = NULLCHAR;\r
12630     }\r
12631 \r
12632     /* [AS] Hide thinking from human user */\r
12633     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
12634         *cpThinkOutput = NULLCHAR;\r
12635         if( thinkOutput[0] != NULLCHAR ) {\r
12636             int i;\r
12637 \r
12638             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
12639                 cpThinkOutput[i] = '.';\r
12640             }\r
12641             cpThinkOutput[i] = NULLCHAR;\r
12642             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
12643         }\r
12644     }\r
12645 \r
12646     if (moveNumber == forwardMostMove - 1 &&\r
12647         gameInfo.resultDetails != NULL) {\r
12648         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
12649             sprintf(res, " %s", PGNResult(gameInfo.result));\r
12650         } else {\r
12651             sprintf(res, " {%s} %s",\r
12652                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
12653         }\r
12654     } else {\r
12655         res[0] = NULLCHAR;\r
12656     }\r
12657 \r
12658     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12659         DisplayMessage(res, cpThinkOutput);\r
12660     } else {\r
12661         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
12662                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
12663                 parseList[moveNumber], res);\r
12664         DisplayMessage(message, cpThinkOutput);\r
12665     }\r
12666 }\r
12667 \r
12668 void\r
12669 DisplayAnalysisText(text)\r
12670      char *text;\r
12671 {\r
12672     char buf[MSG_SIZ];\r
12673 \r
12674     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile \r
12675                || appData.icsEngineAnalyze) {\r
12676         sprintf(buf, "Analysis (%s)", first.tidy);\r
12677         AnalysisPopUp(buf, text);\r
12678     }\r
12679 }\r
12680 \r
12681 static int\r
12682 only_one_move(str)\r
12683      char *str;\r
12684 {\r
12685     while (*str && isspace(*str)) ++str;\r
12686     while (*str && !isspace(*str)) ++str;\r
12687     if (!*str) return 1;\r
12688     while (*str && isspace(*str)) ++str;\r
12689     if (!*str) return 1;\r
12690     return 0;\r
12691 }\r
12692 \r
12693 void\r
12694 DisplayAnalysis()\r
12695 {\r
12696     char buf[MSG_SIZ];\r
12697     char lst[MSG_SIZ / 2];\r
12698     double nps;\r
12699     static char *xtra[] = { "", " (--)", " (++)" };\r
12700     int h, m, s, cs;\r
12701   \r
12702     if (programStats.time == 0) {\r
12703         programStats.time = 1;\r
12704     }\r
12705   \r
12706     if (programStats.got_only_move) {\r
12707         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
12708     } else {\r
12709         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
12710 \r
12711         nps = (u64ToDouble(programStats.nodes) /\r
12712              ((double)programStats.time /100.0));\r
12713 \r
12714         cs = programStats.time % 100;\r
12715         s = programStats.time / 100;\r
12716         h = (s / (60*60));\r
12717         s = s - h*60*60;\r
12718         m = (s/60);\r
12719         s = s - m*60;\r
12720 \r
12721         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
12722           if (programStats.move_name[0] != NULLCHAR) {\r
12723             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12724                     programStats.depth,\r
12725                     programStats.nr_moves-programStats.moves_left,\r
12726                     programStats.nr_moves, programStats.move_name,\r
12727                     ((float)programStats.score)/100.0, lst,\r
12728                     only_one_move(lst)?\r
12729                     xtra[programStats.got_fail] : "",\r
12730                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12731           } else {\r
12732             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12733                     programStats.depth,\r
12734                     programStats.nr_moves-programStats.moves_left,\r
12735                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
12736                     lst,\r
12737                     only_one_move(lst)?\r
12738                     xtra[programStats.got_fail] : "",\r
12739                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12740           }\r
12741         } else {\r
12742             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12743                     programStats.depth,\r
12744                     ((float)programStats.score)/100.0,\r
12745                     lst,\r
12746                     only_one_move(lst)?\r
12747                     xtra[programStats.got_fail] : "",\r
12748                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12749         }\r
12750     }\r
12751     DisplayAnalysisText(buf);\r
12752 }\r
12753 \r
12754 void\r
12755 DisplayComment(moveNumber, text)\r
12756      int moveNumber;\r
12757      char *text;\r
12758 {\r
12759     char title[MSG_SIZ];\r
12760     char buf[8000]; // comment can be long!\r
12761     int score, depth;\r
12762 \r
12763     if( appData.autoDisplayComment ) {\r
12764         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12765             strcpy(title, "Comment");\r
12766         } else {\r
12767             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
12768                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
12769                     parseList[moveNumber]);\r
12770         }\r
12771     } else title[0] = 0;\r
12772 \r
12773     // [HGM] PV info: display PV info together with (or as) comment\r
12774     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
12775         if(text == NULL) text = "";                                           \r
12776         score = pvInfoList[moveNumber].score;\r
12777         sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
12778                               depth, (pvInfoList[moveNumber].time+50)/100, text);\r
12779         CommentPopUp(title, buf);\r
12780     } else\r
12781     if (text != NULL)\r
12782         CommentPopUp(title, text);\r
12783 }\r
12784 \r
12785 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
12786  * might be busy thinking or pondering.  It can be omitted if your\r
12787  * gnuchess is configured to stop thinking immediately on any user\r
12788  * input.  However, that gnuchess feature depends on the FIONREAD\r
12789  * ioctl, which does not work properly on some flavors of Unix.\r
12790  */\r
12791 void\r
12792 Attention(cps)\r
12793      ChessProgramState *cps;\r
12794 {\r
12795 #if ATTENTION\r
12796     if (!cps->useSigint) return;\r
12797     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
12798     switch (gameMode) {\r
12799       case MachinePlaysWhite:\r
12800       case MachinePlaysBlack:\r
12801       case TwoMachinesPlay:\r
12802       case IcsPlayingWhite:\r
12803       case IcsPlayingBlack:\r
12804       case AnalyzeMode:\r
12805       case AnalyzeFile:\r
12806         /* Skip if we know it isn't thinking */\r
12807         if (!cps->maybeThinking) return;\r
12808         if (appData.debugMode)\r
12809           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
12810         InterruptChildProcess(cps->pr);\r
12811         cps->maybeThinking = FALSE;\r
12812         break;\r
12813       default:\r
12814         break;\r
12815     }\r
12816 #endif /*ATTENTION*/\r
12817 }\r
12818 \r
12819 int\r
12820 CheckFlags()\r
12821 {\r
12822     if (whiteTimeRemaining <= 0) {\r
12823         if (!whiteFlag) {\r
12824             whiteFlag = TRUE;\r
12825             if (appData.icsActive) {\r
12826                 if (appData.autoCallFlag &&\r
12827                     gameMode == IcsPlayingBlack && !blackFlag) {\r
12828                   SendToICS(ics_prefix);\r
12829                   SendToICS("flag\n");\r
12830                 }\r
12831             } else {\r
12832                 if (blackFlag) {\r
12833                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12834                 } else {\r
12835                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));\r
12836                     if (appData.autoCallFlag) {\r
12837                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
12838                         return TRUE;\r
12839                     }\r
12840                 }\r
12841             }\r
12842         }\r
12843     }\r
12844     if (blackTimeRemaining <= 0) {\r
12845         if (!blackFlag) {\r
12846             blackFlag = TRUE;\r
12847             if (appData.icsActive) {\r
12848                 if (appData.autoCallFlag &&\r
12849                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
12850                   SendToICS(ics_prefix);\r
12851                   SendToICS("flag\n");\r
12852                 }\r
12853             } else {\r
12854                 if (whiteFlag) {\r
12855                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12856                 } else {\r
12857                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));\r
12858                     if (appData.autoCallFlag) {\r
12859                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
12860                         return TRUE;\r
12861                     }\r
12862                 }\r
12863             }\r
12864         }\r
12865     }\r
12866     return FALSE;\r
12867 }\r
12868 \r
12869 void\r
12870 CheckTimeControl()\r
12871 {\r
12872     if (!appData.clockMode || appData.icsActive ||\r
12873         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
12874 \r
12875     /*\r
12876      * add time to clocks when time control is achieved ([HGM] now also used for increment)\r
12877      */\r
12878     if ( !WhiteOnMove(forwardMostMove) )\r
12879         /* White made time control */\r
12880         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12881         /* [HGM] time odds: correct new time quota for time odds! */\r
12882                                             / WhitePlayer()->timeOdds;\r
12883       else\r
12884         /* Black made time control */\r
12885         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12886                                             / WhitePlayer()->other->timeOdds;\r
12887 }\r
12888 \r
12889 void\r
12890 DisplayBothClocks()\r
12891 {\r
12892     int wom = gameMode == EditPosition ?\r
12893       !blackPlaysFirst : WhiteOnMove(currentMove);\r
12894     DisplayWhiteClock(whiteTimeRemaining, wom);\r
12895     DisplayBlackClock(blackTimeRemaining, !wom);\r
12896 }\r
12897 \r
12898 \r
12899 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
12900    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
12901    to use other calls if you don't.  Clocks will be less accurate if\r
12902    you have neither ftime nor gettimeofday.\r
12903 */\r
12904 \r
12905 /* VS 2008 requires the #include outside of the function */\r
12906 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME\r
12907 #include <sys/timeb.h>\r
12908 #endif\r
12909 \r
12910 /* Get the current time as a TimeMark */\r
12911 void\r
12912 GetTimeMark(tm)\r
12913      TimeMark *tm;\r
12914 {\r
12915 #if HAVE_GETTIMEOFDAY\r
12916 \r
12917     struct timeval timeVal;\r
12918     struct timezone timeZone;\r
12919 \r
12920     gettimeofday(&timeVal, &timeZone);\r
12921     tm->sec = (long) timeVal.tv_sec; \r
12922     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
12923 \r
12924 #else /*!HAVE_GETTIMEOFDAY*/\r
12925 #if HAVE_FTIME\r
12926 \r
12927 // include <sys/timeb.h> / moved to just above start of function\r
12928     struct timeb timeB;\r
12929 \r
12930     ftime(&timeB);\r
12931     tm->sec = (long) timeB.time;\r
12932     tm->ms = (int) timeB.millitm;\r
12933 \r
12934 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
12935     tm->sec = (long) time(NULL);\r
12936     tm->ms = 0;\r
12937 #endif\r
12938 #endif\r
12939 }\r
12940 \r
12941 /* Return the difference in milliseconds between two\r
12942    time marks.  We assume the difference will fit in a long!\r
12943 */\r
12944 long\r
12945 SubtractTimeMarks(tm2, tm1)\r
12946      TimeMark *tm2, *tm1;\r
12947 {\r
12948     return 1000L*(tm2->sec - tm1->sec) +\r
12949            (long) (tm2->ms - tm1->ms);\r
12950 }\r
12951 \r
12952 \r
12953 /*\r
12954  * Code to manage the game clocks.\r
12955  *\r
12956  * In tournament play, black starts the clock and then white makes a move.\r
12957  * We give the human user a slight advantage if he is playing white---the\r
12958  * clocks don't run until he makes his first move, so it takes zero time.\r
12959  * Also, we don't account for network lag, so we could get out of sync\r
12960  * with GNU Chess's clock -- but then, referees are always right.  \r
12961  */\r
12962 \r
12963 static TimeMark tickStartTM;\r
12964 static long intendedTickLength;\r
12965 \r
12966 long\r
12967 NextTickLength(timeRemaining)\r
12968      long timeRemaining;\r
12969 {\r
12970     long nominalTickLength, nextTickLength;\r
12971 \r
12972     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
12973       nominalTickLength = 100L;\r
12974     else\r
12975       nominalTickLength = 1000L;\r
12976     nextTickLength = timeRemaining % nominalTickLength;\r
12977     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
12978 \r
12979     return nextTickLength;\r
12980 }\r
12981 \r
12982 /* Adjust clock one minute up or down */\r
12983 void\r
12984 AdjustClock(Boolean which, int dir)\r
12985 {\r
12986     if(which) blackTimeRemaining += 60000*dir;\r
12987     else      whiteTimeRemaining += 60000*dir;\r
12988     DisplayBothClocks();\r
12989 }\r
12990 \r
12991 /* Stop clocks and reset to a fresh time control */\r
12992 void\r
12993 ResetClocks() \r
12994 {\r
12995     (void) StopClockTimer();\r
12996     if (appData.icsActive) {\r
12997         whiteTimeRemaining = blackTimeRemaining = 0;\r
12998     } else { /* [HGM] correct new time quote for time odds */\r
12999         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
13000         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
13001     }\r
13002     if (whiteFlag || blackFlag) {\r
13003         DisplayTitle("");\r
13004         whiteFlag = blackFlag = FALSE;\r
13005     }\r
13006     DisplayBothClocks();\r
13007 }\r
13008 \r
13009 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
13010 \r
13011 /* Decrement running clock by amount of time that has passed */\r
13012 void\r
13013 DecrementClocks()\r
13014 {\r
13015     long timeRemaining;\r
13016     long lastTickLength, fudge;\r
13017     TimeMark now;\r
13018 \r
13019     if (!appData.clockMode) return;\r
13020     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
13021         \r
13022     GetTimeMark(&now);\r
13023 \r
13024     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13025 \r
13026     /* Fudge if we woke up a little too soon */\r
13027     fudge = intendedTickLength - lastTickLength;\r
13028     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
13029 \r
13030     if (WhiteOnMove(forwardMostMove)) {\r
13031         if(whiteNPS >= 0) lastTickLength = 0;\r
13032         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
13033         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
13034                           WhiteOnMove(currentMove));\r
13035     } else {\r
13036         if(blackNPS >= 0) lastTickLength = 0;\r
13037         timeRemaining = blackTimeRemaining -= lastTickLength;\r
13038         DisplayBlackClock(blackTimeRemaining - fudge,\r
13039                           !WhiteOnMove(currentMove));\r
13040     }\r
13041 \r
13042     if (CheckFlags()) return;\r
13043         \r
13044     tickStartTM = now;\r
13045     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
13046     StartClockTimer(intendedTickLength);\r
13047 \r
13048     /* if the time remaining has fallen below the alarm threshold, sound the\r
13049      * alarm. if the alarm has sounded and (due to a takeback or time control\r
13050      * with increment) the time remaining has increased to a level above the\r
13051      * threshold, reset the alarm so it can sound again. \r
13052      */\r
13053     \r
13054     if (appData.icsActive && appData.icsAlarm) {\r
13055 \r
13056         /* make sure we are dealing with the user's clock */\r
13057         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
13058                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
13059            )) return;\r
13060 \r
13061         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
13062             alarmSounded = FALSE;\r
13063         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
13064             PlayAlarmSound();\r
13065             alarmSounded = TRUE;\r
13066         }\r
13067     }\r
13068 }\r
13069 \r
13070 \r
13071 /* A player has just moved, so stop the previously running\r
13072    clock and (if in clock mode) start the other one.\r
13073    We redisplay both clocks in case we're in ICS mode, because\r
13074    ICS gives us an update to both clocks after every move.\r
13075    Note that this routine is called *after* forwardMostMove\r
13076    is updated, so the last fractional tick must be subtracted\r
13077    from the color that is *not* on move now.\r
13078 */\r
13079 void\r
13080 SwitchClocks()\r
13081 {\r
13082     long lastTickLength;\r
13083     TimeMark now;\r
13084     int flagged = FALSE;\r
13085 \r
13086     GetTimeMark(&now);\r
13087 \r
13088     if (StopClockTimer() && appData.clockMode) {\r
13089         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13090         if (WhiteOnMove(forwardMostMove)) {\r
13091             if(blackNPS >= 0) lastTickLength = 0;\r
13092             blackTimeRemaining -= lastTickLength;\r
13093            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13094 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13095                  pvInfoList[forwardMostMove-1].time =               // use GUI time\r
13096                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
13097         } else {\r
13098            if(whiteNPS >= 0) lastTickLength = 0;\r
13099            whiteTimeRemaining -= lastTickLength;\r
13100            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13101 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13102                  pvInfoList[forwardMostMove-1].time = \r
13103                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
13104         }\r
13105         flagged = CheckFlags();\r
13106     }\r
13107     CheckTimeControl();\r
13108 \r
13109     if (flagged || !appData.clockMode) return;\r
13110 \r
13111     switch (gameMode) {\r
13112       case MachinePlaysBlack:\r
13113       case MachinePlaysWhite:\r
13114       case BeginningOfGame:\r
13115         if (pausing) return;\r
13116         break;\r
13117 \r
13118       case EditGame:\r
13119       case PlayFromGameFile:\r
13120       case IcsExamining:\r
13121         return;\r
13122 \r
13123       default:\r
13124         break;\r
13125     }\r
13126 \r
13127     tickStartTM = now;\r
13128     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13129       whiteTimeRemaining : blackTimeRemaining);\r
13130     StartClockTimer(intendedTickLength);\r
13131 }\r
13132         \r
13133 \r
13134 /* Stop both clocks */\r
13135 void\r
13136 StopClocks()\r
13137 {       \r
13138     long lastTickLength;\r
13139     TimeMark now;\r
13140 \r
13141     if (!StopClockTimer()) return;\r
13142     if (!appData.clockMode) return;\r
13143 \r
13144     GetTimeMark(&now);\r
13145 \r
13146     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13147     if (WhiteOnMove(forwardMostMove)) {\r
13148         if(whiteNPS >= 0) lastTickLength = 0;\r
13149         whiteTimeRemaining -= lastTickLength;\r
13150         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
13151     } else {\r
13152         if(blackNPS >= 0) lastTickLength = 0;\r
13153         blackTimeRemaining -= lastTickLength;\r
13154         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
13155     }\r
13156     CheckFlags();\r
13157 }\r
13158         \r
13159 /* Start clock of player on move.  Time may have been reset, so\r
13160    if clock is already running, stop and restart it. */\r
13161 void\r
13162 StartClocks()\r
13163 {\r
13164     (void) StopClockTimer(); /* in case it was running already */\r
13165     DisplayBothClocks();\r
13166     if (CheckFlags()) return;\r
13167 \r
13168     if (!appData.clockMode) return;\r
13169     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
13170 \r
13171     GetTimeMark(&tickStartTM);\r
13172     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13173       whiteTimeRemaining : blackTimeRemaining);\r
13174 \r
13175    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
13176     whiteNPS = blackNPS = -1; \r
13177     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
13178        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
13179         whiteNPS = first.nps;\r
13180     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
13181        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
13182         blackNPS = first.nps;\r
13183     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
13184         whiteNPS = second.nps;\r
13185     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
13186         blackNPS = second.nps;\r
13187     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
13188 \r
13189     StartClockTimer(intendedTickLength);\r
13190 }\r
13191 \r
13192 char *\r
13193 TimeString(ms)\r
13194      long ms;\r
13195 {\r
13196     long second, minute, hour, day;\r
13197     char *sign = "";\r
13198     static char buf[32];\r
13199     \r
13200     if (ms > 0 && ms <= 9900) {\r
13201       /* convert milliseconds to tenths, rounding up */\r
13202       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
13203 \r
13204       sprintf(buf, " %03.1f ", tenths/10.0);\r
13205       return buf;\r
13206     }\r
13207 \r
13208     /* convert milliseconds to seconds, rounding up */\r
13209     /* use floating point to avoid strangeness of integer division\r
13210        with negative dividends on many machines */\r
13211     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
13212 \r
13213     if (second < 0) {\r
13214         sign = "-";\r
13215         second = -second;\r
13216     }\r
13217     \r
13218     day = second / (60 * 60 * 24);\r
13219     second = second % (60 * 60 * 24);\r
13220     hour = second / (60 * 60);\r
13221     second = second % (60 * 60);\r
13222     minute = second / 60;\r
13223     second = second % 60;\r
13224     \r
13225     if (day > 0)\r
13226       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
13227               sign, day, hour, minute, second);\r
13228     else if (hour > 0)\r
13229       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
13230     else\r
13231       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
13232     \r
13233     return buf;\r
13234 }\r
13235 \r
13236 \r
13237 /*\r
13238  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
13239  */\r
13240 char *\r
13241 StrStr(string, match)\r
13242      char *string, *match;\r
13243 {\r
13244     int i, length;\r
13245     \r
13246     length = strlen(match);\r
13247     \r
13248     for (i = strlen(string) - length; i >= 0; i--, string++)\r
13249       if (!strncmp(match, string, length))\r
13250         return string;\r
13251     \r
13252     return NULL;\r
13253 }\r
13254 \r
13255 char *\r
13256 StrCaseStr(string, match)\r
13257      char *string, *match;\r
13258 {\r
13259     int i, j, length;\r
13260     \r
13261     length = strlen(match);\r
13262     \r
13263     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
13264         for (j = 0; j < length; j++) {\r
13265             if (ToLower(match[j]) != ToLower(string[j]))\r
13266               break;\r
13267         }\r
13268         if (j == length) return string;\r
13269     }\r
13270 \r
13271     return NULL;\r
13272 }\r
13273 \r
13274 #ifndef _amigados\r
13275 int\r
13276 StrCaseCmp(s1, s2)\r
13277      char *s1, *s2;\r
13278 {\r
13279     char c1, c2;\r
13280     \r
13281     for (;;) {\r
13282         c1 = ToLower(*s1++);\r
13283         c2 = ToLower(*s2++);\r
13284         if (c1 > c2) return 1;\r
13285         if (c1 < c2) return -1;\r
13286         if (c1 == NULLCHAR) return 0;\r
13287     }\r
13288 }\r
13289 \r
13290 \r
13291 int\r
13292 ToLower(c)\r
13293      int c;\r
13294 {\r
13295     return isupper(c) ? tolower(c) : c;\r
13296 }\r
13297 \r
13298 \r
13299 int\r
13300 ToUpper(c)\r
13301      int c;\r
13302 {\r
13303     return islower(c) ? toupper(c) : c;\r
13304 }\r
13305 #endif /* !_amigados    */\r
13306 \r
13307 char *\r
13308 StrSave(s)\r
13309      char *s;\r
13310 {\r
13311     char *ret;\r
13312 \r
13313     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
13314         strcpy(ret, s);\r
13315     }\r
13316     return ret;\r
13317 }\r
13318 \r
13319 char *\r
13320 StrSavePtr(s, savePtr)\r
13321      char *s, **savePtr;\r
13322 {\r
13323     if (*savePtr) {\r
13324         free(*savePtr);\r
13325     }\r
13326     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
13327         strcpy(*savePtr, s);\r
13328     }\r
13329     return(*savePtr);\r
13330 }\r
13331 \r
13332 char *\r
13333 PGNDate()\r
13334 {\r
13335     time_t clock;\r
13336     struct tm *tm;\r
13337     char buf[MSG_SIZ];\r
13338 \r
13339     clock = time((time_t *)NULL);\r
13340     tm = localtime(&clock);\r
13341     sprintf(buf, "%04d.%02d.%02d",\r
13342             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
13343     return StrSave(buf);\r
13344 }\r
13345 \r
13346 \r
13347 char *\r
13348 PositionToFEN(move, useFEN960)\r
13349      int move;\r
13350      int useFEN960;\r
13351 {\r
13352     int i, j, fromX, fromY, toX, toY;\r
13353     int whiteToPlay;\r
13354     char buf[128];\r
13355     char *p, *q;\r
13356     int emptycount;\r
13357     ChessSquare piece;\r
13358 \r
13359     whiteToPlay = (gameMode == EditPosition) ?\r
13360       !blackPlaysFirst : (move % 2 == 0);\r
13361     p = buf;\r
13362 \r
13363     /* Piece placement data */\r
13364     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13365         emptycount = 0;\r
13366         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
13367             if (boards[move][i][j] == EmptySquare) {\r
13368                 emptycount++;\r
13369             } else { ChessSquare piece = boards[move][i][j];\r
13370                 if (emptycount > 0) {\r
13371                     if(emptycount<10) /* [HGM] can be >= 10 */\r
13372                         *p++ = '0' + emptycount;\r
13373                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13374                     emptycount = 0;\r
13375                 }\r
13376                 if(PieceToChar(piece) == '+') {\r
13377                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
13378                     *p++ = '+';\r
13379                     piece = (ChessSquare)(DEMOTED piece);\r
13380                 } \r
13381                 *p++ = PieceToChar(piece);\r
13382                 if(p[-1] == '~') {\r
13383                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
13384                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
13385                     *p++ = '~';\r
13386                 }\r
13387             }\r
13388         }\r
13389         if (emptycount > 0) {\r
13390             if(emptycount<10) /* [HGM] can be >= 10 */\r
13391                 *p++ = '0' + emptycount;\r
13392             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13393             emptycount = 0;\r
13394         }\r
13395         *p++ = '/';\r
13396     }\r
13397     *(p - 1) = ' ';\r
13398 \r
13399     /* [HGM] print Crazyhouse or Shogi holdings */\r
13400     if( gameInfo.holdingsWidth ) {\r
13401         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
13402         q = p;\r
13403         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
13404             piece = boards[move][i][BOARD_WIDTH-1];\r
13405             if( piece != EmptySquare )\r
13406               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
13407                   *p++ = PieceToChar(piece);\r
13408         }\r
13409         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
13410             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
13411             if( piece != EmptySquare )\r
13412               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
13413                   *p++ = PieceToChar(piece);\r
13414         }\r
13415 \r
13416         if( q == p ) *p++ = '-';\r
13417         *p++ = ']';\r
13418         *p++ = ' ';\r
13419     }\r
13420 \r
13421     /* Active color */\r
13422     *p++ = whiteToPlay ? 'w' : 'b';\r
13423     *p++ = ' ';\r
13424 \r
13425   if(nrCastlingRights) {\r
13426      q = p;\r
13427      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
13428        /* [HGM] write directly from rights */\r
13429            if(castlingRights[move][2] >= 0 &&\r
13430               castlingRights[move][0] >= 0   )\r
13431                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
13432            if(castlingRights[move][2] >= 0 &&\r
13433               castlingRights[move][1] >= 0   )\r
13434                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
13435            if(castlingRights[move][5] >= 0 &&\r
13436               castlingRights[move][3] >= 0   )\r
13437                 *p++ = castlingRights[move][3] + AAA;\r
13438            if(castlingRights[move][5] >= 0 &&\r
13439               castlingRights[move][4] >= 0   )\r
13440                 *p++ = castlingRights[move][4] + AAA;\r
13441      } else {\r
13442 \r
13443         /* [HGM] write true castling rights */\r
13444         if( nrCastlingRights == 6 ) {\r
13445             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
13446                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
13447             if(castlingRights[move][1] == BOARD_LEFT &&\r
13448                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
13449             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
13450                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
13451             if(castlingRights[move][4] == BOARD_LEFT &&\r
13452                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
13453         }\r
13454      }\r
13455      if (q == p) *p++ = '-'; /* No castling rights */\r
13456      *p++ = ' ';\r
13457   }\r
13458 \r
13459   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13460      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13461     /* En passant target square */\r
13462     if (move > backwardMostMove) {\r
13463         fromX = moveList[move - 1][0] - AAA;\r
13464         fromY = moveList[move - 1][1] - ONE;\r
13465         toX = moveList[move - 1][2] - AAA;\r
13466         toY = moveList[move - 1][3] - ONE;\r
13467         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
13468             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
13469             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
13470             fromX == toX) {\r
13471             /* 2-square pawn move just happened */\r
13472             *p++ = toX + AAA;\r
13473             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
13474         } else {\r
13475             *p++ = '-';\r
13476         }\r
13477     } else {\r
13478         *p++ = '-';\r
13479     }\r
13480     *p++ = ' ';\r
13481   }\r
13482 \r
13483     /* [HGM] find reversible plies */\r
13484     {   int i = 0, j=move;\r
13485 \r
13486         if (appData.debugMode) { int k;\r
13487             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
13488             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
13489                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
13490 \r
13491         }\r
13492 \r
13493         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
13494         if( j == backwardMostMove ) i += initialRulePlies;\r
13495         sprintf(p, "%d ", i);\r
13496         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
13497     }\r
13498     /* Fullmove number */\r
13499     sprintf(p, "%d", (move / 2) + 1);\r
13500     \r
13501     return StrSave(buf);\r
13502 }\r
13503 \r
13504 Boolean\r
13505 ParseFEN(board, blackPlaysFirst, fen)\r
13506     Board board;\r
13507      int *blackPlaysFirst;\r
13508      char *fen;\r
13509 {\r
13510     int i, j;\r
13511     char *p;\r
13512     int emptycount;\r
13513     ChessSquare piece;\r
13514 \r
13515     p = fen;\r
13516 \r
13517     /* [HGM] by default clear Crazyhouse holdings, if present */\r
13518     if(gameInfo.holdingsWidth) {\r
13519        for(i=0; i<BOARD_HEIGHT; i++) {\r
13520            board[i][0]             = EmptySquare; /* black holdings */\r
13521            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
13522            board[i][1]             = (ChessSquare) 0; /* black counts */\r
13523            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
13524        }\r
13525     }\r
13526 \r
13527     /* Piece placement data */\r
13528     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13529         j = 0;\r
13530         for (;;) {\r
13531             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
13532                 if (*p == '/') p++;\r
13533                 emptycount = gameInfo.boardWidth - j;\r
13534                 while (emptycount--)\r
13535                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13536                 break;\r
13537 #if(BOARD_SIZE >= 10)\r
13538             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
13539                 p++; emptycount=10;\r
13540                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13541                 while (emptycount--)\r
13542                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13543 #endif\r
13544             } else if (isdigit(*p)) {\r
13545                 emptycount = *p++ - '0';\r
13546                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
13547                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13548                 while (emptycount--)\r
13549                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13550             } else if (*p == '+' || isalpha(*p)) {\r
13551                 if (j >= gameInfo.boardWidth) return FALSE;\r
13552                 if(*p=='+') {\r
13553                     piece = CharToPiece(*++p);\r
13554                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
13555                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
13556                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
13557                 } else piece = CharToPiece(*p++);\r
13558 \r
13559                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
13560                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
13561                     piece = (ChessSquare) (PROMOTED piece);\r
13562                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
13563                     p++;\r
13564                 }\r
13565                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
13566             } else {\r
13567                 return FALSE;\r
13568             }\r
13569         }\r
13570     }\r
13571     while (*p == '/' || *p == ' ') p++;\r
13572 \r
13573     /* [HGM] look for Crazyhouse holdings here */\r
13574     while(*p==' ') p++;\r
13575     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
13576         if(*p == '[') p++;\r
13577         if(*p == '-' ) *p++; /* empty holdings */ else {\r
13578             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
13579             /* if we would allow FEN reading to set board size, we would   */\r
13580             /* have to add holdings and shift the board read so far here   */\r
13581             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
13582                 *p++;\r
13583                 if((int) piece >= (int) BlackPawn ) {\r
13584                     i = (int)piece - (int)BlackPawn;\r
13585                     i = PieceToNumber((ChessSquare)i);\r
13586                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13587                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
13588                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
13589                 } else {\r
13590                     i = (int)piece - (int)WhitePawn;\r
13591                     i = PieceToNumber((ChessSquare)i);\r
13592                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13593                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
13594                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
13595                 }\r
13596             }\r
13597         }\r
13598         if(*p == ']') *p++;\r
13599     }\r
13600 \r
13601     while(*p == ' ') p++;\r
13602 \r
13603     /* Active color */\r
13604     switch (*p++) {\r
13605       case 'w':\r
13606         *blackPlaysFirst = FALSE;\r
13607         break;\r
13608       case 'b': \r
13609         *blackPlaysFirst = TRUE;\r
13610         break;\r
13611       default:\r
13612         return FALSE;\r
13613     }\r
13614 \r
13615     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
13616     /* return the extra info in global variiables             */\r
13617 \r
13618     /* set defaults in case FEN is incomplete */\r
13619     FENepStatus = EP_UNKNOWN;\r
13620     for(i=0; i<nrCastlingRights; i++ ) {\r
13621         FENcastlingRights[i] =\r
13622             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
13623     }   /* assume possible unless obviously impossible */\r
13624     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
13625     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
13626     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
13627     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
13628     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
13629     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
13630     FENrulePlies = 0;\r
13631 \r
13632     while(*p==' ') p++;\r
13633     if(nrCastlingRights) {\r
13634       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
13635           /* castling indicator present, so default becomes no castlings */\r
13636           for(i=0; i<nrCastlingRights; i++ ) {\r
13637                  FENcastlingRights[i] = -1;\r
13638           }\r
13639       }\r
13640       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
13641              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
13642              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
13643              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
13644         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
13645 \r
13646         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
13647             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
13648             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
13649         }\r
13650         switch(c) {\r
13651           case'K':\r
13652               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
13653               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
13654               FENcastlingRights[2] = whiteKingFile;\r
13655               break;\r
13656           case'Q':\r
13657               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
13658               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
13659               FENcastlingRights[2] = whiteKingFile;\r
13660               break;\r
13661           case'k':\r
13662               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
13663               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
13664               FENcastlingRights[5] = blackKingFile;\r
13665               break;\r
13666           case'q':\r
13667               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
13668               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
13669               FENcastlingRights[5] = blackKingFile;\r
13670           case '-':\r
13671               break;\r
13672           default: /* FRC castlings */\r
13673               if(c >= 'a') { /* black rights */\r
13674                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13675                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
13676                   if(i == BOARD_RGHT) break;\r
13677                   FENcastlingRights[5] = i;\r
13678                   c -= AAA;\r
13679                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
13680                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
13681                   if(c > i)\r
13682                       FENcastlingRights[3] = c;\r
13683                   else\r
13684                       FENcastlingRights[4] = c;\r
13685               } else { /* white rights */\r
13686                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13687                     if(board[0][i] == WhiteKing) break;\r
13688                   if(i == BOARD_RGHT) break;\r
13689                   FENcastlingRights[2] = i;\r
13690                   c -= AAA - 'a' + 'A';\r
13691                   if(board[0][c] >= WhiteKing) break;\r
13692                   if(c > i)\r
13693                       FENcastlingRights[0] = c;\r
13694                   else\r
13695                       FENcastlingRights[1] = c;\r
13696               }\r
13697         }\r
13698       }\r
13699     if (appData.debugMode) {\r
13700         fprintf(debugFP, "FEN castling rights:");\r
13701         for(i=0; i<nrCastlingRights; i++)\r
13702         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
13703         fprintf(debugFP, "\n");\r
13704     }\r
13705 \r
13706       while(*p==' ') p++;\r
13707     }\r
13708 \r
13709     /* read e.p. field in games that know e.p. capture */\r
13710     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13711        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13712       if(*p=='-') {\r
13713         p++; FENepStatus = EP_NONE;\r
13714       } else {\r
13715          char c = *p++ - AAA;\r
13716 \r
13717          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
13718          if(*p >= '0' && *p <='9') *p++;\r
13719          FENepStatus = c;\r
13720       }\r
13721     }\r
13722 \r
13723 \r
13724     if(sscanf(p, "%d", &i) == 1) {\r
13725         FENrulePlies = i; /* 50-move ply counter */\r
13726         /* (The move number is still ignored)    */\r
13727     }\r
13728 \r
13729     return TRUE;\r
13730 }\r
13731       \r
13732 void\r
13733 EditPositionPasteFEN(char *fen)\r
13734 {\r
13735   if (fen != NULL) {\r
13736     Board initial_position;\r
13737 \r
13738     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
13739       DisplayError(_("Bad FEN position in clipboard"), 0);\r
13740       return ;\r
13741     } else {\r
13742       int savedBlackPlaysFirst = blackPlaysFirst;\r
13743       EditPositionEvent();\r
13744       blackPlaysFirst = savedBlackPlaysFirst;\r
13745       CopyBoard(boards[0], initial_position);\r
13746           /* [HGM] copy FEN attributes as well */\r
13747           {   int i;\r
13748               initialRulePlies = FENrulePlies;\r
13749               epStatus[0] = FENepStatus;\r
13750               for( i=0; i<nrCastlingRights; i++ )\r
13751                   castlingRights[0][i] = FENcastlingRights[i];\r
13752           }\r
13753       EditPositionDone();\r
13754       DisplayBothClocks();\r
13755       DrawPosition(FALSE, boards[currentMove]);\r
13756     }\r
13757   }\r
13758 }\r