added option for work-around for some FRC engines in regards to FRCFENs
[xboard.git] / backend.c
1 /*\r
2  * backend.c -- Common back end for X and Windows NT versions of\r
3  * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $\r
4  *\r
5  * Copyright 1991 by Digital Equipment Corporation, Maynard,\r
6  * Massachusetts.  Enhancements Copyright\r
7  * 1992-2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software\r
8  * Foundation, Inc.\r
9  *\r
10  * The following terms apply to Digital Equipment Corporation's copyright\r
11  * interest in XBoard:\r
12  * ------------------------------------------------------------------------\r
13  * All Rights Reserved\r
14  *\r
15  * Permission to use, copy, modify, and distribute this software and its\r
16  * documentation for any purpose and without fee is hereby granted,\r
17  * provided that the above copyright notice appear in all copies and that\r
18  * both that copyright notice and this permission notice appear in\r
19  * supporting documentation, and that the name of Digital not be\r
20  * used in advertising or publicity pertaining to distribution of the\r
21  * software without specific, written prior permission.\r
22  *\r
23  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING\r
24  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL\r
25  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR\r
26  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,\r
27  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\r
28  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS\r
29  * SOFTWARE.\r
30  * ------------------------------------------------------------------------\r
31  *\r
32  * The following terms apply to the enhanced version of XBoard\r
33  * distributed by the Free Software Foundation:\r
34  * ------------------------------------------------------------------------\r
35  *\r
36  * GNU XBoard is free software: you can redistribute it and/or modify\r
37  * it under the terms of the GNU General Public License as published by\r
38  * the Free Software Foundation, either version 3 of the License, or (at\r
39  * your option) any later version.\r
40  *\r
41  * GNU XBoard is distributed in the hope that it will be useful, but\r
42  * WITHOUT ANY WARRANTY; without even the implied warranty of\r
43  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
44  * General Public License for more details.\r
45  *\r
46  * You should have received a copy of the GNU General Public License\r
47  * along with this program. If not, see http://www.gnu.org/licenses/.  *\r
48  *\r
49  *------------------------------------------------------------------------\r
50  ** See the file ChangeLog for a revision history.  */\r
51 \r
52 /* [AS] Also useful here for debugging */\r
53 #ifdef WIN32\r
54 #include <windows.h>\r
55 \r
56 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );\r
57 \r
58 #else\r
59 \r
60 #define DoSleep( n ) if( (n) >= 0) sleep(n)\r
61 \r
62 #endif\r
63 \r
64 #include "config.h"\r
65 \r
66 #include <assert.h>\r
67 #include <stdio.h>\r
68 #include <ctype.h>\r
69 #include <errno.h>\r
70 #include <sys/types.h>\r
71 #include <sys/stat.h>\r
72 #include <math.h>\r
73 #include <ctype.h>\r
74 \r
75 #if STDC_HEADERS\r
76 # include <stdlib.h>\r
77 # include <string.h>\r
78 #else /* not STDC_HEADERS */\r
79 # if HAVE_STRING_H\r
80 #  include <string.h>\r
81 # else /* not HAVE_STRING_H */\r
82 #  include <strings.h>\r
83 # endif /* not HAVE_STRING_H */\r
84 #endif /* not STDC_HEADERS */\r
85 \r
86 #if HAVE_SYS_FCNTL_H\r
87 # include <sys/fcntl.h>\r
88 #else /* not HAVE_SYS_FCNTL_H */\r
89 # if HAVE_FCNTL_H\r
90 #  include <fcntl.h>\r
91 # endif /* HAVE_FCNTL_H */\r
92 #endif /* not HAVE_SYS_FCNTL_H */\r
93 \r
94 #if TIME_WITH_SYS_TIME\r
95 # include <sys/time.h>\r
96 # include <time.h>\r
97 #else\r
98 # if HAVE_SYS_TIME_H\r
99 #  include <sys/time.h>\r
100 # else\r
101 #  include <time.h>\r
102 # endif\r
103 #endif\r
104 \r
105 #if defined(_amigados) && !defined(__GNUC__)\r
106 struct timezone {\r
107     int tz_minuteswest;\r
108     int tz_dsttime;\r
109 };\r
110 extern int gettimeofday(struct timeval *, struct timezone *);\r
111 #endif\r
112 \r
113 #if HAVE_UNISTD_H\r
114 # include <unistd.h>\r
115 #endif\r
116 \r
117 #include "common.h"\r
118 #include "frontend.h"\r
119 #include "backend.h"\r
120 #include "parser.h"\r
121 #include "moves.h"\r
122 #if ZIPPY\r
123 # include "zippy.h"\r
124 #endif\r
125 #include "backendz.h"\r
126 #include "gettext.h" \r
127  \r
128 #ifdef ENABLE_NLS \r
129 # define _(s) gettext (s) \r
130 # define N_(s) gettext_noop (s) \r
131 #else \r
132 # define _(s) (s) \r
133 # define N_(s) s \r
134 #endif \r
135 \r
136 \r
137 /* A point in time */\r
138 typedef struct {\r
139     long sec;  /* Assuming this is >= 32 bits */\r
140     int ms;    /* Assuming this is >= 16 bits */\r
141 } TimeMark;\r
142 \r
143 int establish P((void));\r
144 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,\r
145                          char *buf, int count, int error));\r
146 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,\r
147                       char *buf, int count, int error));\r
148 void SendToICS P((char *s));\r
149 void SendToICSDelayed P((char *s, long msdelay));\r
150 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,\r
151                       int toX, int toY));\r
152 void InitPosition P((int redraw));\r
153 void HandleMachineMove P((char *message, ChessProgramState *cps));\r
154 int AutoPlayOneMove P((void));\r
155 int LoadGameOneMove P((ChessMove readAhead));\r
156 int LoadGameFromFile P((char *filename, int n, char *title, int useList));\r
157 int LoadPositionFromFile P((char *filename, int n, char *title));\r
158 int SavePositionToFile P((char *filename));\r
159 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,\r
160                   Board board));\r
161 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));\r
162 void ShowMove P((int fromX, int fromY, int toX, int toY));\r
163 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,\r
164                    /*char*/int promoChar));\r
165 void BackwardInner P((int target));\r
166 void ForwardInner P((int target));\r
167 void GameEnds P((ChessMove result, char *resultDetails, int whosays));\r
168 void EditPositionDone P((void));\r
169 void PrintOpponents P((FILE *fp));\r
170 void PrintPosition P((FILE *fp, int move));\r
171 void StartChessProgram P((ChessProgramState *cps));\r
172 void SendToProgram P((char *message, ChessProgramState *cps));\r
173 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));\r
174 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,\r
175                            char *buf, int count, int error));\r
176 void SendTimeControl P((ChessProgramState *cps,\r
177                         int mps, long tc, int inc, int sd, int st));\r
178 char *TimeControlTagValue P((void));\r
179 void Attention P((ChessProgramState *cps));\r
180 void FeedMovesToProgram P((ChessProgramState *cps, int upto));\r
181 void ResurrectChessProgram P((void));\r
182 void DisplayComment P((int moveNumber, char *text));\r
183 void DisplayMove P((int moveNumber));\r
184 void DisplayAnalysis P((void));\r
185 \r
186 void ParseGameHistory P((char *game));\r
187 void ParseBoard12 P((char *string));\r
188 void StartClocks P((void));\r
189 void SwitchClocks P((void));\r
190 void StopClocks P((void));\r
191 void ResetClocks P((void));\r
192 char *PGNDate P((void));\r
193 void SetGameInfo P((void));\r
194 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));\r
195 int RegisterMove P((void));\r
196 void MakeRegisteredMove P((void));\r
197 void TruncateGame P((void));\r
198 int looking_at P((char *, int *, char *));\r
199 void CopyPlayerNameIntoFileName P((char **, char *));\r
200 char *SavePart P((char *));\r
201 int SaveGameOldStyle P((FILE *));\r
202 int SaveGamePGN P((FILE *));\r
203 void GetTimeMark P((TimeMark *));\r
204 long SubtractTimeMarks P((TimeMark *, TimeMark *));\r
205 int CheckFlags P((void));\r
206 long NextTickLength P((long));\r
207 void CheckTimeControl P((void));\r
208 void show_bytes P((FILE *, char *, int));\r
209 int string_to_rating P((char *str));\r
210 void ParseFeatures P((char* args, ChessProgramState *cps));\r
211 void InitBackEnd3 P((void));\r
212 void FeatureDone P((ChessProgramState* cps, int val));\r
213 void InitChessProgram P((ChessProgramState *cps, int setup));\r
214 void OutputKibitz(int window, char *text);\r
215 int PerpetualChase(int first, int last);\r
216 int EngineOutputIsUp();\r
217 void InitDrawingSizes(int x, int y);\r
218 \r
219 #ifdef WIN32\r
220        extern void ConsoleCreate();\r
221 #endif\r
222 \r
223 ChessProgramState *WhitePlayer();\r
224 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c\r
225 int VerifyDisplayMode P(());\r
226 \r
227 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment\r
228 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c\r
229 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move\r
230 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book\r
231 extern char installDir[MSG_SIZ];\r
232 \r
233 extern int tinyLayout, smallLayout;\r
234 ChessProgramStats programStats;\r
235 static int exiting = 0; /* [HGM] moved to top */\r
236 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;\r
237 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */\r
238 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */\r
239 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */\r
240 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */\r
241 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */\r
242 int opponentKibitzes;\r
243 \r
244 /* States for ics_getting_history */\r
245 #define H_FALSE 0\r
246 #define H_REQUESTED 1\r
247 #define H_GOT_REQ_HEADER 2\r
248 #define H_GOT_UNREQ_HEADER 3\r
249 #define H_GETTING_MOVES 4\r
250 #define H_GOT_UNWANTED_HEADER 5\r
251 \r
252 /* whosays values for GameEnds */\r
253 #define GE_ICS 0\r
254 #define GE_ENGINE 1\r
255 #define GE_PLAYER 2\r
256 #define GE_FILE 3\r
257 #define GE_XBOARD 4\r
258 #define GE_ENGINE1 5\r
259 #define GE_ENGINE2 6\r
260 \r
261 /* Maximum number of games in a cmail message */\r
262 #define CMAIL_MAX_GAMES 20\r
263 \r
264 /* Different types of move when calling RegisterMove */\r
265 #define CMAIL_MOVE   0\r
266 #define CMAIL_RESIGN 1\r
267 #define CMAIL_DRAW   2\r
268 #define CMAIL_ACCEPT 3\r
269 \r
270 /* Different types of result to remember for each game */\r
271 #define CMAIL_NOT_RESULT 0\r
272 #define CMAIL_OLD_RESULT 1\r
273 #define CMAIL_NEW_RESULT 2\r
274 \r
275 /* Telnet protocol constants */\r
276 #define TN_WILL 0373\r
277 #define TN_WONT 0374\r
278 #define TN_DO   0375\r
279 #define TN_DONT 0376\r
280 #define TN_IAC  0377\r
281 #define TN_ECHO 0001\r
282 #define TN_SGA  0003\r
283 #define TN_PORT 23\r
284 \r
285 /* [AS] */\r
286 static char * safeStrCpy( char * dst, const char * src, size_t count )\r
287 {\r
288     assert( dst != NULL );\r
289     assert( src != NULL );\r
290     assert( count > 0 );\r
291 \r
292     strncpy( dst, src, count );\r
293     dst[ count-1 ] = '\0';\r
294     return dst;\r
295 }\r
296 \r
297 #if 0\r
298 //[HGM] for future use? Conditioned out for now to suppress warning.\r
299 static char * safeStrCat( char * dst, const char * src, size_t count )\r
300 {\r
301     size_t  dst_len;\r
302 \r
303     assert( dst != NULL );\r
304     assert( src != NULL );\r
305     assert( count > 0 );\r
306 \r
307     dst_len = strlen(dst);\r
308 \r
309     assert( count > dst_len ); /* Buffer size must be greater than current length */\r
310 \r
311     safeStrCpy( dst + dst_len, src, count - dst_len );\r
312 \r
313     return dst;\r
314 }\r
315 #endif\r
316 \r
317 /* Some compiler can't cast u64 to double\r
318  * This function do the job for us:\r
319 \r
320  * We use the highest bit for cast, this only\r
321  * works if the highest bit is not\r
322  * in use (This should not happen)\r
323  *\r
324  * We used this for all compiler\r
325  */\r
326 double\r
327 u64ToDouble(u64 value)\r
328 {\r
329   double r;\r
330   u64 tmp = value & u64Const(0x7fffffffffffffff);\r
331   r = (double)(s64)tmp;\r
332   if (value & u64Const(0x8000000000000000))\r
333        r +=  9.2233720368547758080e18; /* 2^63 */\r
334  return r;\r
335 }\r
336 \r
337 /* Fake up flags for now, as we aren't keeping track of castling\r
338    availability yet. [HGM] Change of logic: the flag now only\r
339    indicates the type of castlings allowed by the rule of the game.\r
340    The actual rights themselves are maintained in the array\r
341    castlingRights, as part of the game history, and are not probed\r
342    by this function.\r
343  */\r
344 int\r
345 PosFlags(index)\r
346 {\r
347   int flags = F_ALL_CASTLE_OK;\r
348   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;\r
349   switch (gameInfo.variant) {\r
350   case VariantSuicide:\r
351     flags &= ~F_ALL_CASTLE_OK;\r
352   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!\r
353     flags |= F_IGNORE_CHECK;\r
354   case VariantLosers:\r
355     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist\r
356     break;\r
357   case VariantAtomic:\r
358     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;\r
359     break;\r
360   case VariantKriegspiel:\r
361     flags |= F_KRIEGSPIEL_CAPTURE;\r
362     break;\r
363   case VariantCapaRandom: \r
364   case VariantFischeRandom:\r
365     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */\r
366   case VariantNoCastle:\r
367   case VariantShatranj:\r
368   case VariantCourier:\r
369     flags &= ~F_ALL_CASTLE_OK;\r
370     break;\r
371   default:\r
372     break;\r
373   }\r
374   return flags;\r
375 }\r
376 \r
377 FILE *gameFileFP, *debugFP;\r
378 \r
379 /* \r
380     [AS] Note: sometimes, the sscanf() function is used to parse the input\r
381     into a fixed-size buffer. Because of this, we must be prepared to\r
382     receive strings as long as the size of the input buffer, which is currently\r
383     set to 4K for Windows and 8K for the rest.\r
384     So, we must either allocate sufficiently large buffers here, or\r
385     reduce the size of the input buffer in the input reading part.\r
386 */\r
387 \r
388 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];\r
389 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];\r
390 char thinkOutput1[MSG_SIZ*10];\r
391 \r
392 ChessProgramState first, second;\r
393 \r
394 /* premove variables */\r
395 int premoveToX = 0;\r
396 int premoveToY = 0;\r
397 int premoveFromX = 0;\r
398 int premoveFromY = 0;\r
399 int premovePromoChar = 0;\r
400 int gotPremove = 0;\r
401 Boolean alarmSounded;\r
402 /* end premove variables */\r
403 \r
404 char *ics_prefix = "$";\r
405 int ics_type = ICS_GENERIC;\r
406 \r
407 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;\r
408 int pauseExamForwardMostMove = 0;\r
409 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;\r
410 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];\r
411 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;\r
412 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;\r
413 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;\r
414 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;\r
415 int whiteFlag = FALSE, blackFlag = FALSE;\r
416 int userOfferedDraw = FALSE;\r
417 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;\r
418 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;\r
419 int cmailMoveType[CMAIL_MAX_GAMES];\r
420 long ics_clock_paused = 0;\r
421 ProcRef icsPR = NoProc, cmailPR = NoProc;\r
422 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;\r
423 GameMode gameMode = BeginningOfGame;\r
424 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];\r
425 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];\r
426 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */\r
427 int hiddenThinkOutputState = 0; /* [AS] */\r
428 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */\r
429 int adjudicateLossPlies = 6;\r
430 char white_holding[64], black_holding[64];\r
431 TimeMark lastNodeCountTime;\r
432 long lastNodeCount=0;\r
433 int have_sent_ICS_logon = 0;\r
434 int movesPerSession;\r
435 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;\r
436 long timeControl_2; /* [AS] Allow separate time controls */\r
437 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */\r
438 long timeRemaining[2][MAX_MOVES];\r
439 int matchGame = 0;\r
440 TimeMark programStartTime;\r
441 char ics_handle[MSG_SIZ];\r
442 int have_set_title = 0;\r
443 \r
444 /* animateTraining preserves the state of appData.animate\r
445  * when Training mode is activated. This allows the\r
446  * response to be animated when appData.animate == TRUE and\r
447  * appData.animateDragging == TRUE.\r
448  */\r
449 Boolean animateTraining;\r
450 \r
451 GameInfo gameInfo;\r
452 \r
453 AppData appData;\r
454 \r
455 Board boards[MAX_MOVES];\r
456 /* [HGM] Following 7 needed for accurate legality tests: */\r
457 char  epStatus[MAX_MOVES];\r
458 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1\r
459 char  castlingRank[BOARD_SIZE]; // and corresponding ranks\r
460 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];\r
461 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status\r
462 int   initialRulePlies, FENrulePlies;\r
463 char  FENepStatus;\r
464 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)\r
465 int loadFlag = 0; \r
466 int shuffleOpenings;\r
467 \r
468 ChessSquare  FIDEArray[2][BOARD_SIZE] = {\r
469     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
470         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
471     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
472         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
473 };\r
474 \r
475 ChessSquare twoKingsArray[2][BOARD_SIZE] = {\r
476     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
477         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },\r
478     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
479         BlackKing, BlackKing, BlackKnight, BlackRook }\r
480 };\r
481 \r
482 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {\r
483     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,\r
484         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },\r
485     { BlackRook, BlackMan, BlackBishop, BlackQueen,\r
486         BlackUnicorn, BlackBishop, BlackMan, BlackRook }\r
487 };\r
488 \r
489 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */\r
490     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,\r
491         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
492     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,\r
493         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
494 };\r
495 \r
496 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */\r
497     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,\r
498         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
499     { BlackRook, BlackKnight, BlackAlfil, BlackKing,\r
500         BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
501 };\r
502 \r
503 \r
504 #if (BOARD_SIZE>=10)\r
505 ChessSquare ShogiArray[2][BOARD_SIZE] = {\r
506     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,\r
507         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },\r
508     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,\r
509         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }\r
510 };\r
511 \r
512 ChessSquare XiangqiArray[2][BOARD_SIZE] = {\r
513     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,\r
514         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
515     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,\r
516         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
517 };\r
518 \r
519 ChessSquare CapablancaArray[2][BOARD_SIZE] = {\r
520     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, \r
521         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },\r
522     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, \r
523         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
524 };\r
525 \r
526 ChessSquare GreatArray[2][BOARD_SIZE] = {\r
527     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, \r
528         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },\r
529     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, \r
530         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },\r
531 };\r
532 \r
533 ChessSquare JanusArray[2][BOARD_SIZE] = {\r
534     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, \r
535         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },\r
536     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, \r
537         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }\r
538 };\r
539 \r
540 #ifdef GOTHIC\r
541 ChessSquare GothicArray[2][BOARD_SIZE] = {\r
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, \r
543         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },\r
544     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, \r
545         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }\r
546 };\r
547 #else // !GOTHIC\r
548 #define GothicArray CapablancaArray\r
549 #endif // !GOTHIC\r
550 \r
551 #ifdef FALCON\r
552 ChessSquare FalconArray[2][BOARD_SIZE] = {\r
553     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, \r
554         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },\r
555     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, \r
556         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }\r
557 };\r
558 #else // !FALCON\r
559 #define FalconArray CapablancaArray\r
560 #endif // !FALCON\r
561 \r
562 #else // !(BOARD_SIZE>=10)\r
563 #define XiangqiPosition FIDEArray\r
564 #define CapablancaArray FIDEArray\r
565 #define GothicArray FIDEArray\r
566 #define GreatArray FIDEArray\r
567 #endif // !(BOARD_SIZE>=10)\r
568 \r
569 #if (BOARD_SIZE>=12)\r
570 ChessSquare CourierArray[2][BOARD_SIZE] = {\r
571     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,\r
572         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },\r
573     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,\r
574         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }\r
575 };\r
576 #else // !(BOARD_SIZE>=12)\r
577 #define CourierArray CapablancaArray\r
578 #endif // !(BOARD_SIZE>=12)\r
579 \r
580 \r
581 Board initialPosition;\r
582 \r
583 \r
584 /* Convert str to a rating. Checks for special cases of "----",\r
585 \r
586    "++++", etc. Also strips ()'s */\r
587 int\r
588 string_to_rating(str)\r
589   char *str;\r
590 {\r
591   while(*str && !isdigit(*str)) ++str;\r
592   if (!*str)\r
593     return 0;   /* One of the special "no rating" cases */\r
594   else\r
595     return atoi(str);\r
596 }\r
597 \r
598 void\r
599 ClearProgramStats()\r
600 {\r
601     /* Init programStats */\r
602     programStats.movelist[0] = 0;\r
603     programStats.depth = 0;\r
604     programStats.nr_moves = 0;\r
605     programStats.moves_left = 0;\r
606     programStats.nodes = 0;\r
607     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output\r
608     programStats.score = 0;\r
609     programStats.got_only_move = 0;\r
610     programStats.got_fail = 0;\r
611     programStats.line_is_book = 0;\r
612 }\r
613 \r
614 void\r
615 InitBackEnd1()\r
616 {\r
617     int matched, min, sec;\r
618 \r
619     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options\r
620 \r
621     GetTimeMark(&programStartTime);\r
622     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level\r
623 \r
624     ClearProgramStats();\r
625     programStats.ok_to_send = 1;\r
626     programStats.seen_stat = 0;\r
627 \r
628     /*\r
629      * Initialize game list\r
630      */\r
631     ListNew(&gameList);\r
632 \r
633 \r
634     /*\r
635      * Internet chess server status\r
636      */\r
637     if (appData.icsActive) {\r
638         appData.matchMode = FALSE;\r
639         appData.matchGames = 0;\r
640 #if ZIPPY       \r
641         appData.noChessProgram = !appData.zippyPlay;\r
642 #else\r
643         appData.zippyPlay = FALSE;\r
644         appData.zippyTalk = FALSE;\r
645         appData.noChessProgram = TRUE;\r
646 #endif\r
647         if (*appData.icsHelper != NULLCHAR) {\r
648             appData.useTelnet = TRUE;\r
649             appData.telnetProgram = appData.icsHelper;\r
650         }\r
651     } else {\r
652         appData.zippyTalk = appData.zippyPlay = FALSE;\r
653     }\r
654 \r
655     /* [AS] Initialize pv info list [HGM] and game state */\r
656     {\r
657         int i, j;\r
658 \r
659         for( i=0; i<MAX_MOVES; i++ ) {\r
660             pvInfoList[i].depth = -1;\r
661             epStatus[i]=EP_NONE;\r
662             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
663         }\r
664     }\r
665 \r
666     /*\r
667      * Parse timeControl resource\r
668      */\r
669     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,\r
670                           appData.movesPerSession)) {\r
671         char buf[MSG_SIZ];\r
672         sprintf(buf, _("bad timeControl option %s"), appData.timeControl);\r
673         DisplayFatalError(buf, 0, 2);\r
674     }\r
675 \r
676     /*\r
677      * Parse searchTime resource\r
678      */\r
679     if (*appData.searchTime != NULLCHAR) {\r
680         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);\r
681         if (matched == 1) {\r
682             searchTime = min * 60;\r
683         } else if (matched == 2) {\r
684             searchTime = min * 60 + sec;\r
685         } else {\r
686             char buf[MSG_SIZ];\r
687             sprintf(buf, _("bad searchTime option %s"), appData.searchTime);\r
688             DisplayFatalError(buf, 0, 2);\r
689         }\r
690     }\r
691 \r
692     /* [AS] Adjudication threshold */\r
693     adjudicateLossThreshold = appData.adjudicateLossThreshold;\r
694     \r
695     first.which = "first";\r
696     second.which = "second";\r
697     first.maybeThinking = second.maybeThinking = FALSE;\r
698     first.pr = second.pr = NoProc;\r
699     first.isr = second.isr = NULL;\r
700     first.sendTime = second.sendTime = 2;\r
701     first.sendDrawOffers = 1;\r
702     if (appData.firstPlaysBlack) {\r
703         first.twoMachinesColor = "black\n";\r
704         second.twoMachinesColor = "white\n";\r
705     } else {\r
706         first.twoMachinesColor = "white\n";\r
707         second.twoMachinesColor = "black\n";\r
708     }\r
709     first.program = appData.firstChessProgram;\r
710     second.program = appData.secondChessProgram;\r
711     first.host = appData.firstHost;\r
712     second.host = appData.secondHost;\r
713     first.dir = appData.firstDirectory;\r
714     second.dir = appData.secondDirectory;\r
715     first.other = &second;\r
716     second.other = &first;\r
717     first.initString = appData.initString;\r
718     second.initString = appData.secondInitString;\r
719     first.computerString = appData.firstComputerString;\r
720     second.computerString = appData.secondComputerString;\r
721     first.useSigint = second.useSigint = TRUE;\r
722     first.useSigterm = second.useSigterm = TRUE;\r
723     first.reuse = appData.reuseFirst;\r
724     second.reuse = appData.reuseSecond;\r
725     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second\r
726     second.nps = appData.secondNPS;\r
727     first.useSetboard = second.useSetboard = FALSE;\r
728     first.useSAN = second.useSAN = FALSE;\r
729     first.usePing = second.usePing = FALSE;\r
730     first.lastPing = second.lastPing = 0;\r
731     first.lastPong = second.lastPong = 0;\r
732     first.usePlayother = second.usePlayother = FALSE;\r
733     first.useColors = second.useColors = TRUE;\r
734     first.useUsermove = second.useUsermove = FALSE;\r
735     first.sendICS = second.sendICS = FALSE;\r
736     first.sendName = second.sendName = appData.icsActive;\r
737     first.sdKludge = second.sdKludge = FALSE;\r
738     first.stKludge = second.stKludge = FALSE;\r
739     TidyProgramName(first.program, first.host, first.tidy);\r
740     TidyProgramName(second.program, second.host, second.tidy);\r
741     first.matchWins = second.matchWins = 0;\r
742     strcpy(first.variants, appData.variant);\r
743     strcpy(second.variants, appData.variant);\r
744     first.analysisSupport = second.analysisSupport = 2; /* detect */\r
745     first.analyzing = second.analyzing = FALSE;\r
746     first.initDone = second.initDone = FALSE;\r
747 \r
748     /* New features added by Tord: */\r
749     first.useFEN960 = FALSE; second.useFEN960 = FALSE;\r
750     first.useOOCastle = TRUE; second.useOOCastle = TRUE;\r
751     /* End of new features added by Tord. */\r
752     first.fenOverride  = appData.fenOverride1;\r
753     second.fenOverride = appData.fenOverride2;\r
754 \r
755     /* [HGM] time odds: set factor for each machine */\r
756     first.timeOdds  = appData.firstTimeOdds;\r
757     second.timeOdds = appData.secondTimeOdds;\r
758     { int norm = 1;\r
759         if(appData.timeOddsMode) {\r
760             norm = first.timeOdds;\r
761             if(norm > second.timeOdds) norm = second.timeOdds;\r
762         }\r
763         first.timeOdds /= norm;\r
764         second.timeOdds /= norm;\r
765     }\r
766 \r
767     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/\r
768     first.accumulateTC = appData.firstAccumulateTC;\r
769     second.accumulateTC = appData.secondAccumulateTC;\r
770     first.maxNrOfSessions = second.maxNrOfSessions = 1;\r
771 \r
772     /* [HGM] debug */\r
773     first.debug = second.debug = FALSE;\r
774     first.supportsNPS = second.supportsNPS = UNKNOWN;\r
775 \r
776     /* [HGM] options */\r
777     first.optionSettings  = appData.firstOptions;\r
778     second.optionSettings = appData.secondOptions;\r
779 \r
780     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
781     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
782     first.isUCI = appData.firstIsUCI; /* [AS] */\r
783     second.isUCI = appData.secondIsUCI; /* [AS] */\r
784     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
785     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
786 \r
787     if (appData.firstProtocolVersion > PROTOVER ||\r
788         appData.firstProtocolVersion < 1) {\r
789       char buf[MSG_SIZ];\r
790       sprintf(buf, _("protocol version %d not supported"),\r
791               appData.firstProtocolVersion);\r
792       DisplayFatalError(buf, 0, 2);\r
793     } else {\r
794       first.protocolVersion = appData.firstProtocolVersion;\r
795     }\r
796 \r
797     if (appData.secondProtocolVersion > PROTOVER ||\r
798         appData.secondProtocolVersion < 1) {\r
799       char buf[MSG_SIZ];\r
800       sprintf(buf, _("protocol version %d not supported"),\r
801               appData.secondProtocolVersion);\r
802       DisplayFatalError(buf, 0, 2);\r
803     } else {\r
804       second.protocolVersion = appData.secondProtocolVersion;\r
805     }\r
806 \r
807     if (appData.icsActive) {\r
808         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */\r
809     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
810         appData.clockMode = FALSE;\r
811         first.sendTime = second.sendTime = 0;\r
812     }\r
813     \r
814 #if ZIPPY\r
815     /* Override some settings from environment variables, for backward\r
816        compatibility.  Unfortunately it's not feasible to have the env\r
817        vars just set defaults, at least in xboard.  Ugh.\r
818     */\r
819     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
820       ZippyInit();\r
821     }\r
822 #endif\r
823     \r
824     if (appData.noChessProgram) {\r
825         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
826                                         + strlen(PATCHLEVEL));\r
827         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
828     } else {\r
829 #if 0\r
830         char *p, *q;\r
831         q = first.program;\r
832         while (*q != ' ' && *q != NULLCHAR) q++;\r
833         p = q;\r
834         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */\r
835         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
836                                         + strlen(PATCHLEVEL) + (q - p));\r
837         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
838         strncat(programVersion, p, q - p);\r
839 #else\r
840         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */\r
841         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
842                                         + strlen(PATCHLEVEL) + strlen(first.tidy));\r
843         sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);\r
844 #endif\r
845     }\r
846 \r
847     if (!appData.icsActive) {\r
848       char buf[MSG_SIZ];\r
849       /* Check for variants that are supported only in ICS mode,\r
850          or not at all.  Some that are accepted here nevertheless\r
851          have bugs; see comments below.\r
852       */\r
853       VariantClass variant = StringToVariant(appData.variant);\r
854       switch (variant) {\r
855       case VariantBughouse:     /* need four players and two boards */\r
856       case VariantKriegspiel:   /* need to hide pieces and move details */\r
857       /* case VariantFischeRandom: (Fabien: moved below) */\r
858         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);\r
859         DisplayFatalError(buf, 0, 2);\r
860         return;\r
861 \r
862       case VariantUnknown:\r
863       case VariantLoadable:\r
864       case Variant29:\r
865       case Variant30:\r
866       case Variant31:\r
867       case Variant32:\r
868       case Variant33:\r
869       case Variant34:\r
870       case Variant35:\r
871       case Variant36:\r
872       default:\r
873         sprintf(buf, _("Unknown variant name %s"), appData.variant);\r
874         DisplayFatalError(buf, 0, 2);\r
875         return;\r
876 \r
877       case VariantXiangqi:    /* [HGM] repetition rules not implemented */\r
878       case VariantFairy:      /* [HGM] TestLegality definitely off! */\r
879       case VariantGothic:     /* [HGM] should work */\r
880       case VariantCapablanca: /* [HGM] should work */\r
881       case VariantCourier:    /* [HGM] initial forced moves not implemented */\r
882       case VariantShogi:      /* [HGM] drops not tested for legality */\r
883       case VariantKnightmate: /* [HGM] should work */\r
884       case VariantCylinder:   /* [HGM] untested */\r
885       case VariantFalcon:     /* [HGM] untested */\r
886       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)\r
887                                  offboard interposition not understood */\r
888       case VariantNormal:     /* definitely works! */\r
889       case VariantWildCastle: /* pieces not automatically shuffled */\r
890       case VariantNoCastle:   /* pieces not automatically shuffled */\r
891       case VariantFischeRandom: /* [HGM] works and shuffles pieces */\r
892       case VariantLosers:     /* should work except for win condition,\r
893                                  and doesn't know captures are mandatory */\r
894       case VariantSuicide:    /* should work except for win condition,\r
895                                  and doesn't know captures are mandatory */\r
896       case VariantGiveaway:   /* should work except for win condition,\r
897                                  and doesn't know captures are mandatory */\r
898       case VariantTwoKings:   /* should work */\r
899       case VariantAtomic:     /* should work except for win condition */\r
900       case Variant3Check:     /* should work except for win condition */\r
901       case VariantShatranj:   /* should work except for all win conditions */\r
902       case VariantBerolina:   /* might work if TestLegality is off */\r
903       case VariantCapaRandom: /* should work */\r
904       case VariantJanus:      /* should work */\r
905       case VariantSuper:      /* experimental */\r
906       case VariantGreat:      /* experimental, requires legality testing to be off */\r
907         break;\r
908       }\r
909     }\r
910 \r
911     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard\r
912     InitEngineUCI( installDir, &second );\r
913 }\r
914 \r
915 int NextIntegerFromString( char ** str, long * value )\r
916 {\r
917     int result = -1;\r
918     char * s = *str;\r
919 \r
920     while( *s == ' ' || *s == '\t' ) {\r
921         s++;\r
922     }\r
923 \r
924     *value = 0;\r
925 \r
926     if( *s >= '0' && *s <= '9' ) {\r
927         while( *s >= '0' && *s <= '9' ) {\r
928             *value = *value * 10 + (*s - '0');\r
929             s++;\r
930         }\r
931 \r
932         result = 0;\r
933     }\r
934 \r
935     *str = s;\r
936 \r
937     return result;\r
938 }\r
939 \r
940 int NextTimeControlFromString( char ** str, long * value )\r
941 {\r
942     long temp;\r
943     int result = NextIntegerFromString( str, &temp );\r
944 \r
945     if( result == 0 ) {\r
946         *value = temp * 60; /* Minutes */\r
947         if( **str == ':' ) {\r
948             (*str)++;\r
949             result = NextIntegerFromString( str, &temp );\r
950             *value += temp; /* Seconds */\r
951         }\r
952     }\r
953 \r
954     return result;\r
955 }\r
956 \r
957 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)\r
958 {   /* [HGM] routine added to read '+moves/time' for secondary time control */\r
959     int result = -1; long temp, temp2;\r
960 \r
961     if(**str != '+') return -1; // old params remain in force!\r
962     (*str)++;\r
963     if( NextTimeControlFromString( str, &temp ) ) return -1;\r
964 \r
965     if(**str != '/') {\r
966         /* time only: incremental or sudden-death time control */\r
967         if(**str == '+') { /* increment follows; read it */\r
968             (*str)++;\r
969             if(result = NextIntegerFromString( str, &temp2)) return -1;\r
970             *inc = temp2 * 1000;\r
971         } else *inc = 0;\r
972         *moves = 0; *tc = temp * 1000; \r
973         return 0;\r
974     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */\r
975 \r
976     (*str)++; /* classical time control */\r
977     result = NextTimeControlFromString( str, &temp2);\r
978     if(result == 0) {\r
979         *moves = temp/60;\r
980         *tc    = temp2 * 1000;\r
981         *inc   = 0;\r
982     }\r
983     return result;\r
984 }\r
985 \r
986 int GetTimeQuota(int movenr)\r
987 {   /* [HGM] get time to add from the multi-session time-control string */\r
988     int moves=1; /* kludge to force reading of first session */\r
989     long time, increment;\r
990     char *s = fullTimeControlString;\r
991 \r
992     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);\r
993     do {\r
994         if(moves) NextSessionFromString(&s, &moves, &time, &increment);\r
995         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);\r
996         if(movenr == -1) return time;    /* last move before new session     */\r
997         if(!moves) return increment;     /* current session is incremental   */\r
998         if(movenr >= 0) movenr -= moves; /* we already finished this session */\r
999     } while(movenr >= -1);               /* try again for next session       */\r
1000 \r
1001     return 0; // no new time quota on this move\r
1002 }\r
1003 \r
1004 int\r
1005 ParseTimeControl(tc, ti, mps)\r
1006      char *tc;\r
1007      int ti;\r
1008      int mps;\r
1009 {\r
1010 #if 0\r
1011     int matched, min, sec;\r
1012 \r
1013     matched = sscanf(tc, "%d:%d", &min, &sec);\r
1014     if (matched == 1) {\r
1015         timeControl = min * 60 * 1000;\r
1016     } else if (matched == 2) {\r
1017         timeControl = (min * 60 + sec) * 1000;\r
1018     } else {\r
1019         return FALSE;\r
1020     }\r
1021 #else\r
1022     long tc1;\r
1023     long tc2;\r
1024     char buf[MSG_SIZ];\r
1025 \r
1026     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;\r
1027     if(ti > 0) {\r
1028         if(mps)\r
1029              sprintf(buf, "+%d/%s+%d", mps, tc, ti);\r
1030         else sprintf(buf, "+%s+%d", tc, ti);\r
1031     } else {\r
1032         if(mps)\r
1033              sprintf(buf, "+%d/%s", mps, tc);\r
1034         else sprintf(buf, "+%s", tc);\r
1035     }\r
1036     fullTimeControlString = StrSave(buf);\r
1037 \r
1038     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
1039         return FALSE;\r
1040     }\r
1041 \r
1042     if( *tc == '/' ) {\r
1043         /* Parse second time control */\r
1044         tc++;\r
1045 \r
1046         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
1047             return FALSE;\r
1048         }\r
1049 \r
1050         if( tc2 == 0 ) {\r
1051             return FALSE;\r
1052         }\r
1053 \r
1054         timeControl_2 = tc2 * 1000;\r
1055     }\r
1056     else {\r
1057         timeControl_2 = 0;\r
1058     }\r
1059 \r
1060     if( tc1 == 0 ) {\r
1061         return FALSE;\r
1062     }\r
1063 \r
1064     timeControl = tc1 * 1000;\r
1065 #endif\r
1066 \r
1067     if (ti >= 0) {\r
1068         timeIncrement = ti * 1000;  /* convert to ms */\r
1069         movesPerSession = 0;\r
1070     } else {\r
1071         timeIncrement = 0;\r
1072         movesPerSession = mps;\r
1073     }\r
1074     return TRUE;\r
1075 }\r
1076 \r
1077 void\r
1078 InitBackEnd2()\r
1079 {\r
1080     if (appData.debugMode) {\r
1081         fprintf(debugFP, "%s\n", programVersion);\r
1082     }\r
1083 \r
1084     if (appData.matchGames > 0) {\r
1085         appData.matchMode = TRUE;\r
1086     } else if (appData.matchMode) {\r
1087         appData.matchGames = 1;\r
1088     }\r
1089     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */\r
1090         appData.matchGames = appData.sameColorGames;\r
1091     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */\r
1092         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;\r
1093         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;\r
1094     }\r
1095     Reset(TRUE, FALSE);\r
1096     if (appData.noChessProgram || first.protocolVersion == 1) {\r
1097       InitBackEnd3();\r
1098     } else {\r
1099       /* kludge: allow timeout for initial "feature" commands */\r
1100       FreezeUI();\r
1101       DisplayMessage("", _("Starting chess program"));\r
1102       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
1103     }\r
1104 }\r
1105 \r
1106 void\r
1107 InitBackEnd3 P((void))\r
1108 {\r
1109     GameMode initialMode;\r
1110     char buf[MSG_SIZ];\r
1111     int err;\r
1112 \r
1113     InitChessProgram(&first, startedFromSetupPosition);\r
1114 \r
1115 \r
1116     if (appData.icsActive) {\r
1117 #ifdef WIN32\r
1118         /* [DM] Make a console window if needed [HGM] merged ifs */\r
1119         ConsoleCreate(); \r
1120 #endif\r
1121         err = establish();\r
1122         if (err != 0) {\r
1123             if (*appData.icsCommPort != NULLCHAR) {\r
1124                 sprintf(buf, _("Could not open comm port %s"),  \r
1125                         appData.icsCommPort);\r
1126             } else {\r
1127                 sprintf(buf, _("Could not connect to host %s, port %s"),  \r
1128                         appData.icsHost, appData.icsPort);\r
1129             }\r
1130             DisplayFatalError(buf, err, 1);\r
1131             return;\r
1132         }\r
1133         SetICSMode();\r
1134         telnetISR =\r
1135           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
1136         fromUserISR =\r
1137           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
1138     } else if (appData.noChessProgram) {\r
1139         SetNCPMode();\r
1140     } else {\r
1141         SetGNUMode();\r
1142     }\r
1143 \r
1144     if (*appData.cmailGameName != NULLCHAR) {\r
1145         SetCmailMode();\r
1146         OpenLoopback(&cmailPR);\r
1147         cmailISR =\r
1148           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
1149     }\r
1150     \r
1151     ThawUI();\r
1152     DisplayMessage("", "");\r
1153     if (StrCaseCmp(appData.initialMode, "") == 0) {\r
1154       initialMode = BeginningOfGame;\r
1155     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
1156       initialMode = TwoMachinesPlay;\r
1157     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
1158       initialMode = AnalyzeFile; \r
1159     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
1160       initialMode = AnalyzeMode;\r
1161     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
1162       initialMode = MachinePlaysWhite;\r
1163     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
1164       initialMode = MachinePlaysBlack;\r
1165     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
1166       initialMode = EditGame;\r
1167     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
1168       initialMode = EditPosition;\r
1169     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
1170       initialMode = Training;\r
1171     } else {\r
1172       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);\r
1173       DisplayFatalError(buf, 0, 2);\r
1174       return;\r
1175     }\r
1176 \r
1177     if (appData.matchMode) {\r
1178         /* Set up machine vs. machine match */\r
1179         if (appData.noChessProgram) {\r
1180             DisplayFatalError(_("Can't have a match with no chess programs"),\r
1181                               0, 2);\r
1182             return;\r
1183         }\r
1184         matchMode = TRUE;\r
1185         matchGame = 1;\r
1186         if (*appData.loadGameFile != NULLCHAR) {\r
1187             int index = appData.loadGameIndex; // [HGM] autoinc\r
1188             if(index<0) lastIndex = index = 1;\r
1189             if (!LoadGameFromFile(appData.loadGameFile,\r
1190                                   index,\r
1191                                   appData.loadGameFile, FALSE)) {\r
1192                 DisplayFatalError(_("Bad game file"), 0, 1);\r
1193                 return;\r
1194             }\r
1195         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1196             int index = appData.loadPositionIndex; // [HGM] autoinc\r
1197             if(index<0) lastIndex = index = 1;\r
1198             if (!LoadPositionFromFile(appData.loadPositionFile,\r
1199                                       index,\r
1200                                       appData.loadPositionFile)) {\r
1201                 DisplayFatalError(_("Bad position file"), 0, 1);\r
1202                 return;\r
1203             }\r
1204         }\r
1205         TwoMachinesEvent();\r
1206     } else if (*appData.cmailGameName != NULLCHAR) {\r
1207         /* Set up cmail mode */\r
1208         ReloadCmailMsgEvent(TRUE);\r
1209     } else {\r
1210         /* Set up other modes */\r
1211         if (initialMode == AnalyzeFile) {\r
1212           if (*appData.loadGameFile == NULLCHAR) {\r
1213             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);\r
1214             return;\r
1215           }\r
1216         }\r
1217         if (*appData.loadGameFile != NULLCHAR) {\r
1218             (void) LoadGameFromFile(appData.loadGameFile,\r
1219                                     appData.loadGameIndex,\r
1220                                     appData.loadGameFile, TRUE);\r
1221         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1222             (void) LoadPositionFromFile(appData.loadPositionFile,\r
1223                                         appData.loadPositionIndex,\r
1224                                         appData.loadPositionFile);\r
1225             /* [HGM] try to make self-starting even after FEN load */\r
1226             /* to allow automatic setup of fairy variants with wtm */\r
1227             if(initialMode == BeginningOfGame && !blackPlaysFirst) {\r
1228                 gameMode = BeginningOfGame;\r
1229                 setboardSpoiledMachineBlack = 1;\r
1230             }\r
1231             /* [HGM] loadPos: make that every new game uses the setup */\r
1232             /* from file as long as we do not switch variant          */\r
1233             if(!blackPlaysFirst) { int i;\r
1234                 startedFromPositionFile = TRUE;\r
1235                 CopyBoard(filePosition, boards[0]);\r
1236                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];\r
1237             }\r
1238         }\r
1239         if (initialMode == AnalyzeMode) {\r
1240           if (appData.noChessProgram) {\r
1241             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);\r
1242             return;\r
1243           }\r
1244           if (appData.icsActive) {\r
1245             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);\r
1246             return;\r
1247           }\r
1248           AnalyzeModeEvent();\r
1249         } else if (initialMode == AnalyzeFile) {\r
1250           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent\r
1251           ShowThinkingEvent();\r
1252           AnalyzeFileEvent();\r
1253           AnalysisPeriodicEvent(1);\r
1254         } else if (initialMode == MachinePlaysWhite) {\r
1255           if (appData.noChessProgram) {\r
1256             DisplayFatalError(_("MachineWhite mode requires a chess engine"),\r
1257                               0, 2);\r
1258             return;\r
1259           }\r
1260           if (appData.icsActive) {\r
1261             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),\r
1262                               0, 2);\r
1263             return;\r
1264           }\r
1265           MachineWhiteEvent();\r
1266         } else if (initialMode == MachinePlaysBlack) {\r
1267           if (appData.noChessProgram) {\r
1268             DisplayFatalError(_("MachineBlack mode requires a chess engine"),\r
1269                               0, 2);\r
1270             return;\r
1271           }\r
1272           if (appData.icsActive) {\r
1273             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),\r
1274                               0, 2);\r
1275             return;\r
1276           }\r
1277           MachineBlackEvent();\r
1278         } else if (initialMode == TwoMachinesPlay) {\r
1279           if (appData.noChessProgram) {\r
1280             DisplayFatalError(_("TwoMachines mode requires a chess engine"),\r
1281                               0, 2);\r
1282             return;\r
1283           }\r
1284           if (appData.icsActive) {\r
1285             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),\r
1286                               0, 2);\r
1287             return;\r
1288           }\r
1289           TwoMachinesEvent();\r
1290         } else if (initialMode == EditGame) {\r
1291           EditGameEvent();\r
1292         } else if (initialMode == EditPosition) {\r
1293           EditPositionEvent();\r
1294         } else if (initialMode == Training) {\r
1295           if (*appData.loadGameFile == NULLCHAR) {\r
1296             DisplayFatalError(_("Training mode requires a game file"), 0, 2);\r
1297             return;\r
1298           }\r
1299           TrainingEvent();\r
1300         }\r
1301     }\r
1302 }\r
1303 \r
1304 /*\r
1305  * Establish will establish a contact to a remote host.port.\r
1306  * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
1307  *  used to talk to the host.\r
1308  * Returns 0 if okay, error code if not.\r
1309  */\r
1310 int\r
1311 establish()\r
1312 {\r
1313     char buf[MSG_SIZ];\r
1314 \r
1315     if (*appData.icsCommPort != NULLCHAR) {\r
1316         /* Talk to the host through a serial comm port */\r
1317         return OpenCommPort(appData.icsCommPort, &icsPR);\r
1318 \r
1319     } else if (*appData.gateway != NULLCHAR) {\r
1320         if (*appData.remoteShell == NULLCHAR) {\r
1321             /* Use the rcmd protocol to run telnet program on a gateway host */\r
1322             sprintf(buf, "%s %s %s",\r
1323                     appData.telnetProgram, appData.icsHost, appData.icsPort);\r
1324             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
1325 \r
1326         } else {\r
1327             /* Use the rsh program to run telnet program on a gateway host */\r
1328             if (*appData.remoteUser == NULLCHAR) {\r
1329                 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,\r
1330                         appData.gateway, appData.telnetProgram,\r
1331                         appData.icsHost, appData.icsPort);\r
1332             } else {\r
1333                 sprintf(buf, "%s %s -l %s %s %s %s",\r
1334                         appData.remoteShell, appData.gateway, \r
1335                         appData.remoteUser, appData.telnetProgram,\r
1336                         appData.icsHost, appData.icsPort);\r
1337             }\r
1338             return StartChildProcess(buf, "", &icsPR);\r
1339 \r
1340         }\r
1341     } else if (appData.useTelnet) {\r
1342         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
1343 \r
1344     } else {\r
1345         /* TCP socket interface differs somewhat between\r
1346            Unix and NT; handle details in the front end.\r
1347            */\r
1348         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
1349     }\r
1350 }\r
1351 \r
1352 void\r
1353 show_bytes(fp, buf, count)\r
1354      FILE *fp;\r
1355      char *buf;\r
1356      int count;\r
1357 {\r
1358     while (count--) {\r
1359         if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
1360             fprintf(fp, "\\%03o", *buf & 0xff);\r
1361         } else {\r
1362             putc(*buf, fp);\r
1363         }\r
1364         buf++;\r
1365     }\r
1366     fflush(fp);\r
1367 }\r
1368 \r
1369 /* Returns an errno value */\r
1370 int\r
1371 OutputMaybeTelnet(pr, message, count, outError)\r
1372      ProcRef pr;\r
1373      char *message;\r
1374      int count;\r
1375      int *outError;\r
1376 {\r
1377     char buf[8192], *p, *q, *buflim;\r
1378     int left, newcount, outcount;\r
1379 \r
1380     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
1381         *appData.gateway != NULLCHAR) {\r
1382         if (appData.debugMode) {\r
1383             fprintf(debugFP, ">ICS: ");\r
1384             show_bytes(debugFP, message, count);\r
1385             fprintf(debugFP, "\n");\r
1386         }\r
1387         return OutputToProcess(pr, message, count, outError);\r
1388     }\r
1389 \r
1390     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
1391     p = message;\r
1392     q = buf;\r
1393     left = count;\r
1394     newcount = 0;\r
1395     while (left) {\r
1396         if (q >= buflim) {\r
1397             if (appData.debugMode) {\r
1398                 fprintf(debugFP, ">ICS: ");\r
1399                 show_bytes(debugFP, buf, newcount);\r
1400                 fprintf(debugFP, "\n");\r
1401             }\r
1402             outcount = OutputToProcess(pr, buf, newcount, outError);\r
1403             if (outcount < newcount) return -1; /* to be sure */\r
1404             q = buf;\r
1405             newcount = 0;\r
1406         }\r
1407         if (*p == '\n') {\r
1408             *q++ = '\r';\r
1409             newcount++;\r
1410         } else if (((unsigned char) *p) == TN_IAC) {\r
1411             *q++ = (char) TN_IAC;\r
1412             newcount ++;\r
1413         }\r
1414         *q++ = *p++;\r
1415         newcount++;\r
1416         left--;\r
1417     }\r
1418     if (appData.debugMode) {\r
1419         fprintf(debugFP, ">ICS: ");\r
1420         show_bytes(debugFP, buf, newcount);\r
1421         fprintf(debugFP, "\n");\r
1422     }\r
1423     outcount = OutputToProcess(pr, buf, newcount, outError);\r
1424     if (outcount < newcount) return -1; /* to be sure */\r
1425     return count;\r
1426 }\r
1427 \r
1428 void\r
1429 read_from_player(isr, closure, message, count, error)\r
1430      InputSourceRef isr;\r
1431      VOIDSTAR closure;\r
1432      char *message;\r
1433      int count;\r
1434      int error;\r
1435 {\r
1436     int outError, outCount;\r
1437     static int gotEof = 0;\r
1438 \r
1439     /* Pass data read from player on to ICS */\r
1440     if (count > 0) {\r
1441         gotEof = 0;\r
1442         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
1443         if (outCount < count) {\r
1444             DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1445         }\r
1446     } else if (count < 0) {\r
1447         RemoveInputSource(isr);\r
1448         DisplayFatalError(_("Error reading from keyboard"), error, 1);\r
1449     } else if (gotEof++ > 0) {\r
1450         RemoveInputSource(isr);\r
1451         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);\r
1452     }\r
1453 }\r
1454 \r
1455 void\r
1456 SendToICS(s)\r
1457      char *s;\r
1458 {\r
1459     int count, outCount, outError;\r
1460 \r
1461     if (icsPR == NULL) return;\r
1462 \r
1463     count = strlen(s);\r
1464     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
1465     if (outCount < count) {\r
1466         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1467     }\r
1468 }\r
1469 \r
1470 /* This is used for sending logon scripts to the ICS. Sending\r
1471    without a delay causes problems when using timestamp on ICC\r
1472    (at least on my machine). */\r
1473 void\r
1474 SendToICSDelayed(s,msdelay)\r
1475      char *s;\r
1476      long msdelay;\r
1477 {\r
1478     int count, outCount, outError;\r
1479 \r
1480     if (icsPR == NULL) return;\r
1481 \r
1482     count = strlen(s);\r
1483     if (appData.debugMode) {\r
1484         fprintf(debugFP, ">ICS: ");\r
1485         show_bytes(debugFP, s, count);\r
1486         fprintf(debugFP, "\n");\r
1487     }\r
1488     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
1489                                       msdelay);\r
1490     if (outCount < count) {\r
1491         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1492     }\r
1493 }\r
1494 \r
1495 \r
1496 /* Remove all highlighting escape sequences in s\r
1497    Also deletes any suffix starting with '(' \r
1498    */\r
1499 char *\r
1500 StripHighlightAndTitle(s)\r
1501      char *s;\r
1502 {\r
1503     static char retbuf[MSG_SIZ];\r
1504     char *p = retbuf;\r
1505 \r
1506     while (*s != NULLCHAR) {\r
1507         while (*s == '\033') {\r
1508             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1509             if (*s != NULLCHAR) s++;\r
1510         }\r
1511         while (*s != NULLCHAR && *s != '\033') {\r
1512             if (*s == '(' || *s == '[') {\r
1513                 *p = NULLCHAR;\r
1514                 return retbuf;\r
1515             }\r
1516             *p++ = *s++;\r
1517         }\r
1518     }\r
1519     *p = NULLCHAR;\r
1520     return retbuf;\r
1521 }\r
1522 \r
1523 /* Remove all highlighting escape sequences in s */\r
1524 char *\r
1525 StripHighlight(s)\r
1526      char *s;\r
1527 {\r
1528     static char retbuf[MSG_SIZ];\r
1529     char *p = retbuf;\r
1530 \r
1531     while (*s != NULLCHAR) {\r
1532         while (*s == '\033') {\r
1533             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1534             if (*s != NULLCHAR) s++;\r
1535         }\r
1536         while (*s != NULLCHAR && *s != '\033') {\r
1537             *p++ = *s++;\r
1538         }\r
1539     }\r
1540     *p = NULLCHAR;\r
1541     return retbuf;\r
1542 }\r
1543 \r
1544 char *variantNames[] = VARIANT_NAMES;\r
1545 char *\r
1546 VariantName(v)\r
1547      VariantClass v;\r
1548 {\r
1549     return variantNames[v];\r
1550 }\r
1551 \r
1552 \r
1553 /* Identify a variant from the strings the chess servers use or the\r
1554    PGN Variant tag names we use. */\r
1555 VariantClass\r
1556 StringToVariant(e)\r
1557      char *e;\r
1558 {\r
1559     char *p;\r
1560     int wnum = -1;\r
1561     VariantClass v = VariantNormal;\r
1562     int i, found = FALSE;\r
1563     char buf[MSG_SIZ];\r
1564 \r
1565     if (!e) return v;\r
1566 \r
1567     /* [HGM] skip over optional board-size prefixes */\r
1568     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||\r
1569         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
1570         while( *e++ != '_');\r
1571     }\r
1572 \r
1573     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1574       if (StrCaseStr(e, variantNames[i])) {\r
1575         v = (VariantClass) i;\r
1576         found = TRUE;\r
1577         break;\r
1578       }\r
1579     }\r
1580 \r
1581     if (!found) {\r
1582       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1583           || StrCaseStr(e, "wild/fr") \r
1584           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {\r
1585         v = VariantFischeRandom;\r
1586       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1587                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1588         p += i;\r
1589         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1590         if (isdigit(*p)) {\r
1591           wnum = atoi(p);\r
1592         } else {\r
1593           wnum = -1;\r
1594         }\r
1595         switch (wnum) {\r
1596         case 0: /* FICS only, actually */\r
1597         case 1:\r
1598           /* Castling legal even if K starts on d-file */\r
1599           v = VariantWildCastle;\r
1600           break;\r
1601         case 2:\r
1602         case 3:\r
1603         case 4:\r
1604           /* Castling illegal even if K & R happen to start in\r
1605              normal positions. */\r
1606           v = VariantNoCastle;\r
1607           break;\r
1608         case 5:\r
1609         case 7:\r
1610         case 8:\r
1611         case 10:\r
1612         case 11:\r
1613         case 12:\r
1614         case 13:\r
1615         case 14:\r
1616         case 15:\r
1617         case 18:\r
1618         case 19:\r
1619           /* Castling legal iff K & R start in normal positions */\r
1620           v = VariantNormal;\r
1621           break;\r
1622         case 6:\r
1623         case 20:\r
1624         case 21:\r
1625           /* Special wilds for position setup; unclear what to do here */\r
1626           v = VariantLoadable;\r
1627           break;\r
1628         case 9:\r
1629           /* Bizarre ICC game */\r
1630           v = VariantTwoKings;\r
1631           break;\r
1632         case 16:\r
1633           v = VariantKriegspiel;\r
1634           break;\r
1635         case 17:\r
1636           v = VariantLosers;\r
1637           break;\r
1638         case 22:\r
1639           v = VariantFischeRandom;\r
1640           break;\r
1641         case 23:\r
1642           v = VariantCrazyhouse;\r
1643           break;\r
1644         case 24:\r
1645           v = VariantBughouse;\r
1646           break;\r
1647         case 25:\r
1648           v = Variant3Check;\r
1649           break;\r
1650         case 26:\r
1651           /* Not quite the same as FICS suicide! */\r
1652           v = VariantGiveaway;\r
1653           break;\r
1654         case 27:\r
1655           v = VariantAtomic;\r
1656           break;\r
1657         case 28:\r
1658           v = VariantShatranj;\r
1659           break;\r
1660 \r
1661         /* Temporary names for future ICC types.  The name *will* change in \r
1662            the next xboard/WinBoard release after ICC defines it. */\r
1663         case 29:\r
1664           v = Variant29;\r
1665           break;\r
1666         case 30:\r
1667           v = Variant30;\r
1668           break;\r
1669         case 31:\r
1670           v = Variant31;\r
1671           break;\r
1672         case 32:\r
1673           v = Variant32;\r
1674           break;\r
1675         case 33:\r
1676           v = Variant33;\r
1677           break;\r
1678         case 34:\r
1679           v = Variant34;\r
1680           break;\r
1681         case 35:\r
1682           v = Variant35;\r
1683           break;\r
1684         case 36:\r
1685           v = Variant36;\r
1686           break;\r
1687         case 37:\r
1688           v = VariantShogi;\r
1689           break;\r
1690         case 38:\r
1691           v = VariantXiangqi;\r
1692           break;\r
1693         case 39:\r
1694           v = VariantCourier;\r
1695           break;\r
1696         case 40:\r
1697           v = VariantGothic;\r
1698           break;\r
1699         case 41:\r
1700           v = VariantCapablanca;\r
1701           break;\r
1702         case 42:\r
1703           v = VariantKnightmate;\r
1704           break;\r
1705         case 43:\r
1706           v = VariantFairy;\r
1707           break;\r
1708         case 44:\r
1709           v = VariantCylinder;\r
1710           break;\r
1711         case 45:\r
1712           v = VariantFalcon;\r
1713           break;\r
1714         case 46:\r
1715           v = VariantCapaRandom;\r
1716           break;\r
1717         case 47:\r
1718           v = VariantBerolina;\r
1719           break;\r
1720         case 48:\r
1721           v = VariantJanus;\r
1722           break;\r
1723         case 49:\r
1724           v = VariantSuper;\r
1725           break;\r
1726         case 50:\r
1727           v = VariantGreat;\r
1728           break;\r
1729         case -1:\r
1730           /* Found "wild" or "w" in the string but no number;\r
1731              must assume it's normal chess. */\r
1732           v = VariantNormal;\r
1733           break;\r
1734         default:\r
1735           sprintf(buf, _("Unknown wild type %d"), wnum);\r
1736           DisplayError(buf, 0);\r
1737           v = VariantUnknown;\r
1738           break;\r
1739         }\r
1740       }\r
1741     }\r
1742     if (appData.debugMode) {\r
1743       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),\r
1744               e, wnum, VariantName(v));\r
1745     }\r
1746     return v;\r
1747 }\r
1748 \r
1749 static int leftover_start = 0, leftover_len = 0;\r
1750 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1751 \r
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1753    advance *index beyond it, and set leftover_start to the new value of\r
1754    *index; else return FALSE.  If pattern contains the character '*', it\r
1755    matches any sequence of characters not containing '\r', '\n', or the\r
1756    character following the '*' (if any), and the matched sequence(s) are\r
1757    copied into star_match.\r
1758    */\r
1759 int\r
1760 looking_at(buf, index, pattern)\r
1761      char *buf;\r
1762      int *index;\r
1763      char *pattern;\r
1764 {\r
1765     char *bufp = &buf[*index], *patternp = pattern;\r
1766     int star_count = 0;\r
1767     char *matchp = star_match[0];\r
1768     \r
1769     for (;;) {\r
1770         if (*patternp == NULLCHAR) {\r
1771             *index = leftover_start = bufp - buf;\r
1772             *matchp = NULLCHAR;\r
1773             return TRUE;\r
1774         }\r
1775         if (*bufp == NULLCHAR) return FALSE;\r
1776         if (*patternp == '*') {\r
1777             if (*bufp == *(patternp + 1)) {\r
1778                 *matchp = NULLCHAR;\r
1779                 matchp = star_match[++star_count];\r
1780                 patternp += 2;\r
1781                 bufp++;\r
1782                 continue;\r
1783             } else if (*bufp == '\n' || *bufp == '\r') {\r
1784                 patternp++;\r
1785                 if (*patternp == NULLCHAR)\r
1786                   continue;\r
1787                 else\r
1788                   return FALSE;\r
1789             } else {\r
1790                 *matchp++ = *bufp++;\r
1791                 continue;\r
1792             }\r
1793         }\r
1794         if (*patternp != *bufp) return FALSE;\r
1795         patternp++;\r
1796         bufp++;\r
1797     }\r
1798 }\r
1799 \r
1800 void\r
1801 SendToPlayer(data, length)\r
1802      char *data;\r
1803      int length;\r
1804 {\r
1805     int error, outCount;\r
1806     outCount = OutputToProcess(NoProc, data, length, &error);\r
1807     if (outCount < length) {\r
1808         DisplayFatalError(_("Error writing to display"), error, 1);\r
1809     }\r
1810 }\r
1811 \r
1812 void\r
1813 PackHolding(packed, holding)\r
1814      char packed[];\r
1815      char *holding;\r
1816 {\r
1817     char *p = holding;\r
1818     char *q = packed;\r
1819     int runlength = 0;\r
1820     int curr = 9999;\r
1821     do {\r
1822         if (*p == curr) {\r
1823             runlength++;\r
1824         } else {\r
1825             switch (runlength) {\r
1826               case 0:\r
1827                 break;\r
1828               case 1:\r
1829                 *q++ = curr;\r
1830                 break;\r
1831               case 2:\r
1832                 *q++ = curr;\r
1833                 *q++ = curr;\r
1834                 break;\r
1835               default:\r
1836                 sprintf(q, "%d", runlength);\r
1837                 while (*q) q++;\r
1838                 *q++ = curr;\r
1839                 break;\r
1840             }\r
1841             runlength = 1;\r
1842             curr = *p;\r
1843         }\r
1844     } while (*p++);\r
1845     *q = NULLCHAR;\r
1846 }\r
1847 \r
1848 /* Telnet protocol requests from the front end */\r
1849 void\r
1850 TelnetRequest(ddww, option)\r
1851      unsigned char ddww, option;\r
1852 {\r
1853     unsigned char msg[3];\r
1854     int outCount, outError;\r
1855 \r
1856     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1857 \r
1858     if (appData.debugMode) {\r
1859         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1860         switch (ddww) {\r
1861           case TN_DO:\r
1862             ddwwStr = "DO";\r
1863             break;\r
1864           case TN_DONT:\r
1865             ddwwStr = "DONT";\r
1866             break;\r
1867           case TN_WILL:\r
1868             ddwwStr = "WILL";\r
1869             break;\r
1870           case TN_WONT:\r
1871             ddwwStr = "WONT";\r
1872             break;\r
1873           default:\r
1874             ddwwStr = buf1;\r
1875             sprintf(buf1, "%d", ddww);\r
1876             break;\r
1877         }\r
1878         switch (option) {\r
1879           case TN_ECHO:\r
1880             optionStr = "ECHO";\r
1881             break;\r
1882           default:\r
1883             optionStr = buf2;\r
1884             sprintf(buf2, "%d", option);\r
1885             break;\r
1886         }\r
1887         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1888     }\r
1889     msg[0] = TN_IAC;\r
1890     msg[1] = ddww;\r
1891     msg[2] = option;\r
1892     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1893     if (outCount < 3) {\r
1894         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1895     }\r
1896 }\r
1897 \r
1898 void\r
1899 DoEcho()\r
1900 {\r
1901     if (!appData.icsActive) return;\r
1902     TelnetRequest(TN_DO, TN_ECHO);\r
1903 }\r
1904 \r
1905 void\r
1906 DontEcho()\r
1907 {\r
1908     if (!appData.icsActive) return;\r
1909     TelnetRequest(TN_DONT, TN_ECHO);\r
1910 }\r
1911 \r
1912 void\r
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
1914 {\r
1915     /* put the holdings sent to us by the server on the board holdings area */\r
1916     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
1917     char p;\r
1918     ChessSquare piece;\r
1919 \r
1920     if(gameInfo.holdingsWidth < 2)  return;\r
1921 \r
1922     if( (int)lowestPiece >= BlackPawn ) {\r
1923         holdingsColumn = 0;\r
1924         countsColumn = 1;\r
1925         holdingsStartRow = BOARD_HEIGHT-1;\r
1926         direction = -1;\r
1927     } else {\r
1928         holdingsColumn = BOARD_WIDTH-1;\r
1929         countsColumn = BOARD_WIDTH-2;\r
1930         holdingsStartRow = 0;\r
1931         direction = 1;\r
1932     }\r
1933 \r
1934     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
1935         board[i][holdingsColumn] = EmptySquare;\r
1936         board[i][countsColumn]   = (ChessSquare) 0;\r
1937     }\r
1938     while( (p=*holdings++) != NULLCHAR ) {\r
1939         piece = CharToPiece( ToUpper(p) );\r
1940         if(piece == EmptySquare) continue;\r
1941         /*j = (int) piece - (int) WhitePawn;*/\r
1942         j = PieceToNumber(piece);\r
1943         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1944         if(j < 0) continue;               /* should not happen */\r
1945         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );\r
1946         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
1947         board[holdingsStartRow+j*direction][countsColumn]++;\r
1948     }\r
1949 \r
1950 }\r
1951 \r
1952 \r
1953 void\r
1954 VariantSwitch(Board board, VariantClass newVariant)\r
1955 {\r
1956    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;\r
1957    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;\r
1958 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;\r
1959 \r
1960    startedFromPositionFile = FALSE;\r
1961    if(gameInfo.variant == newVariant) return;\r
1962 \r
1963    /* [HGM] This routine is called each time an assignment is made to\r
1964     * gameInfo.variant during a game, to make sure the board sizes\r
1965     * are set to match the new variant. If that means adding or deleting\r
1966     * holdings, we shift the playing board accordingly\r
1967     * This kludge is needed because in ICS observe mode, we get boards\r
1968     * of an ongoing game without knowing the variant, and learn about the\r
1969     * latter only later. This can be because of the move list we requested,\r
1970     * in which case the game history is refilled from the beginning anyway,\r
1971     * but also when receiving holdings of a crazyhouse game. In the latter\r
1972     * case we want to add those holdings to the already received position.\r
1973     */\r
1974 \r
1975 \r
1976   if (appData.debugMode) {\r
1977     fprintf(debugFP, "Switch board from %s to %s\n",\r
1978                VariantName(gameInfo.variant), VariantName(newVariant));\r
1979     setbuf(debugFP, NULL);\r
1980   }\r
1981     shuffleOpenings = 0;       /* [HGM] shuffle */\r
1982     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
1983     switch(newVariant) {\r
1984             case VariantShogi:\r
1985               newWidth = 9;  newHeight = 9;\r
1986               gameInfo.holdingsSize = 7;\r
1987             case VariantBughouse:\r
1988             case VariantCrazyhouse:\r
1989               newHoldingsWidth = 2; break;\r
1990             default:\r
1991               newHoldingsWidth = gameInfo.holdingsSize = 0;\r
1992     }\r
1993 \r
1994     if(newWidth  != gameInfo.boardWidth  ||\r
1995        newHeight != gameInfo.boardHeight ||\r
1996        newHoldingsWidth != gameInfo.holdingsWidth ) {\r
1997 \r
1998         /* shift position to new playing area, if needed */\r
1999         if(newHoldingsWidth > gameInfo.holdingsWidth) {\r
2000            for(i=0; i<BOARD_HEIGHT; i++) \r
2001                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)\r
2002                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
2003                                                      board[i][j];\r
2004            for(i=0; i<newHeight; i++) {\r
2005                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;\r
2006                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;\r
2007            }\r
2008         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {\r
2009            for(i=0; i<BOARD_HEIGHT; i++)\r
2010                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
2011                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
2012                                                  board[i][j];\r
2013         }\r
2014 \r
2015         gameInfo.boardWidth  = newWidth;\r
2016         gameInfo.boardHeight = newHeight;\r
2017         gameInfo.holdingsWidth = newHoldingsWidth;\r
2018         gameInfo.variant = newVariant;\r
2019         InitDrawingSizes(-2, 0);\r
2020 \r
2021         /* [HGM] The following should definitely be solved in a better way */\r
2022 #if 0\r
2023         CopyBoard(board, tempBoard); /* save position in case it is board[0] */\r
2024         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];\r
2025         saveEP = epStatus[0];\r
2026 #endif\r
2027         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */\r
2028 #if 0\r
2029         epStatus[0] = saveEP;\r
2030         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];\r
2031         CopyBoard(tempBoard, board); /* restore position received from ICS   */\r
2032 #endif\r
2033     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }\r
2034 \r
2035     forwardMostMove = oldForwardMostMove;\r
2036     backwardMostMove = oldBackwardMostMove;\r
2037     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */\r
2038 }\r
2039 \r
2040 static int loggedOn = FALSE;\r
2041 \r
2042 /*-- Game start info cache: --*/\r
2043 int gs_gamenum;\r
2044 char gs_kind[MSG_SIZ];\r
2045 static char player1Name[128] = "";\r
2046 static char player2Name[128] = "";\r
2047 static int player1Rating = -1;\r
2048 static int player2Rating = -1;\r
2049 /*----------------------------*/\r
2050 \r
2051 ColorClass curColor = ColorNormal;\r
2052 int suppressKibitz = 0;\r
2053 \r
2054 void\r
2055 read_from_ics(isr, closure, data, count, error)\r
2056      InputSourceRef isr;\r
2057      VOIDSTAR closure;\r
2058      char *data;\r
2059      int count;\r
2060      int error;\r
2061 {\r
2062 #define BUF_SIZE 8192\r
2063 #define STARTED_NONE 0\r
2064 #define STARTED_MOVES 1\r
2065 #define STARTED_BOARD 2\r
2066 #define STARTED_OBSERVE 3\r
2067 #define STARTED_HOLDINGS 4\r
2068 #define STARTED_CHATTER 5\r
2069 #define STARTED_COMMENT 6\r
2070 #define STARTED_MOVES_NOHIDE 7\r
2071     \r
2072     static int started = STARTED_NONE;\r
2073     static char parse[20000];\r
2074     static int parse_pos = 0;\r
2075     static char buf[BUF_SIZE + 1];\r
2076     static int firstTime = TRUE, intfSet = FALSE;\r
2077     static ColorClass prevColor = ColorNormal;\r
2078     static int savingComment = FALSE;\r
2079     char str[500];\r
2080     int i, oldi;\r
2081     int buf_len;\r
2082     int next_out;\r
2083     int tkind;\r
2084     int backup;    /* [DM] For zippy color lines */\r
2085     char *p;\r
2086 \r
2087     if (appData.debugMode) {\r
2088       if (!error) {\r
2089         fprintf(debugFP, "<ICS: ");\r
2090         show_bytes(debugFP, data, count);\r
2091         fprintf(debugFP, "\n");\r
2092       }\r
2093     }\r
2094 \r
2095     if (appData.debugMode) { int f = forwardMostMove;\r
2096         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,\r
2097                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
2098     }\r
2099     if (count > 0) {\r
2100         /* If last read ended with a partial line that we couldn't parse,\r
2101            prepend it to the new read and try again. */\r
2102         if (leftover_len > 0) {\r
2103             for (i=0; i<leftover_len; i++)\r
2104               buf[i] = buf[leftover_start + i];\r
2105         }\r
2106 \r
2107         /* Copy in new characters, removing nulls and \r's */\r
2108         buf_len = leftover_len;\r
2109         for (i = 0; i < count; i++) {\r
2110             if (data[i] != NULLCHAR && data[i] != '\r')\r
2111               buf[buf_len++] = data[i];\r
2112             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && \r
2113                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') \r
2114                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous\r
2115         }\r
2116 \r
2117         buf[buf_len] = NULLCHAR;\r
2118         next_out = leftover_len;\r
2119         leftover_start = 0;\r
2120         \r
2121         i = 0;\r
2122         while (i < buf_len) {\r
2123             /* Deal with part of the TELNET option negotiation\r
2124                protocol.  We refuse to do anything beyond the\r
2125                defaults, except that we allow the WILL ECHO option,\r
2126                which ICS uses to turn off password echoing when we are\r
2127                directly connected to it.  We reject this option\r
2128                if localLineEditing mode is on (always on in xboard)\r
2129                and we are talking to port 23, which might be a real\r
2130                telnet server that will try to keep WILL ECHO on permanently.\r
2131              */\r
2132             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
2133                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
2134                 unsigned char option;\r
2135                 oldi = i;\r
2136                 switch ((unsigned char) buf[++i]) {\r
2137                   case TN_WILL:\r
2138                     if (appData.debugMode)\r
2139                       fprintf(debugFP, "\n<WILL ");\r
2140                     switch (option = (unsigned char) buf[++i]) {\r
2141                       case TN_ECHO:\r
2142                         if (appData.debugMode)\r
2143                           fprintf(debugFP, "ECHO ");\r
2144                         /* Reply only if this is a change, according\r
2145                            to the protocol rules. */\r
2146                         if (remoteEchoOption) break;\r
2147                         if (appData.localLineEditing &&\r
2148                             atoi(appData.icsPort) == TN_PORT) {\r
2149                             TelnetRequest(TN_DONT, TN_ECHO);\r
2150                         } else {\r
2151                             EchoOff();\r
2152                             TelnetRequest(TN_DO, TN_ECHO);\r
2153                             remoteEchoOption = TRUE;\r
2154                         }\r
2155                         break;\r
2156                       default:\r
2157                         if (appData.debugMode)\r
2158                           fprintf(debugFP, "%d ", option);\r
2159                         /* Whatever this is, we don't want it. */\r
2160                         TelnetRequest(TN_DONT, option);\r
2161                         break;\r
2162                     }\r
2163                     break;\r
2164                   case TN_WONT:\r
2165                     if (appData.debugMode)\r
2166                       fprintf(debugFP, "\n<WONT ");\r
2167                     switch (option = (unsigned char) buf[++i]) {\r
2168                       case TN_ECHO:\r
2169                         if (appData.debugMode)\r
2170                           fprintf(debugFP, "ECHO ");\r
2171                         /* Reply only if this is a change, according\r
2172                            to the protocol rules. */\r
2173                         if (!remoteEchoOption) break;\r
2174                         EchoOn();\r
2175                         TelnetRequest(TN_DONT, TN_ECHO);\r
2176                         remoteEchoOption = FALSE;\r
2177                         break;\r
2178                       default:\r
2179                         if (appData.debugMode)\r
2180                           fprintf(debugFP, "%d ", (unsigned char) option);\r
2181                         /* Whatever this is, it must already be turned\r
2182                            off, because we never agree to turn on\r
2183                            anything non-default, so according to the\r
2184                            protocol rules, we don't reply. */\r
2185                         break;\r
2186                     }\r
2187                     break;\r
2188                   case TN_DO:\r
2189                     if (appData.debugMode)\r
2190                       fprintf(debugFP, "\n<DO ");\r
2191                     switch (option = (unsigned char) buf[++i]) {\r
2192                       default:\r
2193                         /* Whatever this is, we refuse to do it. */\r
2194                         if (appData.debugMode)\r
2195                           fprintf(debugFP, "%d ", option);\r
2196                         TelnetRequest(TN_WONT, option);\r
2197                         break;\r
2198                     }\r
2199                     break;\r
2200                   case TN_DONT:\r
2201                     if (appData.debugMode)\r
2202                       fprintf(debugFP, "\n<DONT ");\r
2203                     switch (option = (unsigned char) buf[++i]) {\r
2204                       default:\r
2205                         if (appData.debugMode)\r
2206                           fprintf(debugFP, "%d ", option);\r
2207                         /* Whatever this is, we are already not doing\r
2208                            it, because we never agree to do anything\r
2209                            non-default, so according to the protocol\r
2210                            rules, we don't reply. */\r
2211                         break;\r
2212                     }\r
2213                     break;\r
2214                   case TN_IAC:\r
2215                     if (appData.debugMode)\r
2216                       fprintf(debugFP, "\n<IAC ");\r
2217                     /* Doubled IAC; pass it through */\r
2218                     i--;\r
2219                     break;\r
2220                   default:\r
2221                     if (appData.debugMode)\r
2222                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
2223                     /* Drop all other telnet commands on the floor */\r
2224                     break;\r
2225                 }\r
2226                 if (oldi > next_out)\r
2227                   SendToPlayer(&buf[next_out], oldi - next_out);\r
2228                 if (++i > next_out)\r
2229                   next_out = i;\r
2230                 continue;\r
2231             }\r
2232                 \r
2233             /* OK, this at least will *usually* work */\r
2234             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
2235                 loggedOn = TRUE;\r
2236             }\r
2237             \r
2238             if (loggedOn && !intfSet) {\r
2239                 if (ics_type == ICS_ICC) {\r
2240                   sprintf(str,\r
2241                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
2242                           programVersion);\r
2243 \r
2244                 } else if (ics_type == ICS_CHESSNET) {\r
2245                   sprintf(str, "/style 12\n");\r
2246                 } else {\r
2247                   strcpy(str, "alias $ @\n$set interface ");\r
2248                   strcat(str, programVersion);\r
2249                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
2250 #ifdef WIN32\r
2251                   strcat(str, "$iset nohighlight 1\n");\r
2252 #endif\r
2253                   strcat(str, "$iset lock 1\n$style 12\n");\r
2254                 }\r
2255                 SendToICS(str);\r
2256                 intfSet = TRUE;\r
2257             }\r
2258 \r
2259             if (started == STARTED_COMMENT) {\r
2260                 /* Accumulate characters in comment */\r
2261                 parse[parse_pos++] = buf[i];\r
2262                 if (buf[i] == '\n') {\r
2263                     parse[parse_pos] = NULLCHAR;\r
2264                     if(!suppressKibitz) // [HGM] kibitz\r
2265                         AppendComment(forwardMostMove, StripHighlight(parse));\r
2266                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window\r
2267                         int nrDigit = 0, nrAlph = 0, i;\r
2268                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input\r
2269                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }\r
2270                         parse[parse_pos] = NULLCHAR;\r
2271                         // try to be smart: if it does not look like search info, it should go to\r
2272                         // ICS interaction window after all, not to engine-output window.\r
2273                         for(i=0; i<parse_pos; i++) { // count letters and digits\r
2274                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');\r
2275                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');\r
2276                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');\r
2277                         }\r
2278                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info\r
2279                             OutputKibitz(suppressKibitz, parse);\r
2280                         } else {\r
2281                             char tmp[MSG_SIZ];\r
2282                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);\r
2283                             SendToPlayer(tmp, strlen(tmp));\r
2284                         }\r
2285                     }\r
2286                     started = STARTED_NONE;\r
2287                 } else {\r
2288                     /* Don't match patterns against characters in chatter */\r
2289                     i++;\r
2290                     continue;\r
2291                 }\r
2292             }\r
2293             if (started == STARTED_CHATTER) {\r
2294                 if (buf[i] != '\n') {\r
2295                     /* Don't match patterns against characters in chatter */\r
2296                     i++;\r
2297                     continue;\r
2298                 }\r
2299                 started = STARTED_NONE;\r
2300             }\r
2301 \r
2302             /* Kludge to deal with rcmd protocol */\r
2303             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2304                 DisplayFatalError(&buf[1], 0, 1);\r
2305                 continue;\r
2306             } else {\r
2307                 firstTime = FALSE;\r
2308             }\r
2309 \r
2310             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2311                 ics_type = ICS_ICC;\r
2312                 ics_prefix = "/";\r
2313                 if (appData.debugMode)\r
2314                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2315                 continue;\r
2316             }\r
2317             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2318                 ics_type = ICS_FICS;\r
2319                 ics_prefix = "$";\r
2320                 if (appData.debugMode)\r
2321                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2322                 continue;\r
2323             }\r
2324             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2325                 ics_type = ICS_CHESSNET;\r
2326                 ics_prefix = "/";\r
2327                 if (appData.debugMode)\r
2328                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2329                 continue;\r
2330             }\r
2331 \r
2332             if (!loggedOn &&\r
2333                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2334                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2335                  looking_at(buf, &i, "will be \"*\""))) {\r
2336               strcpy(ics_handle, star_match[0]);\r
2337               continue;\r
2338             }\r
2339 \r
2340             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2341               char buf[MSG_SIZ];\r
2342               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2343               DisplayIcsInteractionTitle(buf);\r
2344               have_set_title = TRUE;\r
2345             }\r
2346 \r
2347             /* skip finger notes */\r
2348             if (started == STARTED_NONE &&\r
2349                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2350                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2351                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2352               started = STARTED_CHATTER;\r
2353               i += 3;\r
2354               continue;\r
2355             }\r
2356 \r
2357             /* skip formula vars */\r
2358             if (started == STARTED_NONE &&\r
2359                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2360               started = STARTED_CHATTER;\r
2361               i += 3;\r
2362               continue;\r
2363             }\r
2364 \r
2365             oldi = i;\r
2366             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window\r
2367             if (appData.autoKibitz && started == STARTED_NONE && \r
2368                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze\r
2369                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {\r
2370                 if(looking_at(buf, &i, "* kibitzes: ") &&\r
2371                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || \r
2372                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent\r
2373                         suppressKibitz = TRUE;\r
2374                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]\r
2375                                 && (gameMode == IcsPlayingWhite)) ||\r
2376                            (StrStr(star_match[0], gameInfo.black) == star_match[0]\r
2377                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz\r
2378                             started = STARTED_CHATTER; // own kibitz we simply discard\r
2379                         else {\r
2380                             started = STARTED_COMMENT; // make sure it will be collected in parse[]\r
2381                             parse_pos = 0; parse[0] = NULLCHAR;\r
2382                             savingComment = TRUE;\r
2383                             suppressKibitz = gameMode != IcsObserving ? 2 :\r
2384                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;\r
2385                         } \r
2386                         continue;\r
2387                 } else\r
2388                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz\r
2389                     started = STARTED_CHATTER;\r
2390                     suppressKibitz = TRUE;\r
2391                 }\r
2392             } // [HGM] kibitz: end of patch\r
2393 \r
2394             if (appData.zippyTalk || appData.zippyPlay) {\r
2395                 /* [DM] Backup address for color zippy lines */\r
2396                 backup = i;\r
2397 #if ZIPPY\r
2398        #ifdef WIN32\r
2399                if (loggedOn == TRUE)\r
2400                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||\r
2401                           (appData.zippyPlay && ZippyMatch(buf, &backup)));\r
2402        #else\r
2403                 if (ZippyControl(buf, &i) ||\r
2404                     ZippyConverse(buf, &i) ||\r
2405                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2406                       loggedOn = TRUE;\r
2407                       if (!appData.colorize) continue;\r
2408                 }\r
2409        #endif\r
2410 #endif\r
2411             } // [DM] 'else { ' deleted\r
2412                 if (/* Don't color "message" or "messages" output */\r
2413                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2414                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2415                     looking_at(buf, &i, "--* (*:*): ") ||\r
2416                     /* Regular tells and says */\r
2417                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2418                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2419                     looking_at(buf, &i, "* says: ") ||\r
2420                     /* Message notifications (same color as tells) */\r
2421                     looking_at(buf, &i, "* has left a message ") ||\r
2422                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2423                     /* Whispers and kibitzes */\r
2424                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2425                     looking_at(buf, &i, "* kibitzes: ") ||\r
2426                     /* Channel tells */\r
2427                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2428 \r
2429                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2430                       /* Avoid "tells you:" spoofs in channels */\r
2431                      tkind = 3;\r
2432                   }\r
2433                   if (star_match[0][0] == NULLCHAR ||\r
2434                       strchr(star_match[0], ' ') ||\r
2435                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2436                     /* Reject bogus matches */\r
2437                     i = oldi;\r
2438                   } else {\r
2439                     if (appData.colorize) {\r
2440                       if (oldi > next_out) {\r
2441                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2442                         next_out = oldi;\r
2443                       }\r
2444                       switch (tkind) {\r
2445                       case 1:\r
2446                         Colorize(ColorTell, FALSE);\r
2447                         curColor = ColorTell;\r
2448                         break;\r
2449                       case 2:\r
2450                         Colorize(ColorKibitz, FALSE);\r
2451                         curColor = ColorKibitz;\r
2452                         break;\r
2453                       case 3:\r
2454                         p = strrchr(star_match[1], '(');\r
2455                         if (p == NULL) {\r
2456                           p = star_match[1];\r
2457                         } else {\r
2458                           p++;\r
2459                         }\r
2460                         if (atoi(p) == 1) {\r
2461                           Colorize(ColorChannel1, FALSE);\r
2462                           curColor = ColorChannel1;\r
2463                         } else {\r
2464                           Colorize(ColorChannel, FALSE);\r
2465                           curColor = ColorChannel;\r
2466                         }\r
2467                         break;\r
2468                       case 5:\r
2469                         curColor = ColorNormal;\r
2470                         break;\r
2471                       }\r
2472                     }\r
2473                     if (started == STARTED_NONE && appData.autoComment &&\r
2474                         (gameMode == IcsObserving ||\r
2475                          gameMode == IcsPlayingWhite ||\r
2476                          gameMode == IcsPlayingBlack)) {\r
2477                       parse_pos = i - oldi;\r
2478                       memcpy(parse, &buf[oldi], parse_pos);\r
2479                       parse[parse_pos] = NULLCHAR;\r
2480                       started = STARTED_COMMENT;\r
2481                       savingComment = TRUE;\r
2482                     } else {\r
2483                       started = STARTED_CHATTER;\r
2484                       savingComment = FALSE;\r
2485                     }\r
2486                     loggedOn = TRUE;\r
2487                     continue;\r
2488                   }\r
2489                 }\r
2490 \r
2491                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2492                     looking_at(buf, &i, "* c-shouts: ")) {\r
2493                     if (appData.colorize) {\r
2494                         if (oldi > next_out) {\r
2495                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2496                             next_out = oldi;\r
2497                         }\r
2498                         Colorize(ColorSShout, FALSE);\r
2499                         curColor = ColorSShout;\r
2500                     }\r
2501                     loggedOn = TRUE;\r
2502                     started = STARTED_CHATTER;\r
2503                     continue;\r
2504                 }\r
2505 \r
2506                 if (looking_at(buf, &i, "--->")) {\r
2507                     loggedOn = TRUE;\r
2508                     continue;\r
2509                 }\r
2510 \r
2511                 if (looking_at(buf, &i, "* shouts: ") ||\r
2512                     looking_at(buf, &i, "--> ")) {\r
2513                     if (appData.colorize) {\r
2514                         if (oldi > next_out) {\r
2515                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2516                             next_out = oldi;\r
2517                         }\r
2518                         Colorize(ColorShout, FALSE);\r
2519                         curColor = ColorShout;\r
2520                     }\r
2521                     loggedOn = TRUE;\r
2522                     started = STARTED_CHATTER;\r
2523                     continue;\r
2524                 }\r
2525 \r
2526                 if (looking_at( buf, &i, "Challenge:")) {\r
2527                     if (appData.colorize) {\r
2528                         if (oldi > next_out) {\r
2529                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2530                             next_out = oldi;\r
2531                         }\r
2532                         Colorize(ColorChallenge, FALSE);\r
2533                         curColor = ColorChallenge;\r
2534                     }\r
2535                     loggedOn = TRUE;\r
2536                     continue;\r
2537                 }\r
2538 \r
2539                 if (looking_at(buf, &i, "* offers you") ||\r
2540                     looking_at(buf, &i, "* offers to be") ||\r
2541                     looking_at(buf, &i, "* would like to") ||\r
2542                     looking_at(buf, &i, "* requests to") ||\r
2543                     looking_at(buf, &i, "Your opponent offers") ||\r
2544                     looking_at(buf, &i, "Your opponent requests")) {\r
2545 \r
2546                     if (appData.colorize) {\r
2547                         if (oldi > next_out) {\r
2548                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2549                             next_out = oldi;\r
2550                         }\r
2551                         Colorize(ColorRequest, FALSE);\r
2552                         curColor = ColorRequest;\r
2553                     }\r
2554                     continue;\r
2555                 }\r
2556 \r
2557                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2558                     if (appData.colorize) {\r
2559                         if (oldi > next_out) {\r
2560                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2561                             next_out = oldi;\r
2562                         }\r
2563                         Colorize(ColorSeek, FALSE);\r
2564                         curColor = ColorSeek;\r
2565                     }\r
2566                     continue;\r
2567             }\r
2568 \r
2569             if (looking_at(buf, &i, "\\   ")) {\r
2570                 if (prevColor != ColorNormal) {\r
2571                     if (oldi > next_out) {\r
2572                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2573                         next_out = oldi;\r
2574                     }\r
2575                     Colorize(prevColor, TRUE);\r
2576                     curColor = prevColor;\r
2577                 }\r
2578                 if (savingComment) {\r
2579                     parse_pos = i - oldi;\r
2580                     memcpy(parse, &buf[oldi], parse_pos);\r
2581                     parse[parse_pos] = NULLCHAR;\r
2582                     started = STARTED_COMMENT;\r
2583                 } else {\r
2584                     started = STARTED_CHATTER;\r
2585                 }\r
2586                 continue;\r
2587             }\r
2588 \r
2589             if (looking_at(buf, &i, "Black Strength :") ||\r
2590                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2591                 looking_at(buf, &i, "<10>") ||\r
2592                 looking_at(buf, &i, "#@#")) {\r
2593                 /* Wrong board style */\r
2594                 loggedOn = TRUE;\r
2595                 SendToICS(ics_prefix);\r
2596                 SendToICS("set style 12\n");\r
2597                 SendToICS(ics_prefix);\r
2598                 SendToICS("refresh\n");\r
2599                 continue;\r
2600             }\r
2601             \r
2602             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2603                 ICSInitScript();\r
2604                 have_sent_ICS_logon = 1;\r
2605                 continue;\r
2606             }\r
2607               \r
2608             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2609                 (looking_at(buf, &i, "\n<12> ") ||\r
2610                  looking_at(buf, &i, "<12> "))) {\r
2611                 loggedOn = TRUE;\r
2612                 if (oldi > next_out) {\r
2613                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2614                 }\r
2615                 next_out = i;\r
2616                 started = STARTED_BOARD;\r
2617                 parse_pos = 0;\r
2618                 continue;\r
2619             }\r
2620 \r
2621             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2622                 looking_at(buf, &i, "<b1> ")) {\r
2623                 if (oldi > next_out) {\r
2624                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2625                 }\r
2626                 next_out = i;\r
2627                 started = STARTED_HOLDINGS;\r
2628                 parse_pos = 0;\r
2629                 continue;\r
2630             }\r
2631 \r
2632             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2633                 loggedOn = TRUE;\r
2634                 /* Header for a move list -- first line */\r
2635 \r
2636                 switch (ics_getting_history) {\r
2637                   case H_FALSE:\r
2638                     switch (gameMode) {\r
2639                       case IcsIdle:\r
2640                       case BeginningOfGame:\r
2641                         /* User typed "moves" or "oldmoves" while we\r
2642                            were idle.  Pretend we asked for these\r
2643                            moves and soak them up so user can step\r
2644                            through them and/or save them.\r
2645                            */\r
2646                         Reset(FALSE, TRUE);\r
2647                         gameMode = IcsObserving;\r
2648                         ModeHighlight();\r
2649                         ics_gamenum = -1;\r
2650                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2651                         break;\r
2652                       case EditGame: /*?*/\r
2653                       case EditPosition: /*?*/\r
2654                         /* Should above feature work in these modes too? */\r
2655                         /* For now it doesn't */\r
2656                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2657                         break;\r
2658                       default:\r
2659                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2660                         break;\r
2661                     }\r
2662                     break;\r
2663                   case H_REQUESTED:\r
2664                     /* Is this the right one? */\r
2665                     if (gameInfo.white && gameInfo.black &&\r
2666                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2667                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2668                         /* All is well */\r
2669                         ics_getting_history = H_GOT_REQ_HEADER;\r
2670                     }\r
2671                     break;\r
2672                   case H_GOT_REQ_HEADER:\r
2673                   case H_GOT_UNREQ_HEADER:\r
2674                   case H_GOT_UNWANTED_HEADER:\r
2675                   case H_GETTING_MOVES:\r
2676                     /* Should not happen */\r
2677                     DisplayError(_("Error gathering move list: two headers"), 0);\r
2678                     ics_getting_history = H_FALSE;\r
2679                     break;\r
2680                 }\r
2681 \r
2682                 /* Save player ratings into gameInfo if needed */\r
2683                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2684                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2685                     (gameInfo.whiteRating == -1 ||\r
2686                      gameInfo.blackRating == -1)) {\r
2687 \r
2688                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2689                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2690                     if (appData.debugMode)\r
2691                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), \r
2692                               gameInfo.whiteRating, gameInfo.blackRating);\r
2693                 }\r
2694                 continue;\r
2695             }\r
2696 \r
2697             if (looking_at(buf, &i,\r
2698               "* * match, initial time: * minute*, increment: * second")) {\r
2699                 /* Header for a move list -- second line */\r
2700                 /* Initial board will follow if this is a wild game */\r
2701                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2702                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2703                 gameInfo.event = StrSave(str);\r
2704                 /* [HGM] we switched variant. Translate boards if needed. */\r
2705                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2706                 continue;\r
2707             }\r
2708 \r
2709             if (looking_at(buf, &i, "Move  ")) {\r
2710                 /* Beginning of a move list */\r
2711                 switch (ics_getting_history) {\r
2712                   case H_FALSE:\r
2713                     /* Normally should not happen */\r
2714                     /* Maybe user hit reset while we were parsing */\r
2715                     break;\r
2716                   case H_REQUESTED:\r
2717                     /* Happens if we are ignoring a move list that is not\r
2718                      * the one we just requested.  Common if the user\r
2719                      * tries to observe two games without turning off\r
2720                      * getMoveList */\r
2721                     break;\r
2722                   case H_GETTING_MOVES:\r
2723                     /* Should not happen */\r
2724                     DisplayError(_("Error gathering move list: nested"), 0);\r
2725                     ics_getting_history = H_FALSE;\r
2726                     break;\r
2727                   case H_GOT_REQ_HEADER:\r
2728                     ics_getting_history = H_GETTING_MOVES;\r
2729                     started = STARTED_MOVES;\r
2730                     parse_pos = 0;\r
2731                     if (oldi > next_out) {\r
2732                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2733                     }\r
2734                     break;\r
2735                   case H_GOT_UNREQ_HEADER:\r
2736                     ics_getting_history = H_GETTING_MOVES;\r
2737                     started = STARTED_MOVES_NOHIDE;\r
2738                     parse_pos = 0;\r
2739                     break;\r
2740                   case H_GOT_UNWANTED_HEADER:\r
2741                     ics_getting_history = H_FALSE;\r
2742                     break;\r
2743                 }\r
2744                 continue;\r
2745             }                           \r
2746             \r
2747             if (looking_at(buf, &i, "% ") ||\r
2748                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2749                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book\r
2750                 savingComment = FALSE;\r
2751                 switch (started) {\r
2752                   case STARTED_MOVES:\r
2753                   case STARTED_MOVES_NOHIDE:\r
2754                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2755                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2756                     ParseGameHistory(parse);\r
2757 #if ZIPPY\r
2758                     if (appData.zippyPlay && first.initDone) {\r
2759                         FeedMovesToProgram(&first, forwardMostMove);\r
2760                         if (gameMode == IcsPlayingWhite) {\r
2761                             if (WhiteOnMove(forwardMostMove)) {\r
2762                                 if (first.sendTime) {\r
2763                                   if (first.useColors) {\r
2764                                     SendToProgram("black\n", &first); \r
2765                                   }\r
2766                                   SendTimeRemaining(&first, TRUE);\r
2767                                 }\r
2768 #if 0\r
2769                                 if (first.useColors) {\r
2770                                   SendToProgram("white\ngo\n", &first);\r
2771                                 } else {\r
2772                                   SendToProgram("go\n", &first);\r
2773                                 }\r
2774 #else\r
2775                                 if (first.useColors) {\r
2776                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent\r
2777                                 }\r
2778                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos\r
2779 #endif\r
2780                                 first.maybeThinking = TRUE;\r
2781                             } else {\r
2782                                 if (first.usePlayother) {\r
2783                                   if (first.sendTime) {\r
2784                                     SendTimeRemaining(&first, TRUE);\r
2785                                   }\r
2786                                   SendToProgram("playother\n", &first);\r
2787                                   firstMove = FALSE;\r
2788                                 } else {\r
2789                                   firstMove = TRUE;\r
2790                                 }\r
2791                             }\r
2792                         } else if (gameMode == IcsPlayingBlack) {\r
2793                             if (!WhiteOnMove(forwardMostMove)) {\r
2794                                 if (first.sendTime) {\r
2795                                   if (first.useColors) {\r
2796                                     SendToProgram("white\n", &first);\r
2797                                   }\r
2798                                   SendTimeRemaining(&first, FALSE);\r
2799                                 }\r
2800 #if 0\r
2801                                 if (first.useColors) {\r
2802                                   SendToProgram("black\ngo\n", &first);\r
2803                                 } else {\r
2804                                   SendToProgram("go\n", &first);\r
2805                                 }\r
2806 #else\r
2807                                 if (first.useColors) {\r
2808                                   SendToProgram("black\n", &first);\r
2809                                 }\r
2810                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);\r
2811 #endif\r
2812                                 first.maybeThinking = TRUE;\r
2813                             } else {\r
2814                                 if (first.usePlayother) {\r
2815                                   if (first.sendTime) {\r
2816                                     SendTimeRemaining(&first, FALSE);\r
2817                                   }\r
2818                                   SendToProgram("playother\n", &first);\r
2819                                   firstMove = FALSE;\r
2820                                 } else {\r
2821                                   firstMove = TRUE;\r
2822                                 }\r
2823                             }\r
2824                         }                       \r
2825                     }\r
2826 #endif\r
2827                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2828                         /* Moves came from oldmoves or moves command\r
2829                            while we weren't doing anything else.\r
2830                            */\r
2831                         currentMove = forwardMostMove;\r
2832                         ClearHighlights();/*!!could figure this out*/\r
2833                         flipView = appData.flipView;\r
2834                         DrawPosition(FALSE, boards[currentMove]);\r
2835                         DisplayBothClocks();\r
2836                         sprintf(str, "%s vs. %s",\r
2837                                 gameInfo.white, gameInfo.black);\r
2838                         DisplayTitle(str);\r
2839                         gameMode = IcsIdle;\r
2840                     } else {\r
2841                         /* Moves were history of an active game */\r
2842                         if (gameInfo.resultDetails != NULL) {\r
2843                             free(gameInfo.resultDetails);\r
2844                             gameInfo.resultDetails = NULL;\r
2845                         }\r
2846                     }\r
2847                     HistorySet(parseList, backwardMostMove,\r
2848                                forwardMostMove, currentMove-1);\r
2849                     DisplayMove(currentMove - 1);\r
2850                     if (started == STARTED_MOVES) next_out = i;\r
2851                     started = STARTED_NONE;\r
2852                     ics_getting_history = H_FALSE;\r
2853                     break;\r
2854 \r
2855                   case STARTED_OBSERVE:\r
2856                     started = STARTED_NONE;\r
2857                     SendToICS(ics_prefix);\r
2858                     SendToICS("refresh\n");\r
2859                     break;\r
2860 \r
2861                   default:\r
2862                     break;\r
2863                 }\r
2864                 if(bookHit) { // [HGM] book: simulate book reply\r
2865                     static char bookMove[MSG_SIZ]; // a bit generous?\r
2866 \r
2867                     programStats.nodes = programStats.depth = programStats.time = \r
2868                     programStats.score = programStats.got_only_move = 0;\r
2869                     sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
2870 \r
2871                     strcpy(bookMove, "move ");\r
2872                     strcat(bookMove, bookHit);\r
2873                     HandleMachineMove(bookMove, &first);\r
2874                 }\r
2875                 continue;\r
2876             }\r
2877             \r
2878             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2879                  started == STARTED_HOLDINGS ||\r
2880                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2881                 /* Accumulate characters in move list or board */\r
2882                 parse[parse_pos++] = buf[i];\r
2883             }\r
2884             \r
2885             /* Start of game messages.  Mostly we detect start of game\r
2886                when the first board image arrives.  On some versions\r
2887                of the ICS, though, we need to do a "refresh" after starting\r
2888                to observe in order to get the current board right away. */\r
2889             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2890                 started = STARTED_OBSERVE;\r
2891                 continue;\r
2892             }\r
2893 \r
2894             /* Handle auto-observe */\r
2895             if (appData.autoObserve &&\r
2896                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2897                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2898                 char *player;\r
2899                 /* Choose the player that was highlighted, if any. */\r
2900                 if (star_match[0][0] == '\033' ||\r
2901                     star_match[1][0] != '\033') {\r
2902                     player = star_match[0];\r
2903                 } else {\r
2904                     player = star_match[2];\r
2905                 }\r
2906                 sprintf(str, "%sobserve %s\n",\r
2907                         ics_prefix, StripHighlightAndTitle(player));\r
2908                 SendToICS(str);\r
2909 \r
2910                 /* Save ratings from notify string */\r
2911                 strcpy(player1Name, star_match[0]);\r
2912                 player1Rating = string_to_rating(star_match[1]);\r
2913                 strcpy(player2Name, star_match[2]);\r
2914                 player2Rating = string_to_rating(star_match[3]);\r
2915 \r
2916                 if (appData.debugMode)\r
2917                   fprintf(debugFP, \r
2918                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2919                           player1Name, player1Rating,\r
2920                           player2Name, player2Rating);\r
2921 \r
2922                 continue;\r
2923             }\r
2924 \r
2925             /* Deal with automatic examine mode after a game,\r
2926                and with IcsObserving -> IcsExamining transition */\r
2927             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2928                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2929 \r
2930                 int gamenum = atoi(star_match[0]);\r
2931                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2932                     gamenum == ics_gamenum) {\r
2933                     /* We were already playing or observing this game;\r
2934                        no need to refetch history */\r
2935                     gameMode = IcsExamining;\r
2936                     if (pausing) {\r
2937                         pauseExamForwardMostMove = forwardMostMove;\r
2938                     } else if (currentMove < forwardMostMove) {\r
2939                         ForwardInner(forwardMostMove);\r
2940                     }\r
2941                 } else {\r
2942                     /* I don't think this case really can happen */\r
2943                     SendToICS(ics_prefix);\r
2944                     SendToICS("refresh\n");\r
2945                 }\r
2946                 continue;\r
2947             }    \r
2948             \r
2949             /* Error messages */\r
2950             if (ics_user_moved) {\r
2951                 if (looking_at(buf, &i, "Illegal move") ||\r
2952                     looking_at(buf, &i, "Not a legal move") ||\r
2953                     looking_at(buf, &i, "Your king is in check") ||\r
2954                     looking_at(buf, &i, "It isn't your turn") ||\r
2955                     looking_at(buf, &i, "It is not your move")) {\r
2956                     /* Illegal move */\r
2957                     ics_user_moved = 0;\r
2958                     if (forwardMostMove > backwardMostMove) {\r
2959                         currentMove = --forwardMostMove;\r
2960                         DisplayMove(currentMove - 1); /* before DMError */\r
2961                         DisplayMoveError(_("Illegal move (rejected by ICS)"));\r
2962                         DrawPosition(FALSE, boards[currentMove]);\r
2963                         SwitchClocks();\r
2964                         DisplayBothClocks();\r
2965                     }\r
2966                     continue;\r
2967                 }\r
2968             }\r
2969 \r
2970             if (looking_at(buf, &i, "still have time") ||\r
2971                 looking_at(buf, &i, "not out of time") ||\r
2972                 looking_at(buf, &i, "either player is out of time") ||\r
2973                 looking_at(buf, &i, "has timeseal; checking")) {\r
2974                 /* We must have called his flag a little too soon */\r
2975                 whiteFlag = blackFlag = FALSE;\r
2976                 continue;\r
2977             }\r
2978 \r
2979             if (looking_at(buf, &i, "added * seconds to") ||\r
2980                 looking_at(buf, &i, "seconds were added to")) {\r
2981                 /* Update the clocks */\r
2982                 SendToICS(ics_prefix);\r
2983                 SendToICS("refresh\n");\r
2984                 continue;\r
2985             }\r
2986 \r
2987             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2988                 ics_clock_paused = TRUE;\r
2989                 StopClocks();\r
2990                 continue;\r
2991             }\r
2992 \r
2993             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2994                 ics_clock_paused = FALSE;\r
2995                 StartClocks();\r
2996                 continue;\r
2997             }\r
2998 \r
2999             /* Grab player ratings from the Creating: message.\r
3000                Note we have to check for the special case when\r
3001                the ICS inserts things like [white] or [black]. */\r
3002             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
3003                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
3004                 /* star_matches:\r
3005                    0    player 1 name (not necessarily white)\r
3006                    1    player 1 rating\r
3007                    2    empty, white, or black (IGNORED)\r
3008                    3    player 2 name (not necessarily black)\r
3009                    4    player 2 rating\r
3010                    \r
3011                    The names/ratings are sorted out when the game\r
3012                    actually starts (below).\r
3013                 */\r
3014                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
3015                 player1Rating = string_to_rating(star_match[1]);\r
3016                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
3017                 player2Rating = string_to_rating(star_match[4]);\r
3018 \r
3019                 if (appData.debugMode)\r
3020                   fprintf(debugFP, \r
3021                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
3022                           player1Name, player1Rating,\r
3023                           player2Name, player2Rating);\r
3024 \r
3025                 continue;\r
3026             }\r
3027             \r
3028             /* Improved generic start/end-of-game messages */\r
3029             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
3030                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
3031                 /* If tkind == 0: */\r
3032                 /* star_match[0] is the game number */\r
3033                 /*           [1] is the white player's name */\r
3034                 /*           [2] is the black player's name */\r
3035                 /* For end-of-game: */\r
3036                 /*           [3] is the reason for the game end */\r
3037                 /*           [4] is a PGN end game-token, preceded by " " */\r
3038                 /* For start-of-game: */\r
3039                 /*           [3] begins with "Creating" or "Continuing" */\r
3040                 /*           [4] is " *" or empty (don't care). */\r
3041                 int gamenum = atoi(star_match[0]);\r
3042                 char *whitename, *blackname, *why, *endtoken;\r
3043                 ChessMove endtype = (ChessMove) 0;\r
3044 \r
3045                 if (tkind == 0) {\r
3046                   whitename = star_match[1];\r
3047                   blackname = star_match[2];\r
3048                   why = star_match[3];\r
3049                   endtoken = star_match[4];\r
3050                 } else {\r
3051                   whitename = star_match[1];\r
3052                   blackname = star_match[3];\r
3053                   why = star_match[5];\r
3054                   endtoken = star_match[6];\r
3055                 }\r
3056 \r
3057                 /* Game start messages */\r
3058                 if (strncmp(why, "Creating ", 9) == 0 ||\r
3059                     strncmp(why, "Continuing ", 11) == 0) {\r
3060                     gs_gamenum = gamenum;\r
3061                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
3062 #if ZIPPY\r
3063                     if (appData.zippyPlay) {\r
3064                         ZippyGameStart(whitename, blackname);\r
3065                     }\r
3066 #endif /*ZIPPY*/\r
3067                     continue;\r
3068                 }\r
3069 \r
3070                 /* Game end messages */\r
3071                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
3072                     ics_gamenum != gamenum) {\r
3073                     continue;\r
3074                 }\r
3075                 while (endtoken[0] == ' ') endtoken++;\r
3076                 switch (endtoken[0]) {\r
3077                   case '*':\r
3078                   default:\r
3079                     endtype = GameUnfinished;\r
3080                     break;\r
3081                   case '0':\r
3082                     endtype = BlackWins;\r
3083                     break;\r
3084                   case '1':\r
3085                     if (endtoken[1] == '/')\r
3086                       endtype = GameIsDrawn;\r
3087                     else\r
3088                       endtype = WhiteWins;\r
3089                     break;\r
3090                 }\r
3091                 GameEnds(endtype, why, GE_ICS);\r
3092 #if ZIPPY\r
3093                 if (appData.zippyPlay && first.initDone) {\r
3094                     ZippyGameEnd(endtype, why);\r
3095                     if (first.pr == NULL) {\r
3096                       /* Start the next process early so that we'll\r
3097                          be ready for the next challenge */\r
3098                       StartChessProgram(&first);\r
3099                     }\r
3100                     /* Send "new" early, in case this command takes\r
3101                        a long time to finish, so that we'll be ready\r
3102                        for the next challenge. */\r
3103                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'\r
3104                     Reset(TRUE, TRUE);\r
3105                 }\r
3106 #endif /*ZIPPY*/\r
3107                 continue;\r
3108             }\r
3109 \r
3110             if (looking_at(buf, &i, "Removing game * from observation") ||\r
3111                 looking_at(buf, &i, "no longer observing game *") ||\r
3112                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
3113                 if (gameMode == IcsObserving &&\r
3114                     atoi(star_match[0]) == ics_gamenum)\r
3115                   {\r
3116                       /* icsEngineAnalyze */\r
3117                       if (appData.icsEngineAnalyze) {\r
3118                             ExitAnalyzeMode();\r
3119                             ModeHighlight();\r
3120                       }\r
3121                       StopClocks();\r
3122                       gameMode = IcsIdle;\r
3123                       ics_gamenum = -1;\r
3124                       ics_user_moved = FALSE;\r
3125                   }\r
3126                 continue;\r
3127             }\r
3128 \r
3129             if (looking_at(buf, &i, "no longer examining game *")) {\r
3130                 if (gameMode == IcsExamining &&\r
3131                     atoi(star_match[0]) == ics_gamenum)\r
3132                   {\r
3133                       gameMode = IcsIdle;\r
3134                       ics_gamenum = -1;\r
3135                       ics_user_moved = FALSE;\r
3136                   }\r
3137                 continue;\r
3138             }\r
3139 \r
3140             /* Advance leftover_start past any newlines we find,\r
3141                so only partial lines can get reparsed */\r
3142             if (looking_at(buf, &i, "\n")) {\r
3143                 prevColor = curColor;\r
3144                 if (curColor != ColorNormal) {\r
3145                     if (oldi > next_out) {\r
3146                         SendToPlayer(&buf[next_out], oldi - next_out);\r
3147                         next_out = oldi;\r
3148                     }\r
3149                     Colorize(ColorNormal, FALSE);\r
3150                     curColor = ColorNormal;\r
3151                 }\r
3152                 if (started == STARTED_BOARD) {\r
3153                     started = STARTED_NONE;\r
3154                     parse[parse_pos] = NULLCHAR;\r
3155                     ParseBoard12(parse);\r
3156                     ics_user_moved = 0;\r
3157 \r
3158                     /* Send premove here */\r
3159                     if (appData.premove) {\r
3160                       char str[MSG_SIZ];\r
3161                       if (currentMove == 0 &&\r
3162                           gameMode == IcsPlayingWhite &&\r
3163                           appData.premoveWhite) {\r
3164                         sprintf(str, "%s%s\n", ics_prefix,\r
3165                                 appData.premoveWhiteText);\r
3166                         if (appData.debugMode)\r
3167                           fprintf(debugFP, "Sending premove:\n");\r
3168                         SendToICS(str);\r
3169                       } else if (currentMove == 1 &&\r
3170                                  gameMode == IcsPlayingBlack &&\r
3171                                  appData.premoveBlack) {\r
3172                         sprintf(str, "%s%s\n", ics_prefix,\r
3173                                 appData.premoveBlackText);\r
3174                         if (appData.debugMode)\r
3175                           fprintf(debugFP, "Sending premove:\n");\r
3176                         SendToICS(str);\r
3177                       } else if (gotPremove) {\r
3178                         gotPremove = 0;\r
3179                         ClearPremoveHighlights();\r
3180                         if (appData.debugMode)\r
3181                           fprintf(debugFP, "Sending premove:\n");\r
3182                           UserMoveEvent(premoveFromX, premoveFromY, \r
3183                                         premoveToX, premoveToY, \r
3184                                         premovePromoChar);\r
3185                       }\r
3186                     }\r
3187 \r
3188                     /* Usually suppress following prompt */\r
3189                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
3190                         if (looking_at(buf, &i, "*% ")) {\r
3191                             savingComment = FALSE;\r
3192                         }\r
3193                     }\r
3194                     next_out = i;\r
3195                 } else if (started == STARTED_HOLDINGS) {\r
3196                     int gamenum;\r
3197                     char new_piece[MSG_SIZ];\r
3198                     started = STARTED_NONE;\r
3199                     parse[parse_pos] = NULLCHAR;\r
3200                     if (appData.debugMode)\r
3201                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
3202                                                         parse, currentMove);\r
3203                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
3204                         gamenum == ics_gamenum) {\r
3205                         if (gameInfo.variant == VariantNormal) {\r
3206                           /* [HGM] We seem to switch variant during a game!\r
3207                            * Presumably no holdings were displayed, so we have\r
3208                            * to move the position two files to the right to\r
3209                            * create room for them!\r
3210                            */\r
3211                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
3212                           /* Get a move list just to see the header, which\r
3213                              will tell us whether this is really bug or zh */\r
3214                           if (ics_getting_history == H_FALSE) {\r
3215                             ics_getting_history = H_REQUESTED;\r
3216                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3217                             SendToICS(str);\r
3218                           }\r
3219                         }\r
3220                         new_piece[0] = NULLCHAR;\r
3221                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
3222                                &gamenum, white_holding, black_holding,\r
3223                                new_piece);\r
3224                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
3225                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
3226                         /* [HGM] copy holdings to board holdings area */\r
3227                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
3228                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
3229 #if ZIPPY\r
3230                         if (appData.zippyPlay && first.initDone) {\r
3231                             ZippyHoldings(white_holding, black_holding,\r
3232                                           new_piece);\r
3233                         }\r
3234 #endif /*ZIPPY*/\r
3235                         if (tinyLayout || smallLayout) {\r
3236                             char wh[16], bh[16];\r
3237                             PackHolding(wh, white_holding);\r
3238                             PackHolding(bh, black_holding);\r
3239                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
3240                                     gameInfo.white, gameInfo.black);\r
3241                         } else {\r
3242                             sprintf(str, "%s [%s] vs. %s [%s]",\r
3243                                     gameInfo.white, white_holding,\r
3244                                     gameInfo.black, black_holding);\r
3245                         }\r
3246 \r
3247                         DrawPosition(FALSE, boards[currentMove]);\r
3248                         DisplayTitle(str);\r
3249                     }\r
3250                     /* Suppress following prompt */\r
3251                     if (looking_at(buf, &i, "*% ")) {\r
3252                         savingComment = FALSE;\r
3253                     }\r
3254                     next_out = i;\r
3255                 }\r
3256                 continue;\r
3257             }\r
3258 \r
3259             i++;                /* skip unparsed character and loop back */\r
3260         }\r
3261         \r
3262         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window\r
3263             started != STARTED_HOLDINGS && i > next_out) {\r
3264             SendToPlayer(&buf[next_out], i - next_out);\r
3265             next_out = i;\r
3266         }\r
3267         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above\r
3268         \r
3269         leftover_len = buf_len - leftover_start;\r
3270         /* if buffer ends with something we couldn't parse,\r
3271            reparse it after appending the next read */\r
3272         \r
3273     } else if (count == 0) {\r
3274         RemoveInputSource(isr);\r
3275         DisplayFatalError(_("Connection closed by ICS"), 0, 0);\r
3276     } else {\r
3277         DisplayFatalError(_("Error reading from ICS"), error, 1);\r
3278     }\r
3279 }\r
3280 \r
3281 \r
3282 /* Board style 12 looks like this:\r
3283    \r
3284    <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
3285    \r
3286  * The "<12> " is stripped before it gets to this routine.  The two\r
3287  * trailing 0's (flip state and clock ticking) are later addition, and\r
3288  * some chess servers may not have them, or may have only the first.\r
3289  * Additional trailing fields may be added in the future.  \r
3290  */\r
3291 \r
3292 #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
3293 \r
3294 #define RELATION_OBSERVING_PLAYED    0\r
3295 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
3296 #define RELATION_PLAYING_MYMOVE      1\r
3297 #define RELATION_PLAYING_NOTMYMOVE  -1\r
3298 #define RELATION_EXAMINING           2\r
3299 #define RELATION_ISOLATED_BOARD     -3\r
3300 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
3301 \r
3302 void\r
3303 ParseBoard12(string)\r
3304      char *string;\r
3305\r
3306     GameMode newGameMode;\r
3307     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;\r
3308     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;\r
3309     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
3310     char to_play, board_chars[200];\r
3311     char move_str[500], str[500], elapsed_time[500];\r
3312     char black[32], white[32];\r
3313     Board board;\r
3314     int prevMove = currentMove;\r
3315     int ticking = 2;\r
3316     ChessMove moveType;\r
3317     int fromX, fromY, toX, toY;\r
3318     char promoChar;\r
3319     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */\r
3320     char *bookHit = NULL; // [HGM] book\r
3321 \r
3322     fromX = fromY = toX = toY = -1;\r
3323     \r
3324     newGame = FALSE;\r
3325 \r
3326     if (appData.debugMode)\r
3327       fprintf(debugFP, _("Parsing board: %s\n"), string);\r
3328 \r
3329     move_str[0] = NULLCHAR;\r
3330     elapsed_time[0] = NULLCHAR;\r
3331     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */\r
3332         int  i = 0, j;\r
3333         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {\r
3334             if(string[i] == ' ') { ranks++; files = 0; }\r
3335             else files++;\r
3336             i++;\r
3337         }\r
3338         for(j = 0; j <i; j++) board_chars[j] = string[j];\r
3339         board_chars[i] = '\0';\r
3340         string += i + 1;\r
3341     }\r
3342     n = sscanf(string, PATTERN, &to_play, &double_push,\r
3343                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
3344                &gamenum, white, black, &relation, &basetime, &increment,\r
3345                &white_stren, &black_stren, &white_time, &black_time,\r
3346                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
3347                &ticking);\r
3348 \r
3349     if (n < 21) {\r
3350         sprintf(str, _("Failed to parse board string:\n\"%s\""), string);\r
3351         DisplayError(str, 0);\r
3352         return;\r
3353     }\r
3354 \r
3355     /* Convert the move number to internal form */\r
3356     moveNum = (moveNum - 1) * 2;\r
3357     if (to_play == 'B') moveNum++;\r
3358     if (moveNum >= MAX_MOVES) {\r
3359       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
3360                         0, 1);\r
3361       return;\r
3362     }\r
3363     \r
3364     switch (relation) {\r
3365       case RELATION_OBSERVING_PLAYED:\r
3366       case RELATION_OBSERVING_STATIC:\r
3367         if (gamenum == -1) {\r
3368             /* Old ICC buglet */\r
3369             relation = RELATION_OBSERVING_STATIC;\r
3370         }\r
3371         newGameMode = IcsObserving;\r
3372         break;\r
3373       case RELATION_PLAYING_MYMOVE:\r
3374       case RELATION_PLAYING_NOTMYMOVE:\r
3375         newGameMode =\r
3376           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3377             IcsPlayingWhite : IcsPlayingBlack;\r
3378         break;\r
3379       case RELATION_EXAMINING:\r
3380         newGameMode = IcsExamining;\r
3381         break;\r
3382       case RELATION_ISOLATED_BOARD:\r
3383       default:\r
3384         /* Just display this board.  If user was doing something else,\r
3385            we will forget about it until the next board comes. */ \r
3386         newGameMode = IcsIdle;\r
3387         break;\r
3388       case RELATION_STARTING_POSITION:\r
3389         newGameMode = gameMode;\r
3390         break;\r
3391     }\r
3392     \r
3393     /* Modify behavior for initial board display on move listing\r
3394        of wild games.\r
3395        */\r
3396     switch (ics_getting_history) {\r
3397       case H_FALSE:\r
3398       case H_REQUESTED:\r
3399         break;\r
3400       case H_GOT_REQ_HEADER:\r
3401       case H_GOT_UNREQ_HEADER:\r
3402         /* This is the initial position of the current game */\r
3403         gamenum = ics_gamenum;\r
3404         moveNum = 0;            /* old ICS bug workaround */\r
3405         if (to_play == 'B') {\r
3406           startedFromSetupPosition = TRUE;\r
3407           blackPlaysFirst = TRUE;\r
3408           moveNum = 1;\r
3409           if (forwardMostMove == 0) forwardMostMove = 1;\r
3410           if (backwardMostMove == 0) backwardMostMove = 1;\r
3411           if (currentMove == 0) currentMove = 1;\r
3412         }\r
3413         newGameMode = gameMode;\r
3414         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3415         break;\r
3416       case H_GOT_UNWANTED_HEADER:\r
3417         /* This is an initial board that we don't want */\r
3418         return;\r
3419       case H_GETTING_MOVES:\r
3420         /* Should not happen */\r
3421         DisplayError(_("Error gathering move list: extra board"), 0);\r
3422         ics_getting_history = H_FALSE;\r
3423         return;\r
3424     }\r
3425     \r
3426     /* Take action if this is the first board of a new game, or of a\r
3427        different game than is currently being displayed.  */\r
3428     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3429         relation == RELATION_ISOLATED_BOARD) {\r
3430         \r
3431         /* Forget the old game and get the history (if any) of the new one */\r
3432         if (gameMode != BeginningOfGame) {\r
3433           Reset(FALSE, TRUE);\r
3434         }\r
3435         newGame = TRUE;\r
3436         if (appData.autoRaiseBoard) BoardToTop();\r
3437         prevMove = -3;\r
3438         if (gamenum == -1) {\r
3439             newGameMode = IcsIdle;\r
3440         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3441                    appData.getMoveList) {\r
3442             /* Need to get game history */\r
3443             ics_getting_history = H_REQUESTED;\r
3444             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3445             SendToICS(str);\r
3446         }\r
3447         \r
3448         /* Initially flip the board to have black on the bottom if playing\r
3449            black or if the ICS flip flag is set, but let the user change\r
3450            it with the Flip View button. */\r
3451         flipView = appData.autoFlipView ? \r
3452           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3453           appData.flipView;\r
3454         \r
3455         /* Done with values from previous mode; copy in new ones */\r
3456         gameMode = newGameMode;\r
3457         ModeHighlight();\r
3458         ics_gamenum = gamenum;\r
3459         if (gamenum == gs_gamenum) {\r
3460             int klen = strlen(gs_kind);\r
3461             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3462             sprintf(str, "ICS %s", gs_kind);\r
3463             gameInfo.event = StrSave(str);\r
3464         } else {\r
3465             gameInfo.event = StrSave("ICS game");\r
3466         }\r
3467         gameInfo.site = StrSave(appData.icsHost);\r
3468         gameInfo.date = PGNDate();\r
3469         gameInfo.round = StrSave("-");\r
3470         gameInfo.white = StrSave(white);\r
3471         gameInfo.black = StrSave(black);\r
3472         timeControl = basetime * 60 * 1000;\r
3473         timeControl_2 = 0;\r
3474         timeIncrement = increment * 1000;\r
3475         movesPerSession = 0;\r
3476         gameInfo.timeControl = TimeControlTagValue();\r
3477         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3478   if (appData.debugMode) {\r
3479     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3480     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3481     setbuf(debugFP, NULL);\r
3482   }\r
3483 \r
3484         gameInfo.outOfBook = NULL;\r
3485         \r
3486         /* Do we have the ratings? */\r
3487         if (strcmp(player1Name, white) == 0 &&\r
3488             strcmp(player2Name, black) == 0) {\r
3489             if (appData.debugMode)\r
3490               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3491                       player1Rating, player2Rating);\r
3492             gameInfo.whiteRating = player1Rating;\r
3493             gameInfo.blackRating = player2Rating;\r
3494         } else if (strcmp(player2Name, white) == 0 &&\r
3495                    strcmp(player1Name, black) == 0) {\r
3496             if (appData.debugMode)\r
3497               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3498                       player2Rating, player1Rating);\r
3499             gameInfo.whiteRating = player2Rating;\r
3500             gameInfo.blackRating = player1Rating;\r
3501         }\r
3502         player1Name[0] = player2Name[0] = NULLCHAR;\r
3503 \r
3504         /* Silence shouts if requested */\r
3505         if (appData.quietPlay &&\r
3506             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3507             SendToICS(ics_prefix);\r
3508             SendToICS("set shout 0\n");\r
3509         }\r
3510     }\r
3511     \r
3512     /* Deal with midgame name changes */\r
3513     if (!newGame) {\r
3514         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3515             if (gameInfo.white) free(gameInfo.white);\r
3516             gameInfo.white = StrSave(white);\r
3517         }\r
3518         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3519             if (gameInfo.black) free(gameInfo.black);\r
3520             gameInfo.black = StrSave(black);\r
3521         }\r
3522     }\r
3523     \r
3524     /* Throw away game result if anything actually changes in examine mode */\r
3525     if (gameMode == IcsExamining && !newGame) {\r
3526         gameInfo.result = GameUnfinished;\r
3527         if (gameInfo.resultDetails != NULL) {\r
3528             free(gameInfo.resultDetails);\r
3529             gameInfo.resultDetails = NULL;\r
3530         }\r
3531     }\r
3532     \r
3533     /* In pausing && IcsExamining mode, we ignore boards coming\r
3534        in if they are in a different variation than we are. */\r
3535     if (pauseExamInvalid) return;\r
3536     if (pausing && gameMode == IcsExamining) {\r
3537         if (moveNum <= pauseExamForwardMostMove) {\r
3538             pauseExamInvalid = TRUE;\r
3539             forwardMostMove = pauseExamForwardMostMove;\r
3540             return;\r
3541         }\r
3542     }\r
3543     \r
3544   if (appData.debugMode) {\r
3545     fprintf(debugFP, "load %dx%d board\n", files, ranks);\r
3546   }\r
3547     /* Parse the board */\r
3548     for (k = 0; k < ranks; k++) {\r
3549       for (j = 0; j < files; j++)\r
3550         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);\r
3551       if(gameInfo.holdingsWidth > 1) {\r
3552            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3553            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3554       }\r
3555     }\r
3556     CopyBoard(boards[moveNum], board);\r
3557     if (moveNum == 0) {\r
3558         startedFromSetupPosition =\r
3559           !CompareBoards(board, initialPosition);\r
3560         if(startedFromSetupPosition)\r
3561             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */\r
3562     }\r
3563 \r
3564     /* [HGM] Set castling rights. Take the outermost Rooks,\r
3565        to make it also work for FRC opening positions. Note that board12\r
3566        is really defective for later FRC positions, as it has no way to\r
3567        indicate which Rook can castle if they are on the same side of King.\r
3568        For the initial position we grant rights to the outermost Rooks,\r
3569        and remember thos rights, and we then copy them on positions\r
3570        later in an FRC game. This means WB might not recognize castlings with\r
3571        Rooks that have moved back to their original position as illegal,\r
3572        but in ICS mode that is not its job anyway.\r
3573     */\r
3574     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)\r
3575     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;\r
3576 \r
3577         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3578             if(board[0][i] == WhiteRook) j = i;\r
3579         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3580         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3581             if(board[0][i] == WhiteRook) j = i;\r
3582         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3583         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3584             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3585         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3586         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3587             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3588         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3589 \r
3590         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }\r
3591         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3592             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;\r
3593         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3594             if(board[BOARD_HEIGHT-1][k] == bKing)\r
3595                 initialRights[5] = castlingRights[moveNum][5] = k;\r
3596     } else { int r;\r
3597         r = castlingRights[moveNum][0] = initialRights[0];\r
3598         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;\r
3599         r = castlingRights[moveNum][1] = initialRights[1];\r
3600         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;\r
3601         r = castlingRights[moveNum][3] = initialRights[3];\r
3602         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;\r
3603         r = castlingRights[moveNum][4] = initialRights[4];\r
3604         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;\r
3605         /* wildcastle kludge: always assume King has rights */\r
3606         r = castlingRights[moveNum][2] = initialRights[2];\r
3607         r = castlingRights[moveNum][5] = initialRights[5];\r
3608     }\r
3609     /* [HGM] e.p. rights. Assume that ICS sends file number here? */\r
3610     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;\r
3611 \r
3612     \r
3613     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3614         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3615         /* This was an initial position from a move list, not\r
3616            the current position */\r
3617         return;\r
3618     }\r
3619     \r
3620     /* Update currentMove and known move number limits */\r
3621     newMove = newGame || moveNum > forwardMostMove;\r
3622 \r
3623     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */\r
3624     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {\r
3625         takeback = forwardMostMove - moveNum;\r
3626         for (i = 0; i < takeback; i++) {\r
3627              if (appData.debugMode) fprintf(debugFP, "take back move\n");\r
3628              SendToProgram("undo\n", &first);\r
3629         }\r
3630     }\r
3631 \r
3632     if (newGame) {\r
3633         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3634         if (gameMode == IcsExamining && moveNum == 0) {\r
3635           /* Workaround for ICS limitation: we are not told the wild\r
3636              type when starting to examine a game.  But if we ask for\r
3637              the move list, the move list header will tell us */\r
3638             ics_getting_history = H_REQUESTED;\r
3639             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3640             SendToICS(str);\r
3641         }\r
3642     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3643                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3644         forwardMostMove = moveNum;\r
3645         if (!pausing || currentMove > forwardMostMove)\r
3646           currentMove = forwardMostMove;\r
3647     } else {\r
3648         /* New part of history that is not contiguous with old part */ \r
3649         if (pausing && gameMode == IcsExamining) {\r
3650             pauseExamInvalid = TRUE;\r
3651             forwardMostMove = pauseExamForwardMostMove;\r
3652             return;\r
3653         }\r
3654         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3655         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3656             ics_getting_history = H_REQUESTED;\r
3657             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3658             SendToICS(str);\r
3659         }\r
3660     }\r
3661     \r
3662     /* Update the clocks */\r
3663     if (strchr(elapsed_time, '.')) {\r
3664       /* Time is in ms */\r
3665       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3666       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3667     } else {\r
3668       /* Time is in seconds */\r
3669       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3670       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3671     }\r
3672       \r
3673 \r
3674 #if ZIPPY\r
3675     if (appData.zippyPlay && newGame &&\r
3676         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3677         gameMode != IcsExamining)\r
3678       ZippyFirstBoard(moveNum, basetime, increment);\r
3679 #endif\r
3680     \r
3681     /* Put the move on the move list, first converting\r
3682        to canonical algebraic form. */\r
3683     if (moveNum > 0) {\r
3684   if (appData.debugMode) {\r
3685     if (appData.debugMode) { int f = forwardMostMove;\r
3686         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,\r
3687                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
3688     }\r
3689     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3690     fprintf(debugFP, "moveNum = %d\n", moveNum);\r
3691     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3692     setbuf(debugFP, NULL);\r
3693   }\r
3694         if (moveNum <= backwardMostMove) {\r
3695             /* We don't know what the board looked like before\r
3696                this move.  Punt. */\r
3697             strcpy(parseList[moveNum - 1], move_str);\r
3698             strcat(parseList[moveNum - 1], " ");\r
3699             strcat(parseList[moveNum - 1], elapsed_time);\r
3700             moveList[moveNum - 1][0] = NULLCHAR;\r
3701         } else if (strcmp(move_str, "none") == 0) {\r
3702             // [HGM] long SAN: swapped order; test for 'none' before parsing move\r
3703             /* Again, we don't know what the board looked like;\r
3704                this is really the start of the game. */\r
3705             parseList[moveNum - 1][0] = NULLCHAR;\r
3706             moveList[moveNum - 1][0] = NULLCHAR;\r
3707             backwardMostMove = moveNum;\r
3708             startedFromSetupPosition = TRUE;\r
3709             fromX = fromY = toX = toY = -1;\r
3710         } else {\r
3711           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. \r
3712           //                 So we parse the long-algebraic move string in stead of the SAN move\r
3713           int valid; char buf[MSG_SIZ], *prom;\r
3714 \r
3715           // str looks something like "Q/a1-a2"; kill the slash\r
3716           if(str[1] == '/') \r
3717                 sprintf(buf, "%c%s", str[0], str+2);\r
3718           else  strcpy(buf, str); // might be castling\r
3719           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) \r
3720                 strcat(buf, prom); // long move lacks promo specification!\r
3721           if(!appData.testLegality) {\r
3722                 if(appData.debugMode) \r
3723                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);\r
3724                 strcpy(move_str, buf);\r
3725           }\r
3726           valid = ParseOneMove(move_str, moveNum - 1, &moveType,\r
3727                                 &fromX, &fromY, &toX, &toY, &promoChar)\r
3728                || ParseOneMove(buf, moveNum - 1, &moveType,\r
3729                                 &fromX, &fromY, &toX, &toY, &promoChar);\r
3730           // end of long SAN patch\r
3731           if (valid) {\r
3732             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3733                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3734                                      fromY, fromX, toY, toX, promoChar,\r
3735                                      parseList[moveNum-1]);\r
3736             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3737                              castlingRights[moveNum]) ) {\r
3738               case MT_NONE:\r
3739               case MT_STALEMATE:\r
3740               default:\r
3741                 break;\r
3742               case MT_CHECK:\r
3743                 if(gameInfo.variant != VariantShogi)\r
3744                     strcat(parseList[moveNum - 1], "+");\r
3745                 break;\r
3746               case MT_CHECKMATE:\r
3747                 strcat(parseList[moveNum - 1], "#");\r
3748                 break;\r
3749             }\r
3750             strcat(parseList[moveNum - 1], " ");\r
3751             strcat(parseList[moveNum - 1], elapsed_time);\r
3752             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3753             strcpy(moveList[moveNum - 1], currentMoveString);\r
3754             strcat(moveList[moveNum - 1], "\n");\r
3755           } else {\r
3756             /* Move from ICS was illegal!?  Punt. */\r
3757   if (appData.debugMode) {\r
3758     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3759     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3760   }\r
3761 #if 0\r
3762             if (appData.testLegality && appData.debugMode) {\r
3763                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3764                 DisplayError(str, 0);\r
3765             }\r
3766 #endif\r
3767             strcpy(parseList[moveNum - 1], move_str);\r
3768             strcat(parseList[moveNum - 1], " ");\r
3769             strcat(parseList[moveNum - 1], elapsed_time);\r
3770             moveList[moveNum - 1][0] = NULLCHAR;\r
3771             fromX = fromY = toX = toY = -1;\r
3772           }\r
3773         }\r
3774   if (appData.debugMode) {\r
3775     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3776     setbuf(debugFP, NULL);\r
3777   }\r
3778 \r
3779 #if ZIPPY\r
3780         /* Send move to chess program (BEFORE animating it). */\r
3781         if (appData.zippyPlay && !newGame && newMove && \r
3782            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3783 \r
3784             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3785                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3786                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3787                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),\r
3788                             move_str);\r
3789                     DisplayError(str, 0);\r
3790                 } else {\r
3791                     if (first.sendTime) {\r
3792                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3793                     }\r
3794                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book\r
3795                     if (firstMove && !bookHit) {\r
3796                         firstMove = FALSE;\r
3797                         if (first.useColors) {\r
3798                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3799                                         "white\ngo\n" :\r
3800                                         "black\ngo\n", &first);\r
3801                         } else {\r
3802                           SendToProgram("go\n", &first);\r
3803                         }\r
3804                         first.maybeThinking = TRUE;\r
3805                     }\r
3806                 }\r
3807             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3808               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3809                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);\r
3810                 DisplayError(str, 0);\r
3811               } else {\r
3812                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!\r
3813                 SendMoveToProgram(moveNum - 1, &first);\r
3814               }\r
3815             }\r
3816         }\r
3817 #endif\r
3818     }\r
3819 \r
3820     if (moveNum > 0 && !gotPremove) {\r
3821         /* If move comes from a remote source, animate it.  If it\r
3822            isn't remote, it will have already been animated. */\r
3823         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3824             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3825         }\r
3826         if (!pausing && appData.highlightLastMove) {\r
3827             SetHighlights(fromX, fromY, toX, toY);\r
3828         }\r
3829     }\r
3830     \r
3831     /* Start the clocks */\r
3832     whiteFlag = blackFlag = FALSE;\r
3833     appData.clockMode = !(basetime == 0 && increment == 0);\r
3834     if (ticking == 0) {\r
3835       ics_clock_paused = TRUE;\r
3836       StopClocks();\r
3837     } else if (ticking == 1) {\r
3838       ics_clock_paused = FALSE;\r
3839     }\r
3840     if (gameMode == IcsIdle ||\r
3841         relation == RELATION_OBSERVING_STATIC ||\r
3842         relation == RELATION_EXAMINING ||\r
3843         ics_clock_paused)\r
3844       DisplayBothClocks();\r
3845     else\r
3846       StartClocks();\r
3847     \r
3848     /* Display opponents and material strengths */\r
3849     if (gameInfo.variant != VariantBughouse &&\r
3850         gameInfo.variant != VariantCrazyhouse) {\r
3851         if (tinyLayout || smallLayout) {\r
3852             if(gameInfo.variant == VariantNormal)\r
3853                 sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3854                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3855                     basetime, increment);\r
3856             else\r
3857                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", \r
3858                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3859                     basetime, increment, (int) gameInfo.variant);\r
3860         } else {\r
3861             if(gameInfo.variant == VariantNormal)\r
3862                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3863                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3864                     basetime, increment);\r
3865             else\r
3866                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", \r
3867                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3868                     basetime, increment, VariantName(gameInfo.variant));\r
3869         }\r
3870         DisplayTitle(str);\r
3871   if (appData.debugMode) {\r
3872     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);\r
3873   }\r
3874     }\r
3875 \r
3876    \r
3877     /* Display the board */\r
3878     if (!pausing) {\r
3879       \r
3880       if (appData.premove)\r
3881           if (!gotPremove || \r
3882              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3883              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3884               ClearPremoveHighlights();\r
3885 \r
3886       DrawPosition(FALSE, boards[currentMove]);\r
3887       DisplayMove(moveNum - 1);\r
3888       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3889         RingBell();\r
3890     }\r
3891 \r
3892     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3893 #if ZIPPY\r
3894     if(bookHit) { // [HGM] book: simulate book reply\r
3895         static char bookMove[MSG_SIZ]; // a bit generous?\r
3896 \r
3897         programStats.nodes = programStats.depth = programStats.time = \r
3898         programStats.score = programStats.got_only_move = 0;\r
3899         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
3900 \r
3901         strcpy(bookMove, "move ");\r
3902         strcat(bookMove, bookHit);\r
3903         HandleMachineMove(bookMove, &first);\r
3904     }\r
3905 #endif\r
3906 }\r
3907 \r
3908 void\r
3909 GetMoveListEvent()\r
3910 {\r
3911     char buf[MSG_SIZ];\r
3912     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3913         ics_getting_history = H_REQUESTED;\r
3914         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3915         SendToICS(buf);\r
3916     }\r
3917 }\r
3918 \r
3919 void\r
3920 AnalysisPeriodicEvent(force)\r
3921      int force;\r
3922 {\r
3923     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3924          && !force) || !appData.periodicUpdates)\r
3925       return;\r
3926 \r
3927     /* Send . command to Crafty to collect stats */\r
3928     SendToProgram(".\n", &first);\r
3929 \r
3930     /* Don't send another until we get a response (this makes\r
3931        us stop sending to old Crafty's which don't understand\r
3932        the "." command (sending illegal cmds resets node count & time,\r
3933        which looks bad)) */\r
3934     programStats.ok_to_send = 0;\r
3935 }\r
3936 \r
3937 void\r
3938 SendMoveToProgram(moveNum, cps)\r
3939      int moveNum;\r
3940      ChessProgramState *cps;\r
3941 {\r
3942     char buf[MSG_SIZ];\r
3943 \r
3944     if (cps->useUsermove) {\r
3945       SendToProgram("usermove ", cps);\r
3946     }\r
3947     if (cps->useSAN) {\r
3948       char *space;\r
3949       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3950         int len = space - parseList[moveNum];\r
3951         memcpy(buf, parseList[moveNum], len);\r
3952         buf[len++] = '\n';\r
3953         buf[len] = NULLCHAR;\r
3954       } else {\r
3955         sprintf(buf, "%s\n", parseList[moveNum]);\r
3956       }\r
3957       SendToProgram(buf, cps);\r
3958     } else {\r
3959       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */\r
3960         AlphaRank(moveList[moveNum], 4);\r
3961         SendToProgram(moveList[moveNum], cps);\r
3962         AlphaRank(moveList[moveNum], 4); // and back\r
3963       } else\r
3964       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3965        * the engine. It would be nice to have a better way to identify castle \r
3966        * moves here. */\r
3967       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)\r
3968                                                                          && cps->useOOCastle) {\r
3969         int fromX = moveList[moveNum][0] - AAA; \r
3970         int fromY = moveList[moveNum][1] - ONE;\r
3971         int toX = moveList[moveNum][2] - AAA; \r
3972         int toY = moveList[moveNum][3] - ONE;\r
3973         if((boards[moveNum][fromY][fromX] == WhiteKing \r
3974             && boards[moveNum][toY][toX] == WhiteRook)\r
3975            || (boards[moveNum][fromY][fromX] == BlackKing \r
3976                && boards[moveNum][toY][toX] == BlackRook)) {\r
3977           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3978           else SendToProgram("O-O-O\n", cps);\r
3979         }\r
3980         else SendToProgram(moveList[moveNum], cps);\r
3981       }\r
3982       else SendToProgram(moveList[moveNum], cps);\r
3983       /* End of additions by Tord */\r
3984     }\r
3985 \r
3986     /* [HGM] setting up the opening has brought engine in force mode! */\r
3987     /*       Send 'go' if we are in a mode where machine should play. */\r
3988     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
3989         (gameMode == TwoMachinesPlay   ||\r
3990 #ifdef ZIPPY\r
3991          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||\r
3992 #endif\r
3993          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
3994         SendToProgram("go\n", cps);\r
3995   if (appData.debugMode) {\r
3996     fprintf(debugFP, "(extra)\n");\r
3997   }\r
3998     }\r
3999     setboardSpoiledMachineBlack = 0;\r
4000 }\r
4001 \r
4002 void\r
4003 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
4004      ChessMove moveType;\r
4005      int fromX, fromY, toX, toY;\r
4006 {\r
4007     char user_move[MSG_SIZ];\r
4008 \r
4009     switch (moveType) {\r
4010       default:\r
4011         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),\r
4012                 (int)moveType, fromX, fromY, toX, toY);\r
4013         DisplayError(user_move + strlen("say "), 0);\r
4014         break;\r
4015       case WhiteKingSideCastle:\r
4016       case BlackKingSideCastle:\r
4017       case WhiteQueenSideCastleWild:\r
4018       case BlackQueenSideCastleWild:\r
4019       /* PUSH Fabien */\r
4020       case WhiteHSideCastleFR:\r
4021       case BlackHSideCastleFR:\r
4022       /* POP Fabien */\r
4023         sprintf(user_move, "o-o\n");\r
4024         break;\r
4025       case WhiteQueenSideCastle:\r
4026       case BlackQueenSideCastle:\r
4027       case WhiteKingSideCastleWild:\r
4028       case BlackKingSideCastleWild:\r
4029       /* PUSH Fabien */\r
4030       case WhiteASideCastleFR:\r
4031       case BlackASideCastleFR:\r
4032       /* POP Fabien */\r
4033         sprintf(user_move, "o-o-o\n");\r
4034         break;\r
4035       case WhitePromotionQueen:\r
4036       case BlackPromotionQueen:\r
4037       case WhitePromotionRook:\r
4038       case BlackPromotionRook:\r
4039       case WhitePromotionBishop:\r
4040       case BlackPromotionBishop:\r
4041       case WhitePromotionKnight:\r
4042       case BlackPromotionKnight:\r
4043       case WhitePromotionKing:\r
4044       case BlackPromotionKing:\r
4045       case WhitePromotionChancellor:\r
4046       case BlackPromotionChancellor:\r
4047       case WhitePromotionArchbishop:\r
4048       case BlackPromotionArchbishop:\r
4049         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)\r
4050             sprintf(user_move, "%c%c%c%c=%c\n",\r
4051                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4052                 PieceToChar(WhiteFerz));\r
4053         else if(gameInfo.variant == VariantGreat)\r
4054             sprintf(user_move, "%c%c%c%c=%c\n",\r
4055                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4056                 PieceToChar(WhiteMan));\r
4057         else\r
4058             sprintf(user_move, "%c%c%c%c=%c\n",\r
4059                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4060                 PieceToChar(PromoPiece(moveType)));\r
4061         break;\r
4062       case WhiteDrop:\r
4063       case BlackDrop:\r
4064         sprintf(user_move, "%c@%c%c\n",\r
4065                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
4066                 AAA + toX, ONE + toY);\r
4067         break;\r
4068       case NormalMove:\r
4069       case WhiteCapturesEnPassant:\r
4070       case BlackCapturesEnPassant:\r
4071       case IllegalMove:  /* could be a variant we don't quite understand */\r
4072         sprintf(user_move, "%c%c%c%c\n",\r
4073                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
4074         break;\r
4075     }\r
4076     SendToICS(user_move);\r
4077 }\r
4078 \r
4079 void\r
4080 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
4081      int rf, ff, rt, ft;\r
4082      char promoChar;\r
4083      char move[7];\r
4084 {\r
4085     if (rf == DROP_RANK) {\r
4086         sprintf(move, "%c@%c%c\n",\r
4087                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
4088     } else {\r
4089         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
4090             sprintf(move, "%c%c%c%c\n",\r
4091                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
4092         } else {\r
4093             sprintf(move, "%c%c%c%c%c\n",\r
4094                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
4095         }\r
4096     }\r
4097 }\r
4098 \r
4099 void\r
4100 ProcessICSInitScript(f)\r
4101      FILE *f;\r
4102 {\r
4103     char buf[MSG_SIZ];\r
4104 \r
4105     while (fgets(buf, MSG_SIZ, f)) {\r
4106         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
4107     }\r
4108 \r
4109     fclose(f);\r
4110 }\r
4111 \r
4112 \r
4113 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
4114 void\r
4115 AlphaRank(char *move, int n)\r
4116 {\r
4117 //    char *p = move, c; int x, y;\r
4118 \r
4119     if (appData.debugMode) {\r
4120         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
4121     }\r
4122 \r
4123     if(move[1]=='*' && \r
4124        move[2]>='0' && move[2]<='9' &&\r
4125        move[3]>='a' && move[3]<='x'    ) {\r
4126         move[1] = '@';\r
4127         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4128         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4129     } else\r
4130     if(move[0]>='0' && move[0]<='9' &&\r
4131        move[1]>='a' && move[1]<='x' &&\r
4132        move[2]>='0' && move[2]<='9' &&\r
4133        move[3]>='a' && move[3]<='x'    ) {\r
4134         /* input move, Shogi -> normal */\r
4135         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;\r
4136         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\r
4137         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4138         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4139     } else\r
4140     if(move[1]=='@' &&\r
4141        move[3]>='0' && move[3]<='9' &&\r
4142        move[2]>='a' && move[2]<='x'    ) {\r
4143         move[1] = '*';\r
4144         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4145         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4146     } else\r
4147     if(\r
4148        move[0]>='a' && move[0]<='x' &&\r
4149        move[3]>='0' && move[3]<='9' &&\r
4150        move[2]>='a' && move[2]<='x'    ) {\r
4151          /* output move, normal -> Shogi */\r
4152         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
4153         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
4154         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4155         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4156         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
4157     }\r
4158     if (appData.debugMode) {\r
4159         fprintf(debugFP, "   out = '%s'\n", move);\r
4160     }\r
4161 }\r
4162 \r
4163 /* Parser for moves from gnuchess, ICS, or user typein box */\r
4164 Boolean\r
4165 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
4166      char *move;\r
4167      int moveNum;\r
4168      ChessMove *moveType;\r
4169      int *fromX, *fromY, *toX, *toY;\r
4170      char *promoChar;\r
4171 {       \r
4172     if (appData.debugMode) {\r
4173         fprintf(debugFP, "move to parse: %s\n", move);\r
4174     }\r
4175     *moveType = yylexstr(moveNum, move);\r
4176 \r
4177     switch (*moveType) {\r
4178       case WhitePromotionChancellor:\r
4179       case BlackPromotionChancellor:\r
4180       case WhitePromotionArchbishop:\r
4181       case BlackPromotionArchbishop:\r
4182       case WhitePromotionQueen:\r
4183       case BlackPromotionQueen:\r
4184       case WhitePromotionRook:\r
4185       case BlackPromotionRook:\r
4186       case WhitePromotionBishop:\r
4187       case BlackPromotionBishop:\r
4188       case WhitePromotionKnight:\r
4189       case BlackPromotionKnight:\r
4190       case WhitePromotionKing:\r
4191       case BlackPromotionKing:\r
4192       case NormalMove:\r
4193       case WhiteCapturesEnPassant:\r
4194       case BlackCapturesEnPassant:\r
4195       case WhiteKingSideCastle:\r
4196       case WhiteQueenSideCastle:\r
4197       case BlackKingSideCastle:\r
4198       case BlackQueenSideCastle:\r
4199       case WhiteKingSideCastleWild:\r
4200       case WhiteQueenSideCastleWild:\r
4201       case BlackKingSideCastleWild:\r
4202       case BlackQueenSideCastleWild:\r
4203       /* Code added by Tord: */\r
4204       case WhiteHSideCastleFR:\r
4205       case WhiteASideCastleFR:\r
4206       case BlackHSideCastleFR:\r
4207       case BlackASideCastleFR:\r
4208       /* End of code added by Tord */\r
4209       case IllegalMove:         /* bug or odd chess variant */\r
4210         *fromX = currentMoveString[0] - AAA;\r
4211         *fromY = currentMoveString[1] - ONE;\r
4212         *toX = currentMoveString[2] - AAA;\r
4213         *toY = currentMoveString[3] - ONE;\r
4214         *promoChar = currentMoveString[4];\r
4215         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
4216             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
4217     if (appData.debugMode) {\r
4218         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
4219     }\r
4220             *fromX = *fromY = *toX = *toY = 0;\r
4221             return FALSE;\r
4222         }\r
4223         if (appData.testLegality) {\r
4224           return (*moveType != IllegalMove);\r
4225         } else {\r
4226           return !(fromX == fromY && toX == toY);\r
4227         }\r
4228 \r
4229       case WhiteDrop:\r
4230       case BlackDrop:\r
4231         *fromX = *moveType == WhiteDrop ?\r
4232           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
4233           (int) CharToPiece(ToLower(currentMoveString[0]));\r
4234         *fromY = DROP_RANK;\r
4235         *toX = currentMoveString[2] - AAA;\r
4236         *toY = currentMoveString[3] - ONE;\r
4237         *promoChar = NULLCHAR;\r
4238         return TRUE;\r
4239 \r
4240       case AmbiguousMove:\r
4241       case ImpossibleMove:\r
4242       case (ChessMove) 0:       /* end of file */\r
4243       case ElapsedTime:\r
4244       case Comment:\r
4245       case PGNTag:\r
4246       case NAG:\r
4247       case WhiteWins:\r
4248       case BlackWins:\r
4249       case GameIsDrawn:\r
4250       default:\r
4251     if (appData.debugMode) {\r
4252         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
4253     }\r
4254         /* bug? */\r
4255         *fromX = *fromY = *toX = *toY = 0;\r
4256         *promoChar = NULLCHAR;\r
4257         return FALSE;\r
4258     }\r
4259 }\r
4260 \r
4261 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.\r
4262 // All positions will have equal probability, but the current method will not provide a unique\r
4263 // numbering scheme for arrays that contain 3 or more pieces of the same kind.\r
4264 #define DARK 1\r
4265 #define LITE 2\r
4266 #define ANY 3\r
4267 \r
4268 int squaresLeft[4];\r
4269 int piecesLeft[(int)BlackPawn];\r
4270 int seed, nrOfShuffles;\r
4271 \r
4272 void GetPositionNumber()\r
4273 {       // sets global variable seed\r
4274         int i;\r
4275 \r
4276         seed = appData.defaultFrcPosition;\r
4277         if(seed < 0) { // randomize based on time for negative FRC position numbers\r
4278                 for(i=0; i<50; i++) seed += random();\r
4279                 seed = random() ^ random() >> 8 ^ random() << 8;\r
4280                 if(seed<0) seed = -seed;\r
4281         }\r
4282 }\r
4283 \r
4284 int put(Board board, int pieceType, int rank, int n, int shade)\r
4285 // put the piece on the (n-1)-th empty squares of the given shade\r
4286 {\r
4287         int i;\r
4288 \r
4289         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
4290                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {\r
4291                         board[rank][i] = (ChessSquare) pieceType;\r
4292                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;\r
4293                         squaresLeft[ANY]--;\r
4294                         piecesLeft[pieceType]--; \r
4295                         return i;\r
4296                 }\r
4297         }\r
4298         return -1;\r
4299 }\r
4300 \r
4301 \r
4302 void AddOnePiece(Board board, int pieceType, int rank, int shade)\r
4303 // calculate where the next piece goes, (any empty square), and put it there\r
4304 {\r
4305         int i;\r
4306 \r
4307         i = seed % squaresLeft[shade];\r
4308         nrOfShuffles *= squaresLeft[shade];\r
4309         seed /= squaresLeft[shade];\r
4310         put(board, pieceType, rank, i, shade);\r
4311 }\r
4312 \r
4313 void AddTwoPieces(Board board, int pieceType, int rank)\r
4314 // calculate where the next 2 identical pieces go, (any empty square), and put it there\r
4315 {\r
4316         int i, n=squaresLeft[ANY], j=n-1, k;\r
4317 \r
4318         k = n*(n-1)/2; // nr of possibilities, not counting permutations\r
4319         i = seed % k;  // pick one\r
4320         nrOfShuffles *= k;\r
4321         seed /= k;\r
4322         while(i >= j) i -= j--;\r
4323         j = n - 1 - j; i += j;\r
4324         put(board, pieceType, rank, j, ANY);\r
4325         put(board, pieceType, rank, i, ANY);\r
4326 }\r
4327 \r
4328 void SetUpShuffle(Board board, int number)\r
4329 {\r
4330         int i, p, first=1;\r
4331 \r
4332         GetPositionNumber(); nrOfShuffles = 1;\r
4333 \r
4334         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;\r
4335         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;\r
4336         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];\r
4337 \r
4338         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;\r
4339 \r
4340         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board\r
4341             p = (int) board[0][i];\r
4342             if(p < (int) BlackPawn) piecesLeft[p] ++;\r
4343             board[0][i] = EmptySquare;\r
4344         }\r
4345 \r
4346         if(PosFlags(0) & F_ALL_CASTLE_OK) {\r
4347             // shuffles restricted to allow normal castling put KRR first\r
4348             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle\r
4349                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4350             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles\r
4351                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4352             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling\r
4353                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);\r
4354             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling\r
4355                 put(board, WhiteRook, 0, 0, ANY);\r
4356             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle\r
4357         }\r
4358 \r
4359         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)\r
4360             // only for even boards make effort to put pairs of colorbound pieces on opposite colors\r
4361             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {\r
4362                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;\r
4363                 while(piecesLeft[p] >= 2) {\r
4364                     AddOnePiece(board, p, 0, LITE);\r
4365                     AddOnePiece(board, p, 0, DARK);\r
4366                 }\r
4367                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)\r
4368             }\r
4369 \r
4370         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {\r
4371             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere\r
4372             // but we leave King and Rooks for last, to possibly obey FRC restriction\r
4373             if(p == (int)WhiteRook) continue;\r
4374             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations\r
4375             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece\r
4376         }\r
4377 \r
4378         // now everything is placed, except perhaps King (Unicorn) and Rooks\r
4379 \r
4380         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {\r
4381             // Last King gets castling rights\r
4382             while(piecesLeft[(int)WhiteUnicorn]) {\r
4383                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4384                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4385             }\r
4386 \r
4387             while(piecesLeft[(int)WhiteKing]) {\r
4388                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4389                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4390             }\r
4391 \r
4392 \r
4393         } else {\r
4394             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);\r
4395             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);\r
4396         }\r
4397 \r
4398         // Only Rooks can be left; simply place them all\r
4399         while(piecesLeft[(int)WhiteRook]) {\r
4400                 i = put(board, WhiteRook, 0, 0, ANY);\r
4401                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights\r
4402                         if(first) {\r
4403                                 first=0;\r
4404                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;\r
4405                         }\r
4406                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;\r
4407                 }\r
4408         }\r
4409         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white\r
4410             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;\r
4411         }\r
4412 \r
4413         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize\r
4414 }\r
4415 \r
4416 int SetCharTable( char *table, const char * map )\r
4417 /* [HGM] moved here from winboard.c because of its general usefulness */\r
4418 /*       Basically a safe strcpy that uses the last character as King */\r
4419 {\r
4420     int result = FALSE; int NrPieces;\r
4421 \r
4422     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
4423                     && NrPieces >= 12 && !(NrPieces&1)) {\r
4424         int i; /* [HGM] Accept even length from 12 to 34 */\r
4425 \r
4426         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
4427         for( i=0; i<NrPieces/2-1; i++ ) {\r
4428             table[i] = map[i];\r
4429             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
4430         }\r
4431         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
4432         table[(int) BlackKing]  = map[NrPieces-1];\r
4433 \r
4434         result = TRUE;\r
4435     }\r
4436 \r
4437     return result;\r
4438 }\r
4439 \r
4440 void Prelude(Board board)\r
4441 {       // [HGM] superchess: random selection of exo-pieces\r
4442         int i, j, k; ChessSquare p; \r
4443         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };\r
4444 \r
4445         GetPositionNumber(); // use FRC position number\r
4446 \r
4447         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table\r
4448             SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4449             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) \r
4450                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;\r
4451         }\r
4452 \r
4453         j = seed%4;                 seed /= 4; \r
4454         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4455         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4456         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4457         j = seed%3 + (seed%3 >= j); seed /= 3; \r
4458         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4459         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4460         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4461         j = seed%3;                 seed /= 3; \r
4462         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4463         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4464         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4465         j = seed%2 + (seed%2 >= j); seed /= 2; \r
4466         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4467         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4468         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4469         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);\r
4470         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);\r
4471         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);\r
4472         put(board, exoPieces[0],    0, 0, ANY);\r
4473         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];\r
4474 }\r
4475 \r
4476 void\r
4477 InitPosition(redraw)\r
4478      int redraw;\r
4479 {\r
4480     ChessSquare (* pieces)[BOARD_SIZE];\r
4481     int i, j, pawnRow, overrule,\r
4482     oldx = gameInfo.boardWidth,\r
4483     oldy = gameInfo.boardHeight,\r
4484     oldh = gameInfo.holdingsWidth,\r
4485     oldv = gameInfo.variant;\r
4486 \r
4487     currentMove = forwardMostMove = backwardMostMove = 0;\r
4488     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request\r
4489 \r
4490     /* [AS] Initialize pv info list [HGM] and game status */\r
4491     {\r
4492         for( i=0; i<MAX_MOVES; i++ ) {\r
4493             pvInfoList[i].depth = 0;\r
4494             epStatus[i]=EP_NONE;\r
4495             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
4496         }\r
4497 \r
4498         initialRulePlies = 0; /* 50-move counter start */\r
4499 \r
4500         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4501         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4502     }\r
4503 \r
4504     \r
4505     /* [HGM] logic here is completely changed. In stead of full positions */\r
4506     /* the initialized data only consist of the two backranks. The switch */\r
4507     /* selects which one we will use, which is than copied to the Board   */\r
4508     /* initialPosition, which for the rest is initialized by Pawns and    */\r
4509     /* empty squares. This initial position is then copied to boards[0],  */\r
4510     /* possibly after shuffling, so that it remains available.            */\r
4511 \r
4512     gameInfo.holdingsWidth = 0; /* default board sizes */\r
4513     gameInfo.boardWidth    = 8;\r
4514     gameInfo.boardHeight   = 8;\r
4515     gameInfo.holdingsSize  = 0;\r
4516     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
4517     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
4518     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
4519 \r
4520     switch (gameInfo.variant) {\r
4521     case VariantFischeRandom:\r
4522       shuffleOpenings = TRUE;\r
4523     default:\r
4524       pieces = FIDEArray;\r
4525       break;\r
4526     case VariantShatranj:\r
4527       pieces = ShatranjArray;\r
4528       nrCastlingRights = 0;\r
4529       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
4530       break;\r
4531     case VariantTwoKings:\r
4532       pieces = twoKingsArray;\r
4533       break;\r
4534     case VariantCapaRandom:\r
4535       shuffleOpenings = TRUE;\r
4536     case VariantCapablanca:\r
4537       pieces = CapablancaArray;\r
4538       gameInfo.boardWidth = 10;\r
4539       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4540       break;\r
4541     case VariantGothic:\r
4542       pieces = GothicArray;\r
4543       gameInfo.boardWidth = 10;\r
4544       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4545       break;\r
4546     case VariantJanus:\r
4547       pieces = JanusArray;\r
4548       gameInfo.boardWidth = 10;\r
4549       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
4550       nrCastlingRights = 6;\r
4551         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4552         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4553         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;\r
4554         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4555         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4556         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;\r
4557       break;\r
4558     case VariantFalcon:\r
4559       pieces = FalconArray;\r
4560       gameInfo.boardWidth = 10;\r
4561       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4562       break;\r
4563     case VariantXiangqi:\r
4564       pieces = XiangqiArray;\r
4565       gameInfo.boardWidth  = 9;\r
4566       gameInfo.boardHeight = 10;\r
4567       nrCastlingRights = 0;\r
4568       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4569       break;\r
4570     case VariantShogi:\r
4571       pieces = ShogiArray;\r
4572       gameInfo.boardWidth  = 9;\r
4573       gameInfo.boardHeight = 9;\r
4574       gameInfo.holdingsSize = 7;\r
4575       nrCastlingRights = 0;\r
4576       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4577       break;\r
4578     case VariantCourier:\r
4579       pieces = CourierArray;\r
4580       gameInfo.boardWidth  = 12;\r
4581       nrCastlingRights = 0;\r
4582       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4583       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4584       break;\r
4585     case VariantKnightmate:\r
4586       pieces = KnightmateArray;\r
4587       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4588       break;\r
4589     case VariantFairy:\r
4590       pieces = fairyArray;\r
4591       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4592       break;\r
4593     case VariantGreat:\r
4594       pieces = GreatArray;\r
4595       gameInfo.boardWidth = 10;\r
4596       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");\r
4597       gameInfo.holdingsSize = 8;\r
4598       break;\r
4599     case VariantSuper:\r
4600       pieces = FIDEArray;\r
4601       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
4602       gameInfo.holdingsSize = 8;\r
4603       startedFromSetupPosition = TRUE;\r
4604       break;\r
4605     case VariantCrazyhouse:\r
4606     case VariantBughouse:\r
4607       pieces = FIDEArray;\r
4608       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4609       gameInfo.holdingsSize = 5;\r
4610       break;\r
4611     case VariantWildCastle:\r
4612       pieces = FIDEArray;\r
4613       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4614       shuffleOpenings = 1;\r
4615       break;\r
4616     case VariantNoCastle:\r
4617       pieces = FIDEArray;\r
4618       nrCastlingRights = 0;\r
4619       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4620       /* !!?unconstrained back-rank shuffle */\r
4621       shuffleOpenings = 1;\r
4622       break;\r
4623     }\r
4624 \r
4625     overrule = 0;\r
4626     if(appData.NrFiles >= 0) {\r
4627         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4628         gameInfo.boardWidth = appData.NrFiles;\r
4629     }\r
4630     if(appData.NrRanks >= 0) {\r
4631         gameInfo.boardHeight = appData.NrRanks;\r
4632     }\r
4633     if(appData.holdingsSize >= 0) {\r
4634         i = appData.holdingsSize;\r
4635         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4636         gameInfo.holdingsSize = i;\r
4637     }\r
4638     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4639     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4640         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);\r
4641 \r
4642     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4643     if(pawnRow < 1) pawnRow = 1;\r
4644 \r
4645     /* User pieceToChar list overrules defaults */\r
4646     if(appData.pieceToCharTable != NULL)\r
4647         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4648 \r
4649     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4650 \r
4651         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4652             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4653         for( i=0; i<BOARD_HEIGHT; i++ )\r
4654             initialPosition[i][j] = s;\r
4655 \r
4656         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4657         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4658         initialPosition[pawnRow][j] = WhitePawn;\r
4659         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4660         if(gameInfo.variant == VariantXiangqi) {\r
4661             if(j&1) {\r
4662                 initialPosition[pawnRow][j] = \r
4663                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4664                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4665                    initialPosition[2][j] = WhiteCannon;\r
4666                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4667                 }\r
4668             }\r
4669         }\r
4670         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4671     }\r
4672     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4673 \r
4674             j=BOARD_LEFT+1;\r
4675             initialPosition[1][j] = WhiteBishop;\r
4676             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4677             j=BOARD_RGHT-2;\r
4678             initialPosition[1][j] = WhiteRook;\r
4679             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4680     }\r
4681 \r
4682     if( nrCastlingRights == -1) {\r
4683         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4684         /*       This sets default castling rights from none to normal corners   */\r
4685         /* Variants with other castling rights must set them themselves above    */\r
4686         nrCastlingRights = 6;\r
4687        \r
4688         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4689         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4690         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4691         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4692         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4693         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4694      }\r
4695 \r
4696      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
4697      if(gameInfo.variant == VariantGreat) { // promotion commoners\r
4698         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;\r
4699         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;\r
4700         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;\r
4701         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;\r
4702      }\r
4703 #if 0\r
4704     if(gameInfo.variant == VariantFischeRandom) {\r
4705       if( appData.defaultFrcPosition < 0 ) {\r
4706         ShuffleFRC( initialPosition );\r
4707       }\r
4708       else {\r
4709         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4710       }\r
4711       startedFromSetupPosition = TRUE;\r
4712     } else \r
4713 #else\r
4714   if (appData.debugMode) {\r
4715     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
4716   }\r
4717     if(shuffleOpenings) {\r
4718         SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
4719         startedFromSetupPosition = TRUE;\r
4720     }\r
4721 #endif\r
4722     if(startedFromPositionFile) {\r
4723       /* [HGM] loadPos: use PositionFile for every new game */\r
4724       CopyBoard(initialPosition, filePosition);\r
4725       for(i=0; i<nrCastlingRights; i++)\r
4726           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4727       startedFromSetupPosition = TRUE;\r
4728     }\r
4729 \r
4730     CopyBoard(boards[0], initialPosition);\r
4731 \r
4732     if(oldx != gameInfo.boardWidth ||\r
4733        oldy != gameInfo.boardHeight ||\r
4734        oldh != gameInfo.holdingsWidth\r
4735 #ifdef GOTHIC\r
4736        || oldv == VariantGothic ||        // For licensing popups\r
4737        gameInfo.variant == VariantGothic\r
4738 #endif\r
4739 #ifdef FALCON\r
4740        || oldv == VariantFalcon ||\r
4741        gameInfo.variant == VariantFalcon\r
4742 #endif\r
4743                                          )\r
4744             InitDrawingSizes(-2 ,0);\r
4745 \r
4746     if (redraw)\r
4747       DrawPosition(TRUE, boards[currentMove]);\r
4748 }\r
4749 \r
4750 void\r
4751 SendBoard(cps, moveNum)\r
4752      ChessProgramState *cps;\r
4753      int moveNum;\r
4754 {\r
4755     char message[MSG_SIZ];\r
4756     \r
4757     if (cps->useSetboard) {\r
4758       char* fen = PositionToFEN(moveNum, cps->fenOverride);\r
4759       sprintf(message, "setboard %s\n", fen);\r
4760       SendToProgram(message, cps);\r
4761       free(fen);\r
4762 \r
4763     } else {\r
4764       ChessSquare *bp;\r
4765       int i, j;\r
4766       /* Kludge to set black to move, avoiding the troublesome and now\r
4767        * deprecated "black" command.\r
4768        */\r
4769       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4770 \r
4771       SendToProgram("edit\n", cps);\r
4772       SendToProgram("#\n", cps);\r
4773       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4774         bp = &boards[moveNum][i][BOARD_LEFT];\r
4775         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4776           if ((int) *bp < (int) BlackPawn) {\r
4777             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4778                     AAA + j, ONE + i);\r
4779             if(message[0] == '+' || message[0] == '~') {\r
4780                 sprintf(message, "%c%c%c+\n",\r
4781                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4782                         AAA + j, ONE + i);\r
4783             }\r
4784             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4785                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4786                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4787             }\r
4788             SendToProgram(message, cps);\r
4789           }\r
4790         }\r
4791       }\r
4792     \r
4793       SendToProgram("c\n", cps);\r
4794       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4795         bp = &boards[moveNum][i][BOARD_LEFT];\r
4796         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4797           if (((int) *bp != (int) EmptySquare)\r
4798               && ((int) *bp >= (int) BlackPawn)) {\r
4799             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4800                     AAA + j, ONE + i);\r
4801             if(message[0] == '+' || message[0] == '~') {\r
4802                 sprintf(message, "%c%c%c+\n",\r
4803                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4804                         AAA + j, ONE + i);\r
4805             }\r
4806             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4807                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4808                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4809             }\r
4810             SendToProgram(message, cps);\r
4811           }\r
4812         }\r
4813       }\r
4814     \r
4815       SendToProgram(".\n", cps);\r
4816     }\r
4817     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4818 }\r
4819 \r
4820 int\r
4821 IsPromotion(fromX, fromY, toX, toY)\r
4822      int fromX, fromY, toX, toY;\r
4823 {\r
4824     /* [HGM] add Shogi promotions */\r
4825     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4826     ChessSquare piece;\r
4827 \r
4828     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4829       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4830    /* [HGM] Note to self: line above also weeds out drops */\r
4831     piece = boards[currentMove][fromY][fromX];\r
4832     if(gameInfo.variant == VariantShogi) {\r
4833         promotionZoneSize = 3;\r
4834         highestPromotingPiece = (int)WhiteKing;\r
4835         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4836            and if in normal chess we then allow promotion to King, why not\r
4837            allow promotion of other piece in Shogi?                         */\r
4838     }\r
4839     if((int)piece >= BlackPawn) {\r
4840         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4841              return FALSE;\r
4842         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4843     } else {\r
4844         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4845            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4846     }\r
4847     return ( (int)piece <= highestPromotingPiece );\r
4848 }\r
4849 \r
4850 int\r
4851 InPalace(row, column)\r
4852      int row, column;\r
4853 {   /* [HGM] for Xiangqi */\r
4854     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4855          column < (BOARD_WIDTH + 4)/2 &&\r
4856          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4857     return FALSE;\r
4858 }\r
4859 \r
4860 int\r
4861 PieceForSquare (x, y)\r
4862      int x;\r
4863      int y;\r
4864 {\r
4865   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
4866      return -1;\r
4867   else\r
4868      return boards[currentMove][y][x];\r
4869 }\r
4870 \r
4871 int\r
4872 OKToStartUserMove(x, y)\r
4873      int x, y;\r
4874 {\r
4875     ChessSquare from_piece;\r
4876     int white_piece;\r
4877 \r
4878     if (matchMode) return FALSE;\r
4879     if (gameMode == EditPosition) return TRUE;\r
4880 \r
4881     if (x >= 0 && y >= 0)\r
4882       from_piece = boards[currentMove][y][x];\r
4883     else\r
4884       from_piece = EmptySquare;\r
4885 \r
4886     if (from_piece == EmptySquare) return FALSE;\r
4887 \r
4888     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4889       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4890 \r
4891     switch (gameMode) {\r
4892       case PlayFromGameFile:\r
4893       case AnalyzeFile:\r
4894       case TwoMachinesPlay:\r
4895       case EndOfGame:\r
4896         return FALSE;\r
4897 \r
4898       case IcsObserving:\r
4899       case IcsIdle:\r
4900         return FALSE;\r
4901 \r
4902       case MachinePlaysWhite:\r
4903       case IcsPlayingBlack:\r
4904         if (appData.zippyPlay) return FALSE;\r
4905         if (white_piece) {\r
4906             DisplayMoveError(_("You are playing Black"));\r
4907             return FALSE;\r
4908         }\r
4909         break;\r
4910 \r
4911       case MachinePlaysBlack:\r
4912       case IcsPlayingWhite:\r
4913         if (appData.zippyPlay) return FALSE;\r
4914         if (!white_piece) {\r
4915             DisplayMoveError(_("You are playing White"));\r
4916             return FALSE;\r
4917         }\r
4918         break;\r
4919 \r
4920       case EditGame:\r
4921         if (!white_piece && WhiteOnMove(currentMove)) {\r
4922             DisplayMoveError(_("It is White's turn"));\r
4923             return FALSE;\r
4924         }           \r
4925         if (white_piece && !WhiteOnMove(currentMove)) {\r
4926             DisplayMoveError(_("It is Black's turn"));\r
4927             return FALSE;\r
4928         }           \r
4929         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
4930             /* Editing correspondence game history */\r
4931             /* Could disallow this or prompt for confirmation */\r
4932             cmailOldMove = -1;\r
4933         }\r
4934         if (currentMove < forwardMostMove) {\r
4935             /* Discarding moves */\r
4936             /* Could prompt for confirmation here,\r
4937                but I don't think that's such a good idea */\r
4938             forwardMostMove = currentMove;\r
4939         }\r
4940         break;\r
4941 \r
4942       case BeginningOfGame:\r
4943         if (appData.icsActive) return FALSE;\r
4944         if (!appData.noChessProgram) {\r
4945             if (!white_piece) {\r
4946                 DisplayMoveError(_("You are playing White"));\r
4947                 return FALSE;\r
4948             }\r
4949         }\r
4950         break;\r
4951         \r
4952       case Training:\r
4953         if (!white_piece && WhiteOnMove(currentMove)) {\r
4954             DisplayMoveError(_("It is White's turn"));\r
4955             return FALSE;\r
4956         }           \r
4957         if (white_piece && !WhiteOnMove(currentMove)) {\r
4958             DisplayMoveError(_("It is Black's turn"));\r
4959             return FALSE;\r
4960         }           \r
4961         break;\r
4962 \r
4963       default:\r
4964       case IcsExamining:\r
4965         break;\r
4966     }\r
4967     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
4968         && gameMode != AnalyzeFile && gameMode != Training) {\r
4969         DisplayMoveError(_("Displayed position is not current"));\r
4970         return FALSE;\r
4971     }\r
4972     return TRUE;\r
4973 }\r
4974 \r
4975 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
4976 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
4977 int lastLoadGameUseList = FALSE;\r
4978 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
4979 ChessMove lastLoadGameStart = (ChessMove) 0;\r
4980 \r
4981 \r
4982 ChessMove\r
4983 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
4984      int fromX, fromY, toX, toY;\r
4985      int promoChar;\r
4986 {\r
4987     ChessMove moveType;\r
4988     ChessSquare pdown, pup;\r
4989 \r
4990     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
4991     if ((fromX == toX) && (fromY == toY)) {\r
4992         return ImpossibleMove;\r
4993     }\r
4994 \r
4995     /* [HGM] suppress all moves into holdings area and guard band */\r
4996     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
4997             return ImpossibleMove;\r
4998 \r
4999     /* [HGM] <sameColor> moved to here from winboard.c */\r
5000     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
5001     pdown = boards[currentMove][fromY][fromX];\r
5002     pup = boards[currentMove][toY][toX];\r
5003     if (    gameMode != EditPosition &&\r
5004             (WhitePawn <= pdown && pdown < BlackPawn &&\r
5005              WhitePawn <= pup && pup < BlackPawn  ||\r
5006              BlackPawn <= pdown && pdown < EmptySquare &&\r
5007              BlackPawn <= pup && pup < EmptySquare \r
5008             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
5009                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
5010                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
5011         )           )\r
5012          return ImpossibleMove;\r
5013 \r
5014     /* Check if the user is playing in turn.  This is complicated because we\r
5015        let the user "pick up" a piece before it is his turn.  So the piece he\r
5016        tried to pick up may have been captured by the time he puts it down!\r
5017        Therefore we use the color the user is supposed to be playing in this\r
5018        test, not the color of the piece that is currently on the starting\r
5019        square---except in EditGame mode, where the user is playing both\r
5020        sides; fortunately there the capture race can't happen.  (It can\r
5021        now happen in IcsExamining mode, but that's just too bad.  The user\r
5022        will get a somewhat confusing message in that case.)\r
5023        */\r
5024 \r
5025     switch (gameMode) {\r
5026       case PlayFromGameFile:\r
5027       case AnalyzeFile:\r
5028       case TwoMachinesPlay:\r
5029       case EndOfGame:\r
5030       case IcsObserving:\r
5031       case IcsIdle:\r
5032         /* We switched into a game mode where moves are not accepted,\r
5033            perhaps while the mouse button was down. */\r
5034         return ImpossibleMove;\r
5035 \r
5036       case MachinePlaysWhite:\r
5037         /* User is moving for Black */\r
5038         if (WhiteOnMove(currentMove)) {\r
5039             DisplayMoveError(_("It is White's turn"));\r
5040             return ImpossibleMove;\r
5041         }\r
5042         break;\r
5043 \r
5044       case MachinePlaysBlack:\r
5045         /* User is moving for White */\r
5046         if (!WhiteOnMove(currentMove)) {\r
5047             DisplayMoveError(_("It is Black's turn"));\r
5048             return ImpossibleMove;\r
5049         }\r
5050         break;\r
5051 \r
5052       case EditGame:\r
5053       case IcsExamining:\r
5054       case BeginningOfGame:\r
5055       case AnalyzeMode:\r
5056       case Training:\r
5057         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
5058             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
5059             /* User is moving for Black */\r
5060             if (WhiteOnMove(currentMove)) {\r
5061                 DisplayMoveError(_("It is White's turn"));\r
5062                 return ImpossibleMove;\r
5063             }\r
5064         } else {\r
5065             /* User is moving for White */\r
5066             if (!WhiteOnMove(currentMove)) {\r
5067                 DisplayMoveError(_("It is Black's turn"));\r
5068                 return ImpossibleMove;\r
5069             }\r
5070         }\r
5071         break;\r
5072 \r
5073       case IcsPlayingBlack:\r
5074         /* User is moving for Black */\r
5075         if (WhiteOnMove(currentMove)) {\r
5076             if (!appData.premove) {\r
5077                 DisplayMoveError(_("It is White's turn"));\r
5078             } else if (toX >= 0 && toY >= 0) {\r
5079                 premoveToX = toX;\r
5080                 premoveToY = toY;\r
5081                 premoveFromX = fromX;\r
5082                 premoveFromY = fromY;\r
5083                 premovePromoChar = promoChar;\r
5084                 gotPremove = 1;\r
5085                 if (appData.debugMode) \r
5086                     fprintf(debugFP, "Got premove: fromX %d,"\r
5087                             "fromY %d, toX %d, toY %d\n",\r
5088                             fromX, fromY, toX, toY);\r
5089             }\r
5090             return ImpossibleMove;\r
5091         }\r
5092         break;\r
5093 \r
5094       case IcsPlayingWhite:\r
5095         /* User is moving for White */\r
5096         if (!WhiteOnMove(currentMove)) {\r
5097             if (!appData.premove) {\r
5098                 DisplayMoveError(_("It is Black's turn"));\r
5099             } else if (toX >= 0 && toY >= 0) {\r
5100                 premoveToX = toX;\r
5101                 premoveToY = toY;\r
5102                 premoveFromX = fromX;\r
5103                 premoveFromY = fromY;\r
5104                 premovePromoChar = promoChar;\r
5105                 gotPremove = 1;\r
5106                 if (appData.debugMode) \r
5107                     fprintf(debugFP, "Got premove: fromX %d,"\r
5108                             "fromY %d, toX %d, toY %d\n",\r
5109                             fromX, fromY, toX, toY);\r
5110             }\r
5111             return ImpossibleMove;\r
5112         }\r
5113         break;\r
5114 \r
5115       default:\r
5116         break;\r
5117 \r
5118       case EditPosition:\r
5119         /* EditPosition, empty square, or different color piece;\r
5120            click-click move is possible */\r
5121         if (toX == -2 || toY == -2) {\r
5122             boards[0][fromY][fromX] = EmptySquare;\r
5123             return AmbiguousMove;\r
5124         } else if (toX >= 0 && toY >= 0) {\r
5125             boards[0][toY][toX] = boards[0][fromY][fromX];\r
5126             boards[0][fromY][fromX] = EmptySquare;\r
5127             return AmbiguousMove;\r
5128         }\r
5129         return ImpossibleMove;\r
5130     }\r
5131 \r
5132     /* [HGM] If move started in holdings, it means a drop */\r
5133     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
5134          if( pup != EmptySquare ) return ImpossibleMove;\r
5135          if(appData.testLegality) {\r
5136              /* it would be more logical if LegalityTest() also figured out\r
5137               * which drops are legal. For now we forbid pawns on back rank.\r
5138               * Shogi is on its own here...\r
5139               */\r
5140              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
5141                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
5142                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
5143          }\r
5144          return WhiteDrop; /* Not needed to specify white or black yet */\r
5145     }\r
5146 \r
5147     userOfferedDraw = FALSE;\r
5148         \r
5149     /* [HGM] always test for legality, to get promotion info */\r
5150     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
5151                           epStatus[currentMove], castlingRights[currentMove],\r
5152                                          fromY, fromX, toY, toX, promoChar);\r
5153 \r
5154     /* [HGM] but possibly ignore an IllegalMove result */\r
5155     if (appData.testLegality) {\r
5156         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
5157             DisplayMoveError(_("Illegal move"));\r
5158             return ImpossibleMove;\r
5159         }\r
5160     }\r
5161 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);\r
5162     return moveType;\r
5163     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
5164        function is made into one that returns an OK move type if FinishMove\r
5165        should be called. This to give the calling driver routine the\r
5166        opportunity to finish the userMove input with a promotion popup,\r
5167        without bothering the user with this for invalid or illegal moves */\r
5168 \r
5169 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
5170 }\r
5171 \r
5172 /* Common tail of UserMoveEvent and DropMenuEvent */\r
5173 int\r
5174 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
5175      ChessMove moveType;\r
5176      int fromX, fromY, toX, toY;\r
5177      /*char*/int promoChar;\r
5178 {\r
5179     char *bookHit = 0;\r
5180 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);\r
5181     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { \r
5182         // [HGM] superchess: suppress promotions to non-available piece\r
5183         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5184         if(WhiteOnMove(currentMove)) {\r
5185             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;\r
5186         } else {\r
5187             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;\r
5188         }\r
5189     }\r
5190 \r
5191     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion\r
5192        move type in caller when we know the move is a legal promotion */\r
5193     if(moveType == NormalMove && promoChar)\r
5194         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
5195 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);\r
5196     /* [HGM] convert drag-and-drop piece drops to standard form */\r
5197     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
5198          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
5199          fromX = boards[currentMove][fromY][fromX];\r
5200          fromY = DROP_RANK;\r
5201     }\r
5202 \r
5203     /* [HGM] <popupFix> The following if has been moved here from\r
5204        UserMoveEvent(). Because it seemed to belon here (why not allow\r
5205        piece drops in training games?), and because it can only be\r
5206        performed after it is known to what we promote. */\r
5207     if (gameMode == Training) {\r
5208       /* compare the move played on the board to the next move in the\r
5209        * game. If they match, display the move and the opponent's response. \r
5210        * If they don't match, display an error message.\r
5211        */\r
5212       int saveAnimate;\r
5213       Board testBoard;\r
5214       CopyBoard(testBoard, boards[currentMove]);\r
5215       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
5216 \r
5217       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
5218         ForwardInner(currentMove+1);\r
5219 \r
5220         /* Autoplay the opponent's response.\r
5221          * if appData.animate was TRUE when Training mode was entered,\r
5222          * the response will be animated.\r
5223          */\r
5224         saveAnimate = appData.animate;\r
5225         appData.animate = animateTraining;\r
5226         ForwardInner(currentMove+1);\r
5227         appData.animate = saveAnimate;\r
5228 \r
5229         /* check for the end of the game */\r
5230         if (currentMove >= forwardMostMove) {\r
5231           gameMode = PlayFromGameFile;\r
5232           ModeHighlight();\r
5233           SetTrainingModeOff();\r
5234           DisplayInformation(_("End of game"));\r
5235         }\r
5236       } else {\r
5237         DisplayError(_("Incorrect move"), 0);\r
5238       }\r
5239       return 1;\r
5240     }\r
5241 \r
5242   /* Ok, now we know that the move is good, so we can kill\r
5243      the previous line in Analysis Mode */\r
5244   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
5245     forwardMostMove = currentMove;\r
5246   }\r
5247 \r
5248   /* If we need the chess program but it's dead, restart it */\r
5249   ResurrectChessProgram();\r
5250 \r
5251   /* A user move restarts a paused game*/\r
5252   if (pausing)\r
5253     PauseEvent();\r
5254 \r
5255   thinkOutput[0] = NULLCHAR;\r
5256 \r
5257   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
5258 \r
5259     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) \r
5260                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
5261         // [HGM] superchess: take promotion piece out of holdings\r
5262         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5263         if(WhiteOnMove(forwardMostMove-1)) {\r
5264             if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])\r
5265                 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;\r
5266         } else {\r
5267             if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])\r
5268                 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;\r
5269         }\r
5270     }\r
5271 \r
5272   if (gameMode == BeginningOfGame) {\r
5273     if (appData.noChessProgram) {\r
5274       gameMode = EditGame;\r
5275       SetGameInfo();\r
5276     } else {\r
5277       char buf[MSG_SIZ];\r
5278       gameMode = MachinePlaysBlack;\r
5279       StartClocks();\r
5280       SetGameInfo();\r
5281       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
5282       DisplayTitle(buf);\r
5283       if (first.sendName) {\r
5284         sprintf(buf, "name %s\n", gameInfo.white);\r
5285         SendToProgram(buf, &first);\r
5286       }\r
5287       StartClocks();\r
5288     }\r
5289     ModeHighlight();\r
5290   }\r
5291 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);\r
5292   /* Relay move to ICS or chess engine */\r
5293   if (appData.icsActive) {\r
5294     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
5295         gameMode == IcsExamining) {\r
5296       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5297       ics_user_moved = 1;\r
5298     }\r
5299   } else {\r
5300     if (first.sendTime && (gameMode == BeginningOfGame ||\r
5301                            gameMode == MachinePlaysWhite ||\r
5302                            gameMode == MachinePlaysBlack)) {\r
5303       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
5304     }\r
5305     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
5306          // [HGM] book: if program might be playing, let it use book\r
5307         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);\r
5308         first.maybeThinking = TRUE;\r
5309     } else SendMoveToProgram(forwardMostMove-1, &first);\r
5310     if (currentMove == cmailOldMove + 1) {\r
5311       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
5312     }\r
5313   }\r
5314 \r
5315   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5316 \r
5317   switch (gameMode) {\r
5318   case EditGame:\r
5319     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
5320                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
5321     case MT_NONE:\r
5322     case MT_CHECK:\r
5323       break;\r
5324     case MT_CHECKMATE:\r
5325       if (WhiteOnMove(currentMove)) {\r
5326         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
5327       } else {\r
5328         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
5329       }\r
5330       break;\r
5331     case MT_STALEMATE:\r
5332       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
5333       break;\r
5334     }\r
5335     break;\r
5336     \r
5337   case MachinePlaysBlack:\r
5338   case MachinePlaysWhite:\r
5339     /* disable certain menu options while machine is thinking */\r
5340     SetMachineThinkingEnables();\r
5341     break;\r
5342 \r
5343   default:\r
5344     break;\r
5345   }\r
5346 \r
5347   if(bookHit) { // [HGM] book: simulate book reply\r
5348         static char bookMove[MSG_SIZ]; // a bit generous?\r
5349 \r
5350         programStats.nodes = programStats.depth = programStats.time = \r
5351         programStats.score = programStats.got_only_move = 0;\r
5352         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
5353 \r
5354         strcpy(bookMove, "move ");\r
5355         strcat(bookMove, bookHit);\r
5356         HandleMachineMove(bookMove, &first);\r
5357   }\r
5358   return 1;\r
5359 }\r
5360 \r
5361 void\r
5362 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
5363      int fromX, fromY, toX, toY;\r
5364      int promoChar;\r
5365 {\r
5366     /* [HGM] This routine was added to allow calling of its two logical\r
5367        parts from other modules in the old way. Before, UserMoveEvent()\r
5368        automatically called FinishMove() if the move was OK, and returned\r
5369        otherwise. I separated the two, in order to make it possible to\r
5370        slip a promotion popup in between. But that it always needs two\r
5371        calls, to the first part, (now called UserMoveTest() ), and to\r
5372        FinishMove if the first part succeeded. Calls that do not need\r
5373        to do anything in between, can call this routine the old way. \r
5374     */\r
5375     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
5376 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);\r
5377     if(moveType != ImpossibleMove)\r
5378         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
5379 }\r
5380 \r
5381 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
5382 {\r
5383 //    char * hint = lastHint;\r
5384     FrontEndProgramStats stats;\r
5385 \r
5386     stats.which = cps == &first ? 0 : 1;\r
5387     stats.depth = cpstats->depth;\r
5388     stats.nodes = cpstats->nodes;\r
5389     stats.score = cpstats->score;\r
5390     stats.time = cpstats->time;\r
5391     stats.pv = cpstats->movelist;\r
5392     stats.hint = lastHint;\r
5393     stats.an_move_index = 0;\r
5394     stats.an_move_count = 0;\r
5395 \r
5396     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
5397         stats.hint = cpstats->move_name;\r
5398         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
5399         stats.an_move_count = cpstats->nr_moves;\r
5400     }\r
5401 \r
5402     SetProgramStats( &stats );\r
5403 }\r
5404 \r
5405 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)\r
5406 {   // [HGM] book: this routine intercepts moves to simulate book replies\r
5407     char *bookHit = NULL;\r
5408 \r
5409     //first determine if the incoming move brings opponent into his book\r
5410     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
5411         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
5412     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");\r
5413     if(bookHit != NULL && !cps->bookSuspend) {\r
5414         // make sure opponent is not going to reply after receiving move to book position\r
5415         SendToProgram("force\n", cps);\r
5416         cps->bookSuspend = TRUE; // flag indicating it has to be restarted\r
5417     }\r
5418     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move\r
5419     // now arrange restart after book miss\r
5420     if(bookHit) {\r
5421         // after a book hit we never send 'go', and the code after the call to this routine\r
5422         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').\r
5423         char buf[MSG_SIZ];\r
5424         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(\r
5425         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it\r
5426         SendToProgram(buf, cps);\r
5427         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'\r
5428     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine\r
5429         SendToProgram("go\n", cps);\r
5430         cps->bookSuspend = FALSE; // after a 'go' we are never suspended\r
5431     } else { // 'go' might be sent based on 'firstMove' after this routine returns\r
5432         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return\r
5433             SendToProgram("go\n", cps); \r
5434         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss\r
5435     }\r
5436     return bookHit; // notify caller of hit, so it can take action to send move to opponent\r
5437 }\r
5438 \r
5439 char *savedMessage;\r
5440 ChessProgramState *savedState;\r
5441 void DeferredBookMove(void)\r
5442 {\r
5443         if(savedState->lastPing != savedState->lastPong)\r
5444                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
5445         else\r
5446         HandleMachineMove(savedMessage, savedState);\r
5447 }\r
5448 \r
5449 void\r
5450 HandleMachineMove(message, cps)\r
5451      char *message;\r
5452      ChessProgramState *cps;\r
5453 {\r
5454     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
5455     char realname[MSG_SIZ];\r
5456     int fromX, fromY, toX, toY;\r
5457     ChessMove moveType;\r
5458     char promoChar;\r
5459     char *p;\r
5460     int machineWhite;\r
5461     char *bookHit;\r
5462 \r
5463 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit\r
5464     /*\r
5465      * Kludge to ignore BEL characters\r
5466      */\r
5467     while (*message == '\007') message++;\r
5468 \r
5469     /*\r
5470      * [HGM] engine debug message: ignore lines starting with '#' character\r
5471      */\r
5472     if(cps->debug && *message == '#') return;\r
5473 \r
5474     /*\r
5475      * Look for book output\r
5476      */\r
5477     if (cps == &first && bookRequested) {\r
5478         if (message[0] == '\t' || message[0] == ' ') {\r
5479             /* Part of the book output is here; append it */\r
5480             strcat(bookOutput, message);\r
5481             strcat(bookOutput, "  \n");\r
5482             return;\r
5483         } else if (bookOutput[0] != NULLCHAR) {\r
5484             /* All of book output has arrived; display it */\r
5485             char *p = bookOutput;\r
5486             while (*p != NULLCHAR) {\r
5487                 if (*p == '\t') *p = ' ';\r
5488                 p++;\r
5489             }\r
5490             DisplayInformation(bookOutput);\r
5491             bookRequested = FALSE;\r
5492             /* Fall through to parse the current output */\r
5493         }\r
5494     }\r
5495 \r
5496     /*\r
5497      * Look for machine move.\r
5498      */\r
5499     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
5500         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
5501     {\r
5502         /* This method is only useful on engines that support ping */\r
5503         if (cps->lastPing != cps->lastPong) {\r
5504           if (gameMode == BeginningOfGame) {\r
5505             /* Extra move from before last new; ignore */\r
5506             if (appData.debugMode) {\r
5507                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5508             }\r
5509           } else {\r
5510             if (appData.debugMode) {\r
5511                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5512                         cps->which, gameMode);\r
5513             }\r
5514 \r
5515             SendToProgram("undo\n", cps);\r
5516           }\r
5517           return;\r
5518         }\r
5519 \r
5520         switch (gameMode) {\r
5521           case BeginningOfGame:\r
5522             /* Extra move from before last reset; ignore */\r
5523             if (appData.debugMode) {\r
5524                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5525             }\r
5526             return;\r
5527 \r
5528           case EndOfGame:\r
5529           case IcsIdle:\r
5530           default:\r
5531             /* Extra move after we tried to stop.  The mode test is\r
5532                not a reliable way of detecting this problem, but it's\r
5533                the best we can do on engines that don't support ping.\r
5534             */\r
5535             if (appData.debugMode) {\r
5536                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5537                         cps->which, gameMode);\r
5538             }\r
5539             SendToProgram("undo\n", cps);\r
5540             return;\r
5541 \r
5542           case MachinePlaysWhite:\r
5543           case IcsPlayingWhite:\r
5544             machineWhite = TRUE;\r
5545             break;\r
5546 \r
5547           case MachinePlaysBlack:\r
5548           case IcsPlayingBlack:\r
5549             machineWhite = FALSE;\r
5550             break;\r
5551 \r
5552           case TwoMachinesPlay:\r
5553             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5554             break;\r
5555         }\r
5556         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5557             if (appData.debugMode) {\r
5558                 fprintf(debugFP,\r
5559                         "Ignoring move out of turn by %s, gameMode %d"\r
5560                         ", forwardMost %d\n",\r
5561                         cps->which, gameMode, forwardMostMove);\r
5562             }\r
5563             return;\r
5564         }\r
5565 \r
5566     if (appData.debugMode) { int f = forwardMostMove;\r
5567         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5568                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5569     }\r
5570         if(cps->alphaRank) AlphaRank(machineMove, 4);\r
5571         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5572                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5573             /* Machine move could not be parsed; ignore it. */\r
5574             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),\r
5575                     machineMove, cps->which);\r
5576             DisplayError(buf1, 0);\r
5577             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",\r
5578                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);\r
5579             if (gameMode == TwoMachinesPlay) {\r
5580               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5581                        buf1, GE_XBOARD);\r
5582             }\r
5583             return;\r
5584         }\r
5585 \r
5586         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5587         /* So we have to redo legality test with true e.p. status here,  */\r
5588         /* to make sure an illegal e.p. capture does not slip through,   */\r
5589         /* to cause a forfeit on a justified illegal-move complaint      */\r
5590         /* of the opponent.                                              */\r
5591         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5592             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5593                                                               ) {\r
5594            ChessMove moveType;\r
5595            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5596                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5597                              fromY, fromX, toY, toX, promoChar);\r
5598             if (appData.debugMode) {\r
5599                 int i;\r
5600                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5601                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5602                 fprintf(debugFP, "castling rights\n");\r
5603             }\r
5604             if(moveType == IllegalMove) {\r
5605                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5606                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5607                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5608                            buf1, GE_XBOARD);\r
5609            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
5610            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5611               when they shouldn't (like TSCP-Gothic) */\r
5612            switch(moveType) {\r
5613              case WhiteASideCastleFR:\r
5614              case BlackASideCastleFR:\r
5615                toX+=2;\r
5616                currentMoveString[2]++;\r
5617                break;\r
5618              case WhiteHSideCastleFR:\r
5619              case BlackHSideCastleFR:\r
5620                toX--;\r
5621                currentMoveString[2]--;\r
5622                break;\r
5623              default: ; // nothing to do, but suppresses warning of pedantic compilers\r
5624            }\r
5625         }\r
5626         hintRequested = FALSE;\r
5627         lastHint[0] = NULLCHAR;\r
5628         bookRequested = FALSE;\r
5629         /* Program may be pondering now */\r
5630         cps->maybeThinking = TRUE;\r
5631         if (cps->sendTime == 2) cps->sendTime = 1;\r
5632         if (cps->offeredDraw) cps->offeredDraw--;\r
5633 \r
5634 #if ZIPPY\r
5635         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5636             first.initDone) {\r
5637           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5638           ics_user_moved = 1;\r
5639           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
5640                 char buf[3*MSG_SIZ];\r
5641 \r
5642                 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",\r
5643                         programStats.depth,\r
5644                         programStats.score / 100.,\r
5645                         programStats.time / 100.,\r
5646                         u64ToDouble(programStats.nodes),\r
5647                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),\r
5648                         programStats.movelist);\r
5649                 SendToICS(buf);\r
5650           }\r
5651         }\r
5652 #endif\r
5653         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5654         strcpy(machineMove, currentMoveString);\r
5655         strcat(machineMove, "\n");\r
5656         strcpy(moveList[forwardMostMove], machineMove);\r
5657 \r
5658         /* [AS] Save move info and clear stats for next move */\r
5659         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5660         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5661         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5662         ClearProgramStats();\r
5663         thinkOutput[0] = NULLCHAR;\r
5664         hiddenThinkOutputState = 0;\r
5665 \r
5666         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5667 \r
5668         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5669         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5670             int count = 0;\r
5671 \r
5672             while( count < adjudicateLossPlies ) {\r
5673                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5674 \r
5675                 if( count & 1 ) {\r
5676                     score = -score; /* Flip score for winning side */\r
5677                 }\r
5678 \r
5679                 if( score > adjudicateLossThreshold ) {\r
5680                     break;\r
5681                 }\r
5682 \r
5683                 count++;\r
5684             }\r
5685 \r
5686             if( count >= adjudicateLossPlies ) {\r
5687                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5688 \r
5689                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5690                     "Xboard adjudication", \r
5691                     GE_XBOARD );\r
5692 \r
5693                 return;\r
5694             }\r
5695         }\r
5696 \r
5697         if( gameMode == TwoMachinesPlay ) {\r
5698           // [HGM] some adjudications useful with buggy engines\r
5699             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
5700           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {\r
5701 \r
5702             if(appData.testLegality)\r
5703             // don't wait for engine to announce game end if we can judge ourselves\r
5704             switch (MateTest(boards[forwardMostMove],\r
5705                                  PosFlags(forwardMostMove), epFile,\r
5706                                        castlingRights[forwardMostMove]) ) {\r
5707               case MT_NONE:\r
5708               case MT_CHECK:\r
5709               default:\r
5710                 break;\r
5711               case MT_STALEMATE:\r
5712                 epStatus[forwardMostMove] = EP_STALEMATE;\r
5713                 if(appData.checkMates) {\r
5714                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5715                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5716                     if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantSuicide\r
5717                                                          || gameInfo.variant == VariantGiveaway) // [HGM] losers:\r
5718                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, // stalemated side wins!\r
5719                                 "Xboard adjudication: Stalemate", GE_XBOARD );\r
5720                     else\r
5721                         GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate", GE_XBOARD );\r
5722                     return;\r
5723                 }\r
5724                 break;\r
5725               case MT_CHECKMATE:\r
5726                 epStatus[forwardMostMove] = EP_CHECKMATE;\r
5727                 if(appData.checkMates) {\r
5728                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5729                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5730                     GameEnds( WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers) // [HGM] losers:\r
5731                              ? BlackWins : WhiteWins,            // reverse the result ( A!=1 is !A for a boolean)\r
5732                              "Xboard adjudication: Checkmate", GE_XBOARD );\r
5733                     return;\r
5734                 }\r
5735                 break;\r
5736             }\r
5737 \r
5738             if( appData.testLegality )\r
5739             {   /* [HGM] Some more adjudications for obstinate engines */\r
5740                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5741                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,\r
5742                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;\r
5743                 static int moveCount = 6;\r
5744 \r
5745                 /* First absolutely insufficient mating material. Count what is on board. */\r
5746                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5747                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5748                     int m=i;\r
5749 \r
5750                     switch((int) p)\r
5751                     {   /* count B,N,R and other of each side */\r
5752                         case WhiteKing:\r
5753                         case BlackKing:\r
5754                              NrK++; break; // [HGM] atomic: count Kings\r
5755                         case WhiteKnight:\r
5756                              NrWN++; break;\r
5757                         case WhiteBishop:\r
5758                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5759                              bishopsColor |= 1 << ((i^j)&1);\r
5760                              NrWB++; break;\r
5761                         case BlackKnight:\r
5762                              NrBN++; break;\r
5763                         case BlackBishop:\r
5764                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5765                              bishopsColor |= 1 << ((i^j)&1);\r
5766                              NrBB++; break;\r
5767                         case WhiteRook:\r
5768                              NrWR++; break;\r
5769                         case BlackRook:\r
5770                              NrBR++; break;\r
5771                         case WhiteQueen:\r
5772                              NrWQ++; break;\r
5773                         case BlackQueen:\r
5774                              NrBQ++; break;\r
5775                         case EmptySquare: \r
5776                              break;\r
5777                         case BlackPawn:\r
5778                              m = 7-i;\r
5779                         case WhitePawn:\r
5780                              PawnAdvance += m; NrPawns++;\r
5781                     }\r
5782                     NrPieces += (p != EmptySquare);\r
5783                     NrW += ((int)p < (int)BlackPawn);\r
5784                     if(gameInfo.variant == VariantXiangqi && \r
5785                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {\r
5786                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces\r
5787                         NrW -= ((int)p < (int)BlackPawn);\r
5788                     }\r
5789                 }\r
5790 \r
5791                 if(gameInfo.variant == VariantAtomic && NrK < 2) {\r
5792                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal\r
5793                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated\r
5794                      if(appData.checkMates) {\r
5795                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move\r
5796                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5797                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5798                                                         "Xboard adjudication: King destroyed", GE_XBOARD );\r
5799                          return;\r
5800                      }\r
5801                 }\r
5802 \r
5803                 /* Bare King in Shatranj (loses) or Losers (wins) */\r
5804                 if( NrW == 1 || NrPieces - NrW == 1) {\r
5805                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)\r
5806                      epStatus[forwardMostMove] = EP_STALEMATE; // kludge to make position claimable as win\r
5807                      if(appData.checkMates) {\r
5808                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5809                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5810                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5811                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5812                          return;\r
5813                      }\r
5814                   } else\r
5815                   if( gameInfo.variant == VariantShatranj && --bare < 0)\r
5816                   {    /* bare King */\r
5817                         epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as win for stm\r
5818                         if(appData.checkMates) {\r
5819                             /* but only adjudicate if adjudication enabled */\r
5820                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move\r
5821                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5822                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
5823                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5824                             return;\r
5825                         }\r
5826                   }\r
5827                 } else bare = 1;\r
5828 \r
5829 \r
5830                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && \r
5831                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible\r
5832                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||\r
5833                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color\r
5834                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5835 \r
5836                      /* always flag draws, for judging claims */\r
5837                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5838 \r
5839                      if(appData.materialDraws) {\r
5840                          /* but only adjudicate them if adjudication enabled */\r
5841                          SendToProgram("force\n", cps->other); // suppress reply\r
5842                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */\r
5843                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5844                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5845                          return;\r
5846                      }\r
5847                 }\r
5848 \r
5849                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5850                 if(NrPieces == 4 && \r
5851                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5852                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5853                    || NrWN==2 || NrBN==2     /* KNNK */\r
5854                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5855                   ) ) {\r
5856                      if(--moveCount < 0 && appData.trivialDraws)\r
5857                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5858                           SendToProgram("force\n", cps->other); // suppress reply\r
5859                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5860                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5861                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5862                           return;\r
5863                      }\r
5864                 } else moveCount = 6;\r
5865             }\r
5866           }\r
5867 #if 1\r
5868     if (appData.debugMode) { int i;\r
5869       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5870               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5871               appData.drawRepeats);\r
5872       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5873            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5874 \r
5875     }\r
5876 #endif\r
5877                 /* Check for rep-draws */\r
5878                 count = 0;\r
5879                 for(k = forwardMostMove-2;\r
5880                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5881                         epStatus[k] < EP_UNKNOWN &&\r
5882                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5883                     k-=2)\r
5884                 {   int rights=0;\r
5885 #if 0\r
5886     if (appData.debugMode) {\r
5887       fprintf(debugFP, " loop\n");\r
5888     }\r
5889 #endif\r
5890                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5891 #if 0\r
5892     if (appData.debugMode) {\r
5893       fprintf(debugFP, "match\n");\r
5894     }\r
5895 #endif\r
5896                         /* compare castling rights */\r
5897                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5898                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5899                                 rights++; /* King lost rights, while rook still had them */\r
5900                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5901                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5902                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5903                                    rights++; /* but at least one rook lost them */\r
5904                         }\r
5905                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5906                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5907                                 rights++; \r
5908                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5909                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5910                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5911                                    rights++;\r
5912                         }\r
5913 #if 0\r
5914     if (appData.debugMode) {\r
5915       for(i=0; i<nrCastlingRights; i++)\r
5916       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5917     }\r
5918 \r
5919     if (appData.debugMode) {\r
5920       fprintf(debugFP, " %d %d\n", rights, k);\r
5921     }\r
5922 #endif\r
5923                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5924                             && appData.drawRepeats > 1) {\r
5925                              /* adjudicate after user-specified nr of repeats */\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                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { \r
5930                                 // [HGM] xiangqi: check for forbidden perpetuals\r
5931                                 int m, ourPerpetual = 1, hisPerpetual = 1;\r
5932                                 for(m=forwardMostMove; m>k; m-=2) {\r
5933                                     if(MateTest(boards[m], PosFlags(m), \r
5934                                                         EP_NONE, castlingRights[m]) != MT_CHECK)\r
5935                                         ourPerpetual = 0; // the current mover did not always check\r
5936                                     if(MateTest(boards[m-1], PosFlags(m-1), \r
5937                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)\r
5938                                         hisPerpetual = 0; // the opponent did not always check\r
5939                                 }\r
5940                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",\r
5941                                                                         ourPerpetual, hisPerpetual);\r
5942                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
5943                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5944                                            "Xboard adjudication: perpetual checking", GE_XBOARD );\r
5945                                     return;\r
5946                                 }\r
5947                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet\r
5948                                     break; // (or we would have caught him before). Abort repetition-checking loop.\r
5949                                 // Now check for perpetual chases\r
5950                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase\r
5951                                     hisPerpetual = PerpetualChase(k, forwardMostMove);\r
5952                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);\r
5953                                     if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
5954                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5955                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );\r
5956                                         return;\r
5957                                     }\r
5958                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet\r
5959                                         break; // Abort repetition-checking loop.\r
5960                                 }\r
5961                                 // if neither of us is checking or chasing all the time, or both are, it is draw\r
5962                              }\r
5963                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
5964                              return;\r
5965                         }\r
5966                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
5967                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
5968                     }\r
5969                 }\r
5970 \r
5971                 /* Now we test for 50-move draws. Determine ply count */\r
5972                 count = forwardMostMove;\r
5973                 /* look for last irreversble move */\r
5974                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
5975                     count--;\r
5976                 /* if we hit starting position, add initial plies */\r
5977                 if( count == backwardMostMove )\r
5978                     count -= initialRulePlies;\r
5979                 count = forwardMostMove - count; \r
5980                 if( count >= 100)\r
5981                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
5982                          /* this is used to judge if draw claims are legal */\r
5983                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
5984                          SendToProgram("force\n", cps->other); // suppress reply\r
5985                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5986                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5987                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
5988                          return;\r
5989                 }\r
5990 \r
5991                 /* if draw offer is pending, treat it as a draw claim\r
5992                  * when draw condition present, to allow engines a way to\r
5993                  * claim draws before making their move to avoid a race\r
5994                  * condition occurring after their move\r
5995                  */\r
5996                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
5997                          char *p = NULL;\r
5998                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
5999                              p = "Draw claim: 50-move rule";\r
6000                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
6001                              p = "Draw claim: 3-fold repetition";\r
6002                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
6003                              p = "Draw claim: insufficient mating material";\r
6004                          if( p != NULL ) {\r
6005                              SendToProgram("force\n", cps->other); // suppress reply\r
6006                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6007                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
6008                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6009                              return;\r
6010                          }\r
6011                 }\r
6012 \r
6013 \r
6014                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
6015                     SendToProgram("force\n", cps->other); // suppress reply\r
6016                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6017                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6018 \r
6019                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
6020 \r
6021                     return;\r
6022                 }\r
6023         }\r
6024 \r
6025         bookHit = NULL;\r
6026         if (gameMode == TwoMachinesPlay) {\r
6027             /* [HGM] relaying draw offers moved to after reception of move */\r
6028             /* and interpreting offer as claim if it brings draw condition */\r
6029             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
6030                 SendToProgram("draw\n", cps->other);\r
6031             }\r
6032             if (cps->other->sendTime) {\r
6033                 SendTimeRemaining(cps->other,\r
6034                                   cps->other->twoMachinesColor[0] == 'w');\r
6035             }\r
6036             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);\r
6037             if (firstMove && !bookHit) {\r
6038                 firstMove = FALSE;\r
6039                 if (cps->other->useColors) {\r
6040                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
6041                 }\r
6042                 SendToProgram("go\n", cps->other);\r
6043             }\r
6044             cps->other->maybeThinking = TRUE;\r
6045         }\r
6046 \r
6047         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6048         \r
6049         if (!pausing && appData.ringBellAfterMoves) {\r
6050             RingBell();\r
6051         }\r
6052 \r
6053         /* \r
6054          * Reenable menu items that were disabled while\r
6055          * machine was thinking\r
6056          */\r
6057         if (gameMode != TwoMachinesPlay)\r
6058             SetUserThinkingEnables();\r
6059 \r
6060         // [HGM] book: after book hit opponent has received move and is now in force mode\r
6061         // force the book reply into it, and then fake that it outputted this move by jumping\r
6062         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move\r
6063         if(bookHit) {\r
6064                 static char bookMove[MSG_SIZ]; // a bit generous?\r
6065 \r
6066                 strcpy(bookMove, "move ");\r
6067                 strcat(bookMove, bookHit);\r
6068                 message = bookMove;\r
6069                 cps = cps->other;\r
6070                 programStats.nodes = programStats.depth = programStats.time = \r
6071                 programStats.score = programStats.got_only_move = 0;\r
6072                 sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
6073 \r
6074                 if(cps->lastPing != cps->lastPong) {\r
6075                     savedMessage = message; // args for deferred call\r
6076                     savedState = cps;\r
6077                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
6078                     return;\r
6079                 }\r
6080                 goto FakeBookMove;\r
6081         }\r
6082 \r
6083         return;\r
6084     }\r
6085 \r
6086     /* Set special modes for chess engines.  Later something general\r
6087      *  could be added here; for now there is just one kludge feature,\r
6088      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
6089      *  when "xboard" is given as an interactive command.\r
6090      */\r
6091     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
6092         cps->useSigint = FALSE;\r
6093         cps->useSigterm = FALSE;\r
6094     }\r
6095 \r
6096     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
6097      * want this, I was asked to put it in, and obliged.\r
6098      */\r
6099     if (!strncmp(message, "setboard ", 9)) {\r
6100         Board initial_position; int i;\r
6101 \r
6102         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
6103 \r
6104         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
6105             DisplayError(_("Bad FEN received from engine"), 0);\r
6106             return ;\r
6107         } else {\r
6108            Reset(FALSE, FALSE);\r
6109            CopyBoard(boards[0], initial_position);\r
6110            initialRulePlies = FENrulePlies;\r
6111            epStatus[0] = FENepStatus;\r
6112            for( i=0; i<nrCastlingRights; i++ )\r
6113                 castlingRights[0][i] = FENcastlingRights[i];\r
6114            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
6115            else gameMode = MachinePlaysBlack;                 \r
6116            DrawPosition(FALSE, boards[currentMove]);\r
6117         }\r
6118         return;\r
6119     }\r
6120 \r
6121     /*\r
6122      * Look for communication commands\r
6123      */\r
6124     if (!strncmp(message, "telluser ", 9)) {\r
6125         DisplayNote(message + 9);\r
6126         return;\r
6127     }\r
6128     if (!strncmp(message, "tellusererror ", 14)) {\r
6129         DisplayError(message + 14, 0);\r
6130         return;\r
6131     }\r
6132     if (!strncmp(message, "tellopponent ", 13)) {\r
6133       if (appData.icsActive) {\r
6134         if (loggedOn) {\r
6135           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
6136           SendToICS(buf1);\r
6137         }\r
6138       } else {\r
6139         DisplayNote(message + 13);\r
6140       }\r
6141       return;\r
6142     }\r
6143     if (!strncmp(message, "tellothers ", 11)) {\r
6144       if (appData.icsActive) {\r
6145         if (loggedOn) {\r
6146           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
6147           SendToICS(buf1);\r
6148         }\r
6149       }\r
6150       return;\r
6151     }\r
6152     if (!strncmp(message, "tellall ", 8)) {\r
6153       if (appData.icsActive) {\r
6154         if (loggedOn) {\r
6155           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
6156           SendToICS(buf1);\r
6157         }\r
6158       } else {\r
6159         DisplayNote(message + 8);\r
6160       }\r
6161       return;\r
6162     }\r
6163     if (strncmp(message, "warning", 7) == 0) {\r
6164         /* Undocumented feature, use tellusererror in new code */\r
6165         DisplayError(message, 0);\r
6166         return;\r
6167     }\r
6168     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
6169         strcpy(realname, cps->tidy);\r
6170         strcat(realname, " query");\r
6171         AskQuestion(realname, buf2, buf1, cps->pr);\r
6172         return;\r
6173     }\r
6174     /* Commands from the engine directly to ICS.  We don't allow these to be \r
6175      *  sent until we are logged on. Crafty kibitzes have been known to \r
6176      *  interfere with the login process.\r
6177      */\r
6178     if (loggedOn) {\r
6179         if (!strncmp(message, "tellics ", 8)) {\r
6180             SendToICS(message + 8);\r
6181             SendToICS("\n");\r
6182             return;\r
6183         }\r
6184         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
6185             SendToICS(ics_prefix);\r
6186             SendToICS(message + 15);\r
6187             SendToICS("\n");\r
6188             return;\r
6189         }\r
6190         /* The following are for backward compatibility only */\r
6191         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
6192             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
6193             SendToICS(ics_prefix);\r
6194             SendToICS(message);\r
6195             SendToICS("\n");\r
6196             return;\r
6197         }\r
6198     }\r
6199     if (strncmp(message, "feature ", 8) == 0) {\r
6200       ParseFeatures(message+8, cps);\r
6201     }\r
6202     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
6203         return;\r
6204     }\r
6205     /*\r
6206      * If the move is illegal, cancel it and redraw the board.\r
6207      * Also deal with other error cases.  Matching is rather loose\r
6208      * here to accommodate engines written before the spec.\r
6209      */\r
6210     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
6211         strncmp(message, "Error", 5) == 0) {\r
6212         if (StrStr(message, "name") || \r
6213             StrStr(message, "rating") || StrStr(message, "?") ||\r
6214             StrStr(message, "result") || StrStr(message, "board") ||\r
6215             StrStr(message, "bk") || StrStr(message, "computer") ||\r
6216             StrStr(message, "variant") || StrStr(message, "hint") ||\r
6217             StrStr(message, "random") || StrStr(message, "depth") ||\r
6218             StrStr(message, "accepted")) {\r
6219             return;\r
6220         }\r
6221         if (StrStr(message, "protover")) {\r
6222           /* Program is responding to input, so it's apparently done\r
6223              initializing, and this error message indicates it is\r
6224              protocol version 1.  So we don't need to wait any longer\r
6225              for it to initialize and send feature commands. */\r
6226           FeatureDone(cps, 1);\r
6227           cps->protocolVersion = 1;\r
6228           return;\r
6229         }\r
6230         cps->maybeThinking = FALSE;\r
6231 \r
6232         if (StrStr(message, "draw")) {\r
6233             /* Program doesn't have "draw" command */\r
6234             cps->sendDrawOffers = 0;\r
6235             return;\r
6236         }\r
6237         if (cps->sendTime != 1 &&\r
6238             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
6239           /* Program apparently doesn't have "time" or "otim" command */\r
6240           cps->sendTime = 0;\r
6241           return;\r
6242         }\r
6243         if (StrStr(message, "analyze")) {\r
6244             cps->analysisSupport = FALSE;\r
6245             cps->analyzing = FALSE;\r
6246             Reset(FALSE, TRUE);\r
6247             sprintf(buf2, _("%s does not support analysis"), cps->tidy);\r
6248             DisplayError(buf2, 0);\r
6249             return;\r
6250         }\r
6251         if (StrStr(message, "(no matching move)st")) {\r
6252           /* Special kludge for GNU Chess 4 only */\r
6253           cps->stKludge = TRUE;\r
6254           SendTimeControl(cps, movesPerSession, timeControl,\r
6255                           timeIncrement, appData.searchDepth,\r
6256                           searchTime);\r
6257           return;\r
6258         }\r
6259         if (StrStr(message, "(no matching move)sd")) {\r
6260           /* Special kludge for GNU Chess 4 only */\r
6261           cps->sdKludge = TRUE;\r
6262           SendTimeControl(cps, movesPerSession, timeControl,\r
6263                           timeIncrement, appData.searchDepth,\r
6264                           searchTime);\r
6265           return;\r
6266         }\r
6267         if (!StrStr(message, "llegal")) {\r
6268             return;\r
6269         }\r
6270         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6271             gameMode == IcsIdle) return;\r
6272         if (forwardMostMove <= backwardMostMove) return;\r
6273 #if 0\r
6274         /* Following removed: it caused a bug where a real illegal move\r
6275            message in analyze mored would be ignored. */\r
6276         if (cps == &first && programStats.ok_to_send == 0) {\r
6277             /* Bogus message from Crafty responding to "."  This filtering\r
6278                can miss some of the bad messages, but fortunately the bug \r
6279                is fixed in current Crafty versions, so it doesn't matter. */\r
6280             return;\r
6281         }\r
6282 #endif\r
6283         if (pausing) PauseEvent();\r
6284         if (gameMode == PlayFromGameFile) {\r
6285             /* Stop reading this game file */\r
6286             gameMode = EditGame;\r
6287             ModeHighlight();\r
6288         }\r
6289         currentMove = --forwardMostMove;\r
6290         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
6291         SwitchClocks();\r
6292         DisplayBothClocks();\r
6293         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),\r
6294                 parseList[currentMove], cps->which);\r
6295         DisplayMoveError(buf1);\r
6296         DrawPosition(FALSE, boards[currentMove]);\r
6297 \r
6298         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
6299         /* only passes fully legal moves                            */\r
6300         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
6301             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
6302                                 "False illegal-move claim", GE_XBOARD );\r
6303         }\r
6304         return;\r
6305     }\r
6306     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
6307         /* Program has a broken "time" command that\r
6308            outputs a string not ending in newline.\r
6309            Don't use it. */\r
6310         cps->sendTime = 0;\r
6311     }\r
6312     \r
6313     /*\r
6314      * If chess program startup fails, exit with an error message.\r
6315      * Attempts to recover here are futile.\r
6316      */\r
6317     if ((StrStr(message, "unknown host") != NULL)\r
6318         || (StrStr(message, "No remote directory") != NULL)\r
6319         || (StrStr(message, "not found") != NULL)\r
6320         || (StrStr(message, "No such file") != NULL)\r
6321         || (StrStr(message, "can't alloc") != NULL)\r
6322         || (StrStr(message, "Permission denied") != NULL)) {\r
6323 \r
6324         cps->maybeThinking = FALSE;\r
6325         sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),\r
6326                 cps->which, cps->program, cps->host, message);\r
6327         RemoveInputSource(cps->isr);\r
6328         DisplayFatalError(buf1, 0, 1);\r
6329         return;\r
6330     }\r
6331     \r
6332     /* \r
6333      * Look for hint output\r
6334      */\r
6335     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
6336         if (cps == &first && hintRequested) {\r
6337             hintRequested = FALSE;\r
6338             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
6339                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
6340                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
6341                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
6342                                     fromY, fromX, toY, toX, promoChar, buf1);\r
6343                 sprintf(buf2, _("Hint: %s"), buf1);\r
6344                 DisplayInformation(buf2);\r
6345             } else {\r
6346                 /* Hint move could not be parsed!? */\r
6347                 sprintf(buf2,\r
6348                         _("Illegal hint move \"%s\"\nfrom %s chess program"),\r
6349                         buf1, cps->which);\r
6350                 DisplayError(buf2, 0);\r
6351             }\r
6352         } else {\r
6353             strcpy(lastHint, buf1);\r
6354         }\r
6355         return;\r
6356     }\r
6357 \r
6358     /*\r
6359      * Ignore other messages if game is not in progress\r
6360      */\r
6361     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6362         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
6363 \r
6364     /*\r
6365      * look for win, lose, draw, or draw offer\r
6366      */\r
6367     if (strncmp(message, "1-0", 3) == 0) {\r
6368         char *p, *q, *r = "";\r
6369         p = strchr(message, '{');\r
6370         if (p) {\r
6371             q = strchr(p, '}');\r
6372             if (q) {\r
6373                 *q = NULLCHAR;\r
6374                 r = p + 1;\r
6375             }\r
6376         }\r
6377         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
6378         return;\r
6379     } else if (strncmp(message, "0-1", 3) == 0) {\r
6380         char *p, *q, *r = "";\r
6381         p = strchr(message, '{');\r
6382         if (p) {\r
6383             q = strchr(p, '}');\r
6384             if (q) {\r
6385                 *q = NULLCHAR;\r
6386                 r = p + 1;\r
6387             }\r
6388         }\r
6389         /* Kludge for Arasan 4.1 bug */\r
6390         if (strcmp(r, "Black resigns") == 0) {\r
6391             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
6392             return;\r
6393         }\r
6394         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
6395         return;\r
6396     } else if (strncmp(message, "1/2", 3) == 0) {\r
6397         char *p, *q, *r = "";\r
6398         p = strchr(message, '{');\r
6399         if (p) {\r
6400             q = strchr(p, '}');\r
6401             if (q) {\r
6402                 *q = NULLCHAR;\r
6403                 r = p + 1;\r
6404             }\r
6405         }\r
6406             \r
6407         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
6408         return;\r
6409 \r
6410     } else if (strncmp(message, "White resign", 12) == 0) {\r
6411         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6412         return;\r
6413     } else if (strncmp(message, "Black resign", 12) == 0) {\r
6414         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6415         return;\r
6416     } else if (strncmp(message, "White matches", 13) == 0 ||\r
6417                strncmp(message, "Black matches", 13) == 0   ) {\r
6418         /* [HGM] ignore GNUShogi noises */\r
6419         return;\r
6420     } else if (strncmp(message, "White", 5) == 0 &&\r
6421                message[5] != '(' &&\r
6422                StrStr(message, "Black") == NULL) {\r
6423         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6424         return;\r
6425     } else if (strncmp(message, "Black", 5) == 0 &&\r
6426                message[5] != '(') {\r
6427         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6428         return;\r
6429     } else if (strcmp(message, "resign") == 0 ||\r
6430                strcmp(message, "computer resigns") == 0) {\r
6431         switch (gameMode) {\r
6432           case MachinePlaysBlack:\r
6433           case IcsPlayingBlack:\r
6434             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
6435             break;\r
6436           case MachinePlaysWhite:\r
6437           case IcsPlayingWhite:\r
6438             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
6439             break;\r
6440           case TwoMachinesPlay:\r
6441             if (cps->twoMachinesColor[0] == 'w')\r
6442               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6443             else\r
6444               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6445             break;\r
6446           default:\r
6447             /* can't happen */\r
6448             break;\r
6449         }\r
6450         return;\r
6451     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
6452         switch (gameMode) {\r
6453           case MachinePlaysBlack:\r
6454           case IcsPlayingBlack:\r
6455             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6456             break;\r
6457           case MachinePlaysWhite:\r
6458           case IcsPlayingWhite:\r
6459             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
6460             break;\r
6461           case TwoMachinesPlay:\r
6462             if (cps->twoMachinesColor[0] == 'w')\r
6463               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6464             else\r
6465               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6466             break;\r
6467           default:\r
6468             /* can't happen */\r
6469             break;\r
6470         }\r
6471         return;\r
6472     } else if (strncmp(message, "computer mates", 14) == 0) {\r
6473         switch (gameMode) {\r
6474           case MachinePlaysBlack:\r
6475           case IcsPlayingBlack:\r
6476             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
6477             break;\r
6478           case MachinePlaysWhite:\r
6479           case IcsPlayingWhite:\r
6480             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6481             break;\r
6482           case TwoMachinesPlay:\r
6483             if (cps->twoMachinesColor[0] == 'w')\r
6484               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6485             else\r
6486               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6487             break;\r
6488           default:\r
6489             /* can't happen */\r
6490             break;\r
6491         }\r
6492         return;\r
6493     } else if (strncmp(message, "checkmate", 9) == 0) {\r
6494         if (WhiteOnMove(forwardMostMove)) {\r
6495             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6496         } else {\r
6497             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6498         }\r
6499         return;\r
6500     } else if (strstr(message, "Draw") != NULL ||\r
6501                strstr(message, "game is a draw") != NULL) {\r
6502         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
6503         return;\r
6504     } else if (strstr(message, "offer") != NULL &&\r
6505                strstr(message, "draw") != NULL) {\r
6506 #if ZIPPY\r
6507         if (appData.zippyPlay && first.initDone) {\r
6508             /* Relay offer to ICS */\r
6509             SendToICS(ics_prefix);\r
6510             SendToICS("draw\n");\r
6511         }\r
6512 #endif\r
6513         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
6514         if (gameMode == TwoMachinesPlay) {\r
6515             if (cps->other->offeredDraw) {\r
6516                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6517             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
6518             /* until after we also have move, to see if it is really claim */\r
6519             }\r
6520 #if 0\r
6521               else {\r
6522                 if (cps->other->sendDrawOffers) {\r
6523                     SendToProgram("draw\n", cps->other);\r
6524                 }\r
6525             }\r
6526 #endif\r
6527         } else if (gameMode == MachinePlaysWhite ||\r
6528                    gameMode == MachinePlaysBlack) {\r
6529           if (userOfferedDraw) {\r
6530             DisplayInformation(_("Machine accepts your draw offer"));\r
6531             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6532           } else {\r
6533             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));\r
6534           }\r
6535         }\r
6536     }\r
6537 \r
6538     \r
6539     /*\r
6540      * Look for thinking output\r
6541      */\r
6542     if ( appData.showThinking // [HGM] thinking: test all options that cause this output\r
6543           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
6544                                 ) {\r
6545         int plylev, mvleft, mvtot, curscore, time;\r
6546         char mvname[MOVE_LEN];\r
6547         u64 nodes; // [DM]\r
6548         char plyext;\r
6549         int ignore = FALSE;\r
6550         int prefixHint = FALSE;\r
6551         mvname[0] = NULLCHAR;\r
6552 \r
6553         switch (gameMode) {\r
6554           case MachinePlaysBlack:\r
6555           case IcsPlayingBlack:\r
6556             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6557             break;\r
6558           case MachinePlaysWhite:\r
6559           case IcsPlayingWhite:\r
6560             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6561             break;\r
6562           case AnalyzeMode:\r
6563           case AnalyzeFile:\r
6564             break;\r
6565           case IcsObserving: /* [DM] icsEngineAnalyze */\r
6566             if (!appData.icsEngineAnalyze) ignore = TRUE;\r
6567             break;\r
6568           case TwoMachinesPlay:\r
6569             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
6570                 ignore = TRUE;\r
6571             }\r
6572             break;\r
6573           default:\r
6574             ignore = TRUE;\r
6575             break;\r
6576         }\r
6577 \r
6578         if (!ignore) {\r
6579             buf1[0] = NULLCHAR;\r
6580             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6581                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
6582 \r
6583                 if (plyext != ' ' && plyext != '\t') {\r
6584                     time *= 100;\r
6585                 }\r
6586 \r
6587                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6588                 if( cps->scoreIsAbsolute && \r
6589                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
6590                 {\r
6591                     curscore = -curscore;\r
6592                 }\r
6593 \r
6594 \r
6595                 programStats.depth = plylev;\r
6596                 programStats.nodes = nodes;\r
6597                 programStats.time = time;\r
6598                 programStats.score = curscore;\r
6599                 programStats.got_only_move = 0;\r
6600 \r
6601                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
6602                         int ticklen;\r
6603 \r
6604                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time\r
6605                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time\r
6606                         if(WhiteOnMove(forwardMostMove)) \r
6607                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
6608                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
6609                 }\r
6610 \r
6611                 /* Buffer overflow protection */\r
6612                 if (buf1[0] != NULLCHAR) {\r
6613                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
6614                         && appData.debugMode) {\r
6615                         fprintf(debugFP,\r
6616                                 "PV is too long; using the first %d bytes.\n",\r
6617                                 sizeof(programStats.movelist) - 1);\r
6618                     }\r
6619 \r
6620                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
6621                 } else {\r
6622                     sprintf(programStats.movelist, " no PV\n");\r
6623                 }\r
6624 \r
6625                 if (programStats.seen_stat) {\r
6626                     programStats.ok_to_send = 1;\r
6627                 }\r
6628 \r
6629                 if (strchr(programStats.movelist, '(') != NULL) {\r
6630                     programStats.line_is_book = 1;\r
6631                     programStats.nr_moves = 0;\r
6632                     programStats.moves_left = 0;\r
6633                 } else {\r
6634                     programStats.line_is_book = 0;\r
6635                 }\r
6636 \r
6637                 SendProgramStatsToFrontend( cps, &programStats );\r
6638 \r
6639                 /* \r
6640                     [AS] Protect the thinkOutput buffer from overflow... this\r
6641                     is only useful if buf1 hasn't overflowed first!\r
6642                 */\r
6643                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
6644                         plylev, \r
6645                         (gameMode == TwoMachinesPlay ?\r
6646                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
6647                         ((double) curscore) / 100.0,\r
6648                         prefixHint ? lastHint : "",\r
6649                         prefixHint ? " " : "" );\r
6650 \r
6651                 if( buf1[0] != NULLCHAR ) {\r
6652                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
6653 \r
6654                     if( strlen(buf1) > max_len ) {\r
6655                         if( appData.debugMode) {\r
6656                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
6657                         }\r
6658                         buf1[max_len+1] = '\0';\r
6659                     }\r
6660 \r
6661                     strcat( thinkOutput, buf1 );\r
6662                 }\r
6663 \r
6664                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode\r
6665                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6666                     DisplayMove(currentMove - 1);\r
6667                     DisplayAnalysis();\r
6668                 }\r
6669                 return;\r
6670 \r
6671             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6672                 /* crafty (9.25+) says "(only move) <move>"\r
6673                  * if there is only 1 legal move\r
6674                  */\r
6675                 sscanf(p, "(only move) %s", buf1);\r
6676                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6677                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6678                 programStats.depth = 1;\r
6679                 programStats.nr_moves = 1;\r
6680                 programStats.moves_left = 1;\r
6681                 programStats.nodes = 1;\r
6682                 programStats.time = 1;\r
6683                 programStats.got_only_move = 1;\r
6684 \r
6685                 /* Not really, but we also use this member to\r
6686                    mean "line isn't going to change" (Crafty\r
6687                    isn't searching, so stats won't change) */\r
6688                 programStats.line_is_book = 1;\r
6689 \r
6690                 SendProgramStatsToFrontend( cps, &programStats );\r
6691                 \r
6692                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || \r
6693                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6694                     DisplayMove(currentMove - 1);\r
6695                     DisplayAnalysis();\r
6696                 }\r
6697                 return;\r
6698             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",\r
6699                               &time, &nodes, &plylev, &mvleft,\r
6700                               &mvtot, mvname) >= 5) {\r
6701                 /* The stat01: line is from Crafty (9.29+) in response\r
6702                    to the "." command */\r
6703                 programStats.seen_stat = 1;\r
6704                 cps->maybeThinking = TRUE;\r
6705 \r
6706                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6707                   return;\r
6708 \r
6709                 programStats.depth = plylev;\r
6710                 programStats.time = time;\r
6711                 programStats.nodes = nodes;\r
6712                 programStats.moves_left = mvleft;\r
6713                 programStats.nr_moves = mvtot;\r
6714                 strcpy(programStats.move_name, mvname);\r
6715                 programStats.ok_to_send = 1;\r
6716                 programStats.movelist[0] = '\0';\r
6717 \r
6718                 SendProgramStatsToFrontend( cps, &programStats );\r
6719 \r
6720                 DisplayAnalysis();\r
6721                 return;\r
6722 \r
6723             } else if (strncmp(message,"++",2) == 0) {\r
6724                 /* Crafty 9.29+ outputs this */\r
6725                 programStats.got_fail = 2;\r
6726                 return;\r
6727 \r
6728             } else if (strncmp(message,"--",2) == 0) {\r
6729                 /* Crafty 9.29+ outputs this */\r
6730                 programStats.got_fail = 1;\r
6731                 return;\r
6732 \r
6733             } else if (thinkOutput[0] != NULLCHAR &&\r
6734                        strncmp(message, "    ", 4) == 0) {\r
6735                 unsigned message_len;\r
6736 \r
6737                 p = message;\r
6738                 while (*p && *p == ' ') p++;\r
6739 \r
6740                 message_len = strlen( p );\r
6741 \r
6742                 /* [AS] Avoid buffer overflow */\r
6743                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6744                     strcat(thinkOutput, " ");\r
6745                     strcat(thinkOutput, p);\r
6746                 }\r
6747 \r
6748                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6749                     strcat(programStats.movelist, " ");\r
6750                     strcat(programStats.movelist, p);\r
6751                 }\r
6752 \r
6753                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||\r
6754                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6755                     DisplayMove(currentMove - 1);\r
6756                     DisplayAnalysis();\r
6757                 }\r
6758                 return;\r
6759             }\r
6760         }\r
6761         else {\r
6762             buf1[0] = NULLCHAR;\r
6763 \r
6764             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6765                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6766             {\r
6767                 ChessProgramStats cpstats;\r
6768 \r
6769                 if (plyext != ' ' && plyext != '\t') {\r
6770                     time *= 100;\r
6771                 }\r
6772 \r
6773                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6774                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6775                     curscore = -curscore;\r
6776                 }\r
6777 \r
6778                 cpstats.depth = plylev;\r
6779                 cpstats.nodes = nodes;\r
6780                 cpstats.time = time;\r
6781                 cpstats.score = curscore;\r
6782                 cpstats.got_only_move = 0;\r
6783                 cpstats.movelist[0] = '\0';\r
6784 \r
6785                 if (buf1[0] != NULLCHAR) {\r
6786                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6787                 }\r
6788 \r
6789                 cpstats.ok_to_send = 0;\r
6790                 cpstats.line_is_book = 0;\r
6791                 cpstats.nr_moves = 0;\r
6792                 cpstats.moves_left = 0;\r
6793 \r
6794                 SendProgramStatsToFrontend( cps, &cpstats );\r
6795             }\r
6796         }\r
6797     }\r
6798 }\r
6799 \r
6800 \r
6801 /* Parse a game score from the character string "game", and\r
6802    record it as the history of the current game.  The game\r
6803    score is NOT assumed to start from the standard position. \r
6804    The display is not updated in any way.\r
6805    */\r
6806 void\r
6807 ParseGameHistory(game)\r
6808      char *game;\r
6809 {\r
6810     ChessMove moveType;\r
6811     int fromX, fromY, toX, toY, boardIndex;\r
6812     char promoChar;\r
6813     char *p, *q;\r
6814     char buf[MSG_SIZ];\r
6815 \r
6816     if (appData.debugMode)\r
6817       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6818 \r
6819     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6820     gameInfo.site = StrSave(appData.icsHost);\r
6821     gameInfo.date = PGNDate();\r
6822     gameInfo.round = StrSave("-");\r
6823 \r
6824     /* Parse out names of players */\r
6825     while (*game == ' ') game++;\r
6826     p = buf;\r
6827     while (*game != ' ') *p++ = *game++;\r
6828     *p = NULLCHAR;\r
6829     gameInfo.white = StrSave(buf);\r
6830     while (*game == ' ') game++;\r
6831     p = buf;\r
6832     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6833     *p = NULLCHAR;\r
6834     gameInfo.black = StrSave(buf);\r
6835 \r
6836     /* Parse moves */\r
6837     boardIndex = blackPlaysFirst ? 1 : 0;\r
6838     yynewstr(game);\r
6839     for (;;) {\r
6840         yyboardindex = boardIndex;\r
6841         moveType = (ChessMove) yylex();\r
6842         switch (moveType) {\r
6843           case IllegalMove:             /* maybe suicide chess, etc. */\r
6844   if (appData.debugMode) {\r
6845     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6846     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6847     setbuf(debugFP, NULL);\r
6848   }\r
6849           case WhitePromotionChancellor:\r
6850           case BlackPromotionChancellor:\r
6851           case WhitePromotionArchbishop:\r
6852           case BlackPromotionArchbishop:\r
6853           case WhitePromotionQueen:\r
6854           case BlackPromotionQueen:\r
6855           case WhitePromotionRook:\r
6856           case BlackPromotionRook:\r
6857           case WhitePromotionBishop:\r
6858           case BlackPromotionBishop:\r
6859           case WhitePromotionKnight:\r
6860           case BlackPromotionKnight:\r
6861           case WhitePromotionKing:\r
6862           case BlackPromotionKing:\r
6863           case NormalMove:\r
6864           case WhiteCapturesEnPassant:\r
6865           case BlackCapturesEnPassant:\r
6866           case WhiteKingSideCastle:\r
6867           case WhiteQueenSideCastle:\r
6868           case BlackKingSideCastle:\r
6869           case BlackQueenSideCastle:\r
6870           case WhiteKingSideCastleWild:\r
6871           case WhiteQueenSideCastleWild:\r
6872           case BlackKingSideCastleWild:\r
6873           case BlackQueenSideCastleWild:\r
6874           /* PUSH Fabien */\r
6875           case WhiteHSideCastleFR:\r
6876           case WhiteASideCastleFR:\r
6877           case BlackHSideCastleFR:\r
6878           case BlackASideCastleFR:\r
6879           /* POP Fabien */\r
6880             fromX = currentMoveString[0] - AAA;\r
6881             fromY = currentMoveString[1] - ONE;\r
6882             toX = currentMoveString[2] - AAA;\r
6883             toY = currentMoveString[3] - ONE;\r
6884             promoChar = currentMoveString[4];\r
6885             break;\r
6886           case WhiteDrop:\r
6887           case BlackDrop:\r
6888             fromX = moveType == WhiteDrop ?\r
6889               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6890             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6891             fromY = DROP_RANK;\r
6892             toX = currentMoveString[2] - AAA;\r
6893             toY = currentMoveString[3] - ONE;\r
6894             promoChar = NULLCHAR;\r
6895             break;\r
6896           case AmbiguousMove:\r
6897             /* bug? */\r
6898             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);\r
6899   if (appData.debugMode) {\r
6900     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
6901     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6902     setbuf(debugFP, NULL);\r
6903   }\r
6904             DisplayError(buf, 0);\r
6905             return;\r
6906           case ImpossibleMove:\r
6907             /* bug? */\r
6908             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);\r
6909   if (appData.debugMode) {\r
6910     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
6911     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6912     setbuf(debugFP, NULL);\r
6913   }\r
6914             DisplayError(buf, 0);\r
6915             return;\r
6916           case (ChessMove) 0:   /* end of file */\r
6917             if (boardIndex < backwardMostMove) {\r
6918                 /* Oops, gap.  How did that happen? */\r
6919                 DisplayError(_("Gap in move list"), 0);\r
6920                 return;\r
6921             }\r
6922             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
6923             if (boardIndex > forwardMostMove) {\r
6924                 forwardMostMove = boardIndex;\r
6925             }\r
6926             return;\r
6927           case ElapsedTime:\r
6928             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
6929                 strcat(parseList[boardIndex-1], " ");\r
6930                 strcat(parseList[boardIndex-1], yy_text);\r
6931             }\r
6932             continue;\r
6933           case Comment:\r
6934           case PGNTag:\r
6935           case NAG:\r
6936           default:\r
6937             /* ignore */\r
6938             continue;\r
6939           case WhiteWins:\r
6940           case BlackWins:\r
6941           case GameIsDrawn:\r
6942           case GameUnfinished:\r
6943             if (gameMode == IcsExamining) {\r
6944                 if (boardIndex < backwardMostMove) {\r
6945                     /* Oops, gap.  How did that happen? */\r
6946                     return;\r
6947                 }\r
6948                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6949                 return;\r
6950             }\r
6951             gameInfo.result = moveType;\r
6952             p = strchr(yy_text, '{');\r
6953             if (p == NULL) p = strchr(yy_text, '(');\r
6954             if (p == NULL) {\r
6955                 p = yy_text;\r
6956                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
6957             } else {\r
6958                 q = strchr(p, *p == '{' ? '}' : ')');\r
6959                 if (q != NULL) *q = NULLCHAR;\r
6960                 p++;\r
6961             }\r
6962             gameInfo.resultDetails = StrSave(p);\r
6963             continue;\r
6964         }\r
6965         if (boardIndex >= forwardMostMove &&\r
6966             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
6967             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6968             return;\r
6969         }\r
6970         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
6971                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
6972                                  parseList[boardIndex]);\r
6973         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
6974         /* currentMoveString is set as a side-effect of yylex */\r
6975         strcpy(moveList[boardIndex], currentMoveString);\r
6976         strcat(moveList[boardIndex], "\n");\r
6977         boardIndex++;\r
6978         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
6979         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
6980                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
6981           case MT_NONE:\r
6982           case MT_STALEMATE:\r
6983           default:\r
6984             break;\r
6985           case MT_CHECK:\r
6986             if(gameInfo.variant != VariantShogi)\r
6987                 strcat(parseList[boardIndex - 1], "+");\r
6988             break;\r
6989           case MT_CHECKMATE:\r
6990             strcat(parseList[boardIndex - 1], "#");\r
6991             break;\r
6992         }\r
6993     }\r
6994 }\r
6995 \r
6996 \r
6997 /* Apply a move to the given board  */\r
6998 void\r
6999 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
7000      int fromX, fromY, toX, toY;\r
7001      int promoChar;\r
7002      Board board;\r
7003 {\r
7004   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
7005 \r
7006     /* [HGM] compute & store e.p. status and castling rights for new position */\r
7007     /* if we are updating a board for which those exist (i.e. in boards[])    */\r
7008     if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)\r
7009     { int i;\r
7010 \r
7011       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
7012       oldEP = epStatus[p-1];\r
7013       epStatus[p] = EP_NONE;\r
7014 \r
7015       if( board[toY][toX] != EmptySquare ) \r
7016            epStatus[p] = EP_CAPTURE;  \r
7017 \r
7018       if( board[fromY][fromX] == WhitePawn ) {\r
7019            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7020                epStatus[p] = EP_PAWN_MOVE;\r
7021            if( toY-fromY==2) {\r
7022                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
7023                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7024                       epStatus[p] = toX | berolina;\r
7025                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
7026                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7027                       epStatus[p] = toX;\r
7028            }\r
7029       } else \r
7030       if( board[fromY][fromX] == BlackPawn ) {\r
7031            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7032                epStatus[p] = EP_PAWN_MOVE; \r
7033            if( toY-fromY== -2) {\r
7034                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
7035                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7036                       epStatus[p] = toX | berolina;\r
7037                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
7038                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7039                       epStatus[p] = toX;\r
7040            }\r
7041        }\r
7042 \r
7043        for(i=0; i<nrCastlingRights; i++) {\r
7044            castlingRights[p][i] = castlingRights[p-1][i];\r
7045            if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||\r
7046               castlingRights[p][i] == toX   && castlingRank[i] == toY   \r
7047              ) castlingRights[p][i] = -1; // revoke for moved or captured piece\r
7048        }\r
7049 \r
7050     }\r
7051 \r
7052   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
7053   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
7054        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
7055          \r
7056   if (fromX == toX && fromY == toY) return;\r
7057 \r
7058   if (fromY == DROP_RANK) {\r
7059         /* must be first */\r
7060         piece = board[toY][toX] = (ChessSquare) fromX;\r
7061   } else {\r
7062      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
7063      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
7064      if(gameInfo.variant == VariantKnightmate)\r
7065          king += (int) WhiteUnicorn - (int) WhiteKing;\r
7066 \r
7067     /* Code added by Tord: */\r
7068     /* FRC castling assumed when king captures friendly rook. */\r
7069     if (board[fromY][fromX] == WhiteKing &&\r
7070              board[toY][toX] == WhiteRook) {\r
7071       board[fromY][fromX] = EmptySquare;\r
7072       board[toY][toX] = EmptySquare;\r
7073       if(toX > fromX) {\r
7074         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
7075       } else {\r
7076         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
7077       }\r
7078     } else if (board[fromY][fromX] == BlackKing &&\r
7079                board[toY][toX] == BlackRook) {\r
7080       board[fromY][fromX] = EmptySquare;\r
7081       board[toY][toX] = EmptySquare;\r
7082       if(toX > fromX) {\r
7083         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
7084       } else {\r
7085         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
7086       }\r
7087     /* End of code added by Tord */\r
7088 \r
7089     } else if (board[fromY][fromX] == king\r
7090         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7091         && toY == fromY && toX > fromX+1) {\r
7092         board[fromY][fromX] = EmptySquare;\r
7093         board[toY][toX] = king;\r
7094         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7095         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7096     } else if (board[fromY][fromX] == king\r
7097         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7098                && toY == fromY && toX < fromX-1) {\r
7099         board[fromY][fromX] = EmptySquare;\r
7100         board[toY][toX] = king;\r
7101         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7102         board[fromY][BOARD_LEFT] = EmptySquare;\r
7103     } else if (board[fromY][fromX] == WhitePawn\r
7104                && toY == BOARD_HEIGHT-1\r
7105                && gameInfo.variant != VariantXiangqi\r
7106                ) {\r
7107         /* white pawn promotion */\r
7108         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
7109         if (board[toY][toX] == EmptySquare) {\r
7110             board[toY][toX] = WhiteQueen;\r
7111         }\r
7112         if(gameInfo.variant==VariantBughouse ||\r
7113            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7114             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7115         board[fromY][fromX] = EmptySquare;\r
7116     } else if ((fromY == BOARD_HEIGHT-4)\r
7117                && (toX != fromX)\r
7118                && gameInfo.variant != VariantXiangqi\r
7119                && gameInfo.variant != VariantBerolina\r
7120                && (board[fromY][fromX] == WhitePawn)\r
7121                && (board[toY][toX] == EmptySquare)) {\r
7122         board[fromY][fromX] = EmptySquare;\r
7123         board[toY][toX] = WhitePawn;\r
7124         captured = board[toY - 1][toX];\r
7125         board[toY - 1][toX] = EmptySquare;\r
7126     } else if ((fromY == BOARD_HEIGHT-4)\r
7127                && (toX == fromX)\r
7128                && gameInfo.variant == VariantBerolina\r
7129                && (board[fromY][fromX] == WhitePawn)\r
7130                && (board[toY][toX] == EmptySquare)) {\r
7131         board[fromY][fromX] = EmptySquare;\r
7132         board[toY][toX] = WhitePawn;\r
7133         if(oldEP & EP_BEROLIN_A) {\r
7134                 captured = board[fromY][fromX-1];\r
7135                 board[fromY][fromX-1] = EmptySquare;\r
7136         }else{  captured = board[fromY][fromX+1];\r
7137                 board[fromY][fromX+1] = EmptySquare;\r
7138         }\r
7139     } else if (board[fromY][fromX] == king\r
7140         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7141                && toY == fromY && toX > fromX+1) {\r
7142         board[fromY][fromX] = EmptySquare;\r
7143         board[toY][toX] = king;\r
7144         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7145         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7146     } else if (board[fromY][fromX] == king\r
7147         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7148                && toY == fromY && toX < fromX-1) {\r
7149         board[fromY][fromX] = EmptySquare;\r
7150         board[toY][toX] = king;\r
7151         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7152         board[fromY][BOARD_LEFT] = EmptySquare;\r
7153     } else if (fromY == 7 && fromX == 3\r
7154                && board[fromY][fromX] == BlackKing\r
7155                && toY == 7 && toX == 5) {\r
7156         board[fromY][fromX] = EmptySquare;\r
7157         board[toY][toX] = BlackKing;\r
7158         board[fromY][7] = EmptySquare;\r
7159         board[toY][4] = BlackRook;\r
7160     } else if (fromY == 7 && fromX == 3\r
7161                && board[fromY][fromX] == BlackKing\r
7162                && toY == 7 && toX == 1) {\r
7163         board[fromY][fromX] = EmptySquare;\r
7164         board[toY][toX] = BlackKing;\r
7165         board[fromY][0] = EmptySquare;\r
7166         board[toY][2] = BlackRook;\r
7167     } else if (board[fromY][fromX] == BlackPawn\r
7168                && toY == 0\r
7169                && gameInfo.variant != VariantXiangqi\r
7170                ) {\r
7171         /* black pawn promotion */\r
7172         board[0][toX] = CharToPiece(ToLower(promoChar));\r
7173         if (board[0][toX] == EmptySquare) {\r
7174             board[0][toX] = BlackQueen;\r
7175         }\r
7176         if(gameInfo.variant==VariantBughouse ||\r
7177            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7178             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7179         board[fromY][fromX] = EmptySquare;\r
7180     } else if ((fromY == 3)\r
7181                && (toX != fromX)\r
7182                && gameInfo.variant != VariantXiangqi\r
7183                && gameInfo.variant != VariantBerolina\r
7184                && (board[fromY][fromX] == BlackPawn)\r
7185                && (board[toY][toX] == EmptySquare)) {\r
7186         board[fromY][fromX] = EmptySquare;\r
7187         board[toY][toX] = BlackPawn;\r
7188         captured = board[toY + 1][toX];\r
7189         board[toY + 1][toX] = EmptySquare;\r
7190     } else if ((fromY == 3)\r
7191                && (toX == fromX)\r
7192                && gameInfo.variant == VariantBerolina\r
7193                && (board[fromY][fromX] == BlackPawn)\r
7194                && (board[toY][toX] == EmptySquare)) {\r
7195         board[fromY][fromX] = EmptySquare;\r
7196         board[toY][toX] = BlackPawn;\r
7197         if(oldEP & EP_BEROLIN_A) {\r
7198                 captured = board[fromY][fromX-1];\r
7199                 board[fromY][fromX-1] = EmptySquare;\r
7200         }else{  captured = board[fromY][fromX+1];\r
7201                 board[fromY][fromX+1] = EmptySquare;\r
7202         }\r
7203     } else {\r
7204         board[toY][toX] = board[fromY][fromX];\r
7205         board[fromY][fromX] = EmptySquare;\r
7206     }\r
7207 \r
7208     /* [HGM] now we promote for Shogi, if needed */\r
7209     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
7210         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7211   }\r
7212 \r
7213     if (gameInfo.holdingsWidth != 0) {\r
7214 \r
7215       /* !!A lot more code needs to be written to support holdings  */\r
7216       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
7217       /* penultimate board files, so they are automaticlly stored   */\r
7218       /* in the game history.                                       */\r
7219       if (fromY == DROP_RANK) {\r
7220         /* Delete from holdings, by decreasing count */\r
7221         /* and erasing image if necessary            */\r
7222         p = (int) fromX;\r
7223         if(p < (int) BlackPawn) { /* white drop */\r
7224              p -= (int)WhitePawn;\r
7225              if(p >= gameInfo.holdingsSize) p = 0;\r
7226              if(--board[p][BOARD_WIDTH-2] == 0)\r
7227                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
7228         } else {                  /* black drop */\r
7229              p -= (int)BlackPawn;\r
7230              if(p >= gameInfo.holdingsSize) p = 0;\r
7231              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
7232                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
7233         }\r
7234       }\r
7235       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
7236           && gameInfo.variant != VariantBughouse        ) {\r
7237         /* [HGM] holdings: Add to holdings, if holdings exist */\r
7238         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { \r
7239                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
7240                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
7241         }\r
7242         p = (int) captured;\r
7243         if (p >= (int) BlackPawn) {\r
7244           p -= (int)BlackPawn;\r
7245           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7246                   /* in Shogi restore piece to its original  first */\r
7247                   captured = (ChessSquare) (DEMOTED captured);\r
7248                   p = DEMOTED p;\r
7249           }\r
7250           p = PieceToNumber((ChessSquare)p);\r
7251           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
7252           board[p][BOARD_WIDTH-2]++;\r
7253           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;\r
7254         } else {\r
7255           p -= (int)WhitePawn;\r
7256           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7257                   captured = (ChessSquare) (DEMOTED captured);\r
7258                   p = DEMOTED p;\r
7259           }\r
7260           p = PieceToNumber((ChessSquare)p);\r
7261           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
7262           board[BOARD_HEIGHT-1-p][1]++;\r
7263           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;\r
7264         }\r
7265       }\r
7266 \r
7267     } else if (gameInfo.variant == VariantAtomic) {\r
7268       if (captured != EmptySquare) {\r
7269         int y, x;\r
7270         for (y = toY-1; y <= toY+1; y++) {\r
7271           for (x = toX-1; x <= toX+1; x++) {\r
7272             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
7273                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
7274               board[y][x] = EmptySquare;\r
7275             }\r
7276           }\r
7277         }\r
7278         board[toY][toX] = EmptySquare;\r
7279       }\r
7280     }\r
7281     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
7282         /* [HGM] Shogi promotions */\r
7283         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7284     }\r
7285 \r
7286 }\r
7287 \r
7288 /* Updates forwardMostMove */\r
7289 void\r
7290 MakeMove(fromX, fromY, toX, toY, promoChar)\r
7291      int fromX, fromY, toX, toY;\r
7292      int promoChar;\r
7293 {\r
7294 //    forwardMostMove++; // [HGM] bare: moved downstream\r
7295 \r
7296     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */\r
7297         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
7298         piece = boards[forwardMostMove][fromY][fromX];\r
7299         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
7300         if(gameInfo.variant == VariantKnightmate)\r
7301             king += (int) WhiteUnicorn - (int) WhiteKing;\r
7302         if(forwardMostMove == 0) {\r
7303             if(blackPlaysFirst) \r
7304                 fprintf(serverMoves, "%s;", second.tidy);\r
7305             fprintf(serverMoves, "%s;", first.tidy);\r
7306             if(!blackPlaysFirst) \r
7307                 fprintf(serverMoves, "%s;", second.tidy);\r
7308         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
7309         lastLoadFlag = loadFlag;\r
7310         // print base move\r
7311         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
7312         // print castling suffix\r
7313         if( toY == fromY && piece == king ) {\r
7314             if(toX-fromX > 1)\r
7315                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
7316             if(fromX-toX >1)\r
7317                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
7318         }\r
7319         // e.p. suffix\r
7320         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||\r
7321              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&\r
7322              boards[forwardMostMove][toY][toX] == EmptySquare\r
7323              && fromX != toX )\r
7324                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
7325         // promotion suffix\r
7326         if(promoChar != NULLCHAR)\r
7327                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
7328         if(!loadFlag) {\r
7329             fprintf(serverMoves, "/%d/%d",\r
7330                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);\r
7331             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;\r
7332             else                      timeLeft = blackTimeRemaining/1000;\r
7333             fprintf(serverMoves, "/%d", timeLeft);\r
7334         }\r
7335         fflush(serverMoves);\r
7336     }\r
7337 \r
7338     if (forwardMostMove+1 >= MAX_MOVES) {\r
7339       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
7340                         0, 1);\r
7341       return;\r
7342     }\r
7343     SwitchClocks();\r
7344     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;\r
7345     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;\r
7346     if (commentList[forwardMostMove+1] != NULL) {\r
7347         free(commentList[forwardMostMove+1]);\r
7348         commentList[forwardMostMove+1] = NULL;\r
7349     }\r
7350     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);\r
7351     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);\r
7352     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board\r
7353     gameInfo.result = GameUnfinished;\r
7354     if (gameInfo.resultDetails != NULL) {\r
7355         free(gameInfo.resultDetails);\r
7356         gameInfo.resultDetails = NULL;\r
7357     }\r
7358     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
7359                               moveList[forwardMostMove - 1]);\r
7360     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
7361                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
7362                              fromY, fromX, toY, toX, promoChar,\r
7363                              parseList[forwardMostMove - 1]);\r
7364     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
7365                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
7366                             castlingRights[forwardMostMove]) ) {\r
7367       case MT_NONE:\r
7368       case MT_STALEMATE:\r
7369       default:\r
7370         break;\r
7371       case MT_CHECK:\r
7372         if(gameInfo.variant != VariantShogi)\r
7373             strcat(parseList[forwardMostMove - 1], "+");\r
7374         break;\r
7375       case MT_CHECKMATE:\r
7376         strcat(parseList[forwardMostMove - 1], "#");\r
7377         break;\r
7378     }\r
7379     if (appData.debugMode) {\r
7380         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
7381     }\r
7382 \r
7383 }\r
7384 \r
7385 /* Updates currentMove if not pausing */\r
7386 void\r
7387 ShowMove(fromX, fromY, toX, toY)\r
7388 {\r
7389     int instant = (gameMode == PlayFromGameFile) ?\r
7390         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
7391     if(appData.noGUI) return;\r
7392     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
7393         if (!instant) {\r
7394             if (forwardMostMove == currentMove + 1) {\r
7395                 AnimateMove(boards[forwardMostMove - 1],\r
7396                             fromX, fromY, toX, toY);\r
7397             }\r
7398             if (appData.highlightLastMove) {\r
7399                 SetHighlights(fromX, fromY, toX, toY);\r
7400             }\r
7401         }\r
7402         currentMove = forwardMostMove;\r
7403     }\r
7404 \r
7405     if (instant) return;\r
7406 \r
7407     DisplayMove(currentMove - 1);\r
7408     DrawPosition(FALSE, boards[currentMove]);\r
7409     DisplayBothClocks();\r
7410     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
7411 }\r
7412 \r
7413 void SendEgtPath(ChessProgramState *cps)\r
7414 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */\r
7415         char buf[MSG_SIZ], name[MSG_SIZ], *p;\r
7416 \r
7417         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;\r
7418 \r
7419         while(*p) {\r
7420             char c, *q = name+1, *r, *s;\r
7421 \r
7422             name[0] = ','; // extract next format name from feature and copy with prefixed ','\r
7423             while(*p && *p != ',') *q++ = *p++;\r
7424             *q++ = ':'; *q = 0;\r
7425             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && \r
7426                 strcmp(name, ",nalimov:") == 0 ) {\r
7427                 // take nalimov path from the menu-changeable option first, if it is defined\r
7428                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);\r
7429                 SendToProgram(buf,cps);     // send egtbpath command for nalimov\r
7430             } else\r
7431             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||\r
7432                 (s = StrStr(appData.egtFormats, name)) != NULL) {\r
7433                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma\r
7434                 s = r = StrStr(s, ":") + 1; // beginning of path info\r
7435                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string\r
7436                 c = *r; *r = 0;             // temporarily null-terminate path info\r
7437                     *--q = 0;               // strip of trailig ':' from name\r
7438                     sprintf(buf, "egtbpath %s %s\n", name+1, s);\r
7439                 *r = c;\r
7440                 SendToProgram(buf,cps);     // send egtbpath command for this format\r
7441             }\r
7442             if(*p == ',') p++; // read away comma to position for next format name\r
7443         }\r
7444 }\r
7445 \r
7446 void\r
7447 InitChessProgram(cps, setup)\r
7448      ChessProgramState *cps;\r
7449      int setup; /* [HGM] needed to setup FRC opening position */\r
7450 {\r
7451     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
7452     if (appData.noChessProgram) return;\r
7453     hintRequested = FALSE;\r
7454     bookRequested = FALSE;\r
7455 \r
7456     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */\r
7457     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */\r
7458     if(cps->memSize) { /* [HGM] memory */\r
7459         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);\r
7460         SendToProgram(buf, cps);\r
7461     }\r
7462     SendEgtPath(cps); /* [HGM] EGT */\r
7463     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */\r
7464         sprintf(buf, "cores %d\n", appData.smpCores);\r
7465         SendToProgram(buf, cps);\r
7466     }\r
7467 \r
7468     SendToProgram(cps->initString, cps);\r
7469     if (gameInfo.variant != VariantNormal &&\r
7470         gameInfo.variant != VariantLoadable\r
7471         /* [HGM] also send variant if board size non-standard */\r
7472         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
7473                                             ) {\r
7474       char *v = VariantName(gameInfo.variant);\r
7475       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
7476         /* [HGM] in protocol 1 we have to assume all variants valid */\r
7477         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);\r
7478         DisplayFatalError(buf, 0, 1);\r
7479         return;\r
7480       }\r
7481 \r
7482       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
7483       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7484       if( gameInfo.variant == VariantXiangqi )\r
7485            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
7486       if( gameInfo.variant == VariantShogi )\r
7487            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
7488       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
7489            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
7490       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
7491                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
7492            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7493       if( gameInfo.variant == VariantCourier )\r
7494            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7495       if( gameInfo.variant == VariantSuper )\r
7496            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7497       if( gameInfo.variant == VariantGreat )\r
7498            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7499 \r
7500       if(overruled) {\r
7501            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
7502                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
7503            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
7504            if(StrStr(cps->variants, b) == NULL) { \r
7505                // specific sized variant not known, check if general sizing allowed\r
7506                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
7507                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
7508                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
7509                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
7510                        DisplayFatalError(buf, 0, 1);\r
7511                        return;\r
7512                    }\r
7513                    /* [HGM] here we really should compare with the maximum supported board size */\r
7514                }\r
7515            }\r
7516       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
7517       sprintf(buf, "variant %s\n", b);\r
7518       SendToProgram(buf, cps);\r
7519     }\r
7520     currentlyInitializedVariant = gameInfo.variant;\r
7521 \r
7522     /* [HGM] send opening position in FRC to first engine */\r
7523     if(setup) {\r
7524           SendToProgram("force\n", cps);\r
7525           SendBoard(cps, 0);\r
7526           /* engine is now in force mode! Set flag to wake it up after first move. */\r
7527           setboardSpoiledMachineBlack = 1;\r
7528     }\r
7529 \r
7530     if (cps->sendICS) {\r
7531       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
7532       SendToProgram(buf, cps);\r
7533     }\r
7534     cps->maybeThinking = FALSE;\r
7535     cps->offeredDraw = 0;\r
7536     if (!appData.icsActive) {\r
7537         SendTimeControl(cps, movesPerSession, timeControl,\r
7538                         timeIncrement, appData.searchDepth,\r
7539                         searchTime);\r
7540     }\r
7541     if (appData.showThinking \r
7542         // [HGM] thinking: four options require thinking output to be sent\r
7543         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
7544                                 ) {\r
7545         SendToProgram("post\n", cps);\r
7546     }\r
7547     SendToProgram("hard\n", cps);\r
7548     if (!appData.ponderNextMove) {\r
7549         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
7550            it without being sure what state we are in first.  "hard"\r
7551            is not a toggle, so that one is OK.\r
7552          */\r
7553         SendToProgram("easy\n", cps);\r
7554     }\r
7555     if (cps->usePing) {\r
7556       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
7557       SendToProgram(buf, cps);\r
7558     }\r
7559     cps->initDone = TRUE;\r
7560 }   \r
7561 \r
7562 \r
7563 void\r
7564 StartChessProgram(cps)\r
7565      ChessProgramState *cps;\r
7566 {\r
7567     char buf[MSG_SIZ];\r
7568     int err;\r
7569 \r
7570     if (appData.noChessProgram) return;\r
7571     cps->initDone = FALSE;\r
7572 \r
7573     if (strcmp(cps->host, "localhost") == 0) {\r
7574         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
7575     } else if (*appData.remoteShell == NULLCHAR) {\r
7576         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
7577     } else {\r
7578         if (*appData.remoteUser == NULLCHAR) {\r
7579             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
7580                     cps->program);\r
7581         } else {\r
7582             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
7583                     cps->host, appData.remoteUser, cps->program);\r
7584         }\r
7585         err = StartChildProcess(buf, "", &cps->pr);\r
7586     }\r
7587     \r
7588     if (err != 0) {\r
7589         sprintf(buf, _("Startup failure on '%s'"), cps->program);\r
7590         DisplayFatalError(buf, err, 1);\r
7591         cps->pr = NoProc;\r
7592         cps->isr = NULL;\r
7593         return;\r
7594     }\r
7595     \r
7596     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
7597     if (cps->protocolVersion > 1) {\r
7598       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
7599       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options\r
7600       cps->comboCnt = 0;  //                and values of combo boxes\r
7601       SendToProgram(buf, cps);\r
7602     } else {\r
7603       SendToProgram("xboard\n", cps);\r
7604     }\r
7605 }\r
7606 \r
7607 \r
7608 void\r
7609 TwoMachinesEventIfReady P((void))\r
7610 {\r
7611   if (first.lastPing != first.lastPong) {\r
7612     DisplayMessage("", _("Waiting for first chess program"));\r
7613     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7614     return;\r
7615   }\r
7616   if (second.lastPing != second.lastPong) {\r
7617     DisplayMessage("", _("Waiting for second chess program"));\r
7618     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7619     return;\r
7620   }\r
7621   ThawUI();\r
7622   TwoMachinesEvent();\r
7623 }\r
7624 \r
7625 void\r
7626 NextMatchGame P((void))\r
7627 {\r
7628     int index; /* [HGM] autoinc: step lod index during match */\r
7629     Reset(FALSE, TRUE);\r
7630     if (*appData.loadGameFile != NULLCHAR) {\r
7631         index = appData.loadGameIndex;\r
7632         if(index < 0) { // [HGM] autoinc\r
7633             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7634             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7635         } \r
7636         LoadGameFromFile(appData.loadGameFile,\r
7637                          index,\r
7638                          appData.loadGameFile, FALSE);\r
7639     } else if (*appData.loadPositionFile != NULLCHAR) {\r
7640         index = appData.loadPositionIndex;\r
7641         if(index < 0) { // [HGM] autoinc\r
7642             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7643             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7644         } \r
7645         LoadPositionFromFile(appData.loadPositionFile,\r
7646                              index,\r
7647                              appData.loadPositionFile);\r
7648     }\r
7649     TwoMachinesEventIfReady();\r
7650 }\r
7651 \r
7652 void UserAdjudicationEvent( int result )\r
7653 {\r
7654     ChessMove gameResult = GameIsDrawn;\r
7655 \r
7656     if( result > 0 ) {\r
7657         gameResult = WhiteWins;\r
7658     }\r
7659     else if( result < 0 ) {\r
7660         gameResult = BlackWins;\r
7661     }\r
7662 \r
7663     if( gameMode == TwoMachinesPlay ) {\r
7664         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
7665     }\r
7666 }\r
7667 \r
7668 \r
7669 void\r
7670 GameEnds(result, resultDetails, whosays)\r
7671      ChessMove result;\r
7672      char *resultDetails;\r
7673      int whosays;\r
7674 {\r
7675     GameMode nextGameMode;\r
7676     int isIcsGame;\r
7677     char buf[MSG_SIZ];\r
7678 \r
7679     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
7680     endingGame = 1;\r
7681 \r
7682     if (appData.debugMode) {\r
7683       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
7684               result, resultDetails ? resultDetails : "(null)", whosays);\r
7685     }\r
7686 \r
7687     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
7688         /* If we are playing on ICS, the server decides when the\r
7689            game is over, but the engine can offer to draw, claim \r
7690            a draw, or resign. \r
7691          */\r
7692 #if ZIPPY\r
7693         if (appData.zippyPlay && first.initDone) {\r
7694             if (result == GameIsDrawn) {\r
7695                 /* In case draw still needs to be claimed */\r
7696                 SendToICS(ics_prefix);\r
7697                 SendToICS("draw\n");\r
7698             } else if (StrCaseStr(resultDetails, "resign")) {\r
7699                 SendToICS(ics_prefix);\r
7700                 SendToICS("resign\n");\r
7701             }\r
7702         }\r
7703 #endif\r
7704         endingGame = 0; /* [HGM] crash */\r
7705         return;\r
7706     }\r
7707 \r
7708     /* If we're loading the game from a file, stop */\r
7709     if (whosays == GE_FILE) {\r
7710       (void) StopLoadGameTimer();\r
7711       gameFileFP = NULL;\r
7712     }\r
7713 \r
7714     /* Cancel draw offers */\r
7715     first.offeredDraw = second.offeredDraw = 0;\r
7716 \r
7717     /* If this is an ICS game, only ICS can really say it's done;\r
7718        if not, anyone can. */\r
7719     isIcsGame = (gameMode == IcsPlayingWhite || \r
7720                  gameMode == IcsPlayingBlack || \r
7721                  gameMode == IcsObserving    || \r
7722                  gameMode == IcsExamining);\r
7723 \r
7724     if (!isIcsGame || whosays == GE_ICS) {\r
7725         /* OK -- not an ICS game, or ICS said it was done */\r
7726         StopClocks();\r
7727         if (!isIcsGame && !appData.noChessProgram) \r
7728           SetUserThinkingEnables();\r
7729     \r
7730         /* [HGM] if a machine claims the game end we verify this claim */\r
7731         if(gameMode == TwoMachinesPlay && appData.testClaims) {\r
7732             if(appData.testLegality && whosays >= GE_ENGINE1 ) {\r
7733                 char claimer;\r
7734                 ChessMove trueResult = (ChessMove) -1;\r
7735 \r
7736                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
7737                                             first.twoMachinesColor[0] :\r
7738                                             second.twoMachinesColor[0] ;\r
7739 \r
7740                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first\r
7741                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {\r
7742                     /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7743                     trueResult = WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers)\r
7744                         ? BlackWins : WhiteWins; // [HGM] losers: reverse the result in VariantLosers!\r
7745                 } else\r
7746                 if(epStatus[forwardMostMove] == EP_STALEMATE) {\r
7747                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE\r
7748                     if(gameInfo.variant == VariantGiveaway || gameInfo.variant == VariantSuicide || \r
7749                        gameInfo.variant == VariantLosers)  // [HGM] losers: in giveaway variants stalemate wins\r
7750                         trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;\r
7751                 }\r
7752 \r
7753                 // now verify win claims, but not in drop games, as we don't understand those yet\r
7754                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper\r
7755                                                  || gameInfo.variant == VariantGreat) &&\r
7756                     (result == WhiteWins && claimer == 'w' ||\r
7757                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win\r
7758                       if (appData.debugMode) {\r
7759                         fprintf(debugFP, "result=%d sp=%d move=%d\n",\r
7760                                 result, epStatus[forwardMostMove], forwardMostMove);\r
7761                       }\r
7762                       if(result != trueResult) {\r
7763                               sprintf(buf, "False win claim: '%s'", resultDetails);\r
7764                               result = claimer == 'w' ? BlackWins : WhiteWins;\r
7765                               resultDetails = buf;\r
7766                       }\r
7767                 } else\r
7768                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
7769                     && (forwardMostMove <= backwardMostMove ||\r
7770                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
7771                         (claimer=='b')==(forwardMostMove&1))\r
7772                                                                                   ) {\r
7773                       /* [HGM] verify: draws that were not flagged are false claims */\r
7774                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
7775                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7776                       resultDetails = buf;\r
7777                 }\r
7778                 /* (Claiming a loss is accepted no questions asked!) */\r
7779             }\r
7780             /* [HGM] bare: don't allow bare King to win */\r
7781             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)\r
7782                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway \r
7783                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...\r
7784                && result != GameIsDrawn)\r
7785             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
7786                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
7787                         int p = (int)boards[forwardMostMove][i][j] - color;\r
7788                         if(p >= 0 && p <= (int)WhiteKing) k++;\r
7789                 }\r
7790                 if (appData.debugMode) {\r
7791                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",\r
7792                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);\r
7793                 }\r
7794                 if(k <= 1) {\r
7795                         result = GameIsDrawn;\r
7796                         sprintf(buf, "%s but bare king", resultDetails);\r
7797                         resultDetails = buf;\r
7798                 }\r
7799             }\r
7800         }\r
7801 \r
7802 \r
7803         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
7804             if(result==WhiteWins) c = '+';\r
7805             if(result==BlackWins) c = '-';\r
7806             if(resultDetails != NULL)\r
7807                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
7808         }\r
7809         if (resultDetails != NULL) {\r
7810             gameInfo.result = result;\r
7811             gameInfo.resultDetails = StrSave(resultDetails);\r
7812 \r
7813             /* display last move only if game was not loaded from file */\r
7814             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7815                 DisplayMove(currentMove - 1);\r
7816     \r
7817             if (forwardMostMove != 0) {\r
7818                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7819                     if (*appData.saveGameFile != NULLCHAR) {\r
7820                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7821                     } else if (appData.autoSaveGames) {\r
7822                         AutoSaveGame();\r
7823                     }\r
7824                     if (*appData.savePositionFile != NULLCHAR) {\r
7825                         SavePositionToFile(appData.savePositionFile);\r
7826                     }\r
7827                 }\r
7828             }\r
7829 \r
7830             /* Tell program how game ended in case it is learning */\r
7831             /* [HGM] Moved this to after saving the PGN, just in case */\r
7832             /* engine died and we got here through time loss. In that */\r
7833             /* case we will get a fatal error writing the pipe, which */\r
7834             /* would otherwise lose us the PGN.                       */\r
7835             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7836             /* output during GameEnds should never be fatal anymore   */\r
7837             if (gameMode == MachinePlaysWhite ||\r
7838                 gameMode == MachinePlaysBlack ||\r
7839                 gameMode == TwoMachinesPlay ||\r
7840                 gameMode == IcsPlayingWhite ||\r
7841                 gameMode == IcsPlayingBlack ||\r
7842                 gameMode == BeginningOfGame) {\r
7843                 char buf[MSG_SIZ];\r
7844                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7845                         resultDetails);\r
7846                 if (first.pr != NoProc) {\r
7847                     SendToProgram(buf, &first);\r
7848                 }\r
7849                 if (second.pr != NoProc &&\r
7850                     gameMode == TwoMachinesPlay) {\r
7851                     SendToProgram(buf, &second);\r
7852                 }\r
7853             }\r
7854         }\r
7855 \r
7856         if (appData.icsActive) {\r
7857             if (appData.quietPlay &&\r
7858                 (gameMode == IcsPlayingWhite ||\r
7859                  gameMode == IcsPlayingBlack)) {\r
7860                 SendToICS(ics_prefix);\r
7861                 SendToICS("set shout 1\n");\r
7862             }\r
7863             nextGameMode = IcsIdle;\r
7864             ics_user_moved = FALSE;\r
7865             /* clean up premove.  It's ugly when the game has ended and the\r
7866              * premove highlights are still on the board.\r
7867              */\r
7868             if (gotPremove) {\r
7869               gotPremove = FALSE;\r
7870               ClearPremoveHighlights();\r
7871               DrawPosition(FALSE, boards[currentMove]);\r
7872             }\r
7873             if (whosays == GE_ICS) {\r
7874                 switch (result) {\r
7875                 case WhiteWins:\r
7876                     if (gameMode == IcsPlayingWhite)\r
7877                         PlayIcsWinSound();\r
7878                     else if(gameMode == IcsPlayingBlack)\r
7879                         PlayIcsLossSound();\r
7880                     break;\r
7881                 case BlackWins:\r
7882                     if (gameMode == IcsPlayingBlack)\r
7883                         PlayIcsWinSound();\r
7884                     else if(gameMode == IcsPlayingWhite)\r
7885                         PlayIcsLossSound();\r
7886                     break;\r
7887                 case GameIsDrawn:\r
7888                     PlayIcsDrawSound();\r
7889                     break;\r
7890                 default:\r
7891                     PlayIcsUnfinishedSound();\r
7892                 }\r
7893             }\r
7894         } else if (gameMode == EditGame ||\r
7895                    gameMode == PlayFromGameFile || \r
7896                    gameMode == AnalyzeMode || \r
7897                    gameMode == AnalyzeFile) {\r
7898             nextGameMode = gameMode;\r
7899         } else {\r
7900             nextGameMode = EndOfGame;\r
7901         }\r
7902         pausing = FALSE;\r
7903         ModeHighlight();\r
7904     } else {\r
7905         nextGameMode = gameMode;\r
7906     }\r
7907 \r
7908     if (appData.noChessProgram) {\r
7909         gameMode = nextGameMode;\r
7910         ModeHighlight();\r
7911         endingGame = 0; /* [HGM] crash */\r
7912         return;\r
7913     }\r
7914 \r
7915     if (first.reuse) {\r
7916         /* Put first chess program into idle state */\r
7917         if (first.pr != NoProc &&\r
7918             (gameMode == MachinePlaysWhite ||\r
7919              gameMode == MachinePlaysBlack ||\r
7920              gameMode == TwoMachinesPlay ||\r
7921              gameMode == IcsPlayingWhite ||\r
7922              gameMode == IcsPlayingBlack ||\r
7923              gameMode == BeginningOfGame)) {\r
7924             SendToProgram("force\n", &first);\r
7925             if (first.usePing) {\r
7926               char buf[MSG_SIZ];\r
7927               sprintf(buf, "ping %d\n", ++first.lastPing);\r
7928               SendToProgram(buf, &first);\r
7929             }\r
7930         }\r
7931     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7932         /* Kill off first chess program */\r
7933         if (first.isr != NULL)\r
7934           RemoveInputSource(first.isr);\r
7935         first.isr = NULL;\r
7936     \r
7937         if (first.pr != NoProc) {\r
7938             ExitAnalyzeMode();\r
7939             DoSleep( appData.delayBeforeQuit );\r
7940             SendToProgram("quit\n", &first);\r
7941             DoSleep( appData.delayAfterQuit );\r
7942             DestroyChildProcess(first.pr, first.useSigterm);\r
7943         }\r
7944         first.pr = NoProc;\r
7945     }\r
7946     if (second.reuse) {\r
7947         /* Put second chess program into idle state */\r
7948         if (second.pr != NoProc &&\r
7949             gameMode == TwoMachinesPlay) {\r
7950             SendToProgram("force\n", &second);\r
7951             if (second.usePing) {\r
7952               char buf[MSG_SIZ];\r
7953               sprintf(buf, "ping %d\n", ++second.lastPing);\r
7954               SendToProgram(buf, &second);\r
7955             }\r
7956         }\r
7957     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7958         /* Kill off second chess program */\r
7959         if (second.isr != NULL)\r
7960           RemoveInputSource(second.isr);\r
7961         second.isr = NULL;\r
7962     \r
7963         if (second.pr != NoProc) {\r
7964             DoSleep( appData.delayBeforeQuit );\r
7965             SendToProgram("quit\n", &second);\r
7966             DoSleep( appData.delayAfterQuit );\r
7967             DestroyChildProcess(second.pr, second.useSigterm);\r
7968         }\r
7969         second.pr = NoProc;\r
7970     }\r
7971 \r
7972     if (matchMode && gameMode == TwoMachinesPlay) {\r
7973         switch (result) {\r
7974         case WhiteWins:\r
7975           if (first.twoMachinesColor[0] == 'w') {\r
7976             first.matchWins++;\r
7977           } else {\r
7978             second.matchWins++;\r
7979           }\r
7980           break;\r
7981         case BlackWins:\r
7982           if (first.twoMachinesColor[0] == 'b') {\r
7983             first.matchWins++;\r
7984           } else {\r
7985             second.matchWins++;\r
7986           }\r
7987           break;\r
7988         default:\r
7989           break;\r
7990         }\r
7991         if (matchGame < appData.matchGames) {\r
7992             char *tmp;\r
7993             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */\r
7994                 tmp = first.twoMachinesColor;\r
7995                 first.twoMachinesColor = second.twoMachinesColor;\r
7996                 second.twoMachinesColor = tmp;\r
7997             }\r
7998             gameMode = nextGameMode;\r
7999             matchGame++;\r
8000             if(appData.matchPause>10000 || appData.matchPause<10)\r
8001                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
8002             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
8003             endingGame = 0; /* [HGM] crash */\r
8004             return;\r
8005         } else {\r
8006             char buf[MSG_SIZ];\r
8007             gameMode = nextGameMode;\r
8008             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),\r
8009                     first.tidy, second.tidy,\r
8010                     first.matchWins, second.matchWins,\r
8011                     appData.matchGames - (first.matchWins + second.matchWins));\r
8012             DisplayFatalError(buf, 0, 0);\r
8013         }\r
8014     }\r
8015     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
8016         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
8017       ExitAnalyzeMode();\r
8018     gameMode = nextGameMode;\r
8019     ModeHighlight();\r
8020     endingGame = 0;  /* [HGM] crash */\r
8021 }\r
8022 \r
8023 /* Assumes program was just initialized (initString sent).\r
8024    Leaves program in force mode. */\r
8025 void\r
8026 FeedMovesToProgram(cps, upto) \r
8027      ChessProgramState *cps;\r
8028      int upto;\r
8029 {\r
8030     int i;\r
8031     \r
8032     if (appData.debugMode)\r
8033       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
8034               startedFromSetupPosition ? "position and " : "",\r
8035               backwardMostMove, upto, cps->which);\r
8036     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
8037         // [HGM] variantswitch: make engine aware of new variant\r
8038         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
8039                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
8040         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
8041         SendToProgram(buf, cps);\r
8042         currentlyInitializedVariant = gameInfo.variant;\r
8043     }\r
8044     SendToProgram("force\n", cps);\r
8045     if (startedFromSetupPosition) {\r
8046         SendBoard(cps, backwardMostMove);\r
8047     if (appData.debugMode) {\r
8048         fprintf(debugFP, "feedMoves\n");\r
8049     }\r
8050     }\r
8051     for (i = backwardMostMove; i < upto; i++) {\r
8052         SendMoveToProgram(i, cps);\r
8053     }\r
8054 }\r
8055 \r
8056 \r
8057 void\r
8058 ResurrectChessProgram()\r
8059 {\r
8060      /* The chess program may have exited.\r
8061         If so, restart it and feed it all the moves made so far. */\r
8062 \r
8063     if (appData.noChessProgram || first.pr != NoProc) return;\r
8064     \r
8065     StartChessProgram(&first);\r
8066     InitChessProgram(&first, FALSE);\r
8067     FeedMovesToProgram(&first, currentMove);\r
8068 \r
8069     if (!first.sendTime) {\r
8070         /* can't tell gnuchess what its clock should read,\r
8071            so we bow to its notion. */\r
8072         ResetClocks();\r
8073         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
8074         timeRemaining[1][currentMove] = blackTimeRemaining;\r
8075     }\r
8076 \r
8077     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||\r
8078                 appData.icsEngineAnalyze) && first.analysisSupport) {\r
8079       SendToProgram("analyze\n", &first);\r
8080       first.analyzing = TRUE;\r
8081     }\r
8082 }\r
8083 \r
8084 /*\r
8085  * Button procedures\r
8086  */\r
8087 void\r
8088 Reset(redraw, init)\r
8089      int redraw, init;\r
8090 {\r
8091     int i;\r
8092 \r
8093     if (appData.debugMode) {\r
8094         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
8095                 redraw, init, gameMode);\r
8096     }\r
8097     pausing = pauseExamInvalid = FALSE;\r
8098     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
8099     firstMove = TRUE;\r
8100     whiteFlag = blackFlag = FALSE;\r
8101     userOfferedDraw = FALSE;\r
8102     hintRequested = bookRequested = FALSE;\r
8103     first.maybeThinking = FALSE;\r
8104     second.maybeThinking = FALSE;\r
8105     first.bookSuspend = FALSE; // [HGM] book\r
8106     second.bookSuspend = FALSE;\r
8107     thinkOutput[0] = NULLCHAR;\r
8108     lastHint[0] = NULLCHAR;\r
8109     ClearGameInfo(&gameInfo);\r
8110     gameInfo.variant = StringToVariant(appData.variant);\r
8111     ics_user_moved = ics_clock_paused = FALSE;\r
8112     ics_getting_history = H_FALSE;\r
8113     ics_gamenum = -1;\r
8114     white_holding[0] = black_holding[0] = NULLCHAR;\r
8115     ClearProgramStats();\r
8116     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode\r
8117     \r
8118     ResetFrontEnd();\r
8119     ClearHighlights();\r
8120     flipView = appData.flipView;\r
8121     ClearPremoveHighlights();\r
8122     gotPremove = FALSE;\r
8123     alarmSounded = FALSE;\r
8124 \r
8125     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8126     if(appData.serverMovesName != NULL) {\r
8127         /* [HGM] prepare to make moves file for broadcasting */\r
8128         clock_t t = clock();\r
8129         if(serverMoves != NULL) fclose(serverMoves);\r
8130         serverMoves = fopen(appData.serverMovesName, "r");\r
8131         if(serverMoves != NULL) {\r
8132             fclose(serverMoves);\r
8133             /* delay 15 sec before overwriting, so all clients can see end */\r
8134             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
8135         }\r
8136         serverMoves = fopen(appData.serverMovesName, "w");\r
8137     }\r
8138 \r
8139     ExitAnalyzeMode();\r
8140     gameMode = BeginningOfGame;\r
8141     ModeHighlight();\r
8142     if(appData.icsActive) gameInfo.variant = VariantNormal;\r
8143     InitPosition(redraw);\r
8144     for (i = 0; i < MAX_MOVES; i++) {\r
8145         if (commentList[i] != NULL) {\r
8146             free(commentList[i]);\r
8147             commentList[i] = NULL;\r
8148         }\r
8149     }\r
8150     ResetClocks();\r
8151     timeRemaining[0][0] = whiteTimeRemaining;\r
8152     timeRemaining[1][0] = blackTimeRemaining;\r
8153     if (first.pr == NULL) {\r
8154         StartChessProgram(&first);\r
8155     }\r
8156     if (init) {\r
8157             InitChessProgram(&first, startedFromSetupPosition);\r
8158     }\r
8159     DisplayTitle("");\r
8160     DisplayMessage("", "");\r
8161     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8162 }\r
8163 \r
8164 void\r
8165 AutoPlayGameLoop()\r
8166 {\r
8167     for (;;) {\r
8168         if (!AutoPlayOneMove())\r
8169           return;\r
8170         if (matchMode || appData.timeDelay == 0)\r
8171           continue;\r
8172         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
8173           return;\r
8174         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
8175         break;\r
8176     }\r
8177 }\r
8178 \r
8179 \r
8180 int\r
8181 AutoPlayOneMove()\r
8182 {\r
8183     int fromX, fromY, toX, toY;\r
8184 \r
8185     if (appData.debugMode) {\r
8186       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
8187     }\r
8188 \r
8189     if (gameMode != PlayFromGameFile)\r
8190       return FALSE;\r
8191 \r
8192     if (currentMove >= forwardMostMove) {\r
8193       gameMode = EditGame;\r
8194       ModeHighlight();\r
8195 \r
8196       /* [AS] Clear current move marker at the end of a game */\r
8197       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
8198 \r
8199       return FALSE;\r
8200     }\r
8201     \r
8202     toX = moveList[currentMove][2] - AAA;\r
8203     toY = moveList[currentMove][3] - ONE;\r
8204 \r
8205     if (moveList[currentMove][1] == '@') {\r
8206         if (appData.highlightLastMove) {\r
8207             SetHighlights(-1, -1, toX, toY);\r
8208         }\r
8209     } else {\r
8210         fromX = moveList[currentMove][0] - AAA;\r
8211         fromY = moveList[currentMove][1] - ONE;\r
8212 \r
8213         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
8214 \r
8215         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
8216 \r
8217         if (appData.highlightLastMove) {\r
8218             SetHighlights(fromX, fromY, toX, toY);\r
8219         }\r
8220     }\r
8221     DisplayMove(currentMove);\r
8222     SendMoveToProgram(currentMove++, &first);\r
8223     DisplayBothClocks();\r
8224     DrawPosition(FALSE, boards[currentMove]);\r
8225     // [HGM] PV info: always display, routine tests if empty\r
8226     DisplayComment(currentMove - 1, commentList[currentMove]);\r
8227     return TRUE;\r
8228 }\r
8229 \r
8230 \r
8231 int\r
8232 LoadGameOneMove(readAhead)\r
8233      ChessMove readAhead;\r
8234 {\r
8235     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
8236     char promoChar = NULLCHAR;\r
8237     ChessMove moveType;\r
8238     char move[MSG_SIZ];\r
8239     char *p, *q;\r
8240     \r
8241     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
8242         gameMode != AnalyzeMode && gameMode != Training) {\r
8243         gameFileFP = NULL;\r
8244         return FALSE;\r
8245     }\r
8246     \r
8247     yyboardindex = forwardMostMove;\r
8248     if (readAhead != (ChessMove)0) {\r
8249       moveType = readAhead;\r
8250     } else {\r
8251       if (gameFileFP == NULL)\r
8252           return FALSE;\r
8253       moveType = (ChessMove) yylex();\r
8254     }\r
8255     \r
8256     done = FALSE;\r
8257     switch (moveType) {\r
8258       case Comment:\r
8259         if (appData.debugMode) \r
8260           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8261         p = yy_text;\r
8262         if (*p == '{' || *p == '[' || *p == '(') {\r
8263             p[strlen(p) - 1] = NULLCHAR;\r
8264             p++;\r
8265         }\r
8266 \r
8267         /* append the comment but don't display it */\r
8268         while (*p == '\n') p++;\r
8269         AppendComment(currentMove, p);\r
8270         return TRUE;\r
8271 \r
8272       case WhiteCapturesEnPassant:\r
8273       case BlackCapturesEnPassant:\r
8274       case WhitePromotionChancellor:\r
8275       case BlackPromotionChancellor:\r
8276       case WhitePromotionArchbishop:\r
8277       case BlackPromotionArchbishop:\r
8278       case WhitePromotionCentaur:\r
8279       case BlackPromotionCentaur:\r
8280       case WhitePromotionQueen:\r
8281       case BlackPromotionQueen:\r
8282       case WhitePromotionRook:\r
8283       case BlackPromotionRook:\r
8284       case WhitePromotionBishop:\r
8285       case BlackPromotionBishop:\r
8286       case WhitePromotionKnight:\r
8287       case BlackPromotionKnight:\r
8288       case WhitePromotionKing:\r
8289       case BlackPromotionKing:\r
8290       case NormalMove:\r
8291       case WhiteKingSideCastle:\r
8292       case WhiteQueenSideCastle:\r
8293       case BlackKingSideCastle:\r
8294       case BlackQueenSideCastle:\r
8295       case WhiteKingSideCastleWild:\r
8296       case WhiteQueenSideCastleWild:\r
8297       case BlackKingSideCastleWild:\r
8298       case BlackQueenSideCastleWild:\r
8299       /* PUSH Fabien */\r
8300       case WhiteHSideCastleFR:\r
8301       case WhiteASideCastleFR:\r
8302       case BlackHSideCastleFR:\r
8303       case BlackASideCastleFR:\r
8304       /* POP Fabien */\r
8305         if (appData.debugMode)\r
8306           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8307         fromX = currentMoveString[0] - AAA;\r
8308         fromY = currentMoveString[1] - ONE;\r
8309         toX = currentMoveString[2] - AAA;\r
8310         toY = currentMoveString[3] - ONE;\r
8311         promoChar = currentMoveString[4];\r
8312         break;\r
8313 \r
8314       case WhiteDrop:\r
8315       case BlackDrop:\r
8316         if (appData.debugMode)\r
8317           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8318         fromX = moveType == WhiteDrop ?\r
8319           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
8320         (int) CharToPiece(ToLower(currentMoveString[0]));\r
8321         fromY = DROP_RANK;\r
8322         toX = currentMoveString[2] - AAA;\r
8323         toY = currentMoveString[3] - ONE;\r
8324         break;\r
8325 \r
8326       case WhiteWins:\r
8327       case BlackWins:\r
8328       case GameIsDrawn:\r
8329       case GameUnfinished:\r
8330         if (appData.debugMode)\r
8331           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
8332         p = strchr(yy_text, '{');\r
8333         if (p == NULL) p = strchr(yy_text, '(');\r
8334         if (p == NULL) {\r
8335             p = yy_text;\r
8336             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
8337         } else {\r
8338             q = strchr(p, *p == '{' ? '}' : ')');\r
8339             if (q != NULL) *q = NULLCHAR;\r
8340             p++;\r
8341         }\r
8342         GameEnds(moveType, p, GE_FILE);\r
8343         done = TRUE;\r
8344         if (cmailMsgLoaded) {\r
8345             ClearHighlights();\r
8346             flipView = WhiteOnMove(currentMove);\r
8347             if (moveType == GameUnfinished) flipView = !flipView;\r
8348             if (appData.debugMode)\r
8349               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
8350         }\r
8351         break;\r
8352 \r
8353       case (ChessMove) 0:       /* end of file */\r
8354         if (appData.debugMode)\r
8355           fprintf(debugFP, "Parser hit end of file\n");\r
8356         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8357                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8358           case MT_NONE:\r
8359           case MT_CHECK:\r
8360             break;\r
8361           case MT_CHECKMATE:\r
8362             if (WhiteOnMove(currentMove)) {\r
8363                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8364             } else {\r
8365                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8366             }\r
8367             break;\r
8368           case MT_STALEMATE:\r
8369             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8370             break;\r
8371         }\r
8372         done = TRUE;\r
8373         break;\r
8374 \r
8375       case MoveNumberOne:\r
8376         if (lastLoadGameStart == GNUChessGame) {\r
8377             /* GNUChessGames have numbers, but they aren't move numbers */\r
8378             if (appData.debugMode)\r
8379               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8380                       yy_text, (int) moveType);\r
8381             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8382         }\r
8383         /* else fall thru */\r
8384 \r
8385       case XBoardGame:\r
8386       case GNUChessGame:\r
8387       case PGNTag:\r
8388         /* Reached start of next game in file */\r
8389         if (appData.debugMode)\r
8390           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
8391         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8392                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8393           case MT_NONE:\r
8394           case MT_CHECK:\r
8395             break;\r
8396           case MT_CHECKMATE:\r
8397             if (WhiteOnMove(currentMove)) {\r
8398                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8399             } else {\r
8400                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8401             }\r
8402             break;\r
8403           case MT_STALEMATE:\r
8404             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8405             break;\r
8406         }\r
8407         done = TRUE;\r
8408         break;\r
8409 \r
8410       case PositionDiagram:     /* should not happen; ignore */\r
8411       case ElapsedTime:         /* ignore */\r
8412       case NAG:                 /* ignore */\r
8413         if (appData.debugMode)\r
8414           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8415                   yy_text, (int) moveType);\r
8416         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8417 \r
8418       case IllegalMove:\r
8419         if (appData.testLegality) {\r
8420             if (appData.debugMode)\r
8421               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
8422             sprintf(move, _("Illegal move: %d.%s%s"),\r
8423                     (forwardMostMove / 2) + 1,\r
8424                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8425             DisplayError(move, 0);\r
8426             done = TRUE;\r
8427         } else {\r
8428             if (appData.debugMode)\r
8429               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
8430                       yy_text, currentMoveString);\r
8431             fromX = currentMoveString[0] - AAA;\r
8432             fromY = currentMoveString[1] - ONE;\r
8433             toX = currentMoveString[2] - AAA;\r
8434             toY = currentMoveString[3] - ONE;\r
8435             promoChar = currentMoveString[4];\r
8436         }\r
8437         break;\r
8438 \r
8439       case AmbiguousMove:\r
8440         if (appData.debugMode)\r
8441           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
8442         sprintf(move, _("Ambiguous move: %d.%s%s"),\r
8443                 (forwardMostMove / 2) + 1,\r
8444                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8445         DisplayError(move, 0);\r
8446         done = TRUE;\r
8447         break;\r
8448 \r
8449       default:\r
8450       case ImpossibleMove:\r
8451         if (appData.debugMode)\r
8452           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);\r
8453         sprintf(move, _("Illegal move: %d.%s%s"),\r
8454                 (forwardMostMove / 2) + 1,\r
8455                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8456         DisplayError(move, 0);\r
8457         done = TRUE;\r
8458         break;\r
8459     }\r
8460 \r
8461     if (done) {\r
8462         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
8463             DrawPosition(FALSE, boards[currentMove]);\r
8464             DisplayBothClocks();\r
8465             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
8466               DisplayComment(currentMove - 1, commentList[currentMove]);\r
8467         }\r
8468         (void) StopLoadGameTimer();\r
8469         gameFileFP = NULL;\r
8470         cmailOldMove = forwardMostMove;\r
8471         return FALSE;\r
8472     } else {\r
8473         /* currentMoveString is set as a side-effect of yylex */\r
8474         strcat(currentMoveString, "\n");\r
8475         strcpy(moveList[forwardMostMove], currentMoveString);\r
8476         \r
8477         thinkOutput[0] = NULLCHAR;\r
8478         MakeMove(fromX, fromY, toX, toY, promoChar);\r
8479         currentMove = forwardMostMove;\r
8480         return TRUE;\r
8481     }\r
8482 }\r
8483 \r
8484 /* Load the nth game from the given file */\r
8485 int\r
8486 LoadGameFromFile(filename, n, title, useList)\r
8487      char *filename;\r
8488      int n;\r
8489      char *title;\r
8490      /*Boolean*/ int useList;\r
8491 {\r
8492     FILE *f;\r
8493     char buf[MSG_SIZ];\r
8494 \r
8495     if (strcmp(filename, "-") == 0) {\r
8496         f = stdin;\r
8497         title = "stdin";\r
8498     } else {\r
8499         f = fopen(filename, "rb");\r
8500         if (f == NULL) {\r
8501             sprintf(buf, _("Can't open \"%s\""), filename);\r
8502             DisplayError(buf, errno);\r
8503             return FALSE;\r
8504         }\r
8505     }\r
8506     if (fseek(f, 0, 0) == -1) {\r
8507         /* f is not seekable; probably a pipe */\r
8508         useList = FALSE;\r
8509     }\r
8510     if (useList && n == 0) {\r
8511         int error = GameListBuild(f);\r
8512         if (error) {\r
8513             DisplayError(_("Cannot build game list"), error);\r
8514         } else if (!ListEmpty(&gameList) &&\r
8515                    ((ListGame *) gameList.tailPred)->number > 1) {\r
8516             GameListPopUp(f, title);\r
8517             return TRUE;\r
8518         }\r
8519         GameListDestroy();\r
8520         n = 1;\r
8521     }\r
8522     if (n == 0) n = 1;\r
8523     return LoadGame(f, n, title, FALSE);\r
8524 }\r
8525 \r
8526 \r
8527 void\r
8528 MakeRegisteredMove()\r
8529 {\r
8530     int fromX, fromY, toX, toY;\r
8531     char promoChar;\r
8532     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8533         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
8534           case CMAIL_MOVE:\r
8535           case CMAIL_DRAW:\r
8536             if (appData.debugMode)\r
8537               fprintf(debugFP, "Restoring %s for game %d\n",\r
8538                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8539     \r
8540             thinkOutput[0] = NULLCHAR;\r
8541             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
8542             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
8543             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
8544             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
8545             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
8546             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
8547             MakeMove(fromX, fromY, toX, toY, promoChar);\r
8548             ShowMove(fromX, fromY, toX, toY);\r
8549               \r
8550             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8551                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8552               case MT_NONE:\r
8553               case MT_CHECK:\r
8554                 break;\r
8555                 \r
8556               case MT_CHECKMATE:\r
8557                 if (WhiteOnMove(currentMove)) {\r
8558                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
8559                 } else {\r
8560                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
8561                 }\r
8562                 break;\r
8563                 \r
8564               case MT_STALEMATE:\r
8565                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
8566                 break;\r
8567             }\r
8568 \r
8569             break;\r
8570             \r
8571           case CMAIL_RESIGN:\r
8572             if (WhiteOnMove(currentMove)) {\r
8573                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
8574             } else {\r
8575                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
8576             }\r
8577             break;\r
8578             \r
8579           case CMAIL_ACCEPT:\r
8580             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
8581             break;\r
8582               \r
8583           default:\r
8584             break;\r
8585         }\r
8586     }\r
8587 \r
8588     return;\r
8589 }\r
8590 \r
8591 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
8592 int\r
8593 CmailLoadGame(f, gameNumber, title, useList)\r
8594      FILE *f;\r
8595      int gameNumber;\r
8596      char *title;\r
8597      int useList;\r
8598 {\r
8599     int retVal;\r
8600 \r
8601     if (gameNumber > nCmailGames) {\r
8602         DisplayError(_("No more games in this message"), 0);\r
8603         return FALSE;\r
8604     }\r
8605     if (f == lastLoadGameFP) {\r
8606         int offset = gameNumber - lastLoadGameNumber;\r
8607         if (offset == 0) {\r
8608             cmailMsg[0] = NULLCHAR;\r
8609             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8610                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8611                 nCmailMovesRegistered--;\r
8612             }\r
8613             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
8614             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
8615                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
8616             }\r
8617         } else {\r
8618             if (! RegisterMove()) return FALSE;\r
8619         }\r
8620     }\r
8621 \r
8622     retVal = LoadGame(f, gameNumber, title, useList);\r
8623 \r
8624     /* Make move registered during previous look at this game, if any */\r
8625     MakeRegisteredMove();\r
8626 \r
8627     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
8628         commentList[currentMove]\r
8629           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
8630         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8631     }\r
8632 \r
8633     return retVal;\r
8634 }\r
8635 \r
8636 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
8637 int\r
8638 ReloadGame(offset)\r
8639      int offset;\r
8640 {\r
8641     int gameNumber = lastLoadGameNumber + offset;\r
8642     if (lastLoadGameFP == NULL) {\r
8643         DisplayError(_("No game has been loaded yet"), 0);\r
8644         return FALSE;\r
8645     }\r
8646     if (gameNumber <= 0) {\r
8647         DisplayError(_("Can't back up any further"), 0);\r
8648         return FALSE;\r
8649     }\r
8650     if (cmailMsgLoaded) {\r
8651         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
8652                              lastLoadGameTitle, lastLoadGameUseList);\r
8653     } else {\r
8654         return LoadGame(lastLoadGameFP, gameNumber,\r
8655                         lastLoadGameTitle, lastLoadGameUseList);\r
8656     }\r
8657 }\r
8658 \r
8659 \r
8660 \r
8661 /* Load the nth game from open file f */\r
8662 int\r
8663 LoadGame(f, gameNumber, title, useList)\r
8664      FILE *f;\r
8665      int gameNumber;\r
8666      char *title;\r
8667      int useList;\r
8668 {\r
8669     ChessMove cm;\r
8670     char buf[MSG_SIZ];\r
8671     int gn = gameNumber;\r
8672     ListGame *lg = NULL;\r
8673     int numPGNTags = 0;\r
8674     int err;\r
8675     GameMode oldGameMode;\r
8676     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
8677 \r
8678     if (appData.debugMode) \r
8679         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
8680 \r
8681     if (gameMode == Training )\r
8682         SetTrainingModeOff();\r
8683 \r
8684     oldGameMode = gameMode;\r
8685     if (gameMode != BeginningOfGame) {\r
8686       Reset(FALSE, TRUE);\r
8687     }\r
8688 \r
8689     gameFileFP = f;\r
8690     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
8691         fclose(lastLoadGameFP);\r
8692     }\r
8693 \r
8694     if (useList) {\r
8695         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
8696         \r
8697         if (lg) {\r
8698             fseek(f, lg->offset, 0);\r
8699             GameListHighlight(gameNumber);\r
8700             gn = 1;\r
8701         }\r
8702         else {\r
8703             DisplayError(_("Game number out of range"), 0);\r
8704             return FALSE;\r
8705         }\r
8706     } else {\r
8707         GameListDestroy();\r
8708         if (fseek(f, 0, 0) == -1) {\r
8709             if (f == lastLoadGameFP ?\r
8710                 gameNumber == lastLoadGameNumber + 1 :\r
8711                 gameNumber == 1) {\r
8712                 gn = 1;\r
8713             } else {\r
8714                 DisplayError(_("Can't seek on game file"), 0);\r
8715                 return FALSE;\r
8716             }\r
8717         }\r
8718     }\r
8719     lastLoadGameFP = f;\r
8720     lastLoadGameNumber = gameNumber;\r
8721     strcpy(lastLoadGameTitle, title);\r
8722     lastLoadGameUseList = useList;\r
8723 \r
8724     yynewfile(f);\r
8725 \r
8726     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
8727         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
8728                 lg->gameInfo.black);\r
8729             DisplayTitle(buf);\r
8730     } else if (*title != NULLCHAR) {\r
8731         if (gameNumber > 1) {\r
8732             sprintf(buf, "%s %d", title, gameNumber);\r
8733             DisplayTitle(buf);\r
8734         } else {\r
8735             DisplayTitle(title);\r
8736         }\r
8737     }\r
8738 \r
8739     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
8740         gameMode = PlayFromGameFile;\r
8741         ModeHighlight();\r
8742     }\r
8743 \r
8744     currentMove = forwardMostMove = backwardMostMove = 0;\r
8745     CopyBoard(boards[0], initialPosition);\r
8746     StopClocks();\r
8747 \r
8748     /*\r
8749      * Skip the first gn-1 games in the file.\r
8750      * Also skip over anything that precedes an identifiable \r
8751      * start of game marker, to avoid being confused by \r
8752      * garbage at the start of the file.  Currently \r
8753      * recognized start of game markers are the move number "1",\r
8754      * the pattern "gnuchess .* game", the pattern\r
8755      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
8756      * A game that starts with one of the latter two patterns\r
8757      * will also have a move number 1, possibly\r
8758      * following a position diagram.\r
8759      * 5-4-02: Let's try being more lenient and allowing a game to\r
8760      * start with an unnumbered move.  Does that break anything?\r
8761      */\r
8762     cm = lastLoadGameStart = (ChessMove) 0;\r
8763     while (gn > 0) {\r
8764         yyboardindex = forwardMostMove;\r
8765         cm = (ChessMove) yylex();\r
8766         switch (cm) {\r
8767           case (ChessMove) 0:\r
8768             if (cmailMsgLoaded) {\r
8769                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
8770             } else {\r
8771                 Reset(TRUE, TRUE);\r
8772                 DisplayError(_("Game not found in file"), 0);\r
8773             }\r
8774             return FALSE;\r
8775 \r
8776           case GNUChessGame:\r
8777           case XBoardGame:\r
8778             gn--;\r
8779             lastLoadGameStart = cm;\r
8780             break;\r
8781             \r
8782           case MoveNumberOne:\r
8783             switch (lastLoadGameStart) {\r
8784               case GNUChessGame:\r
8785               case XBoardGame:\r
8786               case PGNTag:\r
8787                 break;\r
8788               case MoveNumberOne:\r
8789               case (ChessMove) 0:\r
8790                 gn--;           /* count this game */\r
8791                 lastLoadGameStart = cm;\r
8792                 break;\r
8793               default:\r
8794                 /* impossible */\r
8795                 break;\r
8796             }\r
8797             break;\r
8798 \r
8799           case PGNTag:\r
8800             switch (lastLoadGameStart) {\r
8801               case GNUChessGame:\r
8802               case PGNTag:\r
8803               case MoveNumberOne:\r
8804               case (ChessMove) 0:\r
8805                 gn--;           /* count this game */\r
8806                 lastLoadGameStart = cm;\r
8807                 break;\r
8808               case XBoardGame:\r
8809                 lastLoadGameStart = cm; /* game counted already */\r
8810                 break;\r
8811               default:\r
8812                 /* impossible */\r
8813                 break;\r
8814             }\r
8815             if (gn > 0) {\r
8816                 do {\r
8817                     yyboardindex = forwardMostMove;\r
8818                     cm = (ChessMove) yylex();\r
8819                 } while (cm == PGNTag || cm == Comment);\r
8820             }\r
8821             break;\r
8822 \r
8823           case WhiteWins:\r
8824           case BlackWins:\r
8825           case GameIsDrawn:\r
8826             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8827                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8828                     != CMAIL_OLD_RESULT) {\r
8829                     nCmailResults ++ ;\r
8830                     cmailResult[  CMAIL_MAX_GAMES\r
8831                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8832                 }\r
8833             }\r
8834             break;\r
8835 \r
8836           case NormalMove:\r
8837             /* Only a NormalMove can be at the start of a game\r
8838              * without a position diagram. */\r
8839             if (lastLoadGameStart == (ChessMove) 0) {\r
8840               gn--;\r
8841               lastLoadGameStart = MoveNumberOne;\r
8842             }\r
8843             break;\r
8844 \r
8845           default:\r
8846             break;\r
8847         }\r
8848     }\r
8849     \r
8850     if (appData.debugMode)\r
8851       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8852 \r
8853     if (cm == XBoardGame) {\r
8854         /* Skip any header junk before position diagram and/or move 1 */\r
8855         for (;;) {\r
8856             yyboardindex = forwardMostMove;\r
8857             cm = (ChessMove) yylex();\r
8858 \r
8859             if (cm == (ChessMove) 0 ||\r
8860                 cm == GNUChessGame || cm == XBoardGame) {\r
8861                 /* Empty game; pretend end-of-file and handle later */\r
8862                 cm = (ChessMove) 0;\r
8863                 break;\r
8864             }\r
8865 \r
8866             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8867                 cm == PGNTag || cm == Comment)\r
8868               break;\r
8869         }\r
8870     } else if (cm == GNUChessGame) {\r
8871         if (gameInfo.event != NULL) {\r
8872             free(gameInfo.event);\r
8873         }\r
8874         gameInfo.event = StrSave(yy_text);\r
8875     }   \r
8876 \r
8877     startedFromSetupPosition = FALSE;\r
8878     while (cm == PGNTag) {\r
8879         if (appData.debugMode) \r
8880           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8881         err = ParsePGNTag(yy_text, &gameInfo);\r
8882         if (!err) numPGNTags++;\r
8883 \r
8884         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8885         if(gameInfo.variant != oldVariant) {\r
8886             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8887             InitPosition(TRUE);\r
8888             oldVariant = gameInfo.variant;\r
8889             if (appData.debugMode) \r
8890               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8891         }\r
8892 \r
8893 \r
8894         if (gameInfo.fen != NULL) {\r
8895           Board initial_position;\r
8896           startedFromSetupPosition = TRUE;\r
8897           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8898             Reset(TRUE, TRUE);\r
8899             DisplayError(_("Bad FEN position in file"), 0);\r
8900             return FALSE;\r
8901           }\r
8902           CopyBoard(boards[0], initial_position);\r
8903           if (blackPlaysFirst) {\r
8904             currentMove = forwardMostMove = backwardMostMove = 1;\r
8905             CopyBoard(boards[1], initial_position);\r
8906             strcpy(moveList[0], "");\r
8907             strcpy(parseList[0], "");\r
8908             timeRemaining[0][1] = whiteTimeRemaining;\r
8909             timeRemaining[1][1] = blackTimeRemaining;\r
8910             if (commentList[0] != NULL) {\r
8911               commentList[1] = commentList[0];\r
8912               commentList[0] = NULL;\r
8913             }\r
8914           } else {\r
8915             currentMove = forwardMostMove = backwardMostMove = 0;\r
8916           }\r
8917           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */\r
8918           {   int i;\r
8919               initialRulePlies = FENrulePlies;\r
8920               epStatus[forwardMostMove] = FENepStatus;\r
8921               for( i=0; i< nrCastlingRights; i++ )\r
8922                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
8923           }\r
8924           yyboardindex = forwardMostMove;\r
8925           free(gameInfo.fen);\r
8926           gameInfo.fen = NULL;\r
8927         }\r
8928 \r
8929         yyboardindex = forwardMostMove;\r
8930         cm = (ChessMove) yylex();\r
8931 \r
8932         /* Handle comments interspersed among the tags */\r
8933         while (cm == Comment) {\r
8934             char *p;\r
8935             if (appData.debugMode) \r
8936               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8937             p = yy_text;\r
8938             if (*p == '{' || *p == '[' || *p == '(') {\r
8939                 p[strlen(p) - 1] = NULLCHAR;\r
8940                 p++;\r
8941             }\r
8942             while (*p == '\n') p++;\r
8943             AppendComment(currentMove, p);\r
8944             yyboardindex = forwardMostMove;\r
8945             cm = (ChessMove) yylex();\r
8946         }\r
8947     }\r
8948 \r
8949     /* don't rely on existence of Event tag since if game was\r
8950      * pasted from clipboard the Event tag may not exist\r
8951      */\r
8952     if (numPGNTags > 0){\r
8953         char *tags;\r
8954         if (gameInfo.variant == VariantNormal) {\r
8955           gameInfo.variant = StringToVariant(gameInfo.event);\r
8956         }\r
8957         if (!matchMode) {\r
8958           if( appData.autoDisplayTags ) {\r
8959             tags = PGNTags(&gameInfo);\r
8960             TagsPopUp(tags, CmailMsg());\r
8961             free(tags);\r
8962           }\r
8963         }\r
8964     } else {\r
8965         /* Make something up, but don't display it now */\r
8966         SetGameInfo();\r
8967         TagsPopDown();\r
8968     }\r
8969 \r
8970     if (cm == PositionDiagram) {\r
8971         int i, j;\r
8972         char *p;\r
8973         Board initial_position;\r
8974 \r
8975         if (appData.debugMode)\r
8976           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
8977 \r
8978         if (!startedFromSetupPosition) {\r
8979             p = yy_text;\r
8980             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
8981               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
8982                 switch (*p) {\r
8983                   case '[':\r
8984                   case '-':\r
8985                   case ' ':\r
8986                   case '\t':\r
8987                   case '\n':\r
8988                   case '\r':\r
8989                     break;\r
8990                   default:\r
8991                     initial_position[i][j++] = CharToPiece(*p);\r
8992                     break;\r
8993                 }\r
8994             while (*p == ' ' || *p == '\t' ||\r
8995                    *p == '\n' || *p == '\r') p++;\r
8996         \r
8997             if (strncmp(p, "black", strlen("black"))==0)\r
8998               blackPlaysFirst = TRUE;\r
8999             else\r
9000               blackPlaysFirst = FALSE;\r
9001             startedFromSetupPosition = TRUE;\r
9002         \r
9003             CopyBoard(boards[0], initial_position);\r
9004             if (blackPlaysFirst) {\r
9005                 currentMove = forwardMostMove = backwardMostMove = 1;\r
9006                 CopyBoard(boards[1], initial_position);\r
9007                 strcpy(moveList[0], "");\r
9008                 strcpy(parseList[0], "");\r
9009                 timeRemaining[0][1] = whiteTimeRemaining;\r
9010                 timeRemaining[1][1] = blackTimeRemaining;\r
9011                 if (commentList[0] != NULL) {\r
9012                     commentList[1] = commentList[0];\r
9013                     commentList[0] = NULL;\r
9014                 }\r
9015             } else {\r
9016                 currentMove = forwardMostMove = backwardMostMove = 0;\r
9017             }\r
9018         }\r
9019         yyboardindex = forwardMostMove;\r
9020         cm = (ChessMove) yylex();\r
9021     }\r
9022 \r
9023     if (first.pr == NoProc) {\r
9024         StartChessProgram(&first);\r
9025     }\r
9026     InitChessProgram(&first, FALSE);\r
9027     SendToProgram("force\n", &first);\r
9028     if (startedFromSetupPosition) {\r
9029         SendBoard(&first, forwardMostMove);\r
9030     if (appData.debugMode) {\r
9031         fprintf(debugFP, "Load Game\n");\r
9032     }\r
9033         DisplayBothClocks();\r
9034     }      \r
9035 \r
9036     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
9037     loadFlag = appData.suppressLoadMoves;\r
9038 \r
9039     while (cm == Comment) {\r
9040         char *p;\r
9041         if (appData.debugMode) \r
9042           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
9043         p = yy_text;\r
9044         if (*p == '{' || *p == '[' || *p == '(') {\r
9045             p[strlen(p) - 1] = NULLCHAR;\r
9046             p++;\r
9047         }\r
9048         while (*p == '\n') p++;\r
9049         AppendComment(currentMove, p);\r
9050         yyboardindex = forwardMostMove;\r
9051         cm = (ChessMove) yylex();\r
9052     }\r
9053 \r
9054     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
9055         cm == WhiteWins || cm == BlackWins ||\r
9056         cm == GameIsDrawn || cm == GameUnfinished) {\r
9057         DisplayMessage("", _("No moves in game"));\r
9058         if (cmailMsgLoaded) {\r
9059             if (appData.debugMode)\r
9060               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
9061             ClearHighlights();\r
9062             flipView = FALSE;\r
9063         }\r
9064         DrawPosition(FALSE, boards[currentMove]);\r
9065         DisplayBothClocks();\r
9066         gameMode = EditGame;\r
9067         ModeHighlight();\r
9068         gameFileFP = NULL;\r
9069         cmailOldMove = 0;\r
9070         return TRUE;\r
9071     }\r
9072 \r
9073     // [HGM] PV info: routine tests if comment empty\r
9074     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
9075         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9076     }\r
9077     if (!matchMode && appData.timeDelay != 0) \r
9078       DrawPosition(FALSE, boards[currentMove]);\r
9079 \r
9080     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
9081       programStats.ok_to_send = 1;\r
9082     }\r
9083 \r
9084     /* if the first token after the PGN tags is a move\r
9085      * and not move number 1, retrieve it from the parser \r
9086      */\r
9087     if (cm != MoveNumberOne)\r
9088         LoadGameOneMove(cm);\r
9089 \r
9090     /* load the remaining moves from the file */\r
9091     while (LoadGameOneMove((ChessMove)0)) {\r
9092       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9093       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9094     }\r
9095 \r
9096     /* rewind to the start of the game */\r
9097     currentMove = backwardMostMove;\r
9098 \r
9099     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9100 \r
9101     if (oldGameMode == AnalyzeFile ||\r
9102         oldGameMode == AnalyzeMode) {\r
9103       AnalyzeFileEvent();\r
9104     }\r
9105 \r
9106     if (matchMode || appData.timeDelay == 0) {\r
9107       ToEndEvent();\r
9108       gameMode = EditGame;\r
9109       ModeHighlight();\r
9110     } else if (appData.timeDelay > 0) {\r
9111       AutoPlayGameLoop();\r
9112     }\r
9113 \r
9114     if (appData.debugMode) \r
9115         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
9116 \r
9117     loadFlag = 0; /* [HGM] true game starts */\r
9118     return TRUE;\r
9119 }\r
9120 \r
9121 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
9122 int\r
9123 ReloadPosition(offset)\r
9124      int offset;\r
9125 {\r
9126     int positionNumber = lastLoadPositionNumber + offset;\r
9127     if (lastLoadPositionFP == NULL) {\r
9128         DisplayError(_("No position has been loaded yet"), 0);\r
9129         return FALSE;\r
9130     }\r
9131     if (positionNumber <= 0) {\r
9132         DisplayError(_("Can't back up any further"), 0);\r
9133         return FALSE;\r
9134     }\r
9135     return LoadPosition(lastLoadPositionFP, positionNumber,\r
9136                         lastLoadPositionTitle);\r
9137 }\r
9138 \r
9139 /* Load the nth position from the given file */\r
9140 int\r
9141 LoadPositionFromFile(filename, n, title)\r
9142      char *filename;\r
9143      int n;\r
9144      char *title;\r
9145 {\r
9146     FILE *f;\r
9147     char buf[MSG_SIZ];\r
9148 \r
9149     if (strcmp(filename, "-") == 0) {\r
9150         return LoadPosition(stdin, n, "stdin");\r
9151     } else {\r
9152         f = fopen(filename, "rb");\r
9153         if (f == NULL) {\r
9154             sprintf(buf, _("Can't open \"%s\""), filename);\r
9155             DisplayError(buf, errno);\r
9156             return FALSE;\r
9157         } else {\r
9158             return LoadPosition(f, n, title);\r
9159         }\r
9160     }\r
9161 }\r
9162 \r
9163 /* Load the nth position from the given open file, and close it */\r
9164 int\r
9165 LoadPosition(f, positionNumber, title)\r
9166      FILE *f;\r
9167      int positionNumber;\r
9168      char *title;\r
9169 {\r
9170     char *p, line[MSG_SIZ];\r
9171     Board initial_position;\r
9172     int i, j, fenMode, pn;\r
9173     \r
9174     if (gameMode == Training )\r
9175         SetTrainingModeOff();\r
9176 \r
9177     if (gameMode != BeginningOfGame) {\r
9178         Reset(FALSE, TRUE);\r
9179     }\r
9180     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
9181         fclose(lastLoadPositionFP);\r
9182     }\r
9183     if (positionNumber == 0) positionNumber = 1;\r
9184     lastLoadPositionFP = f;\r
9185     lastLoadPositionNumber = positionNumber;\r
9186     strcpy(lastLoadPositionTitle, title);\r
9187     if (first.pr == NoProc) {\r
9188       StartChessProgram(&first);\r
9189       InitChessProgram(&first, FALSE);\r
9190     }    \r
9191     pn = positionNumber;\r
9192     if (positionNumber < 0) {\r
9193         /* Negative position number means to seek to that byte offset */\r
9194         if (fseek(f, -positionNumber, 0) == -1) {\r
9195             DisplayError(_("Can't seek on position file"), 0);\r
9196             return FALSE;\r
9197         };\r
9198         pn = 1;\r
9199     } else {\r
9200         if (fseek(f, 0, 0) == -1) {\r
9201             if (f == lastLoadPositionFP ?\r
9202                 positionNumber == lastLoadPositionNumber + 1 :\r
9203                 positionNumber == 1) {\r
9204                 pn = 1;\r
9205             } else {\r
9206                 DisplayError(_("Can't seek on position file"), 0);\r
9207                 return FALSE;\r
9208             }\r
9209         }\r
9210     }\r
9211     /* See if this file is FEN or old-style xboard */\r
9212     if (fgets(line, MSG_SIZ, f) == NULL) {\r
9213         DisplayError(_("Position not found in file"), 0);\r
9214         return FALSE;\r
9215     }\r
9216 #if 0\r
9217     switch (line[0]) {\r
9218       case '#':  case 'x':\r
9219       default:\r
9220         fenMode = FALSE;\r
9221         break;\r
9222       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
9223       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
9224       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
9225       case '7':  case '8':  case '9':\r
9226       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
9227       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
9228       case 'C':  case 'W':             case 'c':  case 'w': \r
9229         fenMode = TRUE;\r
9230         break;\r
9231     }\r
9232 #else\r
9233     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
9234     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
9235 #endif\r
9236 \r
9237     if (pn >= 2) {\r
9238         if (fenMode || line[0] == '#') pn--;\r
9239         while (pn > 0) {\r
9240             /* skip positions before number pn */\r
9241             if (fgets(line, MSG_SIZ, f) == NULL) {\r
9242                 Reset(TRUE, TRUE);\r
9243                 DisplayError(_("Position not found in file"), 0);\r
9244                 return FALSE;\r
9245             }\r
9246             if (fenMode || line[0] == '#') pn--;\r
9247         }\r
9248     }\r
9249 \r
9250     if (fenMode) {\r
9251         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
9252             DisplayError(_("Bad FEN position in file"), 0);\r
9253             return FALSE;\r
9254         }\r
9255     } else {\r
9256         (void) fgets(line, MSG_SIZ, f);\r
9257         (void) fgets(line, MSG_SIZ, f);\r
9258     \r
9259         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
9260             (void) fgets(line, MSG_SIZ, f);\r
9261             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
9262                 if (*p == ' ')\r
9263                   continue;\r
9264                 initial_position[i][j++] = CharToPiece(*p);\r
9265             }\r
9266         }\r
9267     \r
9268         blackPlaysFirst = FALSE;\r
9269         if (!feof(f)) {\r
9270             (void) fgets(line, MSG_SIZ, f);\r
9271             if (strncmp(line, "black", strlen("black"))==0)\r
9272               blackPlaysFirst = TRUE;\r
9273         }\r
9274     }\r
9275     startedFromSetupPosition = TRUE;\r
9276     \r
9277     SendToProgram("force\n", &first);\r
9278     CopyBoard(boards[0], initial_position);\r
9279     if (blackPlaysFirst) {\r
9280         currentMove = forwardMostMove = backwardMostMove = 1;\r
9281         strcpy(moveList[0], "");\r
9282         strcpy(parseList[0], "");\r
9283         CopyBoard(boards[1], initial_position);\r
9284         DisplayMessage("", _("Black to play"));\r
9285     } else {\r
9286         currentMove = forwardMostMove = backwardMostMove = 0;\r
9287         DisplayMessage("", _("White to play"));\r
9288     }\r
9289           /* [HGM] copy FEN attributes as well */\r
9290           {   int i;\r
9291               initialRulePlies = FENrulePlies;\r
9292               epStatus[forwardMostMove] = FENepStatus;\r
9293               for( i=0; i< nrCastlingRights; i++ )\r
9294                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
9295           }\r
9296     SendBoard(&first, forwardMostMove);\r
9297     if (appData.debugMode) {\r
9298 int i, j;\r
9299   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}\r
9300   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");\r
9301         fprintf(debugFP, "Load Position\n");\r
9302     }\r
9303 \r
9304     if (positionNumber > 1) {\r
9305         sprintf(line, "%s %d", title, positionNumber);\r
9306         DisplayTitle(line);\r
9307     } else {\r
9308         DisplayTitle(title);\r
9309     }\r
9310     gameMode = EditGame;\r
9311     ModeHighlight();\r
9312     ResetClocks();\r
9313     timeRemaining[0][1] = whiteTimeRemaining;\r
9314     timeRemaining[1][1] = blackTimeRemaining;\r
9315     DrawPosition(FALSE, boards[currentMove]);\r
9316    \r
9317     return TRUE;\r
9318 }\r
9319 \r
9320 \r
9321 void\r
9322 CopyPlayerNameIntoFileName(dest, src)\r
9323      char **dest, *src;\r
9324 {\r
9325     while (*src != NULLCHAR && *src != ',') {\r
9326         if (*src == ' ') {\r
9327             *(*dest)++ = '_';\r
9328             src++;\r
9329         } else {\r
9330             *(*dest)++ = *src++;\r
9331         }\r
9332     }\r
9333 }\r
9334 \r
9335 char *DefaultFileName(ext)\r
9336      char *ext;\r
9337 {\r
9338     static char def[MSG_SIZ];\r
9339     char *p;\r
9340 \r
9341     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
9342         p = def;\r
9343         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
9344         *p++ = '-';\r
9345         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
9346         *p++ = '.';\r
9347         strcpy(p, ext);\r
9348     } else {\r
9349         def[0] = NULLCHAR;\r
9350     }\r
9351     return def;\r
9352 }\r
9353 \r
9354 /* Save the current game to the given file */\r
9355 int\r
9356 SaveGameToFile(filename, append)\r
9357      char *filename;\r
9358      int append;\r
9359 {\r
9360     FILE *f;\r
9361     char buf[MSG_SIZ];\r
9362 \r
9363     if (strcmp(filename, "-") == 0) {\r
9364         return SaveGame(stdout, 0, NULL);\r
9365     } else {\r
9366         f = fopen(filename, append ? "a" : "w");\r
9367         if (f == NULL) {\r
9368             sprintf(buf, _("Can't open \"%s\""), filename);\r
9369             DisplayError(buf, errno);\r
9370             return FALSE;\r
9371         } else {\r
9372             return SaveGame(f, 0, NULL);\r
9373         }\r
9374     }\r
9375 }\r
9376 \r
9377 char *\r
9378 SavePart(str)\r
9379      char *str;\r
9380 {\r
9381     static char buf[MSG_SIZ];\r
9382     char *p;\r
9383     \r
9384     p = strchr(str, ' ');\r
9385     if (p == NULL) return str;\r
9386     strncpy(buf, str, p - str);\r
9387     buf[p - str] = NULLCHAR;\r
9388     return buf;\r
9389 }\r
9390 \r
9391 #define PGN_MAX_LINE 75\r
9392 \r
9393 #define PGN_SIDE_WHITE  0\r
9394 #define PGN_SIDE_BLACK  1\r
9395 \r
9396 /* [AS] */\r
9397 static int FindFirstMoveOutOfBook( int side )\r
9398 {\r
9399     int result = -1;\r
9400 \r
9401     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
9402         int index = backwardMostMove;\r
9403         int has_book_hit = 0;\r
9404 \r
9405         if( (index % 2) != side ) {\r
9406             index++;\r
9407         }\r
9408 \r
9409         while( index < forwardMostMove ) {\r
9410             /* Check to see if engine is in book */\r
9411             int depth = pvInfoList[index].depth;\r
9412             int score = pvInfoList[index].score;\r
9413             int in_book = 0;\r
9414 \r
9415             if( depth <= 2 ) {\r
9416                 in_book = 1;\r
9417             }\r
9418             else if( score == 0 && depth == 63 ) {\r
9419                 in_book = 1; /* Zappa */\r
9420             }\r
9421             else if( score == 2 && depth == 99 ) {\r
9422                 in_book = 1; /* Abrok */\r
9423             }\r
9424 \r
9425             has_book_hit += in_book;\r
9426 \r
9427             if( ! in_book ) {\r
9428                 result = index;\r
9429 \r
9430                 break;\r
9431             }\r
9432 \r
9433             index += 2;\r
9434         }\r
9435     }\r
9436 \r
9437     return result;\r
9438 }\r
9439 \r
9440 /* [AS] */\r
9441 void GetOutOfBookInfo( char * buf )\r
9442 {\r
9443     int oob[2];\r
9444     int i;\r
9445     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9446 \r
9447     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
9448     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
9449 \r
9450     *buf = '\0';\r
9451 \r
9452     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
9453         for( i=0; i<2; i++ ) {\r
9454             int idx = oob[i];\r
9455 \r
9456             if( idx >= 0 ) {\r
9457                 if( i > 0 && oob[0] >= 0 ) {\r
9458                     strcat( buf, "   " );\r
9459                 }\r
9460 \r
9461                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
9462                 sprintf( buf+strlen(buf), "%s%.2f", \r
9463                     pvInfoList[idx].score >= 0 ? "+" : "",\r
9464                     pvInfoList[idx].score / 100.0 );\r
9465             }\r
9466         }\r
9467     }\r
9468 }\r
9469 \r
9470 /* Save game in PGN style and close the file */\r
9471 int\r
9472 SaveGamePGN(f)\r
9473      FILE *f;\r
9474 {\r
9475     int i, offset, linelen, newblock;\r
9476     time_t tm;\r
9477 //    char *movetext;\r
9478     char numtext[32];\r
9479     int movelen, numlen, blank;\r
9480     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
9481 \r
9482     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9483     \r
9484     tm = time((time_t *) NULL);\r
9485     \r
9486     PrintPGNTags(f, &gameInfo);\r
9487     \r
9488     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9489         char *fen = PositionToFEN(backwardMostMove, NULL);\r
9490         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
9491         fprintf(f, "\n{--------------\n");\r
9492         PrintPosition(f, backwardMostMove);\r
9493         fprintf(f, "--------------}\n");\r
9494         free(fen);\r
9495     }\r
9496     else {\r
9497         /* [AS] Out of book annotation */\r
9498         if( appData.saveOutOfBookInfo ) {\r
9499             char buf[64];\r
9500 \r
9501             GetOutOfBookInfo( buf );\r
9502 \r
9503             if( buf[0] != '\0' ) {\r
9504                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
9505             }\r
9506         }\r
9507 \r
9508         fprintf(f, "\n");\r
9509     }\r
9510 \r
9511     i = backwardMostMove;\r
9512     linelen = 0;\r
9513     newblock = TRUE;\r
9514 \r
9515     while (i < forwardMostMove) {\r
9516         /* Print comments preceding this move */\r
9517         if (commentList[i] != NULL) {\r
9518             if (linelen > 0) fprintf(f, "\n");\r
9519             fprintf(f, "{\n%s}\n", commentList[i]);\r
9520             linelen = 0;\r
9521             newblock = TRUE;\r
9522         }\r
9523 \r
9524         /* Format move number */\r
9525         if ((i % 2) == 0) {\r
9526             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
9527         } else {\r
9528             if (newblock) {\r
9529                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
9530             } else {\r
9531                 numtext[0] = NULLCHAR;\r
9532             }\r
9533         }\r
9534         numlen = strlen(numtext);\r
9535         newblock = FALSE;\r
9536 \r
9537         /* Print move number */\r
9538         blank = linelen > 0 && numlen > 0;\r
9539         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
9540             fprintf(f, "\n");\r
9541             linelen = 0;\r
9542             blank = 0;\r
9543         }\r
9544         if (blank) {\r
9545             fprintf(f, " ");\r
9546             linelen++;\r
9547         }\r
9548         fprintf(f, numtext);\r
9549         linelen += numlen;\r
9550 \r
9551         /* Get move */\r
9552         movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */\r
9553 \r
9554         /* Print move */\r
9555         blank = linelen > 0 && movelen > 0;\r
9556         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9557             fprintf(f, "\n");\r
9558             linelen = 0;\r
9559             blank = 0;\r
9560         }\r
9561         if (blank) {\r
9562             fprintf(f, " ");\r
9563             linelen++;\r
9564         }\r
9565         fprintf(f, parseList[i]);\r
9566         linelen += movelen;\r
9567 \r
9568         /* [AS] Add PV info if present */\r
9569         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9570             /* [HGM] add time */\r
9571             char buf[MSG_SIZ]; int seconds = 0;\r
9572 \r
9573 #if 0\r
9574             if(i >= backwardMostMove) {\r
9575                 if(WhiteOnMove(i))\r
9576                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]\r
9577                                   + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;\r
9578                 else\r
9579                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]\r
9580                                   + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;\r
9581             }\r
9582             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest\r
9583 #else\r
9584             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time\r
9585 #endif\r
9586 \r
9587             if( seconds <= 0) buf[0] = 0; else\r
9588             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {\r
9589                 seconds = (seconds + 4)/10; // round to full seconds\r
9590                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else\r
9591                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
9592             }\r
9593 \r
9594             sprintf( move_buffer, "{%s%.2f/%d%s}", \r
9595                 pvInfoList[i].score >= 0 ? "+" : "",\r
9596                 pvInfoList[i].score / 100.0,\r
9597                 pvInfoList[i].depth,\r
9598                 buf );\r
9599 \r
9600             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */\r
9601 \r
9602             /* Print score/depth */\r
9603             blank = linelen > 0 && movelen > 0;\r
9604             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9605                 fprintf(f, "\n");\r
9606                 linelen = 0;\r
9607                 blank = 0;\r
9608             }\r
9609             if (blank) {\r
9610                 fprintf(f, " ");\r
9611                 linelen++;\r
9612             }\r
9613             fprintf(f, move_buffer);\r
9614             linelen += movelen;\r
9615         }\r
9616 \r
9617         i++;\r
9618     }\r
9619     \r
9620     /* Start a new line */\r
9621     if (linelen > 0) fprintf(f, "\n");\r
9622 \r
9623     /* Print comments after last move */\r
9624     if (commentList[i] != NULL) {\r
9625         fprintf(f, "{\n%s}\n", commentList[i]);\r
9626     }\r
9627 \r
9628     /* Print result */\r
9629     if (gameInfo.resultDetails != NULL &&\r
9630         gameInfo.resultDetails[0] != NULLCHAR) {\r
9631         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
9632                 PGNResult(gameInfo.result));\r
9633     } else {\r
9634         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9635     }\r
9636 \r
9637     fclose(f);\r
9638     return TRUE;\r
9639 }\r
9640 \r
9641 /* Save game in old style and close the file */\r
9642 int\r
9643 SaveGameOldStyle(f)\r
9644      FILE *f;\r
9645 {\r
9646     int i, offset;\r
9647     time_t tm;\r
9648     \r
9649     tm = time((time_t *) NULL);\r
9650     \r
9651     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
9652     PrintOpponents(f);\r
9653     \r
9654     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9655         fprintf(f, "\n[--------------\n");\r
9656         PrintPosition(f, backwardMostMove);\r
9657         fprintf(f, "--------------]\n");\r
9658     } else {\r
9659         fprintf(f, "\n");\r
9660     }\r
9661 \r
9662     i = backwardMostMove;\r
9663     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9664 \r
9665     while (i < forwardMostMove) {\r
9666         if (commentList[i] != NULL) {\r
9667             fprintf(f, "[%s]\n", commentList[i]);\r
9668         }\r
9669 \r
9670         if ((i % 2) == 1) {\r
9671             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
9672             i++;\r
9673         } else {\r
9674             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
9675             i++;\r
9676             if (commentList[i] != NULL) {\r
9677                 fprintf(f, "\n");\r
9678                 continue;\r
9679             }\r
9680             if (i >= forwardMostMove) {\r
9681                 fprintf(f, "\n");\r
9682                 break;\r
9683             }\r
9684             fprintf(f, "%s\n", parseList[i]);\r
9685             i++;\r
9686         }\r
9687     }\r
9688     \r
9689     if (commentList[i] != NULL) {\r
9690         fprintf(f, "[%s]\n", commentList[i]);\r
9691     }\r
9692 \r
9693     /* This isn't really the old style, but it's close enough */\r
9694     if (gameInfo.resultDetails != NULL &&\r
9695         gameInfo.resultDetails[0] != NULLCHAR) {\r
9696         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
9697                 gameInfo.resultDetails);\r
9698     } else {\r
9699         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9700     }\r
9701 \r
9702     fclose(f);\r
9703     return TRUE;\r
9704 }\r
9705 \r
9706 /* Save the current game to open file f and close the file */\r
9707 int\r
9708 SaveGame(f, dummy, dummy2)\r
9709      FILE *f;\r
9710      int dummy;\r
9711      char *dummy2;\r
9712 {\r
9713     if (gameMode == EditPosition) EditPositionDone();\r
9714     if (appData.oldSaveStyle)\r
9715       return SaveGameOldStyle(f);\r
9716     else\r
9717       return SaveGamePGN(f);\r
9718 }\r
9719 \r
9720 /* Save the current position to the given file */\r
9721 int\r
9722 SavePositionToFile(filename)\r
9723      char *filename;\r
9724 {\r
9725     FILE *f;\r
9726     char buf[MSG_SIZ];\r
9727 \r
9728     if (strcmp(filename, "-") == 0) {\r
9729         return SavePosition(stdout, 0, NULL);\r
9730     } else {\r
9731         f = fopen(filename, "a");\r
9732         if (f == NULL) {\r
9733             sprintf(buf, _("Can't open \"%s\""), filename);\r
9734             DisplayError(buf, errno);\r
9735             return FALSE;\r
9736         } else {\r
9737             SavePosition(f, 0, NULL);\r
9738             return TRUE;\r
9739         }\r
9740     }\r
9741 }\r
9742 \r
9743 /* Save the current position to the given open file and close the file */\r
9744 int\r
9745 SavePosition(f, dummy, dummy2)\r
9746      FILE *f;\r
9747      int dummy;\r
9748      char *dummy2;\r
9749 {\r
9750     time_t tm;\r
9751     char *fen;\r
9752     \r
9753     if (appData.oldSaveStyle) {\r
9754         tm = time((time_t *) NULL);\r
9755     \r
9756         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
9757         PrintOpponents(f);\r
9758         fprintf(f, "[--------------\n");\r
9759         PrintPosition(f, currentMove);\r
9760         fprintf(f, "--------------]\n");\r
9761     } else {\r
9762         fen = PositionToFEN(currentMove, NULL);\r
9763         fprintf(f, "%s\n", fen);\r
9764         free(fen);\r
9765     }\r
9766     fclose(f);\r
9767     return TRUE;\r
9768 }\r
9769 \r
9770 void\r
9771 ReloadCmailMsgEvent(unregister)\r
9772      int unregister;\r
9773 {\r
9774 #if !WIN32\r
9775     static char *inFilename = NULL;\r
9776     static char *outFilename;\r
9777     int i;\r
9778     struct stat inbuf, outbuf;\r
9779     int status;\r
9780     \r
9781     /* Any registered moves are unregistered if unregister is set, */\r
9782     /* i.e. invoked by the signal handler */\r
9783     if (unregister) {\r
9784         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9785             cmailMoveRegistered[i] = FALSE;\r
9786             if (cmailCommentList[i] != NULL) {\r
9787                 free(cmailCommentList[i]);\r
9788                 cmailCommentList[i] = NULL;\r
9789             }\r
9790         }\r
9791         nCmailMovesRegistered = 0;\r
9792     }\r
9793 \r
9794     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9795         cmailResult[i] = CMAIL_NOT_RESULT;\r
9796     }\r
9797     nCmailResults = 0;\r
9798 \r
9799     if (inFilename == NULL) {\r
9800         /* Because the filenames are static they only get malloced once  */\r
9801         /* and they never get freed                                      */\r
9802         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
9803         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
9804 \r
9805         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
9806         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
9807     }\r
9808     \r
9809     status = stat(outFilename, &outbuf);\r
9810     if (status < 0) {\r
9811         cmailMailedMove = FALSE;\r
9812     } else {\r
9813         status = stat(inFilename, &inbuf);\r
9814         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
9815     }\r
9816     \r
9817     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
9818        counts the games, notes how each one terminated, etc.\r
9819        \r
9820        It would be nice to remove this kludge and instead gather all\r
9821        the information while building the game list.  (And to keep it\r
9822        in the game list nodes instead of having a bunch of fixed-size\r
9823        parallel arrays.)  Note this will require getting each game's\r
9824        termination from the PGN tags, as the game list builder does\r
9825        not process the game moves.  --mann\r
9826        */\r
9827     cmailMsgLoaded = TRUE;\r
9828     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
9829     \r
9830     /* Load first game in the file or popup game menu */\r
9831     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
9832 \r
9833 #endif /* !WIN32 */\r
9834     return;\r
9835 }\r
9836 \r
9837 int\r
9838 RegisterMove()\r
9839 {\r
9840     FILE *f;\r
9841     char string[MSG_SIZ];\r
9842 \r
9843     if (   cmailMailedMove\r
9844         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9845         return TRUE;            /* Allow free viewing  */\r
9846     }\r
9847 \r
9848     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9849     /* with the move registered when the conditions for registering no   */\r
9850     /* longer hold                                                       */\r
9851     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9852         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9853         nCmailMovesRegistered --;\r
9854 \r
9855         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9856           {\r
9857               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9858               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9859           }\r
9860     }\r
9861 \r
9862     if (cmailOldMove == -1) {\r
9863         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);\r
9864         return FALSE;\r
9865     }\r
9866 \r
9867     if (currentMove > cmailOldMove + 1) {\r
9868         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);\r
9869         return FALSE;\r
9870     }\r
9871 \r
9872     if (currentMove < cmailOldMove) {\r
9873         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);\r
9874         return FALSE;\r
9875     }\r
9876 \r
9877     if (forwardMostMove > currentMove) {\r
9878         /* Silently truncate extra moves */\r
9879         TruncateGame();\r
9880     }\r
9881 \r
9882     if (   (currentMove == cmailOldMove + 1)\r
9883         || (   (currentMove == cmailOldMove)\r
9884             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9885                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9886         if (gameInfo.result != GameUnfinished) {\r
9887             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9888         }\r
9889 \r
9890         if (commentList[currentMove] != NULL) {\r
9891             cmailCommentList[lastLoadGameNumber - 1]\r
9892               = StrSave(commentList[currentMove]);\r
9893         }\r
9894         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9895 \r
9896         if (appData.debugMode)\r
9897           fprintf(debugFP, "Saving %s for game %d\n",\r
9898                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9899 \r
9900         sprintf(string,\r
9901                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
9902         \r
9903         f = fopen(string, "w");\r
9904         if (appData.oldSaveStyle) {\r
9905             SaveGameOldStyle(f); /* also closes the file */\r
9906             \r
9907             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
9908             f = fopen(string, "w");\r
9909             SavePosition(f, 0, NULL); /* also closes the file */\r
9910         } else {\r
9911             fprintf(f, "{--------------\n");\r
9912             PrintPosition(f, currentMove);\r
9913             fprintf(f, "--------------}\n\n");\r
9914             \r
9915             SaveGame(f, 0, NULL); /* also closes the file*/\r
9916         }\r
9917         \r
9918         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
9919         nCmailMovesRegistered ++;\r
9920     } else if (nCmailGames == 1) {\r
9921         DisplayError(_("You have not made a move yet"), 0);\r
9922         return FALSE;\r
9923     }\r
9924 \r
9925     return TRUE;\r
9926 }\r
9927 \r
9928 void\r
9929 MailMoveEvent()\r
9930 {\r
9931 #if !WIN32\r
9932     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
9933     FILE *commandOutput;\r
9934     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
9935     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
9936     int nBuffers;\r
9937     int i;\r
9938     int archived;\r
9939     char *arcDir;\r
9940 \r
9941     if (! cmailMsgLoaded) {\r
9942         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);\r
9943         return;\r
9944     }\r
9945 \r
9946     if (nCmailGames == nCmailResults) {\r
9947         DisplayError(_("No unfinished games"), 0);\r
9948         return;\r
9949     }\r
9950 \r
9951 #if CMAIL_PROHIBIT_REMAIL\r
9952     if (cmailMailedMove) {\r
9953         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
9954         DisplayError(msg, 0);\r
9955         return;\r
9956     }\r
9957 #endif\r
9958 \r
9959     if (! (cmailMailedMove || RegisterMove())) return;\r
9960     \r
9961     if (   cmailMailedMove\r
9962         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
9963         sprintf(string, partCommandString,\r
9964                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
9965         commandOutput = popen(string, "r");\r
9966 \r
9967         if (commandOutput == NULL) {\r
9968             DisplayError(_("Failed to invoke cmail"), 0);\r
9969         } else {\r
9970             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
9971                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
9972             }\r
9973             if (nBuffers > 1) {\r
9974                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
9975                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
9976                 nBytes = MSG_SIZ - 1;\r
9977             } else {\r
9978                 (void) memcpy(msg, buffer, nBytes);\r
9979             }\r
9980             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
9981 \r
9982             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
9983                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
9984 \r
9985                 archived = TRUE;\r
9986                 for (i = 0; i < nCmailGames; i ++) {\r
9987                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
9988                         archived = FALSE;\r
9989                     }\r
9990                 }\r
9991                 if (   archived\r
9992                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
9993                         != NULL)) {\r
9994                     sprintf(buffer, "%s/%s.%s.archive",\r
9995                             arcDir,\r
9996                             appData.cmailGameName,\r
9997                             gameInfo.date);\r
9998                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
9999                     cmailMsgLoaded = FALSE;\r
10000                 }\r
10001             }\r
10002 \r
10003             DisplayInformation(msg);\r
10004             pclose(commandOutput);\r
10005         }\r
10006     } else {\r
10007         if ((*cmailMsg) != '\0') {\r
10008             DisplayInformation(cmailMsg);\r
10009         }\r
10010     }\r
10011 \r
10012     return;\r
10013 #endif /* !WIN32 */\r
10014 }\r
10015 \r
10016 char *\r
10017 CmailMsg()\r
10018 {\r
10019 #if WIN32\r
10020     return NULL;\r
10021 #else\r
10022     int  prependComma = 0;\r
10023     char number[5];\r
10024     char string[MSG_SIZ];       /* Space for game-list */\r
10025     int  i;\r
10026     \r
10027     if (!cmailMsgLoaded) return "";\r
10028 \r
10029     if (cmailMailedMove) {\r
10030         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));\r
10031     } else {\r
10032         /* Create a list of games left */\r
10033         sprintf(string, "[");\r
10034         for (i = 0; i < nCmailGames; i ++) {\r
10035             if (! (   cmailMoveRegistered[i]\r
10036                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
10037                 if (prependComma) {\r
10038                     sprintf(number, ",%d", i + 1);\r
10039                 } else {\r
10040                     sprintf(number, "%d", i + 1);\r
10041                     prependComma = 1;\r
10042                 }\r
10043                 \r
10044                 strcat(string, number);\r
10045             }\r
10046         }\r
10047         strcat(string, "]");\r
10048 \r
10049         if (nCmailMovesRegistered + nCmailResults == 0) {\r
10050             switch (nCmailGames) {\r
10051               case 1:\r
10052                 sprintf(cmailMsg,\r
10053                         _("Still need to make move for game\n"));\r
10054                 break;\r
10055                 \r
10056               case 2:\r
10057                 sprintf(cmailMsg,\r
10058                         _("Still need to make moves for both games\n"));\r
10059                 break;\r
10060                 \r
10061               default:\r
10062                 sprintf(cmailMsg,\r
10063                         _("Still need to make moves for all %d games\n"),\r
10064                         nCmailGames);\r
10065                 break;\r
10066             }\r
10067         } else {\r
10068             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
10069               case 1:\r
10070                 sprintf(cmailMsg,\r
10071                         _("Still need to make a move for game %s\n"),\r
10072                         string);\r
10073                 break;\r
10074                 \r
10075               case 0:\r
10076                 if (nCmailResults == nCmailGames) {\r
10077                     sprintf(cmailMsg, _("No unfinished games\n"));\r
10078                 } else {\r
10079                     sprintf(cmailMsg, _("Ready to send mail\n"));\r
10080                 }\r
10081                 break;\r
10082                 \r
10083               default:\r
10084                 sprintf(cmailMsg,\r
10085                         _("Still need to make moves for games %s\n"),\r
10086                         string);\r
10087             }\r
10088         }\r
10089     }\r
10090     return cmailMsg;\r
10091 #endif /* WIN32 */\r
10092 }\r
10093 \r
10094 void\r
10095 ResetGameEvent()\r
10096 {\r
10097     if (gameMode == Training)\r
10098       SetTrainingModeOff();\r
10099 \r
10100     Reset(TRUE, TRUE);\r
10101     cmailMsgLoaded = FALSE;\r
10102     if (appData.icsActive) {\r
10103       SendToICS(ics_prefix);\r
10104       SendToICS("refresh\n");\r
10105     }\r
10106 }\r
10107 \r
10108 void\r
10109 ExitEvent(status)\r
10110      int status;\r
10111 {\r
10112     exiting++;\r
10113     if (exiting > 2) {\r
10114       /* Give up on clean exit */\r
10115       exit(status);\r
10116     }\r
10117     if (exiting > 1) {\r
10118       /* Keep trying for clean exit */\r
10119       return;\r
10120     }\r
10121 \r
10122     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
10123 \r
10124     if (telnetISR != NULL) {\r
10125       RemoveInputSource(telnetISR);\r
10126     }\r
10127     if (icsPR != NoProc) {\r
10128       DestroyChildProcess(icsPR, TRUE);\r
10129     }\r
10130 #if 0\r
10131     /* Save game if resource set and not already saved by GameEnds() */\r
10132     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
10133                              && forwardMostMove > 0) {\r
10134       if (*appData.saveGameFile != NULLCHAR) {\r
10135         SaveGameToFile(appData.saveGameFile, TRUE);\r
10136       } else if (appData.autoSaveGames) {\r
10137         AutoSaveGame();\r
10138       }\r
10139       if (*appData.savePositionFile != NULLCHAR) {\r
10140         SavePositionToFile(appData.savePositionFile);\r
10141       }\r
10142     }\r
10143     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10144 #else\r
10145     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
10146     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);\r
10147 #endif\r
10148     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
10149     /* make sure this other one finishes before killing it!                  */\r
10150     if(endingGame) { int count = 0;\r
10151         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
10152         while(endingGame && count++ < 10) DoSleep(1);\r
10153         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
10154     }\r
10155 \r
10156     /* Kill off chess programs */\r
10157     if (first.pr != NoProc) {\r
10158         ExitAnalyzeMode();\r
10159         \r
10160         DoSleep( appData.delayBeforeQuit );\r
10161         SendToProgram("quit\n", &first);\r
10162         DoSleep( appData.delayAfterQuit );\r
10163         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
10164     }\r
10165     if (second.pr != NoProc) {\r
10166         DoSleep( appData.delayBeforeQuit );\r
10167         SendToProgram("quit\n", &second);\r
10168         DoSleep( appData.delayAfterQuit );\r
10169         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
10170     }\r
10171     if (first.isr != NULL) {\r
10172         RemoveInputSource(first.isr);\r
10173     }\r
10174     if (second.isr != NULL) {\r
10175         RemoveInputSource(second.isr);\r
10176     }\r
10177 \r
10178     ShutDownFrontEnd();\r
10179     exit(status);\r
10180 }\r
10181 \r
10182 void\r
10183 PauseEvent()\r
10184 {\r
10185     if (appData.debugMode)\r
10186         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
10187     if (pausing) {\r
10188         pausing = FALSE;\r
10189         ModeHighlight();\r
10190         if (gameMode == MachinePlaysWhite ||\r
10191             gameMode == MachinePlaysBlack) {\r
10192             StartClocks();\r
10193         } else {\r
10194             DisplayBothClocks();\r
10195         }\r
10196         if (gameMode == PlayFromGameFile) {\r
10197             if (appData.timeDelay >= 0) \r
10198                 AutoPlayGameLoop();\r
10199         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
10200             Reset(FALSE, TRUE);\r
10201             SendToICS(ics_prefix);\r
10202             SendToICS("refresh\n");\r
10203         } else if (currentMove < forwardMostMove) {\r
10204             ForwardInner(forwardMostMove);\r
10205         }\r
10206         pauseExamInvalid = FALSE;\r
10207     } else {\r
10208         switch (gameMode) {\r
10209           default:\r
10210             return;\r
10211           case IcsExamining:\r
10212             pauseExamForwardMostMove = forwardMostMove;\r
10213             pauseExamInvalid = FALSE;\r
10214             /* fall through */\r
10215           case IcsObserving:\r
10216           case IcsPlayingWhite:\r
10217           case IcsPlayingBlack:\r
10218             pausing = TRUE;\r
10219             ModeHighlight();\r
10220             return;\r
10221           case PlayFromGameFile:\r
10222             (void) StopLoadGameTimer();\r
10223             pausing = TRUE;\r
10224             ModeHighlight();\r
10225             break;\r
10226           case BeginningOfGame:\r
10227             if (appData.icsActive) return;\r
10228             /* else fall through */\r
10229           case MachinePlaysWhite:\r
10230           case MachinePlaysBlack:\r
10231           case TwoMachinesPlay:\r
10232             if (forwardMostMove == 0)\r
10233               return;           /* don't pause if no one has moved */\r
10234             if ((gameMode == MachinePlaysWhite &&\r
10235                  !WhiteOnMove(forwardMostMove)) ||\r
10236                 (gameMode == MachinePlaysBlack &&\r
10237                  WhiteOnMove(forwardMostMove))) {\r
10238                 StopClocks();\r
10239             }\r
10240             pausing = TRUE;\r
10241             ModeHighlight();\r
10242             break;\r
10243         }\r
10244     }\r
10245 }\r
10246 \r
10247 void\r
10248 EditCommentEvent()\r
10249 {\r
10250     char title[MSG_SIZ];\r
10251 \r
10252     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
10253         strcpy(title, _("Edit comment"));\r
10254     } else {\r
10255         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,\r
10256                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
10257                 parseList[currentMove - 1]);\r
10258     }\r
10259 \r
10260     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
10261 }\r
10262 \r
10263 \r
10264 void\r
10265 EditTagsEvent()\r
10266 {\r
10267     char *tags = PGNTags(&gameInfo);\r
10268     EditTagsPopUp(tags);\r
10269     free(tags);\r
10270 }\r
10271 \r
10272 void\r
10273 AnalyzeModeEvent()\r
10274 {\r
10275     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
10276       return;\r
10277 \r
10278     if (gameMode != AnalyzeFile) {\r
10279         if (!appData.icsEngineAnalyze) {\r
10280                EditGameEvent();\r
10281                if (gameMode != EditGame) return;\r
10282         }\r
10283         ResurrectChessProgram();\r
10284         SendToProgram("analyze\n", &first);\r
10285         first.analyzing = TRUE;\r
10286         /*first.maybeThinking = TRUE;*/\r
10287         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10288         AnalysisPopUp(_("Analysis"),\r
10289                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10290     }\r
10291     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;\r
10292     pausing = FALSE;\r
10293     ModeHighlight();\r
10294     SetGameInfo();\r
10295 \r
10296     StartAnalysisClock();\r
10297     GetTimeMark(&lastNodeCountTime);\r
10298     lastNodeCount = 0;\r
10299 }\r
10300 \r
10301 void\r
10302 AnalyzeFileEvent()\r
10303 {\r
10304     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
10305       return;\r
10306 \r
10307     if (gameMode != AnalyzeMode) {\r
10308         EditGameEvent();\r
10309         if (gameMode != EditGame) return;\r
10310         ResurrectChessProgram();\r
10311         SendToProgram("analyze\n", &first);\r
10312         first.analyzing = TRUE;\r
10313         /*first.maybeThinking = TRUE;*/\r
10314         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10315         AnalysisPopUp(_("Analysis"),\r
10316                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10317     }\r
10318     gameMode = AnalyzeFile;\r
10319     pausing = FALSE;\r
10320     ModeHighlight();\r
10321     SetGameInfo();\r
10322 \r
10323     StartAnalysisClock();\r
10324     GetTimeMark(&lastNodeCountTime);\r
10325     lastNodeCount = 0;\r
10326 }\r
10327 \r
10328 void\r
10329 MachineWhiteEvent()\r
10330 {\r
10331     char buf[MSG_SIZ];\r
10332     char *bookHit = NULL;\r
10333 \r
10334     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
10335       return;\r
10336 \r
10337 \r
10338     if (gameMode == PlayFromGameFile || \r
10339         gameMode == TwoMachinesPlay  || \r
10340         gameMode == Training         || \r
10341         gameMode == AnalyzeMode      || \r
10342         gameMode == EndOfGame)\r
10343         EditGameEvent();\r
10344 \r
10345     if (gameMode == EditPosition) \r
10346         EditPositionDone();\r
10347 \r
10348     if (!WhiteOnMove(currentMove)) {\r
10349         DisplayError(_("It is not White's turn"), 0);\r
10350         return;\r
10351     }\r
10352   \r
10353     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10354       ExitAnalyzeMode();\r
10355 \r
10356     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10357         gameMode == AnalyzeFile)\r
10358         TruncateGame();\r
10359 \r
10360     ResurrectChessProgram();    /* in case it isn't running */\r
10361     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */\r
10362         gameMode = MachinePlaysWhite;\r
10363         ResetClocks();\r
10364     } else\r
10365     gameMode = MachinePlaysWhite;\r
10366     pausing = FALSE;\r
10367     ModeHighlight();\r
10368     SetGameInfo();\r
10369     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10370     DisplayTitle(buf);\r
10371     if (first.sendName) {\r
10372       sprintf(buf, "name %s\n", gameInfo.black);\r
10373       SendToProgram(buf, &first);\r
10374     }\r
10375     if (first.sendTime) {\r
10376       if (first.useColors) {\r
10377         SendToProgram("black\n", &first); /*gnu kludge*/\r
10378       }\r
10379       SendTimeRemaining(&first, TRUE);\r
10380     }\r
10381     if (first.useColors) {\r
10382       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately\r
10383     }\r
10384     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10385     SetMachineThinkingEnables();\r
10386     first.maybeThinking = TRUE;\r
10387     StartClocks();\r
10388 \r
10389     if (appData.autoFlipView && !flipView) {\r
10390       flipView = !flipView;\r
10391       DrawPosition(FALSE, NULL);\r
10392       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10393     }\r
10394 \r
10395     if(bookHit) { // [HGM] book: simulate book reply\r
10396         static char bookMove[MSG_SIZ]; // a bit generous?\r
10397 \r
10398         programStats.nodes = programStats.depth = programStats.time = \r
10399         programStats.score = programStats.got_only_move = 0;\r
10400         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10401 \r
10402         strcpy(bookMove, "move ");\r
10403         strcat(bookMove, bookHit);\r
10404         HandleMachineMove(bookMove, &first);\r
10405     }\r
10406 }\r
10407 \r
10408 void\r
10409 MachineBlackEvent()\r
10410 {\r
10411     char buf[MSG_SIZ];\r
10412    char *bookHit = NULL;\r
10413 \r
10414     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
10415         return;\r
10416 \r
10417 \r
10418     if (gameMode == PlayFromGameFile || \r
10419         gameMode == TwoMachinesPlay  || \r
10420         gameMode == Training         || \r
10421         gameMode == AnalyzeMode      || \r
10422         gameMode == EndOfGame)\r
10423         EditGameEvent();\r
10424 \r
10425     if (gameMode == EditPosition) \r
10426         EditPositionDone();\r
10427 \r
10428     if (WhiteOnMove(currentMove)) {\r
10429         DisplayError(_("It is not Black's turn"), 0);\r
10430         return;\r
10431     }\r
10432     \r
10433     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10434       ExitAnalyzeMode();\r
10435 \r
10436     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10437         gameMode == AnalyzeFile)\r
10438         TruncateGame();\r
10439 \r
10440     ResurrectChessProgram();    /* in case it isn't running */\r
10441     gameMode = MachinePlaysBlack;\r
10442     pausing = FALSE;\r
10443     ModeHighlight();\r
10444     SetGameInfo();\r
10445     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10446     DisplayTitle(buf);\r
10447     if (first.sendName) {\r
10448       sprintf(buf, "name %s\n", gameInfo.white);\r
10449       SendToProgram(buf, &first);\r
10450     }\r
10451     if (first.sendTime) {\r
10452       if (first.useColors) {\r
10453         SendToProgram("white\n", &first); /*gnu kludge*/\r
10454       }\r
10455       SendTimeRemaining(&first, FALSE);\r
10456     }\r
10457     if (first.useColors) {\r
10458       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately\r
10459     }\r
10460     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10461     SetMachineThinkingEnables();\r
10462     first.maybeThinking = TRUE;\r
10463     StartClocks();\r
10464 \r
10465     if (appData.autoFlipView && flipView) {\r
10466       flipView = !flipView;\r
10467       DrawPosition(FALSE, NULL);\r
10468       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10469     }\r
10470     if(bookHit) { // [HGM] book: simulate book reply\r
10471         static char bookMove[MSG_SIZ]; // a bit generous?\r
10472 \r
10473         programStats.nodes = programStats.depth = programStats.time = \r
10474         programStats.score = programStats.got_only_move = 0;\r
10475         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10476 \r
10477         strcpy(bookMove, "move ");\r
10478         strcat(bookMove, bookHit);\r
10479         HandleMachineMove(bookMove, &first);\r
10480     }\r
10481 }\r
10482 \r
10483 \r
10484 void\r
10485 DisplayTwoMachinesTitle()\r
10486 {\r
10487     char buf[MSG_SIZ];\r
10488     if (appData.matchGames > 0) {\r
10489         if (first.twoMachinesColor[0] == 'w') {\r
10490             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10491                     gameInfo.white, gameInfo.black,\r
10492                     first.matchWins, second.matchWins,\r
10493                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10494         } else {\r
10495             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10496                     gameInfo.white, gameInfo.black,\r
10497                     second.matchWins, first.matchWins,\r
10498                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10499         }\r
10500     } else {\r
10501         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10502     }\r
10503     DisplayTitle(buf);\r
10504 }\r
10505 \r
10506 void\r
10507 TwoMachinesEvent P((void))\r
10508 {\r
10509     int i;\r
10510     char buf[MSG_SIZ];\r
10511     ChessProgramState *onmove;\r
10512     char *bookHit = NULL;\r
10513     \r
10514     if (appData.noChessProgram) return;\r
10515 \r
10516     switch (gameMode) {\r
10517       case TwoMachinesPlay:\r
10518         return;\r
10519       case MachinePlaysWhite:\r
10520       case MachinePlaysBlack:\r
10521         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10522             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
10523             return;\r
10524         }\r
10525         /* fall through */\r
10526       case BeginningOfGame:\r
10527       case PlayFromGameFile:\r
10528       case EndOfGame:\r
10529         EditGameEvent();\r
10530         if (gameMode != EditGame) return;\r
10531         break;\r
10532       case EditPosition:\r
10533         EditPositionDone();\r
10534         break;\r
10535       case AnalyzeMode:\r
10536       case AnalyzeFile:\r
10537         ExitAnalyzeMode();\r
10538         break;\r
10539       case EditGame:\r
10540       default:\r
10541         break;\r
10542     }\r
10543 \r
10544     forwardMostMove = currentMove;\r
10545     ResurrectChessProgram();    /* in case first program isn't running */\r
10546 \r
10547     if (second.pr == NULL) {\r
10548         StartChessProgram(&second);\r
10549         if (second.protocolVersion == 1) {\r
10550           TwoMachinesEventIfReady();\r
10551         } else {\r
10552           /* kludge: allow timeout for initial "feature" command */\r
10553           FreezeUI();\r
10554           DisplayMessage("", _("Starting second chess program"));\r
10555           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
10556         }\r
10557         return;\r
10558     }\r
10559     DisplayMessage("", "");\r
10560     InitChessProgram(&second, FALSE);\r
10561     SendToProgram("force\n", &second);\r
10562     if (startedFromSetupPosition) {\r
10563         SendBoard(&second, backwardMostMove);\r
10564     if (appData.debugMode) {\r
10565         fprintf(debugFP, "Two Machines\n");\r
10566     }\r
10567     }\r
10568     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
10569         SendMoveToProgram(i, &second);\r
10570     }\r
10571 \r
10572     gameMode = TwoMachinesPlay;\r
10573     pausing = FALSE;\r
10574     ModeHighlight();\r
10575     SetGameInfo();\r
10576     DisplayTwoMachinesTitle();\r
10577     firstMove = TRUE;\r
10578     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
10579         onmove = &first;\r
10580     } else {\r
10581         onmove = &second;\r
10582     }\r
10583 \r
10584     SendToProgram(first.computerString, &first);\r
10585     if (first.sendName) {\r
10586       sprintf(buf, "name %s\n", second.tidy);\r
10587       SendToProgram(buf, &first);\r
10588     }\r
10589     SendToProgram(second.computerString, &second);\r
10590     if (second.sendName) {\r
10591       sprintf(buf, "name %s\n", first.tidy);\r
10592       SendToProgram(buf, &second);\r
10593     }\r
10594 \r
10595     ResetClocks();\r
10596     if (!first.sendTime || !second.sendTime) {\r
10597         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10598         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10599     }\r
10600     if (onmove->sendTime) {\r
10601       if (onmove->useColors) {\r
10602         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
10603       }\r
10604       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
10605     }\r
10606     if (onmove->useColors) {\r
10607       SendToProgram(onmove->twoMachinesColor, onmove);\r
10608     }\r
10609     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move\r
10610 //    SendToProgram("go\n", onmove);\r
10611     onmove->maybeThinking = TRUE;\r
10612     SetMachineThinkingEnables();\r
10613 \r
10614     StartClocks();\r
10615 \r
10616     if(bookHit) { // [HGM] book: simulate book reply\r
10617         static char bookMove[MSG_SIZ]; // a bit generous?\r
10618 \r
10619         programStats.nodes = programStats.depth = programStats.time = \r
10620         programStats.score = programStats.got_only_move = 0;\r
10621         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10622 \r
10623         strcpy(bookMove, "move ");\r
10624         strcat(bookMove, bookHit);\r
10625         HandleMachineMove(bookMove, &first);\r
10626     }\r
10627 }\r
10628 \r
10629 void\r
10630 TrainingEvent()\r
10631 {\r
10632     if (gameMode == Training) {\r
10633       SetTrainingModeOff();\r
10634       gameMode = PlayFromGameFile;\r
10635       DisplayMessage("", _("Training mode off"));\r
10636     } else {\r
10637       gameMode = Training;\r
10638       animateTraining = appData.animate;\r
10639 \r
10640       /* make sure we are not already at the end of the game */\r
10641       if (currentMove < forwardMostMove) {\r
10642         SetTrainingModeOn();\r
10643         DisplayMessage("", _("Training mode on"));\r
10644       } else {\r
10645         gameMode = PlayFromGameFile;\r
10646         DisplayError(_("Already at end of game"), 0);\r
10647       }\r
10648     }\r
10649     ModeHighlight();\r
10650 }\r
10651 \r
10652 void\r
10653 IcsClientEvent()\r
10654 {\r
10655     if (!appData.icsActive) return;\r
10656     switch (gameMode) {\r
10657       case IcsPlayingWhite:\r
10658       case IcsPlayingBlack:\r
10659       case IcsObserving:\r
10660       case IcsIdle:\r
10661       case BeginningOfGame:\r
10662       case IcsExamining:\r
10663         return;\r
10664 \r
10665       case EditGame:\r
10666         break;\r
10667 \r
10668       case EditPosition:\r
10669         EditPositionDone();\r
10670         break;\r
10671 \r
10672       case AnalyzeMode:\r
10673       case AnalyzeFile:\r
10674         ExitAnalyzeMode();\r
10675         break;\r
10676         \r
10677       default:\r
10678         EditGameEvent();\r
10679         break;\r
10680     }\r
10681 \r
10682     gameMode = IcsIdle;\r
10683     ModeHighlight();\r
10684     return;\r
10685 }\r
10686 \r
10687 \r
10688 void\r
10689 EditGameEvent()\r
10690 {\r
10691     int i;\r
10692 \r
10693     switch (gameMode) {\r
10694       case Training:\r
10695         SetTrainingModeOff();\r
10696         break;\r
10697       case MachinePlaysWhite:\r
10698       case MachinePlaysBlack:\r
10699       case BeginningOfGame:\r
10700         SendToProgram("force\n", &first);\r
10701         SetUserThinkingEnables();\r
10702         break;\r
10703       case PlayFromGameFile:\r
10704         (void) StopLoadGameTimer();\r
10705         if (gameFileFP != NULL) {\r
10706             gameFileFP = NULL;\r
10707         }\r
10708         break;\r
10709       case EditPosition:\r
10710         EditPositionDone();\r
10711         break;\r
10712       case AnalyzeMode:\r
10713       case AnalyzeFile:\r
10714         ExitAnalyzeMode();\r
10715         SendToProgram("force\n", &first);\r
10716         break;\r
10717       case TwoMachinesPlay:\r
10718         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10719         ResurrectChessProgram();\r
10720         SetUserThinkingEnables();\r
10721         break;\r
10722       case EndOfGame:\r
10723         ResurrectChessProgram();\r
10724         break;\r
10725       case IcsPlayingBlack:\r
10726       case IcsPlayingWhite:\r
10727         DisplayError(_("Warning: You are still playing a game"), 0);\r
10728         break;\r
10729       case IcsObserving:\r
10730         DisplayError(_("Warning: You are still observing a game"), 0);\r
10731         break;\r
10732       case IcsExamining:\r
10733         DisplayError(_("Warning: You are still examining a game"), 0);\r
10734         break;\r
10735       case IcsIdle:\r
10736         break;\r
10737       case EditGame:\r
10738       default:\r
10739         return;\r
10740     }\r
10741     \r
10742     pausing = FALSE;\r
10743     StopClocks();\r
10744     first.offeredDraw = second.offeredDraw = 0;\r
10745 \r
10746     if (gameMode == PlayFromGameFile) {\r
10747         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10748         blackTimeRemaining = timeRemaining[1][currentMove];\r
10749         DisplayTitle("");\r
10750     }\r
10751 \r
10752     if (gameMode == MachinePlaysWhite ||\r
10753         gameMode == MachinePlaysBlack ||\r
10754         gameMode == TwoMachinesPlay ||\r
10755         gameMode == EndOfGame) {\r
10756         i = forwardMostMove;\r
10757         while (i > currentMove) {\r
10758             SendToProgram("undo\n", &first);\r
10759             i--;\r
10760         }\r
10761         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10762         blackTimeRemaining = timeRemaining[1][currentMove];\r
10763         DisplayBothClocks();\r
10764         if (whiteFlag || blackFlag) {\r
10765             whiteFlag = blackFlag = 0;\r
10766         }\r
10767         DisplayTitle("");\r
10768     }           \r
10769     \r
10770     gameMode = EditGame;\r
10771     ModeHighlight();\r
10772     SetGameInfo();\r
10773 }\r
10774 \r
10775 \r
10776 void\r
10777 EditPositionEvent()\r
10778 {\r
10779     if (gameMode == EditPosition) {\r
10780         EditGameEvent();\r
10781         return;\r
10782     }\r
10783     \r
10784     EditGameEvent();\r
10785     if (gameMode != EditGame) return;\r
10786     \r
10787     gameMode = EditPosition;\r
10788     ModeHighlight();\r
10789     SetGameInfo();\r
10790     if (currentMove > 0)\r
10791       CopyBoard(boards[0], boards[currentMove]);\r
10792     \r
10793     blackPlaysFirst = !WhiteOnMove(currentMove);\r
10794     ResetClocks();\r
10795     currentMove = forwardMostMove = backwardMostMove = 0;\r
10796     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10797     DisplayMove(-1);\r
10798 }\r
10799 \r
10800 void\r
10801 ExitAnalyzeMode()\r
10802 {\r
10803     /* [DM] icsEngineAnalyze - possible call from other functions */\r
10804     if (appData.icsEngineAnalyze) {\r
10805         appData.icsEngineAnalyze = FALSE;\r
10806 \r
10807         DisplayMessage("",_("Close ICS engine analyze..."));\r
10808     }\r
10809     if (first.analysisSupport && first.analyzing) {\r
10810       SendToProgram("exit\n", &first);\r
10811       first.analyzing = FALSE;\r
10812     }\r
10813     AnalysisPopDown();\r
10814     thinkOutput[0] = NULLCHAR;\r
10815 }\r
10816 \r
10817 void\r
10818 EditPositionDone()\r
10819 {\r
10820     startedFromSetupPosition = TRUE;\r
10821     InitChessProgram(&first, FALSE);\r
10822     SendToProgram("force\n", &first);\r
10823     if (blackPlaysFirst) {\r
10824         strcpy(moveList[0], "");\r
10825         strcpy(parseList[0], "");\r
10826         currentMove = forwardMostMove = backwardMostMove = 1;\r
10827         CopyBoard(boards[1], boards[0]);\r
10828         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */\r
10829         { int i;\r
10830           epStatus[1] = epStatus[0];\r
10831           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];\r
10832         }\r
10833     } else {\r
10834         currentMove = forwardMostMove = backwardMostMove = 0;\r
10835     }\r
10836     SendBoard(&first, forwardMostMove);\r
10837     if (appData.debugMode) {\r
10838         fprintf(debugFP, "EditPosDone\n");\r
10839     }\r
10840     DisplayTitle("");\r
10841     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10842     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10843     gameMode = EditGame;\r
10844     ModeHighlight();\r
10845     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10846     ClearHighlights(); /* [AS] */\r
10847 }\r
10848 \r
10849 /* Pause for `ms' milliseconds */\r
10850 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10851 void\r
10852 TimeDelay(ms)\r
10853      long ms;\r
10854 {\r
10855     TimeMark m1, m2;\r
10856 \r
10857     GetTimeMark(&m1);\r
10858     do {\r
10859         GetTimeMark(&m2);\r
10860     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
10861 }\r
10862 \r
10863 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10864 void\r
10865 SendMultiLineToICS(buf)\r
10866      char *buf;\r
10867 {\r
10868     char temp[MSG_SIZ+1], *p;\r
10869     int len;\r
10870 \r
10871     len = strlen(buf);\r
10872     if (len > MSG_SIZ)\r
10873       len = MSG_SIZ;\r
10874   \r
10875     strncpy(temp, buf, len);\r
10876     temp[len] = 0;\r
10877 \r
10878     p = temp;\r
10879     while (*p) {\r
10880         if (*p == '\n' || *p == '\r')\r
10881           *p = ' ';\r
10882         ++p;\r
10883     }\r
10884 \r
10885     strcat(temp, "\n");\r
10886     SendToICS(temp);\r
10887     SendToPlayer(temp, strlen(temp));\r
10888 }\r
10889 \r
10890 void\r
10891 SetWhiteToPlayEvent()\r
10892 {\r
10893     if (gameMode == EditPosition) {\r
10894         blackPlaysFirst = FALSE;\r
10895         DisplayBothClocks();    /* works because currentMove is 0 */\r
10896     } else if (gameMode == IcsExamining) {\r
10897         SendToICS(ics_prefix);\r
10898         SendToICS("tomove white\n");\r
10899     }\r
10900 }\r
10901 \r
10902 void\r
10903 SetBlackToPlayEvent()\r
10904 {\r
10905     if (gameMode == EditPosition) {\r
10906         blackPlaysFirst = TRUE;\r
10907         currentMove = 1;        /* kludge */\r
10908         DisplayBothClocks();\r
10909         currentMove = 0;\r
10910     } else if (gameMode == IcsExamining) {\r
10911         SendToICS(ics_prefix);\r
10912         SendToICS("tomove black\n");\r
10913     }\r
10914 }\r
10915 \r
10916 void\r
10917 EditPositionMenuEvent(selection, x, y)\r
10918      ChessSquare selection;\r
10919      int x, y;\r
10920 {\r
10921     char buf[MSG_SIZ];\r
10922     ChessSquare piece = boards[0][y][x];\r
10923 \r
10924     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
10925 \r
10926     switch (selection) {\r
10927       case ClearBoard:\r
10928         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
10929             SendToICS(ics_prefix);\r
10930             SendToICS("bsetup clear\n");\r
10931         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
10932             SendToICS(ics_prefix);\r
10933             SendToICS("clearboard\n");\r
10934         } else {\r
10935             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;\r
10936                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */\r
10937                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
10938                     if (gameMode == IcsExamining) {\r
10939                         if (boards[currentMove][y][x] != EmptySquare) {\r
10940                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
10941                                     AAA + x, ONE + y);\r
10942                             SendToICS(buf);\r
10943                         }\r
10944                     } else {\r
10945                         boards[0][y][x] = p;\r
10946                     }\r
10947                 }\r
10948             }\r
10949         }\r
10950         if (gameMode == EditPosition) {\r
10951             DrawPosition(FALSE, boards[0]);\r
10952         }\r
10953         break;\r
10954 \r
10955       case WhitePlay:\r
10956         SetWhiteToPlayEvent();\r
10957         break;\r
10958 \r
10959       case BlackPlay:\r
10960         SetBlackToPlayEvent();\r
10961         break;\r
10962 \r
10963       case EmptySquare:\r
10964         if (gameMode == IcsExamining) {\r
10965             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
10966             SendToICS(buf);\r
10967         } else {\r
10968             boards[0][y][x] = EmptySquare;\r
10969             DrawPosition(FALSE, boards[0]);\r
10970         }\r
10971         break;\r
10972 \r
10973       case PromotePiece:\r
10974         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
10975            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
10976             selection = (ChessSquare) (PROMOTED piece);\r
10977         } else if(piece == EmptySquare) selection = WhiteSilver;\r
10978         else selection = (ChessSquare)((int)piece - 1);\r
10979         goto defaultlabel;\r
10980 \r
10981       case DemotePiece:\r
10982         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
10983            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
10984             selection = (ChessSquare) (DEMOTED piece);\r
10985         } else if(piece == EmptySquare) selection = BlackSilver;\r
10986         else selection = (ChessSquare)((int)piece + 1);       \r
10987         goto defaultlabel;\r
10988 \r
10989       case WhiteQueen:\r
10990       case BlackQueen:\r
10991         if(gameInfo.variant == VariantShatranj ||\r
10992            gameInfo.variant == VariantXiangqi  ||\r
10993            gameInfo.variant == VariantCourier    )\r
10994             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
10995         goto defaultlabel;\r
10996 \r
10997       case WhiteKing:\r
10998       case BlackKing:\r
10999         if(gameInfo.variant == VariantXiangqi)\r
11000             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
11001         if(gameInfo.variant == VariantKnightmate)\r
11002             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
11003       default:\r
11004         defaultlabel:\r
11005         if (gameMode == IcsExamining) {\r
11006             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
11007                     PieceToChar(selection), AAA + x, ONE + y);\r
11008             SendToICS(buf);\r
11009         } else {\r
11010             boards[0][y][x] = selection;\r
11011             DrawPosition(FALSE, boards[0]);\r
11012         }\r
11013         break;\r
11014     }\r
11015 }\r
11016 \r
11017 \r
11018 void\r
11019 DropMenuEvent(selection, x, y)\r
11020      ChessSquare selection;\r
11021      int x, y;\r
11022 {\r
11023     ChessMove moveType;\r
11024 \r
11025     switch (gameMode) {\r
11026       case IcsPlayingWhite:\r
11027       case MachinePlaysBlack:\r
11028         if (!WhiteOnMove(currentMove)) {\r
11029             DisplayMoveError(_("It is Black's turn"));\r
11030             return;\r
11031         }\r
11032         moveType = WhiteDrop;\r
11033         break;\r
11034       case IcsPlayingBlack:\r
11035       case MachinePlaysWhite:\r
11036         if (WhiteOnMove(currentMove)) {\r
11037             DisplayMoveError(_("It is White's turn"));\r
11038             return;\r
11039         }\r
11040         moveType = BlackDrop;\r
11041         break;\r
11042       case EditGame:\r
11043         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
11044         break;\r
11045       default:\r
11046         return;\r
11047     }\r
11048 \r
11049     if (moveType == BlackDrop && selection < BlackPawn) {\r
11050       selection = (ChessSquare) ((int) selection\r
11051                                  + (int) BlackPawn - (int) WhitePawn);\r
11052     }\r
11053     if (boards[currentMove][y][x] != EmptySquare) {\r
11054         DisplayMoveError(_("That square is occupied"));\r
11055         return;\r
11056     }\r
11057 \r
11058     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
11059 }\r
11060 \r
11061 void\r
11062 AcceptEvent()\r
11063 {\r
11064     /* Accept a pending offer of any kind from opponent */\r
11065     \r
11066     if (appData.icsActive) {\r
11067         SendToICS(ics_prefix);\r
11068         SendToICS("accept\n");\r
11069     } else if (cmailMsgLoaded) {\r
11070         if (currentMove == cmailOldMove &&\r
11071             commentList[cmailOldMove] != NULL &&\r
11072             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11073                    "Black offers a draw" : "White offers a draw")) {\r
11074             TruncateGame();\r
11075             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11076             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11077         } else {\r
11078             DisplayError(_("There is no pending offer on this move"), 0);\r
11079             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11080         }\r
11081     } else {\r
11082         /* Not used for offers from chess program */\r
11083     }\r
11084 }\r
11085 \r
11086 void\r
11087 DeclineEvent()\r
11088 {\r
11089     /* Decline a pending offer of any kind from opponent */\r
11090     \r
11091     if (appData.icsActive) {\r
11092         SendToICS(ics_prefix);\r
11093         SendToICS("decline\n");\r
11094     } else if (cmailMsgLoaded) {\r
11095         if (currentMove == cmailOldMove &&\r
11096             commentList[cmailOldMove] != NULL &&\r
11097             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11098                    "Black offers a draw" : "White offers a draw")) {\r
11099 #ifdef NOTDEF\r
11100             AppendComment(cmailOldMove, "Draw declined");\r
11101             DisplayComment(cmailOldMove - 1, "Draw declined");\r
11102 #endif /*NOTDEF*/\r
11103         } else {\r
11104             DisplayError(_("There is no pending offer on this move"), 0);\r
11105         }\r
11106     } else {\r
11107         /* Not used for offers from chess program */\r
11108     }\r
11109 }\r
11110 \r
11111 void\r
11112 RematchEvent()\r
11113 {\r
11114     /* Issue ICS rematch command */\r
11115     if (appData.icsActive) {\r
11116         SendToICS(ics_prefix);\r
11117         SendToICS("rematch\n");\r
11118     }\r
11119 }\r
11120 \r
11121 void\r
11122 CallFlagEvent()\r
11123 {\r
11124     /* Call your opponent's flag (claim a win on time) */\r
11125     if (appData.icsActive) {\r
11126         SendToICS(ics_prefix);\r
11127         SendToICS("flag\n");\r
11128     } else {\r
11129         switch (gameMode) {\r
11130           default:\r
11131             return;\r
11132           case MachinePlaysWhite:\r
11133             if (whiteFlag) {\r
11134                 if (blackFlag)\r
11135                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11136                            GE_PLAYER);\r
11137                 else\r
11138                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
11139             } else {\r
11140                 DisplayError(_("Your opponent is not out of time"), 0);\r
11141             }\r
11142             break;\r
11143           case MachinePlaysBlack:\r
11144             if (blackFlag) {\r
11145                 if (whiteFlag)\r
11146                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11147                            GE_PLAYER);\r
11148                 else\r
11149                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
11150             } else {\r
11151                 DisplayError(_("Your opponent is not out of time"), 0);\r
11152             }\r
11153             break;\r
11154         }\r
11155     }\r
11156 }\r
11157 \r
11158 void\r
11159 DrawEvent()\r
11160 {\r
11161     /* Offer draw or accept pending draw offer from opponent */\r
11162     \r
11163     if (appData.icsActive) {\r
11164         /* Note: tournament rules require draw offers to be\r
11165            made after you make your move but before you punch\r
11166            your clock.  Currently ICS doesn't let you do that;\r
11167            instead, you immediately punch your clock after making\r
11168            a move, but you can offer a draw at any time. */\r
11169         \r
11170         SendToICS(ics_prefix);\r
11171         SendToICS("draw\n");\r
11172     } else if (cmailMsgLoaded) {\r
11173         if (currentMove == cmailOldMove &&\r
11174             commentList[cmailOldMove] != NULL &&\r
11175             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11176                    "Black offers a draw" : "White offers a draw")) {\r
11177             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11178             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11179         } else if (currentMove == cmailOldMove + 1) {\r
11180             char *offer = WhiteOnMove(cmailOldMove) ?\r
11181               "White offers a draw" : "Black offers a draw";\r
11182             AppendComment(currentMove, offer);\r
11183             DisplayComment(currentMove - 1, offer);\r
11184             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
11185         } else {\r
11186             DisplayError(_("You must make your move before offering a draw"), 0);\r
11187             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11188         }\r
11189     } else if (first.offeredDraw) {\r
11190         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
11191     } else {\r
11192         if (first.sendDrawOffers) {\r
11193             SendToProgram("draw\n", &first);\r
11194             userOfferedDraw = TRUE;\r
11195         }\r
11196     }\r
11197 }\r
11198 \r
11199 void\r
11200 AdjournEvent()\r
11201 {\r
11202     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
11203     \r
11204     if (appData.icsActive) {\r
11205         SendToICS(ics_prefix);\r
11206         SendToICS("adjourn\n");\r
11207     } else {\r
11208         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
11209     }\r
11210 }\r
11211 \r
11212 \r
11213 void\r
11214 AbortEvent()\r
11215 {\r
11216     /* Offer Abort or accept pending Abort offer from opponent */\r
11217     \r
11218     if (appData.icsActive) {\r
11219         SendToICS(ics_prefix);\r
11220         SendToICS("abort\n");\r
11221     } else {\r
11222         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
11223     }\r
11224 }\r
11225 \r
11226 void\r
11227 ResignEvent()\r
11228 {\r
11229     /* Resign.  You can do this even if it's not your turn. */\r
11230     \r
11231     if (appData.icsActive) {\r
11232         SendToICS(ics_prefix);\r
11233         SendToICS("resign\n");\r
11234     } else {\r
11235         switch (gameMode) {\r
11236           case MachinePlaysWhite:\r
11237             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11238             break;\r
11239           case MachinePlaysBlack:\r
11240             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11241             break;\r
11242           case EditGame:\r
11243             if (cmailMsgLoaded) {\r
11244                 TruncateGame();\r
11245                 if (WhiteOnMove(cmailOldMove)) {\r
11246                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11247                 } else {\r
11248                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11249                 }\r
11250                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
11251             }\r
11252             break;\r
11253           default:\r
11254             break;\r
11255         }\r
11256     }\r
11257 }\r
11258 \r
11259 \r
11260 void\r
11261 StopObservingEvent()\r
11262 {\r
11263     /* Stop observing current games */\r
11264     SendToICS(ics_prefix);\r
11265     SendToICS("unobserve\n");\r
11266 }\r
11267 \r
11268 void\r
11269 StopExaminingEvent()\r
11270 {\r
11271     /* Stop observing current game */\r
11272     SendToICS(ics_prefix);\r
11273     SendToICS("unexamine\n");\r
11274 }\r
11275 \r
11276 void\r
11277 ForwardInner(target)\r
11278      int target;\r
11279 {\r
11280     int limit;\r
11281 \r
11282     if (appData.debugMode)\r
11283         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
11284                 target, currentMove, forwardMostMove);\r
11285 \r
11286     if (gameMode == EditPosition)\r
11287       return;\r
11288 \r
11289     if (gameMode == PlayFromGameFile && !pausing)\r
11290       PauseEvent();\r
11291     \r
11292     if (gameMode == IcsExamining && pausing)\r
11293       limit = pauseExamForwardMostMove;\r
11294     else\r
11295       limit = forwardMostMove;\r
11296     \r
11297     if (target > limit) target = limit;\r
11298 \r
11299     if (target > 0 && moveList[target - 1][0]) {\r
11300         int fromX, fromY, toX, toY;\r
11301         toX = moveList[target - 1][2] - AAA;\r
11302         toY = moveList[target - 1][3] - ONE;\r
11303         if (moveList[target - 1][1] == '@') {\r
11304             if (appData.highlightLastMove) {\r
11305                 SetHighlights(-1, -1, toX, toY);\r
11306             }\r
11307         } else {\r
11308             fromX = moveList[target - 1][0] - AAA;\r
11309             fromY = moveList[target - 1][1] - ONE;\r
11310             if (target == currentMove + 1) {\r
11311                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
11312             }\r
11313             if (appData.highlightLastMove) {\r
11314                 SetHighlights(fromX, fromY, toX, toY);\r
11315             }\r
11316         }\r
11317     }\r
11318     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
11319         gameMode == Training || gameMode == PlayFromGameFile || \r
11320         gameMode == AnalyzeFile) {\r
11321         while (currentMove < target) {\r
11322             SendMoveToProgram(currentMove++, &first);\r
11323         }\r
11324     } else {\r
11325         currentMove = target;\r
11326     }\r
11327     \r
11328     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11329         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11330         blackTimeRemaining = timeRemaining[1][currentMove];\r
11331     }\r
11332     DisplayBothClocks();\r
11333     DisplayMove(currentMove - 1);\r
11334     DrawPosition(FALSE, boards[currentMove]);\r
11335     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11336     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
11337         DisplayComment(currentMove - 1, commentList[currentMove]);\r
11338     }\r
11339 }\r
11340 \r
11341 \r
11342 void\r
11343 ForwardEvent()\r
11344 {\r
11345     if (gameMode == IcsExamining && !pausing) {\r
11346         SendToICS(ics_prefix);\r
11347         SendToICS("forward\n");\r
11348     } else {\r
11349         ForwardInner(currentMove + 1);\r
11350     }\r
11351 }\r
11352 \r
11353 void\r
11354 ToEndEvent()\r
11355 {\r
11356     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11357         /* to optimze, we temporarily turn off analysis mode while we feed\r
11358          * the remaining moves to the engine. Otherwise we get analysis output\r
11359          * after each move.\r
11360          */ \r
11361         if (first.analysisSupport) {\r
11362           SendToProgram("exit\nforce\n", &first);\r
11363           first.analyzing = FALSE;\r
11364         }\r
11365     }\r
11366         \r
11367     if (gameMode == IcsExamining && !pausing) {\r
11368         SendToICS(ics_prefix);\r
11369         SendToICS("forward 999999\n");\r
11370     } else {\r
11371         ForwardInner(forwardMostMove);\r
11372     }\r
11373 \r
11374     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11375         /* we have fed all the moves, so reactivate analysis mode */\r
11376         SendToProgram("analyze\n", &first);\r
11377         first.analyzing = TRUE;\r
11378         /*first.maybeThinking = TRUE;*/\r
11379         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11380     }\r
11381 }\r
11382 \r
11383 void\r
11384 BackwardInner(target)\r
11385      int target;\r
11386 {\r
11387     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
11388 \r
11389     if (appData.debugMode)\r
11390         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
11391                 target, currentMove, forwardMostMove);\r
11392 \r
11393     if (gameMode == EditPosition) return;\r
11394     if (currentMove <= backwardMostMove) {\r
11395         ClearHighlights();\r
11396         DrawPosition(full_redraw, boards[currentMove]);\r
11397         return;\r
11398     }\r
11399     if (gameMode == PlayFromGameFile && !pausing)\r
11400       PauseEvent();\r
11401     \r
11402     if (moveList[target][0]) {\r
11403         int fromX, fromY, toX, toY;\r
11404         toX = moveList[target][2] - AAA;\r
11405         toY = moveList[target][3] - ONE;\r
11406         if (moveList[target][1] == '@') {\r
11407             if (appData.highlightLastMove) {\r
11408                 SetHighlights(-1, -1, toX, toY);\r
11409             }\r
11410         } else {\r
11411             fromX = moveList[target][0] - AAA;\r
11412             fromY = moveList[target][1] - ONE;\r
11413             if (target == currentMove - 1) {\r
11414                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
11415             }\r
11416             if (appData.highlightLastMove) {\r
11417                 SetHighlights(fromX, fromY, toX, toY);\r
11418             }\r
11419         }\r
11420     }\r
11421     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
11422         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
11423         while (currentMove > target) {\r
11424             SendToProgram("undo\n", &first);\r
11425             currentMove--;\r
11426         }\r
11427     } else {\r
11428         currentMove = target;\r
11429     }\r
11430     \r
11431     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11432         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11433         blackTimeRemaining = timeRemaining[1][currentMove];\r
11434     }\r
11435     DisplayBothClocks();\r
11436     DisplayMove(currentMove - 1);\r
11437     DrawPosition(full_redraw, boards[currentMove]);\r
11438     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11439     // [HGM] PV info: routine tests if comment empty\r
11440     DisplayComment(currentMove - 1, commentList[currentMove]);\r
11441 }\r
11442 \r
11443 void\r
11444 BackwardEvent()\r
11445 {\r
11446     if (gameMode == IcsExamining && !pausing) {\r
11447         SendToICS(ics_prefix);\r
11448         SendToICS("backward\n");\r
11449     } else {\r
11450         BackwardInner(currentMove - 1);\r
11451     }\r
11452 }\r
11453 \r
11454 void\r
11455 ToStartEvent()\r
11456 {\r
11457     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11458         /* to optimze, we temporarily turn off analysis mode while we undo\r
11459          * all the moves. Otherwise we get analysis output after each undo.\r
11460          */ \r
11461         if (first.analysisSupport) {\r
11462           SendToProgram("exit\nforce\n", &first);\r
11463           first.analyzing = FALSE;\r
11464         }\r
11465     }\r
11466 \r
11467     if (gameMode == IcsExamining && !pausing) {\r
11468         SendToICS(ics_prefix);\r
11469         SendToICS("backward 999999\n");\r
11470     } else {\r
11471         BackwardInner(backwardMostMove);\r
11472     }\r
11473 \r
11474     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11475         /* we have fed all the moves, so reactivate analysis mode */\r
11476         SendToProgram("analyze\n", &first);\r
11477         first.analyzing = TRUE;\r
11478         /*first.maybeThinking = TRUE;*/\r
11479         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11480     }\r
11481 }\r
11482 \r
11483 void\r
11484 ToNrEvent(int to)\r
11485 {\r
11486   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
11487   if (to >= forwardMostMove) to = forwardMostMove;\r
11488   if (to <= backwardMostMove) to = backwardMostMove;\r
11489   if (to < currentMove) {\r
11490     BackwardInner(to);\r
11491   } else {\r
11492     ForwardInner(to);\r
11493   }\r
11494 }\r
11495 \r
11496 void\r
11497 RevertEvent()\r
11498 {\r
11499     if (gameMode != IcsExamining) {\r
11500         DisplayError(_("You are not examining a game"), 0);\r
11501         return;\r
11502     }\r
11503     if (pausing) {\r
11504         DisplayError(_("You can't revert while pausing"), 0);\r
11505         return;\r
11506     }\r
11507     SendToICS(ics_prefix);\r
11508     SendToICS("revert\n");\r
11509 }\r
11510 \r
11511 void\r
11512 RetractMoveEvent()\r
11513 {\r
11514     switch (gameMode) {\r
11515       case MachinePlaysWhite:\r
11516       case MachinePlaysBlack:\r
11517         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
11518             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
11519             return;\r
11520         }\r
11521         if (forwardMostMove < 2) return;\r
11522         currentMove = forwardMostMove = forwardMostMove - 2;\r
11523         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11524         blackTimeRemaining = timeRemaining[1][currentMove];\r
11525         DisplayBothClocks();\r
11526         DisplayMove(currentMove - 1);\r
11527         ClearHighlights();/*!! could figure this out*/\r
11528         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
11529         SendToProgram("remove\n", &first);\r
11530         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
11531         break;\r
11532 \r
11533       case BeginningOfGame:\r
11534       default:\r
11535         break;\r
11536 \r
11537       case IcsPlayingWhite:\r
11538       case IcsPlayingBlack:\r
11539         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
11540             SendToICS(ics_prefix);\r
11541             SendToICS("takeback 2\n");\r
11542         } else {\r
11543             SendToICS(ics_prefix);\r
11544             SendToICS("takeback 1\n");\r
11545         }\r
11546         break;\r
11547     }\r
11548 }\r
11549 \r
11550 void\r
11551 MoveNowEvent()\r
11552 {\r
11553     ChessProgramState *cps;\r
11554 \r
11555     switch (gameMode) {\r
11556       case MachinePlaysWhite:\r
11557         if (!WhiteOnMove(forwardMostMove)) {\r
11558             DisplayError(_("It is your turn"), 0);\r
11559             return;\r
11560         }\r
11561         cps = &first;\r
11562         break;\r
11563       case MachinePlaysBlack:\r
11564         if (WhiteOnMove(forwardMostMove)) {\r
11565             DisplayError(_("It is your turn"), 0);\r
11566             return;\r
11567         }\r
11568         cps = &first;\r
11569         break;\r
11570       case TwoMachinesPlay:\r
11571         if (WhiteOnMove(forwardMostMove) ==\r
11572             (first.twoMachinesColor[0] == 'w')) {\r
11573             cps = &first;\r
11574         } else {\r
11575             cps = &second;\r
11576         }\r
11577         break;\r
11578       case BeginningOfGame:\r
11579       default:\r
11580         return;\r
11581     }\r
11582     SendToProgram("?\n", cps);\r
11583 }\r
11584 \r
11585 void\r
11586 TruncateGameEvent()\r
11587 {\r
11588     EditGameEvent();\r
11589     if (gameMode != EditGame) return;\r
11590     TruncateGame();\r
11591 }\r
11592 \r
11593 void\r
11594 TruncateGame()\r
11595 {\r
11596     if (forwardMostMove > currentMove) {\r
11597         if (gameInfo.resultDetails != NULL) {\r
11598             free(gameInfo.resultDetails);\r
11599             gameInfo.resultDetails = NULL;\r
11600             gameInfo.result = GameUnfinished;\r
11601         }\r
11602         forwardMostMove = currentMove;\r
11603         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
11604                    currentMove-1);\r
11605     }\r
11606 }\r
11607 \r
11608 void\r
11609 HintEvent()\r
11610 {\r
11611     if (appData.noChessProgram) return;\r
11612     switch (gameMode) {\r
11613       case MachinePlaysWhite:\r
11614         if (WhiteOnMove(forwardMostMove)) {\r
11615             DisplayError(_("Wait until your turn"), 0);\r
11616             return;\r
11617         }\r
11618         break;\r
11619       case BeginningOfGame:\r
11620       case MachinePlaysBlack:\r
11621         if (!WhiteOnMove(forwardMostMove)) {\r
11622             DisplayError(_("Wait until your turn"), 0);\r
11623             return;\r
11624         }\r
11625         break;\r
11626       default:\r
11627         DisplayError(_("No hint available"), 0);\r
11628         return;\r
11629     }\r
11630     SendToProgram("hint\n", &first);\r
11631     hintRequested = TRUE;\r
11632 }\r
11633 \r
11634 void\r
11635 BookEvent()\r
11636 {\r
11637     if (appData.noChessProgram) return;\r
11638     switch (gameMode) {\r
11639       case MachinePlaysWhite:\r
11640         if (WhiteOnMove(forwardMostMove)) {\r
11641             DisplayError(_("Wait until your turn"), 0);\r
11642             return;\r
11643         }\r
11644         break;\r
11645       case BeginningOfGame:\r
11646       case MachinePlaysBlack:\r
11647         if (!WhiteOnMove(forwardMostMove)) {\r
11648             DisplayError(_("Wait until your turn"), 0);\r
11649             return;\r
11650         }\r
11651         break;\r
11652       case EditPosition:\r
11653         EditPositionDone();\r
11654         break;\r
11655       case TwoMachinesPlay:\r
11656         return;\r
11657       default:\r
11658         break;\r
11659     }\r
11660     SendToProgram("bk\n", &first);\r
11661     bookOutput[0] = NULLCHAR;\r
11662     bookRequested = TRUE;\r
11663 }\r
11664 \r
11665 void\r
11666 AboutGameEvent()\r
11667 {\r
11668     char *tags = PGNTags(&gameInfo);\r
11669     TagsPopUp(tags, CmailMsg());\r
11670     free(tags);\r
11671 }\r
11672 \r
11673 /* end button procedures */\r
11674 \r
11675 void\r
11676 PrintPosition(fp, move)\r
11677      FILE *fp;\r
11678      int move;\r
11679 {\r
11680     int i, j;\r
11681     \r
11682     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11683         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11684             char c = PieceToChar(boards[move][i][j]);\r
11685             fputc(c == 'x' ? '.' : c, fp);\r
11686             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
11687         }\r
11688     }\r
11689     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
11690       fprintf(fp, "white to play\n");\r
11691     else\r
11692       fprintf(fp, "black to play\n");\r
11693 }\r
11694 \r
11695 void\r
11696 PrintOpponents(fp)\r
11697      FILE *fp;\r
11698 {\r
11699     if (gameInfo.white != NULL) {\r
11700         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
11701     } else {\r
11702         fprintf(fp, "\n");\r
11703     }\r
11704 }\r
11705 \r
11706 /* Find last component of program's own name, using some heuristics */\r
11707 void\r
11708 TidyProgramName(prog, host, buf)\r
11709      char *prog, *host, buf[MSG_SIZ];\r
11710 {\r
11711     char *p, *q;\r
11712     int local = (strcmp(host, "localhost") == 0);\r
11713     while (!local && (p = strchr(prog, ';')) != NULL) {\r
11714         p++;\r
11715         while (*p == ' ') p++;\r
11716         prog = p;\r
11717     }\r
11718     if (*prog == '"' || *prog == '\'') {\r
11719         q = strchr(prog + 1, *prog);\r
11720     } else {\r
11721         q = strchr(prog, ' ');\r
11722     }\r
11723     if (q == NULL) q = prog + strlen(prog);\r
11724     p = q;\r
11725     while (p >= prog && *p != '/' && *p != '\\') p--;\r
11726     p++;\r
11727     if(p == prog && *p == '"') p++;\r
11728     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
11729     memcpy(buf, p, q - p);\r
11730     buf[q - p] = NULLCHAR;\r
11731     if (!local) {\r
11732         strcat(buf, "@");\r
11733         strcat(buf, host);\r
11734     }\r
11735 }\r
11736 \r
11737 char *\r
11738 TimeControlTagValue()\r
11739 {\r
11740     char buf[MSG_SIZ];\r
11741     if (!appData.clockMode) {\r
11742         strcpy(buf, "-");\r
11743     } else if (movesPerSession > 0) {\r
11744         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
11745     } else if (timeIncrement == 0) {\r
11746         sprintf(buf, "%ld", timeControl/1000);\r
11747     } else {\r
11748         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
11749     }\r
11750     return StrSave(buf);\r
11751 }\r
11752 \r
11753 void\r
11754 SetGameInfo()\r
11755 {\r
11756     /* This routine is used only for certain modes */\r
11757     VariantClass v = gameInfo.variant;\r
11758     ClearGameInfo(&gameInfo);\r
11759     gameInfo.variant = v;\r
11760 \r
11761     switch (gameMode) {\r
11762       case MachinePlaysWhite:\r
11763         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11764         gameInfo.site = StrSave(HostName());\r
11765         gameInfo.date = PGNDate();\r
11766         gameInfo.round = StrSave("-");\r
11767         gameInfo.white = StrSave(first.tidy);\r
11768         gameInfo.black = StrSave(UserName());\r
11769         gameInfo.timeControl = TimeControlTagValue();\r
11770         break;\r
11771 \r
11772       case MachinePlaysBlack:\r
11773         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11774         gameInfo.site = StrSave(HostName());\r
11775         gameInfo.date = PGNDate();\r
11776         gameInfo.round = StrSave("-");\r
11777         gameInfo.white = StrSave(UserName());\r
11778         gameInfo.black = StrSave(first.tidy);\r
11779         gameInfo.timeControl = TimeControlTagValue();\r
11780         break;\r
11781 \r
11782       case TwoMachinesPlay:\r
11783         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11784         gameInfo.site = StrSave(HostName());\r
11785         gameInfo.date = PGNDate();\r
11786         if (matchGame > 0) {\r
11787             char buf[MSG_SIZ];\r
11788             sprintf(buf, "%d", matchGame);\r
11789             gameInfo.round = StrSave(buf);\r
11790         } else {\r
11791             gameInfo.round = StrSave("-");\r
11792         }\r
11793         if (first.twoMachinesColor[0] == 'w') {\r
11794             gameInfo.white = StrSave(first.tidy);\r
11795             gameInfo.black = StrSave(second.tidy);\r
11796         } else {\r
11797             gameInfo.white = StrSave(second.tidy);\r
11798             gameInfo.black = StrSave(first.tidy);\r
11799         }\r
11800         gameInfo.timeControl = TimeControlTagValue();\r
11801         break;\r
11802 \r
11803       case EditGame:\r
11804         gameInfo.event = StrSave("Edited game");\r
11805         gameInfo.site = StrSave(HostName());\r
11806         gameInfo.date = PGNDate();\r
11807         gameInfo.round = StrSave("-");\r
11808         gameInfo.white = StrSave("-");\r
11809         gameInfo.black = StrSave("-");\r
11810         break;\r
11811 \r
11812       case EditPosition:\r
11813         gameInfo.event = StrSave("Edited position");\r
11814         gameInfo.site = StrSave(HostName());\r
11815         gameInfo.date = PGNDate();\r
11816         gameInfo.round = StrSave("-");\r
11817         gameInfo.white = StrSave("-");\r
11818         gameInfo.black = StrSave("-");\r
11819         break;\r
11820 \r
11821       case IcsPlayingWhite:\r
11822       case IcsPlayingBlack:\r
11823       case IcsObserving:\r
11824       case IcsExamining:\r
11825         break;\r
11826 \r
11827       case PlayFromGameFile:\r
11828         gameInfo.event = StrSave("Game from non-PGN file");\r
11829         gameInfo.site = StrSave(HostName());\r
11830         gameInfo.date = PGNDate();\r
11831         gameInfo.round = StrSave("-");\r
11832         gameInfo.white = StrSave("?");\r
11833         gameInfo.black = StrSave("?");\r
11834         break;\r
11835 \r
11836       default:\r
11837         break;\r
11838     }\r
11839 }\r
11840 \r
11841 void\r
11842 ReplaceComment(index, text)\r
11843      int index;\r
11844      char *text;\r
11845 {\r
11846     int len;\r
11847 \r
11848     while (*text == '\n') text++;\r
11849     len = strlen(text);\r
11850     while (len > 0 && text[len - 1] == '\n') len--;\r
11851 \r
11852     if (commentList[index] != NULL)\r
11853       free(commentList[index]);\r
11854 \r
11855     if (len == 0) {\r
11856         commentList[index] = NULL;\r
11857         return;\r
11858     }\r
11859     commentList[index] = (char *) malloc(len + 2);\r
11860     strncpy(commentList[index], text, len);\r
11861     commentList[index][len] = '\n';\r
11862     commentList[index][len + 1] = NULLCHAR;\r
11863 }\r
11864 \r
11865 void\r
11866 CrushCRs(text)\r
11867      char *text;\r
11868 {\r
11869   char *p = text;\r
11870   char *q = text;\r
11871   char ch;\r
11872 \r
11873   do {\r
11874     ch = *p++;\r
11875     if (ch == '\r') continue;\r
11876     *q++ = ch;\r
11877   } while (ch != '\0');\r
11878 }\r
11879 \r
11880 void\r
11881 AppendComment(index, text)\r
11882      int index;\r
11883      char *text;\r
11884 {\r
11885     int oldlen, len;\r
11886     char *old;\r
11887 \r
11888     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11889 \r
11890     CrushCRs(text);\r
11891     while (*text == '\n') text++;\r
11892     len = strlen(text);\r
11893     while (len > 0 && text[len - 1] == '\n') len--;\r
11894 \r
11895     if (len == 0) return;\r
11896 \r
11897     if (commentList[index] != NULL) {\r
11898         old = commentList[index];\r
11899         oldlen = strlen(old);\r
11900         commentList[index] = (char *) malloc(oldlen + len + 2);\r
11901         strcpy(commentList[index], old);\r
11902         free(old);\r
11903         strncpy(&commentList[index][oldlen], text, len);\r
11904         commentList[index][oldlen + len] = '\n';\r
11905         commentList[index][oldlen + len + 1] = NULLCHAR;\r
11906     } else {\r
11907         commentList[index] = (char *) malloc(len + 2);\r
11908         strncpy(commentList[index], text, len);\r
11909         commentList[index][len] = '\n';\r
11910         commentList[index][len + 1] = NULLCHAR;\r
11911     }\r
11912 }\r
11913 \r
11914 static char * FindStr( char * text, char * sub_text )\r
11915 {\r
11916     char * result = strstr( text, sub_text );\r
11917 \r
11918     if( result != NULL ) {\r
11919         result += strlen( sub_text );\r
11920     }\r
11921 \r
11922     return result;\r
11923 }\r
11924 \r
11925 /* [AS] Try to extract PV info from PGN comment */\r
11926 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
11927 char *GetInfoFromComment( int index, char * text )\r
11928 {\r
11929     char * sep = text;\r
11930 \r
11931     if( text != NULL && index > 0 ) {\r
11932         int score = 0;\r
11933         int depth = 0;\r
11934         int time = -1, sec = 0, deci;\r
11935         char * s_eval = FindStr( text, "[%eval " );\r
11936         char * s_emt = FindStr( text, "[%emt " );\r
11937 \r
11938         if( s_eval != NULL || s_emt != NULL ) {\r
11939             /* New style */\r
11940             char delim;\r
11941 \r
11942             if( s_eval != NULL ) {\r
11943                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
11944                     return text;\r
11945                 }\r
11946 \r
11947                 if( delim != ']' ) {\r
11948                     return text;\r
11949                 }\r
11950             }\r
11951 \r
11952             if( s_emt != NULL ) {\r
11953             }\r
11954         }\r
11955         else {\r
11956             /* We expect something like: [+|-]nnn.nn/dd */\r
11957             int score_lo = 0;\r
11958 \r
11959             sep = strchr( text, '/' );\r
11960             if( sep == NULL || sep < (text+4) ) {\r
11961                 return text;\r
11962             }\r
11963 \r
11964             time = -1; sec = -1; deci = -1;\r
11965             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
11966                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&\r
11967                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
11968                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
11969                 return text;\r
11970             }\r
11971 \r
11972             if( score_lo < 0 || score_lo >= 100 ) {\r
11973                 return text;\r
11974             }\r
11975 \r
11976             if(sec >= 0) time = 600*time + 10*sec; else\r
11977             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec\r
11978 \r
11979             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
11980 \r
11981             /* [HGM] PV time: now locate end of PV info */\r
11982             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
11983             if(time >= 0)\r
11984             while( *++sep >= '0' && *sep <= '9'); // strip time\r
11985             if(sec >= 0)\r
11986             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
11987             if(deci >= 0)\r
11988             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds\r
11989             while(*sep == ' ') sep++;\r
11990         }\r
11991 \r
11992         if( depth <= 0 ) {\r
11993             return text;\r
11994         }\r
11995 \r
11996         if( time < 0 ) {\r
11997             time = -1;\r
11998         }\r
11999 \r
12000         pvInfoList[index-1].depth = depth;\r
12001         pvInfoList[index-1].score = score;\r
12002         pvInfoList[index-1].time  = 10*time; // centi-sec\r
12003     }\r
12004     return sep;\r
12005 }\r
12006 \r
12007 void\r
12008 SendToProgram(message, cps)\r
12009      char *message;\r
12010      ChessProgramState *cps;\r
12011 {\r
12012     int count, outCount, error;\r
12013     char buf[MSG_SIZ];\r
12014 \r
12015     if (cps->pr == NULL) return;\r
12016     Attention(cps);\r
12017     \r
12018     if (appData.debugMode) {\r
12019         TimeMark now;\r
12020         GetTimeMark(&now);\r
12021         fprintf(debugFP, "%ld >%-6s: %s", \r
12022                 SubtractTimeMarks(&now, &programStartTime),\r
12023                 cps->which, message);\r
12024     }\r
12025     \r
12026     count = strlen(message);\r
12027     outCount = OutputToProcess(cps->pr, message, count, &error);\r
12028     if (outCount < count && !exiting \r
12029                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
12030         sprintf(buf, _("Error writing to %s chess program"), cps->which);\r
12031         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12032             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12033                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12034                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
12035             } else {\r
12036                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12037             }\r
12038             gameInfo.resultDetails = buf;\r
12039         }\r
12040         DisplayFatalError(buf, error, 1);\r
12041     }\r
12042 }\r
12043 \r
12044 void\r
12045 ReceiveFromProgram(isr, closure, message, count, error)\r
12046      InputSourceRef isr;\r
12047      VOIDSTAR closure;\r
12048      char *message;\r
12049      int count;\r
12050      int error;\r
12051 {\r
12052     char *end_str;\r
12053     char buf[MSG_SIZ];\r
12054     ChessProgramState *cps = (ChessProgramState *)closure;\r
12055 \r
12056     if (isr != cps->isr) return; /* Killed intentionally */\r
12057     if (count <= 0) {\r
12058         if (count == 0) {\r
12059             sprintf(buf,\r
12060                     _("Error: %s chess program (%s) exited unexpectedly"),\r
12061                     cps->which, cps->program);\r
12062         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12063                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12064                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12065                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);\r
12066                 } else {\r
12067                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12068                 }\r
12069                 gameInfo.resultDetails = buf;\r
12070             }\r
12071             RemoveInputSource(cps->isr);\r
12072             DisplayFatalError(buf, 0, 1);\r
12073         } else {\r
12074             sprintf(buf,\r
12075                     _("Error reading from %s chess program (%s)"),\r
12076                     cps->which, cps->program);\r
12077             RemoveInputSource(cps->isr);\r
12078 \r
12079             /* [AS] Program is misbehaving badly... kill it */\r
12080             if( count == -2 ) {\r
12081                 DestroyChildProcess( cps->pr, 9 );\r
12082                 cps->pr = NoProc;\r
12083             }\r
12084 \r
12085             DisplayFatalError(buf, error, 1);\r
12086         }\r
12087         return;\r
12088     }\r
12089     \r
12090     if ((end_str = strchr(message, '\r')) != NULL)\r
12091       *end_str = NULLCHAR;\r
12092     if ((end_str = strchr(message, '\n')) != NULL)\r
12093       *end_str = NULLCHAR;\r
12094     \r
12095     if (appData.debugMode) {\r
12096         TimeMark now; int print = 1;\r
12097         char *quote = ""; char c; int i;\r
12098 \r
12099         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
12100                 char start = message[0];\r
12101                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
12102                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
12103                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&\r
12104                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
12105                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
12106                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&\r
12107                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
12108                         { quote = "# "; print = (appData.engineComments == 2); }\r
12109                 message[0] = start; // restore original message\r
12110         }\r
12111         if(print) {\r
12112                 GetTimeMark(&now);\r
12113                 fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
12114                         SubtractTimeMarks(&now, &programStartTime), cps->which, \r
12115                         quote,\r
12116                         message);\r
12117         }\r
12118     }\r
12119 \r
12120     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */\r
12121     if (appData.icsEngineAnalyze) {\r
12122         if (strstr(message, "whisper") != NULL ||\r
12123              strstr(message, "kibitz") != NULL || \r
12124             strstr(message, "tellics") != NULL) return;\r
12125     }\r
12126 \r
12127     HandleMachineMove(message, cps);\r
12128 }\r
12129 \r
12130 \r
12131 void\r
12132 SendTimeControl(cps, mps, tc, inc, sd, st)\r
12133      ChessProgramState *cps;\r
12134      int mps, inc, sd, st;\r
12135      long tc;\r
12136 {\r
12137     char buf[MSG_SIZ];\r
12138     int seconds;\r
12139 \r
12140     if( timeControl_2 > 0 ) {\r
12141         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
12142             tc = timeControl_2;\r
12143         }\r
12144     }\r
12145     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
12146     inc /= cps->timeOdds;\r
12147     st  /= cps->timeOdds;\r
12148 \r
12149     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
12150 \r
12151     if (st > 0) {\r
12152       /* Set exact time per move, normally using st command */\r
12153       if (cps->stKludge) {\r
12154         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
12155         seconds = st % 60;\r
12156         if (seconds == 0) {\r
12157           sprintf(buf, "level 1 %d\n", st/60);\r
12158         } else {\r
12159           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
12160         }\r
12161       } else {\r
12162         sprintf(buf, "st %d\n", st);\r
12163       }\r
12164     } else {\r
12165       /* Set conventional or incremental time control, using level command */\r
12166       if (seconds == 0) {\r
12167         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
12168            Fixed in later versions, but still avoid :seconds\r
12169            when seconds is 0. */\r
12170         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
12171       } else {\r
12172         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
12173                 seconds, inc/1000);\r
12174       }\r
12175     }\r
12176     SendToProgram(buf, cps);\r
12177 \r
12178     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
12179     /* Orthogonally, limit search to given depth */\r
12180     if (sd > 0) {\r
12181       if (cps->sdKludge) {\r
12182         sprintf(buf, "depth\n%d\n", sd);\r
12183       } else {\r
12184         sprintf(buf, "sd %d\n", sd);\r
12185       }\r
12186       SendToProgram(buf, cps);\r
12187     }\r
12188 \r
12189     if(cps->nps > 0) { /* [HGM] nps */\r
12190         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
12191         else {\r
12192                 sprintf(buf, "nps %d\n", cps->nps);\r
12193               SendToProgram(buf, cps);\r
12194         }\r
12195     }\r
12196 }\r
12197 \r
12198 ChessProgramState *WhitePlayer()\r
12199 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
12200 {\r
12201     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || \r
12202        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)\r
12203         return &second;\r
12204     return &first;\r
12205 }\r
12206 \r
12207 void\r
12208 SendTimeRemaining(cps, machineWhite)\r
12209      ChessProgramState *cps;\r
12210      int /*boolean*/ machineWhite;\r
12211 {\r
12212     char message[MSG_SIZ];\r
12213     long time, otime;\r
12214 \r
12215     /* Note: this routine must be called when the clocks are stopped\r
12216        or when they have *just* been set or switched; otherwise\r
12217        it will be off by the time since the current tick started.\r
12218     */\r
12219     if (machineWhite) {\r
12220         time = whiteTimeRemaining / 10;\r
12221         otime = blackTimeRemaining / 10;\r
12222     } else {\r
12223         time = blackTimeRemaining / 10;\r
12224         otime = whiteTimeRemaining / 10;\r
12225     }\r
12226     /* [HGM] translate opponent's time by time-odds factor */\r
12227     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
12228     if (appData.debugMode) {\r
12229         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
12230     }\r
12231 \r
12232     if (time <= 0) time = 1;\r
12233     if (otime <= 0) otime = 1;\r
12234     \r
12235     sprintf(message, "time %ld\n", time);\r
12236     SendToProgram(message, cps);\r
12237 \r
12238     sprintf(message, "otim %ld\n", otime);\r
12239     SendToProgram(message, cps);\r
12240 }\r
12241 \r
12242 int\r
12243 BoolFeature(p, name, loc, cps)\r
12244      char **p;\r
12245      char *name;\r
12246      int *loc;\r
12247      ChessProgramState *cps;\r
12248 {\r
12249   char buf[MSG_SIZ];\r
12250   int len = strlen(name);\r
12251   int val;\r
12252   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12253     (*p) += len + 1;\r
12254     sscanf(*p, "%d", &val);\r
12255     *loc = (val != 0);\r
12256     while (**p && **p != ' ') (*p)++;\r
12257     sprintf(buf, "accepted %s\n", name);\r
12258     SendToProgram(buf, cps);\r
12259     return TRUE;\r
12260   }\r
12261   return FALSE;\r
12262 }\r
12263 \r
12264 int\r
12265 IntFeature(p, name, loc, cps)\r
12266      char **p;\r
12267      char *name;\r
12268      int *loc;\r
12269      ChessProgramState *cps;\r
12270 {\r
12271   char buf[MSG_SIZ];\r
12272   int len = strlen(name);\r
12273   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12274     (*p) += len + 1;\r
12275     sscanf(*p, "%d", loc);\r
12276     while (**p && **p != ' ') (*p)++;\r
12277     sprintf(buf, "accepted %s\n", name);\r
12278     SendToProgram(buf, cps);\r
12279     return TRUE;\r
12280   }\r
12281   return FALSE;\r
12282 }\r
12283 \r
12284 int\r
12285 StringFeature(p, name, loc, cps)\r
12286      char **p;\r
12287      char *name;\r
12288      char loc[];\r
12289      ChessProgramState *cps;\r
12290 {\r
12291   char buf[MSG_SIZ];\r
12292   int len = strlen(name);\r
12293   if (strncmp((*p), name, len) == 0\r
12294       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
12295     (*p) += len + 2;\r
12296     sscanf(*p, "%[^\"]", loc);\r
12297     while (**p && **p != '\"') (*p)++;\r
12298     if (**p == '\"') (*p)++;\r
12299     sprintf(buf, "accepted %s\n", name);\r
12300     SendToProgram(buf, cps);\r
12301     return TRUE;\r
12302   }\r
12303   return FALSE;\r
12304 }\r
12305 \r
12306 int \r
12307 ParseOption(Option *opt, ChessProgramState *cps)\r
12308 // [HGM] options: process the string that defines an engine option, and determine\r
12309 // name, type, default value, and allowed value range\r
12310 {\r
12311         char *p, *q, buf[MSG_SIZ];\r
12312         int n, min = (-1)<<31, max = 1<<31, def;\r
12313 \r
12314         if(p = strstr(opt->name, " -spin ")) {\r
12315             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;\r
12316             if(max < min) max = min; // enforce consistency\r
12317             if(def < min) def = min;\r
12318             if(def > max) def = max;\r
12319             opt->value = def;\r
12320             opt->min = min;\r
12321             opt->max = max;\r
12322             opt->type = Spin;\r
12323         } else if(p = strstr(opt->name, " -string ")) {\r
12324             opt->textValue = p+9;\r
12325             opt->type = TextBox;\r
12326         } else if(p = strstr(opt->name, " -check ")) {\r
12327             if(sscanf(p, " -check %d", &def) < 1) return FALSE;\r
12328             opt->value = (def != 0);\r
12329             opt->type = CheckBox;\r
12330         } else if(p = strstr(opt->name, " -combo ")) {\r
12331             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type\r
12332             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices\r
12333             opt->value = n = 0;\r
12334             while(q = StrStr(q, " /// ")) {\r
12335                 n++; *q = 0;    // count choices, and null-terminate each of them\r
12336                 q += 5;\r
12337                 if(*q == '*') { // remember default, which is marked with * prefix\r
12338                     q++;\r
12339                     opt->value = n;\r
12340                 }\r
12341                 cps->comboList[cps->comboCnt++] = q;\r
12342             }\r
12343             cps->comboList[cps->comboCnt++] = NULL;\r
12344             opt->max = n + 1;\r
12345             opt->type = ComboBox;\r
12346         } else if(p = strstr(opt->name, " -button")) {\r
12347             opt->type = Button;\r
12348         } else if(p = strstr(opt->name, " -save")) {\r
12349             opt->type = SaveButton;\r
12350         } else return FALSE;\r
12351         *p = 0; // terminate option name\r
12352         // now look if the command-line options define a setting for this engine option.\r
12353         if(cps->optionSettings && cps->optionSettings[0])\r
12354             p = strstr(cps->optionSettings, opt->name); else p = NULL;\r
12355         if(p && (p == cps->optionSettings || p[-1] == ',')) {\r
12356                 sprintf(buf, "option %s", p);\r
12357                 if(p = strstr(buf, ",")) *p = 0;\r
12358                 strcat(buf, "\n");\r
12359                 SendToProgram(buf, cps);\r
12360         }\r
12361         return TRUE;\r
12362 }\r
12363 \r
12364 void\r
12365 FeatureDone(cps, val)\r
12366      ChessProgramState* cps;\r
12367      int val;\r
12368 {\r
12369   DelayedEventCallback cb = GetDelayedEvent();\r
12370   if ((cb == InitBackEnd3 && cps == &first) ||\r
12371       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
12372     CancelDelayedEvent();\r
12373     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
12374   }\r
12375   cps->initDone = val;\r
12376 }\r
12377 \r
12378 /* Parse feature command from engine */\r
12379 void\r
12380 ParseFeatures(args, cps)\r
12381      char* args;\r
12382      ChessProgramState *cps;  \r
12383 {\r
12384   char *p = args;\r
12385   char *q;\r
12386   int val;\r
12387   char buf[MSG_SIZ];\r
12388 \r
12389   for (;;) {\r
12390     while (*p == ' ') p++;\r
12391     if (*p == NULLCHAR) return;\r
12392 \r
12393     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
12394     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
12395     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
12396     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
12397     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
12398     if (BoolFeature(&p, "reuse", &val, cps)) {\r
12399       /* Engine can disable reuse, but can't enable it if user said no */\r
12400       if (!val) cps->reuse = FALSE;\r
12401       continue;\r
12402     }\r
12403     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
12404     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
12405       if (gameMode == TwoMachinesPlay) {\r
12406         DisplayTwoMachinesTitle();\r
12407       } else {\r
12408         DisplayTitle("");\r
12409       }\r
12410       continue;\r
12411     }\r
12412     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
12413     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
12414     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
12415     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
12416     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
12417     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
12418     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
12419     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
12420     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
12421     if (IntFeature(&p, "done", &val, cps)) {\r
12422       FeatureDone(cps, val);\r
12423       continue;\r
12424     }\r
12425     /* Added by Tord: */\r
12426     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
12427     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
12428     /* End of additions by Tord */\r
12429 \r
12430     /* [HGM] added features: */\r
12431     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
12432     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
12433     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
12434     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
12435     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12436     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
12437     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {\r
12438         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature\r
12439         if(cps->nrOptions >= MAX_OPTIONS) {\r
12440             cps->nrOptions--;\r
12441             sprintf(buf, "%s engine has too many options\n", cps->which);\r
12442             DisplayError(buf, 0);\r
12443         }\r
12444         continue;\r
12445     }\r
12446     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12447     /* End of additions by HGM */\r
12448 \r
12449     /* unknown feature: complain and skip */\r
12450     q = p;\r
12451     while (*q && *q != '=') q++;\r
12452     sprintf(buf, "rejected %.*s\n", q-p, p);\r
12453     SendToProgram(buf, cps);\r
12454     p = q;\r
12455     if (*p == '=') {\r
12456       p++;\r
12457       if (*p == '\"') {\r
12458         p++;\r
12459         while (*p && *p != '\"') p++;\r
12460         if (*p == '\"') p++;\r
12461       } else {\r
12462         while (*p && *p != ' ') p++;\r
12463       }\r
12464     }\r
12465   }\r
12466 \r
12467 }\r
12468 \r
12469 void\r
12470 PeriodicUpdatesEvent(newState)\r
12471      int newState;\r
12472 {\r
12473     if (newState == appData.periodicUpdates)\r
12474       return;\r
12475 \r
12476     appData.periodicUpdates=newState;\r
12477 \r
12478     /* Display type changes, so update it now */\r
12479     DisplayAnalysis();\r
12480 \r
12481     /* Get the ball rolling again... */\r
12482     if (newState) {\r
12483         AnalysisPeriodicEvent(1);\r
12484         StartAnalysisClock();\r
12485     }\r
12486 }\r
12487 \r
12488 void\r
12489 PonderNextMoveEvent(newState)\r
12490      int newState;\r
12491 {\r
12492     if (newState == appData.ponderNextMove) return;\r
12493     if (gameMode == EditPosition) EditPositionDone();\r
12494     if (newState) {\r
12495         SendToProgram("hard\n", &first);\r
12496         if (gameMode == TwoMachinesPlay) {\r
12497             SendToProgram("hard\n", &second);\r
12498         }\r
12499     } else {\r
12500         SendToProgram("easy\n", &first);\r
12501         thinkOutput[0] = NULLCHAR;\r
12502         if (gameMode == TwoMachinesPlay) {\r
12503             SendToProgram("easy\n", &second);\r
12504         }\r
12505     }\r
12506     appData.ponderNextMove = newState;\r
12507 }\r
12508 \r
12509 void\r
12510 NewSettingEvent(option, command, value)\r
12511      char *command;\r
12512      int option, value;\r
12513 {\r
12514     char buf[MSG_SIZ];\r
12515 \r
12516     if (gameMode == EditPosition) EditPositionDone();\r
12517     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);\r
12518     SendToProgram(buf, &first);\r
12519     if (gameMode == TwoMachinesPlay) {\r
12520         SendToProgram(buf, &second);\r
12521     }\r
12522 }\r
12523 \r
12524 void\r
12525 ShowThinkingEvent()\r
12526 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup\r
12527 {\r
12528     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated\r
12529     int newState = appData.showThinking\r
12530         // [HGM] thinking: other features now need thinking output as well\r
12531         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();\r
12532     \r
12533     if (oldState == newState) return;\r
12534     oldState = newState;\r
12535     if (gameMode == EditPosition) EditPositionDone();\r
12536     if (oldState) {\r
12537         SendToProgram("post\n", &first);\r
12538         if (gameMode == TwoMachinesPlay) {\r
12539             SendToProgram("post\n", &second);\r
12540         }\r
12541     } else {\r
12542         SendToProgram("nopost\n", &first);\r
12543         thinkOutput[0] = NULLCHAR;\r
12544         if (gameMode == TwoMachinesPlay) {\r
12545             SendToProgram("nopost\n", &second);\r
12546         }\r
12547     }\r
12548 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!\r
12549 }\r
12550 \r
12551 void\r
12552 AskQuestionEvent(title, question, replyPrefix, which)\r
12553      char *title; char *question; char *replyPrefix; char *which;\r
12554 {\r
12555   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
12556   if (pr == NoProc) return;\r
12557   AskQuestion(title, question, replyPrefix, pr);\r
12558 }\r
12559 \r
12560 void\r
12561 DisplayMove(moveNumber)\r
12562      int moveNumber;\r
12563 {\r
12564     char message[MSG_SIZ];\r
12565     char res[MSG_SIZ];\r
12566     char cpThinkOutput[MSG_SIZ];\r
12567 \r
12568     if(appData.noGUI) return; // [HGM] fast: suppress display of moves\r
12569     \r
12570     if (moveNumber == forwardMostMove - 1 || \r
12571         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12572 \r
12573         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
12574 \r
12575         if (strchr(cpThinkOutput, '\n')) {\r
12576             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
12577         }\r
12578     } else {\r
12579         *cpThinkOutput = NULLCHAR;\r
12580     }\r
12581 \r
12582     /* [AS] Hide thinking from human user */\r
12583     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
12584         *cpThinkOutput = NULLCHAR;\r
12585         if( thinkOutput[0] != NULLCHAR ) {\r
12586             int i;\r
12587 \r
12588             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
12589                 cpThinkOutput[i] = '.';\r
12590             }\r
12591             cpThinkOutput[i] = NULLCHAR;\r
12592             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
12593         }\r
12594     }\r
12595 \r
12596     if (moveNumber == forwardMostMove - 1 &&\r
12597         gameInfo.resultDetails != NULL) {\r
12598         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
12599             sprintf(res, " %s", PGNResult(gameInfo.result));\r
12600         } else {\r
12601             sprintf(res, " {%s} %s",\r
12602                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
12603         }\r
12604     } else {\r
12605         res[0] = NULLCHAR;\r
12606     }\r
12607 \r
12608     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12609         DisplayMessage(res, cpThinkOutput);\r
12610     } else {\r
12611         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
12612                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
12613                 parseList[moveNumber], res);\r
12614         DisplayMessage(message, cpThinkOutput);\r
12615     }\r
12616 }\r
12617 \r
12618 void\r
12619 DisplayAnalysisText(text)\r
12620      char *text;\r
12621 {\r
12622     char buf[MSG_SIZ];\r
12623 \r
12624     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile \r
12625                || appData.icsEngineAnalyze) {\r
12626         sprintf(buf, "Analysis (%s)", first.tidy);\r
12627         AnalysisPopUp(buf, text);\r
12628     }\r
12629 }\r
12630 \r
12631 static int\r
12632 only_one_move(str)\r
12633      char *str;\r
12634 {\r
12635     while (*str && isspace(*str)) ++str;\r
12636     while (*str && !isspace(*str)) ++str;\r
12637     if (!*str) return 1;\r
12638     while (*str && isspace(*str)) ++str;\r
12639     if (!*str) return 1;\r
12640     return 0;\r
12641 }\r
12642 \r
12643 void\r
12644 DisplayAnalysis()\r
12645 {\r
12646     char buf[MSG_SIZ];\r
12647     char lst[MSG_SIZ / 2];\r
12648     double nps;\r
12649     static char *xtra[] = { "", " (--)", " (++)" };\r
12650     int h, m, s, cs;\r
12651   \r
12652     if (programStats.time == 0) {\r
12653         programStats.time = 1;\r
12654     }\r
12655   \r
12656     if (programStats.got_only_move) {\r
12657         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
12658     } else {\r
12659         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
12660 \r
12661         nps = (u64ToDouble(programStats.nodes) /\r
12662              ((double)programStats.time /100.0));\r
12663 \r
12664         cs = programStats.time % 100;\r
12665         s = programStats.time / 100;\r
12666         h = (s / (60*60));\r
12667         s = s - h*60*60;\r
12668         m = (s/60);\r
12669         s = s - m*60;\r
12670 \r
12671         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
12672           if (programStats.move_name[0] != NULLCHAR) {\r
12673             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12674                     programStats.depth,\r
12675                     programStats.nr_moves-programStats.moves_left,\r
12676                     programStats.nr_moves, programStats.move_name,\r
12677                     ((float)programStats.score)/100.0, lst,\r
12678                     only_one_move(lst)?\r
12679                     xtra[programStats.got_fail] : "",\r
12680                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12681           } else {\r
12682             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12683                     programStats.depth,\r
12684                     programStats.nr_moves-programStats.moves_left,\r
12685                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
12686                     lst,\r
12687                     only_one_move(lst)?\r
12688                     xtra[programStats.got_fail] : "",\r
12689                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12690           }\r
12691         } else {\r
12692             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12693                     programStats.depth,\r
12694                     ((float)programStats.score)/100.0,\r
12695                     lst,\r
12696                     only_one_move(lst)?\r
12697                     xtra[programStats.got_fail] : "",\r
12698                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12699         }\r
12700     }\r
12701     DisplayAnalysisText(buf);\r
12702 }\r
12703 \r
12704 void\r
12705 DisplayComment(moveNumber, text)\r
12706      int moveNumber;\r
12707      char *text;\r
12708 {\r
12709     char title[MSG_SIZ];\r
12710     char buf[8000]; // comment can be long!\r
12711     int score, depth;\r
12712 \r
12713     if( appData.autoDisplayComment ) {\r
12714         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12715             strcpy(title, "Comment");\r
12716         } else {\r
12717             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
12718                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
12719                     parseList[moveNumber]);\r
12720         }\r
12721     } else title[0] = 0;\r
12722 \r
12723     // [HGM] PV info: display PV info together with (or as) comment\r
12724     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
12725         if(text == NULL) text = "";                                           \r
12726         score = pvInfoList[moveNumber].score;\r
12727         sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
12728                               depth, (pvInfoList[moveNumber].time+50)/100, text);\r
12729         CommentPopUp(title, buf);\r
12730     } else\r
12731     if (text != NULL)\r
12732         CommentPopUp(title, text);\r
12733 }\r
12734 \r
12735 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
12736  * might be busy thinking or pondering.  It can be omitted if your\r
12737  * gnuchess is configured to stop thinking immediately on any user\r
12738  * input.  However, that gnuchess feature depends on the FIONREAD\r
12739  * ioctl, which does not work properly on some flavors of Unix.\r
12740  */\r
12741 void\r
12742 Attention(cps)\r
12743      ChessProgramState *cps;\r
12744 {\r
12745 #if ATTENTION\r
12746     if (!cps->useSigint) return;\r
12747     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
12748     switch (gameMode) {\r
12749       case MachinePlaysWhite:\r
12750       case MachinePlaysBlack:\r
12751       case TwoMachinesPlay:\r
12752       case IcsPlayingWhite:\r
12753       case IcsPlayingBlack:\r
12754       case AnalyzeMode:\r
12755       case AnalyzeFile:\r
12756         /* Skip if we know it isn't thinking */\r
12757         if (!cps->maybeThinking) return;\r
12758         if (appData.debugMode)\r
12759           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
12760         InterruptChildProcess(cps->pr);\r
12761         cps->maybeThinking = FALSE;\r
12762         break;\r
12763       default:\r
12764         break;\r
12765     }\r
12766 #endif /*ATTENTION*/\r
12767 }\r
12768 \r
12769 int\r
12770 CheckFlags()\r
12771 {\r
12772     if (whiteTimeRemaining <= 0) {\r
12773         if (!whiteFlag) {\r
12774             whiteFlag = TRUE;\r
12775             if (appData.icsActive) {\r
12776                 if (appData.autoCallFlag &&\r
12777                     gameMode == IcsPlayingBlack && !blackFlag) {\r
12778                   SendToICS(ics_prefix);\r
12779                   SendToICS("flag\n");\r
12780                 }\r
12781             } else {\r
12782                 if (blackFlag) {\r
12783                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12784                 } else {\r
12785                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));\r
12786                     if (appData.autoCallFlag) {\r
12787                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
12788                         return TRUE;\r
12789                     }\r
12790                 }\r
12791             }\r
12792         }\r
12793     }\r
12794     if (blackTimeRemaining <= 0) {\r
12795         if (!blackFlag) {\r
12796             blackFlag = TRUE;\r
12797             if (appData.icsActive) {\r
12798                 if (appData.autoCallFlag &&\r
12799                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
12800                   SendToICS(ics_prefix);\r
12801                   SendToICS("flag\n");\r
12802                 }\r
12803             } else {\r
12804                 if (whiteFlag) {\r
12805                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12806                 } else {\r
12807                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));\r
12808                     if (appData.autoCallFlag) {\r
12809                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
12810                         return TRUE;\r
12811                     }\r
12812                 }\r
12813             }\r
12814         }\r
12815     }\r
12816     return FALSE;\r
12817 }\r
12818 \r
12819 void\r
12820 CheckTimeControl()\r
12821 {\r
12822     if (!appData.clockMode || appData.icsActive ||\r
12823         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
12824 \r
12825     /*\r
12826      * add time to clocks when time control is achieved ([HGM] now also used for increment)\r
12827      */\r
12828     if ( !WhiteOnMove(forwardMostMove) )\r
12829         /* White made time control */\r
12830         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12831         /* [HGM] time odds: correct new time quota for time odds! */\r
12832                                             / WhitePlayer()->timeOdds;\r
12833       else\r
12834         /* Black made time control */\r
12835         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12836                                             / WhitePlayer()->other->timeOdds;\r
12837 }\r
12838 \r
12839 void\r
12840 DisplayBothClocks()\r
12841 {\r
12842     int wom = gameMode == EditPosition ?\r
12843       !blackPlaysFirst : WhiteOnMove(currentMove);\r
12844     DisplayWhiteClock(whiteTimeRemaining, wom);\r
12845     DisplayBlackClock(blackTimeRemaining, !wom);\r
12846 }\r
12847 \r
12848 \r
12849 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
12850    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
12851    to use other calls if you don't.  Clocks will be less accurate if\r
12852    you have neither ftime nor gettimeofday.\r
12853 */\r
12854 \r
12855 /* VS 2008 requires the #include outside of the function */\r
12856 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME\r
12857 #include <sys/timeb.h>\r
12858 #endif\r
12859 \r
12860 /* Get the current time as a TimeMark */\r
12861 void\r
12862 GetTimeMark(tm)\r
12863      TimeMark *tm;\r
12864 {\r
12865 #if HAVE_GETTIMEOFDAY\r
12866 \r
12867     struct timeval timeVal;\r
12868     struct timezone timeZone;\r
12869 \r
12870     gettimeofday(&timeVal, &timeZone);\r
12871     tm->sec = (long) timeVal.tv_sec; \r
12872     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
12873 \r
12874 #else /*!HAVE_GETTIMEOFDAY*/\r
12875 #if HAVE_FTIME\r
12876 \r
12877 // include <sys/timeb.h> / moved to just above start of function\r
12878     struct timeb timeB;\r
12879 \r
12880     ftime(&timeB);\r
12881     tm->sec = (long) timeB.time;\r
12882     tm->ms = (int) timeB.millitm;\r
12883 \r
12884 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
12885     tm->sec = (long) time(NULL);\r
12886     tm->ms = 0;\r
12887 #endif\r
12888 #endif\r
12889 }\r
12890 \r
12891 /* Return the difference in milliseconds between two\r
12892    time marks.  We assume the difference will fit in a long!\r
12893 */\r
12894 long\r
12895 SubtractTimeMarks(tm2, tm1)\r
12896      TimeMark *tm2, *tm1;\r
12897 {\r
12898     return 1000L*(tm2->sec - tm1->sec) +\r
12899            (long) (tm2->ms - tm1->ms);\r
12900 }\r
12901 \r
12902 \r
12903 /*\r
12904  * Code to manage the game clocks.\r
12905  *\r
12906  * In tournament play, black starts the clock and then white makes a move.\r
12907  * We give the human user a slight advantage if he is playing white---the\r
12908  * clocks don't run until he makes his first move, so it takes zero time.\r
12909  * Also, we don't account for network lag, so we could get out of sync\r
12910  * with GNU Chess's clock -- but then, referees are always right.  \r
12911  */\r
12912 \r
12913 static TimeMark tickStartTM;\r
12914 static long intendedTickLength;\r
12915 \r
12916 long\r
12917 NextTickLength(timeRemaining)\r
12918      long timeRemaining;\r
12919 {\r
12920     long nominalTickLength, nextTickLength;\r
12921 \r
12922     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
12923       nominalTickLength = 100L;\r
12924     else\r
12925       nominalTickLength = 1000L;\r
12926     nextTickLength = timeRemaining % nominalTickLength;\r
12927     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
12928 \r
12929     return nextTickLength;\r
12930 }\r
12931 \r
12932 /* Adjust clock one minute up or down */\r
12933 void\r
12934 AdjustClock(Boolean which, int dir)\r
12935 {\r
12936     if(which) blackTimeRemaining += 60000*dir;\r
12937     else      whiteTimeRemaining += 60000*dir;\r
12938     DisplayBothClocks();\r
12939 }\r
12940 \r
12941 /* Stop clocks and reset to a fresh time control */\r
12942 void\r
12943 ResetClocks() \r
12944 {\r
12945     (void) StopClockTimer();\r
12946     if (appData.icsActive) {\r
12947         whiteTimeRemaining = blackTimeRemaining = 0;\r
12948     } else { /* [HGM] correct new time quote for time odds */\r
12949         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
12950         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
12951     }\r
12952     if (whiteFlag || blackFlag) {\r
12953         DisplayTitle("");\r
12954         whiteFlag = blackFlag = FALSE;\r
12955     }\r
12956     DisplayBothClocks();\r
12957 }\r
12958 \r
12959 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
12960 \r
12961 /* Decrement running clock by amount of time that has passed */\r
12962 void\r
12963 DecrementClocks()\r
12964 {\r
12965     long timeRemaining;\r
12966     long lastTickLength, fudge;\r
12967     TimeMark now;\r
12968 \r
12969     if (!appData.clockMode) return;\r
12970     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
12971         \r
12972     GetTimeMark(&now);\r
12973 \r
12974     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
12975 \r
12976     /* Fudge if we woke up a little too soon */\r
12977     fudge = intendedTickLength - lastTickLength;\r
12978     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
12979 \r
12980     if (WhiteOnMove(forwardMostMove)) {\r
12981         if(whiteNPS >= 0) lastTickLength = 0;\r
12982         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
12983         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
12984                           WhiteOnMove(currentMove));\r
12985     } else {\r
12986         if(blackNPS >= 0) lastTickLength = 0;\r
12987         timeRemaining = blackTimeRemaining -= lastTickLength;\r
12988         DisplayBlackClock(blackTimeRemaining - fudge,\r
12989                           !WhiteOnMove(currentMove));\r
12990     }\r
12991 \r
12992     if (CheckFlags()) return;\r
12993         \r
12994     tickStartTM = now;\r
12995     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
12996     StartClockTimer(intendedTickLength);\r
12997 \r
12998     /* if the time remaining has fallen below the alarm threshold, sound the\r
12999      * alarm. if the alarm has sounded and (due to a takeback or time control\r
13000      * with increment) the time remaining has increased to a level above the\r
13001      * threshold, reset the alarm so it can sound again. \r
13002      */\r
13003     \r
13004     if (appData.icsActive && appData.icsAlarm) {\r
13005 \r
13006         /* make sure we are dealing with the user's clock */\r
13007         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
13008                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
13009            )) return;\r
13010 \r
13011         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
13012             alarmSounded = FALSE;\r
13013         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
13014             PlayAlarmSound();\r
13015             alarmSounded = TRUE;\r
13016         }\r
13017     }\r
13018 }\r
13019 \r
13020 \r
13021 /* A player has just moved, so stop the previously running\r
13022    clock and (if in clock mode) start the other one.\r
13023    We redisplay both clocks in case we're in ICS mode, because\r
13024    ICS gives us an update to both clocks after every move.\r
13025    Note that this routine is called *after* forwardMostMove\r
13026    is updated, so the last fractional tick must be subtracted\r
13027    from the color that is *not* on move now.\r
13028 */\r
13029 void\r
13030 SwitchClocks()\r
13031 {\r
13032     long lastTickLength;\r
13033     TimeMark now;\r
13034     int flagged = FALSE;\r
13035 \r
13036     GetTimeMark(&now);\r
13037 \r
13038     if (StopClockTimer() && appData.clockMode) {\r
13039         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13040         if (WhiteOnMove(forwardMostMove)) {\r
13041             if(blackNPS >= 0) lastTickLength = 0;\r
13042             blackTimeRemaining -= lastTickLength;\r
13043            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13044 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13045                  pvInfoList[forwardMostMove-1].time =               // use GUI time\r
13046                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
13047         } else {\r
13048            if(whiteNPS >= 0) lastTickLength = 0;\r
13049            whiteTimeRemaining -= lastTickLength;\r
13050            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13051 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13052                  pvInfoList[forwardMostMove-1].time = \r
13053                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
13054         }\r
13055         flagged = CheckFlags();\r
13056     }\r
13057     CheckTimeControl();\r
13058 \r
13059     if (flagged || !appData.clockMode) return;\r
13060 \r
13061     switch (gameMode) {\r
13062       case MachinePlaysBlack:\r
13063       case MachinePlaysWhite:\r
13064       case BeginningOfGame:\r
13065         if (pausing) return;\r
13066         break;\r
13067 \r
13068       case EditGame:\r
13069       case PlayFromGameFile:\r
13070       case IcsExamining:\r
13071         return;\r
13072 \r
13073       default:\r
13074         break;\r
13075     }\r
13076 \r
13077     tickStartTM = now;\r
13078     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13079       whiteTimeRemaining : blackTimeRemaining);\r
13080     StartClockTimer(intendedTickLength);\r
13081 }\r
13082         \r
13083 \r
13084 /* Stop both clocks */\r
13085 void\r
13086 StopClocks()\r
13087 {       \r
13088     long lastTickLength;\r
13089     TimeMark now;\r
13090 \r
13091     if (!StopClockTimer()) return;\r
13092     if (!appData.clockMode) return;\r
13093 \r
13094     GetTimeMark(&now);\r
13095 \r
13096     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13097     if (WhiteOnMove(forwardMostMove)) {\r
13098         if(whiteNPS >= 0) lastTickLength = 0;\r
13099         whiteTimeRemaining -= lastTickLength;\r
13100         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
13101     } else {\r
13102         if(blackNPS >= 0) lastTickLength = 0;\r
13103         blackTimeRemaining -= lastTickLength;\r
13104         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
13105     }\r
13106     CheckFlags();\r
13107 }\r
13108         \r
13109 /* Start clock of player on move.  Time may have been reset, so\r
13110    if clock is already running, stop and restart it. */\r
13111 void\r
13112 StartClocks()\r
13113 {\r
13114     (void) StopClockTimer(); /* in case it was running already */\r
13115     DisplayBothClocks();\r
13116     if (CheckFlags()) return;\r
13117 \r
13118     if (!appData.clockMode) return;\r
13119     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
13120 \r
13121     GetTimeMark(&tickStartTM);\r
13122     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13123       whiteTimeRemaining : blackTimeRemaining);\r
13124 \r
13125    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
13126     whiteNPS = blackNPS = -1; \r
13127     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
13128        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
13129         whiteNPS = first.nps;\r
13130     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
13131        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
13132         blackNPS = first.nps;\r
13133     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
13134         whiteNPS = second.nps;\r
13135     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
13136         blackNPS = second.nps;\r
13137     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
13138 \r
13139     StartClockTimer(intendedTickLength);\r
13140 }\r
13141 \r
13142 char *\r
13143 TimeString(ms)\r
13144      long ms;\r
13145 {\r
13146     long second, minute, hour, day;\r
13147     char *sign = "";\r
13148     static char buf[32];\r
13149     \r
13150     if (ms > 0 && ms <= 9900) {\r
13151       /* convert milliseconds to tenths, rounding up */\r
13152       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
13153 \r
13154       sprintf(buf, " %03.1f ", tenths/10.0);\r
13155       return buf;\r
13156     }\r
13157 \r
13158     /* convert milliseconds to seconds, rounding up */\r
13159     /* use floating point to avoid strangeness of integer division\r
13160        with negative dividends on many machines */\r
13161     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
13162 \r
13163     if (second < 0) {\r
13164         sign = "-";\r
13165         second = -second;\r
13166     }\r
13167     \r
13168     day = second / (60 * 60 * 24);\r
13169     second = second % (60 * 60 * 24);\r
13170     hour = second / (60 * 60);\r
13171     second = second % (60 * 60);\r
13172     minute = second / 60;\r
13173     second = second % 60;\r
13174     \r
13175     if (day > 0)\r
13176       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
13177               sign, day, hour, minute, second);\r
13178     else if (hour > 0)\r
13179       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
13180     else\r
13181       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
13182     \r
13183     return buf;\r
13184 }\r
13185 \r
13186 \r
13187 /*\r
13188  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
13189  */\r
13190 char *\r
13191 StrStr(string, match)\r
13192      char *string, *match;\r
13193 {\r
13194     int i, length;\r
13195     \r
13196     length = strlen(match);\r
13197     \r
13198     for (i = strlen(string) - length; i >= 0; i--, string++)\r
13199       if (!strncmp(match, string, length))\r
13200         return string;\r
13201     \r
13202     return NULL;\r
13203 }\r
13204 \r
13205 char *\r
13206 StrCaseStr(string, match)\r
13207      char *string, *match;\r
13208 {\r
13209     int i, j, length;\r
13210     \r
13211     length = strlen(match);\r
13212     \r
13213     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
13214         for (j = 0; j < length; j++) {\r
13215             if (ToLower(match[j]) != ToLower(string[j]))\r
13216               break;\r
13217         }\r
13218         if (j == length) return string;\r
13219     }\r
13220 \r
13221     return NULL;\r
13222 }\r
13223 \r
13224 #ifndef _amigados\r
13225 int\r
13226 StrCaseCmp(s1, s2)\r
13227      char *s1, *s2;\r
13228 {\r
13229     char c1, c2;\r
13230     \r
13231     for (;;) {\r
13232         c1 = ToLower(*s1++);\r
13233         c2 = ToLower(*s2++);\r
13234         if (c1 > c2) return 1;\r
13235         if (c1 < c2) return -1;\r
13236         if (c1 == NULLCHAR) return 0;\r
13237     }\r
13238 }\r
13239 \r
13240 \r
13241 int\r
13242 ToLower(c)\r
13243      int c;\r
13244 {\r
13245     return isupper(c) ? tolower(c) : c;\r
13246 }\r
13247 \r
13248 \r
13249 int\r
13250 ToUpper(c)\r
13251      int c;\r
13252 {\r
13253     return islower(c) ? toupper(c) : c;\r
13254 }\r
13255 #endif /* !_amigados    */\r
13256 \r
13257 char *\r
13258 StrSave(s)\r
13259      char *s;\r
13260 {\r
13261     char *ret;\r
13262 \r
13263     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
13264         strcpy(ret, s);\r
13265     }\r
13266     return ret;\r
13267 }\r
13268 \r
13269 char *\r
13270 StrSavePtr(s, savePtr)\r
13271      char *s, **savePtr;\r
13272 {\r
13273     if (*savePtr) {\r
13274         free(*savePtr);\r
13275     }\r
13276     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
13277         strcpy(*savePtr, s);\r
13278     }\r
13279     return(*savePtr);\r
13280 }\r
13281 \r
13282 char *\r
13283 PGNDate()\r
13284 {\r
13285     time_t clock;\r
13286     struct tm *tm;\r
13287     char buf[MSG_SIZ];\r
13288 \r
13289     clock = time((time_t *)NULL);\r
13290     tm = localtime(&clock);\r
13291     sprintf(buf, "%04d.%02d.%02d",\r
13292             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
13293     return StrSave(buf);\r
13294 }\r
13295 \r
13296 \r
13297 char *\r
13298 PositionToFEN(move, overrideCastling)\r
13299      int move;\r
13300      char *overrideCastling;\r
13301 {\r
13302     int i, j, fromX, fromY, toX, toY;\r
13303     int whiteToPlay;\r
13304     char buf[128];\r
13305     char *p, *q;\r
13306     int emptycount;\r
13307     ChessSquare piece;\r
13308 \r
13309     whiteToPlay = (gameMode == EditPosition) ?\r
13310       !blackPlaysFirst : (move % 2 == 0);\r
13311     p = buf;\r
13312 \r
13313     /* Piece placement data */\r
13314     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13315         emptycount = 0;\r
13316         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
13317             if (boards[move][i][j] == EmptySquare) {\r
13318                 emptycount++;\r
13319             } else { ChessSquare piece = boards[move][i][j];\r
13320                 if (emptycount > 0) {\r
13321                     if(emptycount<10) /* [HGM] can be >= 10 */\r
13322                         *p++ = '0' + emptycount;\r
13323                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13324                     emptycount = 0;\r
13325                 }\r
13326                 if(PieceToChar(piece) == '+') {\r
13327                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
13328                     *p++ = '+';\r
13329                     piece = (ChessSquare)(DEMOTED piece);\r
13330                 } \r
13331                 *p++ = PieceToChar(piece);\r
13332                 if(p[-1] == '~') {\r
13333                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
13334                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
13335                     *p++ = '~';\r
13336                 }\r
13337             }\r
13338         }\r
13339         if (emptycount > 0) {\r
13340             if(emptycount<10) /* [HGM] can be >= 10 */\r
13341                 *p++ = '0' + emptycount;\r
13342             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13343             emptycount = 0;\r
13344         }\r
13345         *p++ = '/';\r
13346     }\r
13347     *(p - 1) = ' ';\r
13348 \r
13349     /* [HGM] print Crazyhouse or Shogi holdings */\r
13350     if( gameInfo.holdingsWidth ) {\r
13351         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
13352         q = p;\r
13353         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
13354             piece = boards[move][i][BOARD_WIDTH-1];\r
13355             if( piece != EmptySquare )\r
13356               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
13357                   *p++ = PieceToChar(piece);\r
13358         }\r
13359         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
13360             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
13361             if( piece != EmptySquare )\r
13362               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
13363                   *p++ = PieceToChar(piece);\r
13364         }\r
13365 \r
13366         if( q == p ) *p++ = '-';\r
13367         *p++ = ']';\r
13368         *p++ = ' ';\r
13369     }\r
13370 \r
13371     /* Active color */\r
13372     *p++ = whiteToPlay ? 'w' : 'b';\r
13373     *p++ = ' ';\r
13374 \r
13375   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines\r
13376     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';\r
13377   } else {\r
13378   if(nrCastlingRights) {\r
13379      q = p;\r
13380      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
13381        /* [HGM] write directly from rights */\r
13382            if(castlingRights[move][2] >= 0 &&\r
13383               castlingRights[move][0] >= 0   )\r
13384                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
13385            if(castlingRights[move][2] >= 0 &&\r
13386               castlingRights[move][1] >= 0   )\r
13387                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
13388            if(castlingRights[move][5] >= 0 &&\r
13389               castlingRights[move][3] >= 0   )\r
13390                 *p++ = castlingRights[move][3] + AAA;\r
13391            if(castlingRights[move][5] >= 0 &&\r
13392               castlingRights[move][4] >= 0   )\r
13393                 *p++ = castlingRights[move][4] + AAA;\r
13394      } else {\r
13395 \r
13396         /* [HGM] write true castling rights */\r
13397         if( nrCastlingRights == 6 ) {\r
13398             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
13399                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
13400             if(castlingRights[move][1] == BOARD_LEFT &&\r
13401                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
13402             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
13403                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
13404             if(castlingRights[move][4] == BOARD_LEFT &&\r
13405                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
13406         }\r
13407      }\r
13408      if (q == p) *p++ = '-'; /* No castling rights */\r
13409      *p++ = ' ';\r
13410   }\r
13411 \r
13412   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13413      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13414     /* En passant target square */\r
13415     if (move > backwardMostMove) {\r
13416         fromX = moveList[move - 1][0] - AAA;\r
13417         fromY = moveList[move - 1][1] - ONE;\r
13418         toX = moveList[move - 1][2] - AAA;\r
13419         toY = moveList[move - 1][3] - ONE;\r
13420         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
13421             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
13422             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
13423             fromX == toX) {\r
13424             /* 2-square pawn move just happened */\r
13425             *p++ = toX + AAA;\r
13426             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
13427         } else {\r
13428             *p++ = '-';\r
13429         }\r
13430     } else {\r
13431         *p++ = '-';\r
13432     }\r
13433     *p++ = ' ';\r
13434   }\r
13435   }\r
13436 \r
13437     /* [HGM] find reversible plies */\r
13438     {   int i = 0, j=move;\r
13439 \r
13440         if (appData.debugMode) { int k;\r
13441             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
13442             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
13443                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
13444 \r
13445         }\r
13446 \r
13447         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
13448         if( j == backwardMostMove ) i += initialRulePlies;\r
13449         sprintf(p, "%d ", i);\r
13450         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
13451     }\r
13452     /* Fullmove number */\r
13453     sprintf(p, "%d", (move / 2) + 1);\r
13454     \r
13455     return StrSave(buf);\r
13456 }\r
13457 \r
13458 Boolean\r
13459 ParseFEN(board, blackPlaysFirst, fen)\r
13460     Board board;\r
13461      int *blackPlaysFirst;\r
13462      char *fen;\r
13463 {\r
13464     int i, j;\r
13465     char *p;\r
13466     int emptycount;\r
13467     ChessSquare piece;\r
13468 \r
13469     p = fen;\r
13470 \r
13471     /* [HGM] by default clear Crazyhouse holdings, if present */\r
13472     if(gameInfo.holdingsWidth) {\r
13473        for(i=0; i<BOARD_HEIGHT; i++) {\r
13474            board[i][0]             = EmptySquare; /* black holdings */\r
13475            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
13476            board[i][1]             = (ChessSquare) 0; /* black counts */\r
13477            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
13478        }\r
13479     }\r
13480 \r
13481     /* Piece placement data */\r
13482     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13483         j = 0;\r
13484         for (;;) {\r
13485             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
13486                 if (*p == '/') p++;\r
13487                 emptycount = gameInfo.boardWidth - j;\r
13488                 while (emptycount--)\r
13489                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13490                 break;\r
13491 #if(BOARD_SIZE >= 10)\r
13492             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
13493                 p++; emptycount=10;\r
13494                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13495                 while (emptycount--)\r
13496                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13497 #endif\r
13498             } else if (isdigit(*p)) {\r
13499                 emptycount = *p++ - '0';\r
13500                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
13501                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13502                 while (emptycount--)\r
13503                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13504             } else if (*p == '+' || isalpha(*p)) {\r
13505                 if (j >= gameInfo.boardWidth) return FALSE;\r
13506                 if(*p=='+') {\r
13507                     piece = CharToPiece(*++p);\r
13508                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
13509                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
13510                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
13511                 } else piece = CharToPiece(*p++);\r
13512 \r
13513                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
13514                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
13515                     piece = (ChessSquare) (PROMOTED piece);\r
13516                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
13517                     p++;\r
13518                 }\r
13519                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
13520             } else {\r
13521                 return FALSE;\r
13522             }\r
13523         }\r
13524     }\r
13525     while (*p == '/' || *p == ' ') p++;\r
13526 \r
13527     /* [HGM] look for Crazyhouse holdings here */\r
13528     while(*p==' ') p++;\r
13529     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
13530         if(*p == '[') p++;\r
13531         if(*p == '-' ) *p++; /* empty holdings */ else {\r
13532             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
13533             /* if we would allow FEN reading to set board size, we would   */\r
13534             /* have to add holdings and shift the board read so far here   */\r
13535             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
13536                 *p++;\r
13537                 if((int) piece >= (int) BlackPawn ) {\r
13538                     i = (int)piece - (int)BlackPawn;\r
13539                     i = PieceToNumber((ChessSquare)i);\r
13540                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13541                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
13542                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
13543                 } else {\r
13544                     i = (int)piece - (int)WhitePawn;\r
13545                     i = PieceToNumber((ChessSquare)i);\r
13546                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13547                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
13548                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
13549                 }\r
13550             }\r
13551         }\r
13552         if(*p == ']') *p++;\r
13553     }\r
13554 \r
13555     while(*p == ' ') p++;\r
13556 \r
13557     /* Active color */\r
13558     switch (*p++) {\r
13559       case 'w':\r
13560         *blackPlaysFirst = FALSE;\r
13561         break;\r
13562       case 'b': \r
13563         *blackPlaysFirst = TRUE;\r
13564         break;\r
13565       default:\r
13566         return FALSE;\r
13567     }\r
13568 \r
13569     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
13570     /* return the extra info in global variiables             */\r
13571 \r
13572     /* set defaults in case FEN is incomplete */\r
13573     FENepStatus = EP_UNKNOWN;\r
13574     for(i=0; i<nrCastlingRights; i++ ) {\r
13575         FENcastlingRights[i] =\r
13576             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
13577     }   /* assume possible unless obviously impossible */\r
13578     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
13579     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
13580     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
13581     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
13582     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
13583     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
13584     FENrulePlies = 0;\r
13585 \r
13586     while(*p==' ') p++;\r
13587     if(nrCastlingRights) {\r
13588       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
13589           /* castling indicator present, so default becomes no castlings */\r
13590           for(i=0; i<nrCastlingRights; i++ ) {\r
13591                  FENcastlingRights[i] = -1;\r
13592           }\r
13593       }\r
13594       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
13595              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
13596              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
13597              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
13598         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
13599 \r
13600         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
13601             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
13602             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
13603         }\r
13604         switch(c) {\r
13605           case'K':\r
13606               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
13607               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
13608               FENcastlingRights[2] = whiteKingFile;\r
13609               break;\r
13610           case'Q':\r
13611               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
13612               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
13613               FENcastlingRights[2] = whiteKingFile;\r
13614               break;\r
13615           case'k':\r
13616               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
13617               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
13618               FENcastlingRights[5] = blackKingFile;\r
13619               break;\r
13620           case'q':\r
13621               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
13622               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
13623               FENcastlingRights[5] = blackKingFile;\r
13624           case '-':\r
13625               break;\r
13626           default: /* FRC castlings */\r
13627               if(c >= 'a') { /* black rights */\r
13628                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13629                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
13630                   if(i == BOARD_RGHT) break;\r
13631                   FENcastlingRights[5] = i;\r
13632                   c -= AAA;\r
13633                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
13634                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
13635                   if(c > i)\r
13636                       FENcastlingRights[3] = c;\r
13637                   else\r
13638                       FENcastlingRights[4] = c;\r
13639               } else { /* white rights */\r
13640                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13641                     if(board[0][i] == WhiteKing) break;\r
13642                   if(i == BOARD_RGHT) break;\r
13643                   FENcastlingRights[2] = i;\r
13644                   c -= AAA - 'a' + 'A';\r
13645                   if(board[0][c] >= WhiteKing) break;\r
13646                   if(c > i)\r
13647                       FENcastlingRights[0] = c;\r
13648                   else\r
13649                       FENcastlingRights[1] = c;\r
13650               }\r
13651         }\r
13652       }\r
13653     if (appData.debugMode) {\r
13654         fprintf(debugFP, "FEN castling rights:");\r
13655         for(i=0; i<nrCastlingRights; i++)\r
13656         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
13657         fprintf(debugFP, "\n");\r
13658     }\r
13659 \r
13660       while(*p==' ') p++;\r
13661     }\r
13662 \r
13663     /* read e.p. field in games that know e.p. capture */\r
13664     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13665        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13666       if(*p=='-') {\r
13667         p++; FENepStatus = EP_NONE;\r
13668       } else {\r
13669          char c = *p++ - AAA;\r
13670 \r
13671          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
13672          if(*p >= '0' && *p <='9') *p++;\r
13673          FENepStatus = c;\r
13674       }\r
13675     }\r
13676 \r
13677 \r
13678     if(sscanf(p, "%d", &i) == 1) {\r
13679         FENrulePlies = i; /* 50-move ply counter */\r
13680         /* (The move number is still ignored)    */\r
13681     }\r
13682 \r
13683     return TRUE;\r
13684 }\r
13685       \r
13686 void\r
13687 EditPositionPasteFEN(char *fen)\r
13688 {\r
13689   if (fen != NULL) {\r
13690     Board initial_position;\r
13691 \r
13692     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
13693       DisplayError(_("Bad FEN position in clipboard"), 0);\r
13694       return ;\r
13695     } else {\r
13696       int savedBlackPlaysFirst = blackPlaysFirst;\r
13697       EditPositionEvent();\r
13698       blackPlaysFirst = savedBlackPlaysFirst;\r
13699       CopyBoard(boards[0], initial_position);\r
13700           /* [HGM] copy FEN attributes as well */\r
13701           {   int i;\r
13702               initialRulePlies = FENrulePlies;\r
13703               epStatus[0] = FENepStatus;\r
13704               for( i=0; i<nrCastlingRights; i++ )\r
13705                   castlingRights[0][i] = FENcastlingRights[i];\r
13706           }\r
13707       EditPositionDone();\r
13708       DisplayBothClocks();\r
13709       DrawPosition(FALSE, boards[currentMove]);\r
13710     }\r
13711   }\r
13712 }\r