I put in testing for the winning condition in 3checks
[xboard.git] / backend.c
1 /*\r
2  * backend.c -- Common back end for X and Windows NT versions of\r
3  * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $\r
4  *\r
5  * Copyright 1991 by Digital Equipment Corporation, Maynard,\r
6  * Massachusetts.  Enhancements Copyright\r
7  * 1992-2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software\r
8  * Foundation, Inc.\r
9  *\r
10  * The following terms apply to Digital Equipment Corporation's copyright\r
11  * interest in XBoard:\r
12  * ------------------------------------------------------------------------\r
13  * All Rights Reserved\r
14  *\r
15  * Permission to use, copy, modify, and distribute this software and its\r
16  * documentation for any purpose and without fee is hereby granted,\r
17  * provided that the above copyright notice appear in all copies and that\r
18  * both that copyright notice and this permission notice appear in\r
19  * supporting documentation, and that the name of Digital not be\r
20  * used in advertising or publicity pertaining to distribution of the\r
21  * software without specific, written prior permission.\r
22  *\r
23  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING\r
24  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL\r
25  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR\r
26  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,\r
27  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\r
28  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS\r
29  * SOFTWARE.\r
30  * ------------------------------------------------------------------------\r
31  *\r
32  * The following terms apply to the enhanced version of XBoard\r
33  * distributed by the Free Software Foundation:\r
34  * ------------------------------------------------------------------------\r
35  *\r
36  * GNU XBoard is free software: you can redistribute it and/or modify\r
37  * it under the terms of the GNU General Public License as published by\r
38  * the Free Software Foundation, either version 3 of the License, or (at\r
39  * your option) any later version.\r
40  *\r
41  * GNU XBoard is distributed in the hope that it will be useful, but\r
42  * WITHOUT ANY WARRANTY; without even the implied warranty of\r
43  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
44  * General Public License for more details.\r
45  *\r
46  * You should have received a copy of the GNU General Public License\r
47  * along with this program. If not, see http://www.gnu.org/licenses/.  *\r
48  *\r
49  *------------------------------------------------------------------------\r
50  ** See the file ChangeLog for a revision history.  */\r
51 \r
52 /* [AS] Also useful here for debugging */\r
53 #ifdef WIN32\r
54 #include <windows.h>\r
55 \r
56 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );\r
57 \r
58 #else\r
59 \r
60 #define DoSleep( n ) if( (n) >= 0) sleep(n)\r
61 \r
62 #endif\r
63 \r
64 #include "config.h"\r
65 \r
66 #include <assert.h>\r
67 #include <stdio.h>\r
68 #include <ctype.h>\r
69 #include <errno.h>\r
70 #include <sys/types.h>\r
71 #include <sys/stat.h>\r
72 #include <math.h>\r
73 #include <ctype.h>\r
74 \r
75 #if STDC_HEADERS\r
76 # include <stdlib.h>\r
77 # include <string.h>\r
78 #else /* not STDC_HEADERS */\r
79 # if HAVE_STRING_H\r
80 #  include <string.h>\r
81 # else /* not HAVE_STRING_H */\r
82 #  include <strings.h>\r
83 # endif /* not HAVE_STRING_H */\r
84 #endif /* not STDC_HEADERS */\r
85 \r
86 #if HAVE_SYS_FCNTL_H\r
87 # include <sys/fcntl.h>\r
88 #else /* not HAVE_SYS_FCNTL_H */\r
89 # if HAVE_FCNTL_H\r
90 #  include <fcntl.h>\r
91 # endif /* HAVE_FCNTL_H */\r
92 #endif /* not HAVE_SYS_FCNTL_H */\r
93 \r
94 #if TIME_WITH_SYS_TIME\r
95 # include <sys/time.h>\r
96 # include <time.h>\r
97 #else\r
98 # if HAVE_SYS_TIME_H\r
99 #  include <sys/time.h>\r
100 # else\r
101 #  include <time.h>\r
102 # endif\r
103 #endif\r
104 \r
105 #if defined(_amigados) && !defined(__GNUC__)\r
106 struct timezone {\r
107     int tz_minuteswest;\r
108     int tz_dsttime;\r
109 };\r
110 extern int gettimeofday(struct timeval *, struct timezone *);\r
111 #endif\r
112 \r
113 #if HAVE_UNISTD_H\r
114 # include <unistd.h>\r
115 #endif\r
116 \r
117 #include "common.h"\r
118 #include "frontend.h"\r
119 #include "backend.h"\r
120 #include "parser.h"\r
121 #include "moves.h"\r
122 #if ZIPPY\r
123 # include "zippy.h"\r
124 #endif\r
125 #include "backendz.h"\r
126 #include "gettext.h" \r
127  \r
128 #ifdef ENABLE_NLS \r
129 # define _(s) gettext (s) \r
130 # define N_(s) gettext_noop (s) \r
131 #else \r
132 # define _(s) (s) \r
133 # define N_(s) s \r
134 #endif \r
135 \r
136 \r
137 /* A point in time */\r
138 typedef struct {\r
139     long sec;  /* Assuming this is >= 32 bits */\r
140     int ms;    /* Assuming this is >= 16 bits */\r
141 } TimeMark;\r
142 \r
143 int establish P((void));\r
144 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,\r
145                          char *buf, int count, int error));\r
146 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,\r
147                       char *buf, int count, int error));\r
148 void SendToICS P((char *s));\r
149 void SendToICSDelayed P((char *s, long msdelay));\r
150 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,\r
151                       int toX, int toY));\r
152 void InitPosition P((int redraw));\r
153 void HandleMachineMove P((char *message, ChessProgramState *cps));\r
154 int AutoPlayOneMove P((void));\r
155 int LoadGameOneMove P((ChessMove readAhead));\r
156 int LoadGameFromFile P((char *filename, int n, char *title, int useList));\r
157 int LoadPositionFromFile P((char *filename, int n, char *title));\r
158 int SavePositionToFile P((char *filename));\r
159 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,\r
160                   Board board, char *castle, char *ep));\r
161 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));\r
162 void ShowMove P((int fromX, int fromY, int toX, int toY));\r
163 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,\r
164                    /*char*/int promoChar));\r
165 void BackwardInner P((int target));\r
166 void ForwardInner P((int target));\r
167 void GameEnds P((ChessMove result, char *resultDetails, int whosays));\r
168 void EditPositionDone P((void));\r
169 void PrintOpponents P((FILE *fp));\r
170 void PrintPosition P((FILE *fp, int move));\r
171 void StartChessProgram P((ChessProgramState *cps));\r
172 void SendToProgram P((char *message, ChessProgramState *cps));\r
173 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));\r
174 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,\r
175                            char *buf, int count, int error));\r
176 void SendTimeControl P((ChessProgramState *cps,\r
177                         int mps, long tc, int inc, int sd, int st));\r
178 char *TimeControlTagValue P((void));\r
179 void Attention P((ChessProgramState *cps));\r
180 void FeedMovesToProgram P((ChessProgramState *cps, int upto));\r
181 void ResurrectChessProgram P((void));\r
182 void DisplayComment P((int moveNumber, char *text));\r
183 void DisplayMove P((int moveNumber));\r
184 void DisplayAnalysis P((void));\r
185 \r
186 void ParseGameHistory P((char *game));\r
187 void ParseBoard12 P((char *string));\r
188 void StartClocks P((void));\r
189 void SwitchClocks P((void));\r
190 void StopClocks P((void));\r
191 void ResetClocks P((void));\r
192 char *PGNDate P((void));\r
193 void SetGameInfo P((void));\r
194 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));\r
195 int RegisterMove P((void));\r
196 void MakeRegisteredMove P((void));\r
197 void TruncateGame P((void));\r
198 int looking_at P((char *, int *, char *));\r
199 void CopyPlayerNameIntoFileName P((char **, char *));\r
200 char *SavePart P((char *));\r
201 int SaveGameOldStyle P((FILE *));\r
202 int SaveGamePGN P((FILE *));\r
203 void GetTimeMark P((TimeMark *));\r
204 long SubtractTimeMarks P((TimeMark *, TimeMark *));\r
205 int CheckFlags P((void));\r
206 long NextTickLength P((long));\r
207 void CheckTimeControl P((void));\r
208 void show_bytes P((FILE *, char *, int));\r
209 int string_to_rating P((char *str));\r
210 void ParseFeatures P((char* args, ChessProgramState *cps));\r
211 void InitBackEnd3 P((void));\r
212 void FeatureDone P((ChessProgramState* cps, int val));\r
213 void InitChessProgram P((ChessProgramState *cps, int setup));\r
214 void OutputKibitz(int window, char *text);\r
215 int PerpetualChase(int first, int last);\r
216 int EngineOutputIsUp();\r
217 void InitDrawingSizes(int x, int y);\r
218 \r
219 #ifdef WIN32\r
220        extern void ConsoleCreate();\r
221 #endif\r
222 \r
223 ChessProgramState *WhitePlayer();\r
224 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c\r
225 int VerifyDisplayMode P(());\r
226 \r
227 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment\r
228 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c\r
229 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move\r
230 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book\r
231 extern char installDir[MSG_SIZ];\r
232 \r
233 extern int tinyLayout, smallLayout;\r
234 ChessProgramStats programStats;\r
235 static int exiting = 0; /* [HGM] moved to top */\r
236 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;\r
237 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */\r
238 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */\r
239 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */\r
240 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */\r
241 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */\r
242 int opponentKibitzes;\r
243 \r
244 /* States for ics_getting_history */\r
245 #define H_FALSE 0\r
246 #define H_REQUESTED 1\r
247 #define H_GOT_REQ_HEADER 2\r
248 #define H_GOT_UNREQ_HEADER 3\r
249 #define H_GETTING_MOVES 4\r
250 #define H_GOT_UNWANTED_HEADER 5\r
251 \r
252 /* whosays values for GameEnds */\r
253 #define GE_ICS 0\r
254 #define GE_ENGINE 1\r
255 #define GE_PLAYER 2\r
256 #define GE_FILE 3\r
257 #define GE_XBOARD 4\r
258 #define GE_ENGINE1 5\r
259 #define GE_ENGINE2 6\r
260 \r
261 /* Maximum number of games in a cmail message */\r
262 #define CMAIL_MAX_GAMES 20\r
263 \r
264 /* Different types of move when calling RegisterMove */\r
265 #define CMAIL_MOVE   0\r
266 #define CMAIL_RESIGN 1\r
267 #define CMAIL_DRAW   2\r
268 #define CMAIL_ACCEPT 3\r
269 \r
270 /* Different types of result to remember for each game */\r
271 #define CMAIL_NOT_RESULT 0\r
272 #define CMAIL_OLD_RESULT 1\r
273 #define CMAIL_NEW_RESULT 2\r
274 \r
275 /* Telnet protocol constants */\r
276 #define TN_WILL 0373\r
277 #define TN_WONT 0374\r
278 #define TN_DO   0375\r
279 #define TN_DONT 0376\r
280 #define TN_IAC  0377\r
281 #define TN_ECHO 0001\r
282 #define TN_SGA  0003\r
283 #define TN_PORT 23\r
284 \r
285 /* [AS] */\r
286 static char * safeStrCpy( char * dst, const char * src, size_t count )\r
287 {\r
288     assert( dst != NULL );\r
289     assert( src != NULL );\r
290     assert( count > 0 );\r
291 \r
292     strncpy( dst, src, count );\r
293     dst[ count-1 ] = '\0';\r
294     return dst;\r
295 }\r
296 \r
297 #if 0\r
298 //[HGM] for future use? Conditioned out for now to suppress warning.\r
299 static char * safeStrCat( char * dst, const char * src, size_t count )\r
300 {\r
301     size_t  dst_len;\r
302 \r
303     assert( dst != NULL );\r
304     assert( src != NULL );\r
305     assert( count > 0 );\r
306 \r
307     dst_len = strlen(dst);\r
308 \r
309     assert( count > dst_len ); /* Buffer size must be greater than current length */\r
310 \r
311     safeStrCpy( dst + dst_len, src, count - dst_len );\r
312 \r
313     return dst;\r
314 }\r
315 #endif\r
316 \r
317 /* Some compiler can't cast u64 to double\r
318  * This function do the job for us:\r
319 \r
320  * We use the highest bit for cast, this only\r
321  * works if the highest bit is not\r
322  * in use (This should not happen)\r
323  *\r
324  * We used this for all compiler\r
325  */\r
326 double\r
327 u64ToDouble(u64 value)\r
328 {\r
329   double r;\r
330   u64 tmp = value & u64Const(0x7fffffffffffffff);\r
331   r = (double)(s64)tmp;\r
332   if (value & u64Const(0x8000000000000000))\r
333        r +=  9.2233720368547758080e18; /* 2^63 */\r
334  return r;\r
335 }\r
336 \r
337 /* Fake up flags for now, as we aren't keeping track of castling\r
338    availability yet. [HGM] Change of logic: the flag now only\r
339    indicates the type of castlings allowed by the rule of the game.\r
340    The actual rights themselves are maintained in the array\r
341    castlingRights, as part of the game history, and are not probed\r
342    by this function.\r
343  */\r
344 int\r
345 PosFlags(index)\r
346 {\r
347   int flags = F_ALL_CASTLE_OK;\r
348   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;\r
349   switch (gameInfo.variant) {\r
350   case VariantSuicide:\r
351     flags &= ~F_ALL_CASTLE_OK;\r
352   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!\r
353     flags |= F_IGNORE_CHECK;\r
354   case VariantLosers:\r
355     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist\r
356     break;\r
357   case VariantAtomic:\r
358     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;\r
359     break;\r
360   case VariantKriegspiel:\r
361     flags |= F_KRIEGSPIEL_CAPTURE;\r
362     break;\r
363   case VariantCapaRandom: \r
364   case VariantFischeRandom:\r
365     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */\r
366   case VariantNoCastle:\r
367   case VariantShatranj:\r
368   case VariantCourier:\r
369     flags &= ~F_ALL_CASTLE_OK;\r
370     break;\r
371   default:\r
372     break;\r
373   }\r
374   return flags;\r
375 }\r
376 \r
377 FILE *gameFileFP, *debugFP;\r
378 \r
379 /* \r
380     [AS] Note: sometimes, the sscanf() function is used to parse the input\r
381     into a fixed-size buffer. Because of this, we must be prepared to\r
382     receive strings as long as the size of the input buffer, which is currently\r
383     set to 4K for Windows and 8K for the rest.\r
384     So, we must either allocate sufficiently large buffers here, or\r
385     reduce the size of the input buffer in the input reading part.\r
386 */\r
387 \r
388 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];\r
389 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];\r
390 char thinkOutput1[MSG_SIZ*10];\r
391 \r
392 ChessProgramState first, second;\r
393 \r
394 /* premove variables */\r
395 int premoveToX = 0;\r
396 int premoveToY = 0;\r
397 int premoveFromX = 0;\r
398 int premoveFromY = 0;\r
399 int premovePromoChar = 0;\r
400 int gotPremove = 0;\r
401 Boolean alarmSounded;\r
402 /* end premove variables */\r
403 \r
404 char *ics_prefix = "$";\r
405 int ics_type = ICS_GENERIC;\r
406 \r
407 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;\r
408 int pauseExamForwardMostMove = 0;\r
409 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;\r
410 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];\r
411 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;\r
412 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;\r
413 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;\r
414 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;\r
415 int whiteFlag = FALSE, blackFlag = FALSE;\r
416 int userOfferedDraw = FALSE;\r
417 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;\r
418 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;\r
419 int cmailMoveType[CMAIL_MAX_GAMES];\r
420 long ics_clock_paused = 0;\r
421 ProcRef icsPR = NoProc, cmailPR = NoProc;\r
422 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;\r
423 GameMode gameMode = BeginningOfGame;\r
424 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];\r
425 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];\r
426 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */\r
427 int hiddenThinkOutputState = 0; /* [AS] */\r
428 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */\r
429 int adjudicateLossPlies = 6;\r
430 char white_holding[64], black_holding[64];\r
431 TimeMark lastNodeCountTime;\r
432 long lastNodeCount=0;\r
433 int have_sent_ICS_logon = 0;\r
434 int movesPerSession;\r
435 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;\r
436 long timeControl_2; /* [AS] Allow separate time controls */\r
437 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */\r
438 long timeRemaining[2][MAX_MOVES];\r
439 int matchGame = 0;\r
440 TimeMark programStartTime;\r
441 char ics_handle[MSG_SIZ];\r
442 int have_set_title = 0;\r
443 \r
444 /* animateTraining preserves the state of appData.animate\r
445  * when Training mode is activated. This allows the\r
446  * response to be animated when appData.animate == TRUE and\r
447  * appData.animateDragging == TRUE.\r
448  */\r
449 Boolean animateTraining;\r
450 \r
451 GameInfo gameInfo;\r
452 \r
453 AppData appData;\r
454 \r
455 Board boards[MAX_MOVES];\r
456 /* [HGM] Following 7 needed for accurate legality tests: */\r
457 char  epStatus[MAX_MOVES];\r
458 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1\r
459 char  castlingRank[BOARD_SIZE]; // and corresponding ranks\r
460 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];\r
461 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status\r
462 int   initialRulePlies, FENrulePlies;\r
463 char  FENepStatus;\r
464 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)\r
465 int loadFlag = 0; \r
466 int shuffleOpenings;\r
467 \r
468 ChessSquare  FIDEArray[2][BOARD_SIZE] = {\r
469     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
470         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
471     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
472         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
473 };\r
474 \r
475 ChessSquare twoKingsArray[2][BOARD_SIZE] = {\r
476     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
477         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },\r
478     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
479         BlackKing, BlackKing, BlackKnight, BlackRook }\r
480 };\r
481 \r
482 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {\r
483     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,\r
484         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },\r
485     { BlackRook, BlackMan, BlackBishop, BlackQueen,\r
486         BlackUnicorn, BlackBishop, BlackMan, BlackRook }\r
487 };\r
488 \r
489 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */\r
490     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,\r
491         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
492     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,\r
493         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
494 };\r
495 \r
496 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */\r
497     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,\r
498         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
499     { BlackRook, BlackKnight, BlackAlfil, BlackKing,\r
500         BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
501 };\r
502 \r
503 \r
504 #if (BOARD_SIZE>=10)\r
505 ChessSquare ShogiArray[2][BOARD_SIZE] = {\r
506     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,\r
507         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },\r
508     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,\r
509         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }\r
510 };\r
511 \r
512 ChessSquare XiangqiArray[2][BOARD_SIZE] = {\r
513     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,\r
514         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
515     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,\r
516         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
517 };\r
518 \r
519 ChessSquare CapablancaArray[2][BOARD_SIZE] = {\r
520     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, \r
521         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },\r
522     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, \r
523         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
524 };\r
525 \r
526 ChessSquare GreatArray[2][BOARD_SIZE] = {\r
527     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, \r
528         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },\r
529     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, \r
530         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },\r
531 };\r
532 \r
533 ChessSquare JanusArray[2][BOARD_SIZE] = {\r
534     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, \r
535         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },\r
536     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, \r
537         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }\r
538 };\r
539 \r
540 #ifdef GOTHIC\r
541 ChessSquare GothicArray[2][BOARD_SIZE] = {\r
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, \r
543         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },\r
544     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, \r
545         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }\r
546 };\r
547 #else // !GOTHIC\r
548 #define GothicArray CapablancaArray\r
549 #endif // !GOTHIC\r
550 \r
551 #ifdef FALCON\r
552 ChessSquare FalconArray[2][BOARD_SIZE] = {\r
553     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, \r
554         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },\r
555     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, \r
556         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }\r
557 };\r
558 #else // !FALCON\r
559 #define FalconArray CapablancaArray\r
560 #endif // !FALCON\r
561 \r
562 #else // !(BOARD_SIZE>=10)\r
563 #define XiangqiPosition FIDEArray\r
564 #define CapablancaArray FIDEArray\r
565 #define GothicArray FIDEArray\r
566 #define GreatArray FIDEArray\r
567 #endif // !(BOARD_SIZE>=10)\r
568 \r
569 #if (BOARD_SIZE>=12)\r
570 ChessSquare CourierArray[2][BOARD_SIZE] = {\r
571     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,\r
572         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },\r
573     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,\r
574         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }\r
575 };\r
576 #else // !(BOARD_SIZE>=12)\r
577 #define CourierArray CapablancaArray\r
578 #endif // !(BOARD_SIZE>=12)\r
579 \r
580 \r
581 Board initialPosition;\r
582 \r
583 \r
584 /* Convert str to a rating. Checks for special cases of "----",\r
585 \r
586    "++++", etc. Also strips ()'s */\r
587 int\r
588 string_to_rating(str)\r
589   char *str;\r
590 {\r
591   while(*str && !isdigit(*str)) ++str;\r
592   if (!*str)\r
593     return 0;   /* One of the special "no rating" cases */\r
594   else\r
595     return atoi(str);\r
596 }\r
597 \r
598 void\r
599 ClearProgramStats()\r
600 {\r
601     /* Init programStats */\r
602     programStats.movelist[0] = 0;\r
603     programStats.depth = 0;\r
604     programStats.nr_moves = 0;\r
605     programStats.moves_left = 0;\r
606     programStats.nodes = 0;\r
607     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output\r
608     programStats.score = 0;\r
609     programStats.got_only_move = 0;\r
610     programStats.got_fail = 0;\r
611     programStats.line_is_book = 0;\r
612 }\r
613 \r
614 void\r
615 InitBackEnd1()\r
616 {\r
617     int matched, min, sec;\r
618 \r
619     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options\r
620 \r
621     GetTimeMark(&programStartTime);\r
622     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level\r
623 \r
624     ClearProgramStats();\r
625     programStats.ok_to_send = 1;\r
626     programStats.seen_stat = 0;\r
627 \r
628     /*\r
629      * Initialize game list\r
630      */\r
631     ListNew(&gameList);\r
632 \r
633 \r
634     /*\r
635      * Internet chess server status\r
636      */\r
637     if (appData.icsActive) {\r
638         appData.matchMode = FALSE;\r
639         appData.matchGames = 0;\r
640 #if ZIPPY       \r
641         appData.noChessProgram = !appData.zippyPlay;\r
642 #else\r
643         appData.zippyPlay = FALSE;\r
644         appData.zippyTalk = FALSE;\r
645         appData.noChessProgram = TRUE;\r
646 #endif\r
647         if (*appData.icsHelper != NULLCHAR) {\r
648             appData.useTelnet = TRUE;\r
649             appData.telnetProgram = appData.icsHelper;\r
650         }\r
651     } else {\r
652         appData.zippyTalk = appData.zippyPlay = FALSE;\r
653     }\r
654 \r
655     /* [AS] Initialize pv info list [HGM] and game state */\r
656     {\r
657         int i, j;\r
658 \r
659         for( i=0; i<MAX_MOVES; i++ ) {\r
660             pvInfoList[i].depth = -1;\r
661             epStatus[i]=EP_NONE;\r
662             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
663         }\r
664     }\r
665 \r
666     /*\r
667      * Parse timeControl resource\r
668      */\r
669     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,\r
670                           appData.movesPerSession)) {\r
671         char buf[MSG_SIZ];\r
672         sprintf(buf, _("bad timeControl option %s"), appData.timeControl);\r
673         DisplayFatalError(buf, 0, 2);\r
674     }\r
675 \r
676     /*\r
677      * Parse searchTime resource\r
678      */\r
679     if (*appData.searchTime != NULLCHAR) {\r
680         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);\r
681         if (matched == 1) {\r
682             searchTime = min * 60;\r
683         } else if (matched == 2) {\r
684             searchTime = min * 60 + sec;\r
685         } else {\r
686             char buf[MSG_SIZ];\r
687             sprintf(buf, _("bad searchTime option %s"), appData.searchTime);\r
688             DisplayFatalError(buf, 0, 2);\r
689         }\r
690     }\r
691 \r
692     /* [AS] Adjudication threshold */\r
693     adjudicateLossThreshold = appData.adjudicateLossThreshold;\r
694     \r
695     first.which = "first";\r
696     second.which = "second";\r
697     first.maybeThinking = second.maybeThinking = FALSE;\r
698     first.pr = second.pr = NoProc;\r
699     first.isr = second.isr = NULL;\r
700     first.sendTime = second.sendTime = 2;\r
701     first.sendDrawOffers = 1;\r
702     if (appData.firstPlaysBlack) {\r
703         first.twoMachinesColor = "black\n";\r
704         second.twoMachinesColor = "white\n";\r
705     } else {\r
706         first.twoMachinesColor = "white\n";\r
707         second.twoMachinesColor = "black\n";\r
708     }\r
709     first.program = appData.firstChessProgram;\r
710     second.program = appData.secondChessProgram;\r
711     first.host = appData.firstHost;\r
712     second.host = appData.secondHost;\r
713     first.dir = appData.firstDirectory;\r
714     second.dir = appData.secondDirectory;\r
715     first.other = &second;\r
716     second.other = &first;\r
717     first.initString = appData.initString;\r
718     second.initString = appData.secondInitString;\r
719     first.computerString = appData.firstComputerString;\r
720     second.computerString = appData.secondComputerString;\r
721     first.useSigint = second.useSigint = TRUE;\r
722     first.useSigterm = second.useSigterm = TRUE;\r
723     first.reuse = appData.reuseFirst;\r
724     second.reuse = appData.reuseSecond;\r
725     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second\r
726     second.nps = appData.secondNPS;\r
727     first.useSetboard = second.useSetboard = FALSE;\r
728     first.useSAN = second.useSAN = FALSE;\r
729     first.usePing = second.usePing = FALSE;\r
730     first.lastPing = second.lastPing = 0;\r
731     first.lastPong = second.lastPong = 0;\r
732     first.usePlayother = second.usePlayother = FALSE;\r
733     first.useColors = second.useColors = TRUE;\r
734     first.useUsermove = second.useUsermove = FALSE;\r
735     first.sendICS = second.sendICS = FALSE;\r
736     first.sendName = second.sendName = appData.icsActive;\r
737     first.sdKludge = second.sdKludge = FALSE;\r
738     first.stKludge = second.stKludge = FALSE;\r
739     TidyProgramName(first.program, first.host, first.tidy);\r
740     TidyProgramName(second.program, second.host, second.tidy);\r
741     first.matchWins = second.matchWins = 0;\r
742     strcpy(first.variants, appData.variant);\r
743     strcpy(second.variants, appData.variant);\r
744     first.analysisSupport = second.analysisSupport = 2; /* detect */\r
745     first.analyzing = second.analyzing = FALSE;\r
746     first.initDone = second.initDone = FALSE;\r
747 \r
748     /* New features added by Tord: */\r
749     first.useFEN960 = FALSE; second.useFEN960 = FALSE;\r
750     first.useOOCastle = TRUE; second.useOOCastle = TRUE;\r
751     /* End of new features added by Tord. */\r
752     first.fenOverride  = appData.fenOverride1;\r
753     second.fenOverride = appData.fenOverride2;\r
754 \r
755     /* [HGM] time odds: set factor for each machine */\r
756     first.timeOdds  = appData.firstTimeOdds;\r
757     second.timeOdds = appData.secondTimeOdds;\r
758     { int norm = 1;\r
759         if(appData.timeOddsMode) {\r
760             norm = first.timeOdds;\r
761             if(norm > second.timeOdds) norm = second.timeOdds;\r
762         }\r
763         first.timeOdds /= norm;\r
764         second.timeOdds /= norm;\r
765     }\r
766 \r
767     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/\r
768     first.accumulateTC = appData.firstAccumulateTC;\r
769     second.accumulateTC = appData.secondAccumulateTC;\r
770     first.maxNrOfSessions = second.maxNrOfSessions = 1;\r
771 \r
772     /* [HGM] debug */\r
773     first.debug = second.debug = FALSE;\r
774     first.supportsNPS = second.supportsNPS = UNKNOWN;\r
775 \r
776     /* [HGM] options */\r
777     first.optionSettings  = appData.firstOptions;\r
778     second.optionSettings = appData.secondOptions;\r
779 \r
780     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
781     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
782     first.isUCI = appData.firstIsUCI; /* [AS] */\r
783     second.isUCI = appData.secondIsUCI; /* [AS] */\r
784     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
785     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
786 \r
787     if (appData.firstProtocolVersion > PROTOVER ||\r
788         appData.firstProtocolVersion < 1) {\r
789       char buf[MSG_SIZ];\r
790       sprintf(buf, _("protocol version %d not supported"),\r
791               appData.firstProtocolVersion);\r
792       DisplayFatalError(buf, 0, 2);\r
793     } else {\r
794       first.protocolVersion = appData.firstProtocolVersion;\r
795     }\r
796 \r
797     if (appData.secondProtocolVersion > PROTOVER ||\r
798         appData.secondProtocolVersion < 1) {\r
799       char buf[MSG_SIZ];\r
800       sprintf(buf, _("protocol version %d not supported"),\r
801               appData.secondProtocolVersion);\r
802       DisplayFatalError(buf, 0, 2);\r
803     } else {\r
804       second.protocolVersion = appData.secondProtocolVersion;\r
805     }\r
806 \r
807     if (appData.icsActive) {\r
808         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */\r
809     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
810         appData.clockMode = FALSE;\r
811         first.sendTime = second.sendTime = 0;\r
812     }\r
813     \r
814 #if ZIPPY\r
815     /* Override some settings from environment variables, for backward\r
816        compatibility.  Unfortunately it's not feasible to have the env\r
817        vars just set defaults, at least in xboard.  Ugh.\r
818     */\r
819     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
820       ZippyInit();\r
821     }\r
822 #endif\r
823     \r
824     if (appData.noChessProgram) {\r
825         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
826                                         + strlen(PATCHLEVEL));\r
827         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
828     } else {\r
829 #if 0\r
830         char *p, *q;\r
831         q = first.program;\r
832         while (*q != ' ' && *q != NULLCHAR) q++;\r
833         p = q;\r
834         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */\r
835         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
836                                         + strlen(PATCHLEVEL) + (q - p));\r
837         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
838         strncat(programVersion, p, q - p);\r
839 #else\r
840         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */\r
841         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
842                                         + strlen(PATCHLEVEL) + strlen(first.tidy));\r
843         sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);\r
844 #endif\r
845     }\r
846 \r
847     if (!appData.icsActive) {\r
848       char buf[MSG_SIZ];\r
849       /* Check for variants that are supported only in ICS mode,\r
850          or not at all.  Some that are accepted here nevertheless\r
851          have bugs; see comments below.\r
852       */\r
853       VariantClass variant = StringToVariant(appData.variant);\r
854       switch (variant) {\r
855       case VariantBughouse:     /* need four players and two boards */\r
856       case VariantKriegspiel:   /* need to hide pieces and move details */\r
857       /* case VariantFischeRandom: (Fabien: moved below) */\r
858         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);\r
859         DisplayFatalError(buf, 0, 2);\r
860         return;\r
861 \r
862       case VariantUnknown:\r
863       case VariantLoadable:\r
864       case Variant29:\r
865       case Variant30:\r
866       case Variant31:\r
867       case Variant32:\r
868       case Variant33:\r
869       case Variant34:\r
870       case Variant35:\r
871       case Variant36:\r
872       default:\r
873         sprintf(buf, _("Unknown variant name %s"), appData.variant);\r
874         DisplayFatalError(buf, 0, 2);\r
875         return;\r
876 \r
877       case VariantXiangqi:    /* [HGM] repetition rules not implemented */\r
878       case VariantFairy:      /* [HGM] TestLegality definitely off! */\r
879       case VariantGothic:     /* [HGM] should work */\r
880       case VariantCapablanca: /* [HGM] should work */\r
881       case VariantCourier:    /* [HGM] initial forced moves not implemented */\r
882       case VariantShogi:      /* [HGM] drops not tested for legality */\r
883       case VariantKnightmate: /* [HGM] should work */\r
884       case VariantCylinder:   /* [HGM] untested */\r
885       case VariantFalcon:     /* [HGM] untested */\r
886       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)\r
887                                  offboard interposition not understood */\r
888       case VariantNormal:     /* definitely works! */\r
889       case VariantWildCastle: /* pieces not automatically shuffled */\r
890       case VariantNoCastle:   /* pieces not automatically shuffled */\r
891       case VariantFischeRandom: /* [HGM] works and shuffles pieces */\r
892       case VariantLosers:     /* should work except for win condition,\r
893                                  and doesn't know captures are mandatory */\r
894       case VariantSuicide:    /* should work except for win condition,\r
895                                  and doesn't know captures are mandatory */\r
896       case VariantGiveaway:   /* should work except for win condition,\r
897                                  and doesn't know captures are mandatory */\r
898       case VariantTwoKings:   /* should work */\r
899       case VariantAtomic:     /* should work except for win condition */\r
900       case Variant3Check:     /* should work except for win condition */\r
901       case VariantShatranj:   /* should work except for all win conditions */\r
902       case VariantBerolina:   /* might work if TestLegality is off */\r
903       case VariantCapaRandom: /* should work */\r
904       case VariantJanus:      /* should work */\r
905       case VariantSuper:      /* experimental */\r
906       case VariantGreat:      /* experimental, requires legality testing to be off */\r
907         break;\r
908       }\r
909     }\r
910 \r
911     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard\r
912     InitEngineUCI( installDir, &second );\r
913 }\r
914 \r
915 int NextIntegerFromString( char ** str, long * value )\r
916 {\r
917     int result = -1;\r
918     char * s = *str;\r
919 \r
920     while( *s == ' ' || *s == '\t' ) {\r
921         s++;\r
922     }\r
923 \r
924     *value = 0;\r
925 \r
926     if( *s >= '0' && *s <= '9' ) {\r
927         while( *s >= '0' && *s <= '9' ) {\r
928             *value = *value * 10 + (*s - '0');\r
929             s++;\r
930         }\r
931 \r
932         result = 0;\r
933     }\r
934 \r
935     *str = s;\r
936 \r
937     return result;\r
938 }\r
939 \r
940 int NextTimeControlFromString( char ** str, long * value )\r
941 {\r
942     long temp;\r
943     int result = NextIntegerFromString( str, &temp );\r
944 \r
945     if( result == 0 ) {\r
946         *value = temp * 60; /* Minutes */\r
947         if( **str == ':' ) {\r
948             (*str)++;\r
949             result = NextIntegerFromString( str, &temp );\r
950             *value += temp; /* Seconds */\r
951         }\r
952     }\r
953 \r
954     return result;\r
955 }\r
956 \r
957 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)\r
958 {   /* [HGM] routine added to read '+moves/time' for secondary time control */\r
959     int result = -1; long temp, temp2;\r
960 \r
961     if(**str != '+') return -1; // old params remain in force!\r
962     (*str)++;\r
963     if( NextTimeControlFromString( str, &temp ) ) return -1;\r
964 \r
965     if(**str != '/') {\r
966         /* time only: incremental or sudden-death time control */\r
967         if(**str == '+') { /* increment follows; read it */\r
968             (*str)++;\r
969             if(result = NextIntegerFromString( str, &temp2)) return -1;\r
970             *inc = temp2 * 1000;\r
971         } else *inc = 0;\r
972         *moves = 0; *tc = temp * 1000; \r
973         return 0;\r
974     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */\r
975 \r
976     (*str)++; /* classical time control */\r
977     result = NextTimeControlFromString( str, &temp2);\r
978     if(result == 0) {\r
979         *moves = temp/60;\r
980         *tc    = temp2 * 1000;\r
981         *inc   = 0;\r
982     }\r
983     return result;\r
984 }\r
985 \r
986 int GetTimeQuota(int movenr)\r
987 {   /* [HGM] get time to add from the multi-session time-control string */\r
988     int moves=1; /* kludge to force reading of first session */\r
989     long time, increment;\r
990     char *s = fullTimeControlString;\r
991 \r
992     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);\r
993     do {\r
994         if(moves) NextSessionFromString(&s, &moves, &time, &increment);\r
995         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);\r
996         if(movenr == -1) return time;    /* last move before new session     */\r
997         if(!moves) return increment;     /* current session is incremental   */\r
998         if(movenr >= 0) movenr -= moves; /* we already finished this session */\r
999     } while(movenr >= -1);               /* try again for next session       */\r
1000 \r
1001     return 0; // no new time quota on this move\r
1002 }\r
1003 \r
1004 int\r
1005 ParseTimeControl(tc, ti, mps)\r
1006      char *tc;\r
1007      int ti;\r
1008      int mps;\r
1009 {\r
1010 #if 0\r
1011     int matched, min, sec;\r
1012 \r
1013     matched = sscanf(tc, "%d:%d", &min, &sec);\r
1014     if (matched == 1) {\r
1015         timeControl = min * 60 * 1000;\r
1016     } else if (matched == 2) {\r
1017         timeControl = (min * 60 + sec) * 1000;\r
1018     } else {\r
1019         return FALSE;\r
1020     }\r
1021 #else\r
1022     long tc1;\r
1023     long tc2;\r
1024     char buf[MSG_SIZ];\r
1025 \r
1026     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;\r
1027     if(ti > 0) {\r
1028         if(mps)\r
1029              sprintf(buf, "+%d/%s+%d", mps, tc, ti);\r
1030         else sprintf(buf, "+%s+%d", tc, ti);\r
1031     } else {\r
1032         if(mps)\r
1033              sprintf(buf, "+%d/%s", mps, tc);\r
1034         else sprintf(buf, "+%s", tc);\r
1035     }\r
1036     fullTimeControlString = StrSave(buf);\r
1037 \r
1038     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
1039         return FALSE;\r
1040     }\r
1041 \r
1042     if( *tc == '/' ) {\r
1043         /* Parse second time control */\r
1044         tc++;\r
1045 \r
1046         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
1047             return FALSE;\r
1048         }\r
1049 \r
1050         if( tc2 == 0 ) {\r
1051             return FALSE;\r
1052         }\r
1053 \r
1054         timeControl_2 = tc2 * 1000;\r
1055     }\r
1056     else {\r
1057         timeControl_2 = 0;\r
1058     }\r
1059 \r
1060     if( tc1 == 0 ) {\r
1061         return FALSE;\r
1062     }\r
1063 \r
1064     timeControl = tc1 * 1000;\r
1065 #endif\r
1066 \r
1067     if (ti >= 0) {\r
1068         timeIncrement = ti * 1000;  /* convert to ms */\r
1069         movesPerSession = 0;\r
1070     } else {\r
1071         timeIncrement = 0;\r
1072         movesPerSession = mps;\r
1073     }\r
1074     return TRUE;\r
1075 }\r
1076 \r
1077 void\r
1078 InitBackEnd2()\r
1079 {\r
1080     if (appData.debugMode) {\r
1081         fprintf(debugFP, "%s\n", programVersion);\r
1082     }\r
1083 \r
1084     if (appData.matchGames > 0) {\r
1085         appData.matchMode = TRUE;\r
1086     } else if (appData.matchMode) {\r
1087         appData.matchGames = 1;\r
1088     }\r
1089     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */\r
1090         appData.matchGames = appData.sameColorGames;\r
1091     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */\r
1092         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;\r
1093         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;\r
1094     }\r
1095     Reset(TRUE, FALSE);\r
1096     if (appData.noChessProgram || first.protocolVersion == 1) {\r
1097       InitBackEnd3();\r
1098     } else {\r
1099       /* kludge: allow timeout for initial "feature" commands */\r
1100       FreezeUI();\r
1101       DisplayMessage("", _("Starting chess program"));\r
1102       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
1103     }\r
1104 }\r
1105 \r
1106 void\r
1107 InitBackEnd3 P((void))\r
1108 {\r
1109     GameMode initialMode;\r
1110     char buf[MSG_SIZ];\r
1111     int err;\r
1112 \r
1113     InitChessProgram(&first, startedFromSetupPosition);\r
1114 \r
1115 \r
1116     if (appData.icsActive) {\r
1117 #ifdef WIN32\r
1118         /* [DM] Make a console window if needed [HGM] merged ifs */\r
1119         ConsoleCreate(); \r
1120 #endif\r
1121         err = establish();\r
1122         if (err != 0) {\r
1123             if (*appData.icsCommPort != NULLCHAR) {\r
1124                 sprintf(buf, _("Could not open comm port %s"),  \r
1125                         appData.icsCommPort);\r
1126             } else {\r
1127                 sprintf(buf, _("Could not connect to host %s, port %s"),  \r
1128                         appData.icsHost, appData.icsPort);\r
1129             }\r
1130             DisplayFatalError(buf, err, 1);\r
1131             return;\r
1132         }\r
1133         SetICSMode();\r
1134         telnetISR =\r
1135           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
1136         fromUserISR =\r
1137           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
1138     } else if (appData.noChessProgram) {\r
1139         SetNCPMode();\r
1140     } else {\r
1141         SetGNUMode();\r
1142     }\r
1143 \r
1144     if (*appData.cmailGameName != NULLCHAR) {\r
1145         SetCmailMode();\r
1146         OpenLoopback(&cmailPR);\r
1147         cmailISR =\r
1148           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
1149     }\r
1150     \r
1151     ThawUI();\r
1152     DisplayMessage("", "");\r
1153     if (StrCaseCmp(appData.initialMode, "") == 0) {\r
1154       initialMode = BeginningOfGame;\r
1155     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
1156       initialMode = TwoMachinesPlay;\r
1157     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
1158       initialMode = AnalyzeFile; \r
1159     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
1160       initialMode = AnalyzeMode;\r
1161     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
1162       initialMode = MachinePlaysWhite;\r
1163     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
1164       initialMode = MachinePlaysBlack;\r
1165     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
1166       initialMode = EditGame;\r
1167     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
1168       initialMode = EditPosition;\r
1169     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
1170       initialMode = Training;\r
1171     } else {\r
1172       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);\r
1173       DisplayFatalError(buf, 0, 2);\r
1174       return;\r
1175     }\r
1176 \r
1177     if (appData.matchMode) {\r
1178         /* Set up machine vs. machine match */\r
1179         if (appData.noChessProgram) {\r
1180             DisplayFatalError(_("Can't have a match with no chess programs"),\r
1181                               0, 2);\r
1182             return;\r
1183         }\r
1184         matchMode = TRUE;\r
1185         matchGame = 1;\r
1186         if (*appData.loadGameFile != NULLCHAR) {\r
1187             int index = appData.loadGameIndex; // [HGM] autoinc\r
1188             if(index<0) lastIndex = index = 1;\r
1189             if (!LoadGameFromFile(appData.loadGameFile,\r
1190                                   index,\r
1191                                   appData.loadGameFile, FALSE)) {\r
1192                 DisplayFatalError(_("Bad game file"), 0, 1);\r
1193                 return;\r
1194             }\r
1195         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1196             int index = appData.loadPositionIndex; // [HGM] autoinc\r
1197             if(index<0) lastIndex = index = 1;\r
1198             if (!LoadPositionFromFile(appData.loadPositionFile,\r
1199                                       index,\r
1200                                       appData.loadPositionFile)) {\r
1201                 DisplayFatalError(_("Bad position file"), 0, 1);\r
1202                 return;\r
1203             }\r
1204         }\r
1205         TwoMachinesEvent();\r
1206     } else if (*appData.cmailGameName != NULLCHAR) {\r
1207         /* Set up cmail mode */\r
1208         ReloadCmailMsgEvent(TRUE);\r
1209     } else {\r
1210         /* Set up other modes */\r
1211         if (initialMode == AnalyzeFile) {\r
1212           if (*appData.loadGameFile == NULLCHAR) {\r
1213             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);\r
1214             return;\r
1215           }\r
1216         }\r
1217         if (*appData.loadGameFile != NULLCHAR) {\r
1218             (void) LoadGameFromFile(appData.loadGameFile,\r
1219                                     appData.loadGameIndex,\r
1220                                     appData.loadGameFile, TRUE);\r
1221         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1222             (void) LoadPositionFromFile(appData.loadPositionFile,\r
1223                                         appData.loadPositionIndex,\r
1224                                         appData.loadPositionFile);\r
1225             /* [HGM] try to make self-starting even after FEN load */\r
1226             /* to allow automatic setup of fairy variants with wtm */\r
1227             if(initialMode == BeginningOfGame && !blackPlaysFirst) {\r
1228                 gameMode = BeginningOfGame;\r
1229                 setboardSpoiledMachineBlack = 1;\r
1230             }\r
1231             /* [HGM] loadPos: make that every new game uses the setup */\r
1232             /* from file as long as we do not switch variant          */\r
1233             if(!blackPlaysFirst) { int i;\r
1234                 startedFromPositionFile = TRUE;\r
1235                 CopyBoard(filePosition, boards[0]);\r
1236                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];\r
1237             }\r
1238         }\r
1239         if (initialMode == AnalyzeMode) {\r
1240           if (appData.noChessProgram) {\r
1241             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);\r
1242             return;\r
1243           }\r
1244           if (appData.icsActive) {\r
1245             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);\r
1246             return;\r
1247           }\r
1248           AnalyzeModeEvent();\r
1249         } else if (initialMode == AnalyzeFile) {\r
1250           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent\r
1251           ShowThinkingEvent();\r
1252           AnalyzeFileEvent();\r
1253           AnalysisPeriodicEvent(1);\r
1254         } else if (initialMode == MachinePlaysWhite) {\r
1255           if (appData.noChessProgram) {\r
1256             DisplayFatalError(_("MachineWhite mode requires a chess engine"),\r
1257                               0, 2);\r
1258             return;\r
1259           }\r
1260           if (appData.icsActive) {\r
1261             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),\r
1262                               0, 2);\r
1263             return;\r
1264           }\r
1265           MachineWhiteEvent();\r
1266         } else if (initialMode == MachinePlaysBlack) {\r
1267           if (appData.noChessProgram) {\r
1268             DisplayFatalError(_("MachineBlack mode requires a chess engine"),\r
1269                               0, 2);\r
1270             return;\r
1271           }\r
1272           if (appData.icsActive) {\r
1273             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),\r
1274                               0, 2);\r
1275             return;\r
1276           }\r
1277           MachineBlackEvent();\r
1278         } else if (initialMode == TwoMachinesPlay) {\r
1279           if (appData.noChessProgram) {\r
1280             DisplayFatalError(_("TwoMachines mode requires a chess engine"),\r
1281                               0, 2);\r
1282             return;\r
1283           }\r
1284           if (appData.icsActive) {\r
1285             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),\r
1286                               0, 2);\r
1287             return;\r
1288           }\r
1289           TwoMachinesEvent();\r
1290         } else if (initialMode == EditGame) {\r
1291           EditGameEvent();\r
1292         } else if (initialMode == EditPosition) {\r
1293           EditPositionEvent();\r
1294         } else if (initialMode == Training) {\r
1295           if (*appData.loadGameFile == NULLCHAR) {\r
1296             DisplayFatalError(_("Training mode requires a game file"), 0, 2);\r
1297             return;\r
1298           }\r
1299           TrainingEvent();\r
1300         }\r
1301     }\r
1302 }\r
1303 \r
1304 /*\r
1305  * Establish will establish a contact to a remote host.port.\r
1306  * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
1307  *  used to talk to the host.\r
1308  * Returns 0 if okay, error code if not.\r
1309  */\r
1310 int\r
1311 establish()\r
1312 {\r
1313     char buf[MSG_SIZ];\r
1314 \r
1315     if (*appData.icsCommPort != NULLCHAR) {\r
1316         /* Talk to the host through a serial comm port */\r
1317         return OpenCommPort(appData.icsCommPort, &icsPR);\r
1318 \r
1319     } else if (*appData.gateway != NULLCHAR) {\r
1320         if (*appData.remoteShell == NULLCHAR) {\r
1321             /* Use the rcmd protocol to run telnet program on a gateway host */\r
1322             sprintf(buf, "%s %s %s",\r
1323                     appData.telnetProgram, appData.icsHost, appData.icsPort);\r
1324             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
1325 \r
1326         } else {\r
1327             /* Use the rsh program to run telnet program on a gateway host */\r
1328             if (*appData.remoteUser == NULLCHAR) {\r
1329                 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,\r
1330                         appData.gateway, appData.telnetProgram,\r
1331                         appData.icsHost, appData.icsPort);\r
1332             } else {\r
1333                 sprintf(buf, "%s %s -l %s %s %s %s",\r
1334                         appData.remoteShell, appData.gateway, \r
1335                         appData.remoteUser, appData.telnetProgram,\r
1336                         appData.icsHost, appData.icsPort);\r
1337             }\r
1338             return StartChildProcess(buf, "", &icsPR);\r
1339 \r
1340         }\r
1341     } else if (appData.useTelnet) {\r
1342         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
1343 \r
1344     } else {\r
1345         /* TCP socket interface differs somewhat between\r
1346            Unix and NT; handle details in the front end.\r
1347            */\r
1348         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
1349     }\r
1350 }\r
1351 \r
1352 void\r
1353 show_bytes(fp, buf, count)\r
1354      FILE *fp;\r
1355      char *buf;\r
1356      int count;\r
1357 {\r
1358     while (count--) {\r
1359         if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
1360             fprintf(fp, "\\%03o", *buf & 0xff);\r
1361         } else {\r
1362             putc(*buf, fp);\r
1363         }\r
1364         buf++;\r
1365     }\r
1366     fflush(fp);\r
1367 }\r
1368 \r
1369 /* Returns an errno value */\r
1370 int\r
1371 OutputMaybeTelnet(pr, message, count, outError)\r
1372      ProcRef pr;\r
1373      char *message;\r
1374      int count;\r
1375      int *outError;\r
1376 {\r
1377     char buf[8192], *p, *q, *buflim;\r
1378     int left, newcount, outcount;\r
1379 \r
1380     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
1381         *appData.gateway != NULLCHAR) {\r
1382         if (appData.debugMode) {\r
1383             fprintf(debugFP, ">ICS: ");\r
1384             show_bytes(debugFP, message, count);\r
1385             fprintf(debugFP, "\n");\r
1386         }\r
1387         return OutputToProcess(pr, message, count, outError);\r
1388     }\r
1389 \r
1390     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
1391     p = message;\r
1392     q = buf;\r
1393     left = count;\r
1394     newcount = 0;\r
1395     while (left) {\r
1396         if (q >= buflim) {\r
1397             if (appData.debugMode) {\r
1398                 fprintf(debugFP, ">ICS: ");\r
1399                 show_bytes(debugFP, buf, newcount);\r
1400                 fprintf(debugFP, "\n");\r
1401             }\r
1402             outcount = OutputToProcess(pr, buf, newcount, outError);\r
1403             if (outcount < newcount) return -1; /* to be sure */\r
1404             q = buf;\r
1405             newcount = 0;\r
1406         }\r
1407         if (*p == '\n') {\r
1408             *q++ = '\r';\r
1409             newcount++;\r
1410         } else if (((unsigned char) *p) == TN_IAC) {\r
1411             *q++ = (char) TN_IAC;\r
1412             newcount ++;\r
1413         }\r
1414         *q++ = *p++;\r
1415         newcount++;\r
1416         left--;\r
1417     }\r
1418     if (appData.debugMode) {\r
1419         fprintf(debugFP, ">ICS: ");\r
1420         show_bytes(debugFP, buf, newcount);\r
1421         fprintf(debugFP, "\n");\r
1422     }\r
1423     outcount = OutputToProcess(pr, buf, newcount, outError);\r
1424     if (outcount < newcount) return -1; /* to be sure */\r
1425     return count;\r
1426 }\r
1427 \r
1428 void\r
1429 read_from_player(isr, closure, message, count, error)\r
1430      InputSourceRef isr;\r
1431      VOIDSTAR closure;\r
1432      char *message;\r
1433      int count;\r
1434      int error;\r
1435 {\r
1436     int outError, outCount;\r
1437     static int gotEof = 0;\r
1438 \r
1439     /* Pass data read from player on to ICS */\r
1440     if (count > 0) {\r
1441         gotEof = 0;\r
1442         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
1443         if (outCount < count) {\r
1444             DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1445         }\r
1446     } else if (count < 0) {\r
1447         RemoveInputSource(isr);\r
1448         DisplayFatalError(_("Error reading from keyboard"), error, 1);\r
1449     } else if (gotEof++ > 0) {\r
1450         RemoveInputSource(isr);\r
1451         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);\r
1452     }\r
1453 }\r
1454 \r
1455 void\r
1456 SendToICS(s)\r
1457      char *s;\r
1458 {\r
1459     int count, outCount, outError;\r
1460 \r
1461     if (icsPR == NULL) return;\r
1462 \r
1463     count = strlen(s);\r
1464     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
1465     if (outCount < count) {\r
1466         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1467     }\r
1468 }\r
1469 \r
1470 /* This is used for sending logon scripts to the ICS. Sending\r
1471    without a delay causes problems when using timestamp on ICC\r
1472    (at least on my machine). */\r
1473 void\r
1474 SendToICSDelayed(s,msdelay)\r
1475      char *s;\r
1476      long msdelay;\r
1477 {\r
1478     int count, outCount, outError;\r
1479 \r
1480     if (icsPR == NULL) return;\r
1481 \r
1482     count = strlen(s);\r
1483     if (appData.debugMode) {\r
1484         fprintf(debugFP, ">ICS: ");\r
1485         show_bytes(debugFP, s, count);\r
1486         fprintf(debugFP, "\n");\r
1487     }\r
1488     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
1489                                       msdelay);\r
1490     if (outCount < count) {\r
1491         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1492     }\r
1493 }\r
1494 \r
1495 \r
1496 /* Remove all highlighting escape sequences in s\r
1497    Also deletes any suffix starting with '(' \r
1498    */\r
1499 char *\r
1500 StripHighlightAndTitle(s)\r
1501      char *s;\r
1502 {\r
1503     static char retbuf[MSG_SIZ];\r
1504     char *p = retbuf;\r
1505 \r
1506     while (*s != NULLCHAR) {\r
1507         while (*s == '\033') {\r
1508             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1509             if (*s != NULLCHAR) s++;\r
1510         }\r
1511         while (*s != NULLCHAR && *s != '\033') {\r
1512             if (*s == '(' || *s == '[') {\r
1513                 *p = NULLCHAR;\r
1514                 return retbuf;\r
1515             }\r
1516             *p++ = *s++;\r
1517         }\r
1518     }\r
1519     *p = NULLCHAR;\r
1520     return retbuf;\r
1521 }\r
1522 \r
1523 /* Remove all highlighting escape sequences in s */\r
1524 char *\r
1525 StripHighlight(s)\r
1526      char *s;\r
1527 {\r
1528     static char retbuf[MSG_SIZ];\r
1529     char *p = retbuf;\r
1530 \r
1531     while (*s != NULLCHAR) {\r
1532         while (*s == '\033') {\r
1533             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1534             if (*s != NULLCHAR) s++;\r
1535         }\r
1536         while (*s != NULLCHAR && *s != '\033') {\r
1537             *p++ = *s++;\r
1538         }\r
1539     }\r
1540     *p = NULLCHAR;\r
1541     return retbuf;\r
1542 }\r
1543 \r
1544 char *variantNames[] = VARIANT_NAMES;\r
1545 char *\r
1546 VariantName(v)\r
1547      VariantClass v;\r
1548 {\r
1549     return variantNames[v];\r
1550 }\r
1551 \r
1552 \r
1553 /* Identify a variant from the strings the chess servers use or the\r
1554    PGN Variant tag names we use. */\r
1555 VariantClass\r
1556 StringToVariant(e)\r
1557      char *e;\r
1558 {\r
1559     char *p;\r
1560     int wnum = -1;\r
1561     VariantClass v = VariantNormal;\r
1562     int i, found = FALSE;\r
1563     char buf[MSG_SIZ];\r
1564 \r
1565     if (!e) return v;\r
1566 \r
1567     /* [HGM] skip over optional board-size prefixes */\r
1568     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||\r
1569         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
1570         while( *e++ != '_');\r
1571     }\r
1572 \r
1573     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1574       if (StrCaseStr(e, variantNames[i])) {\r
1575         v = (VariantClass) i;\r
1576         found = TRUE;\r
1577         break;\r
1578       }\r
1579     }\r
1580 \r
1581     if (!found) {\r
1582       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1583           || StrCaseStr(e, "wild/fr") \r
1584           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {\r
1585         v = VariantFischeRandom;\r
1586       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1587                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1588         p += i;\r
1589         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1590         if (isdigit(*p)) {\r
1591           wnum = atoi(p);\r
1592         } else {\r
1593           wnum = -1;\r
1594         }\r
1595         switch (wnum) {\r
1596         case 0: /* FICS only, actually */\r
1597         case 1:\r
1598           /* Castling legal even if K starts on d-file */\r
1599           v = VariantWildCastle;\r
1600           break;\r
1601         case 2:\r
1602         case 3:\r
1603         case 4:\r
1604           /* Castling illegal even if K & R happen to start in\r
1605              normal positions. */\r
1606           v = VariantNoCastle;\r
1607           break;\r
1608         case 5:\r
1609         case 7:\r
1610         case 8:\r
1611         case 10:\r
1612         case 11:\r
1613         case 12:\r
1614         case 13:\r
1615         case 14:\r
1616         case 15:\r
1617         case 18:\r
1618         case 19:\r
1619           /* Castling legal iff K & R start in normal positions */\r
1620           v = VariantNormal;\r
1621           break;\r
1622         case 6:\r
1623         case 20:\r
1624         case 21:\r
1625           /* Special wilds for position setup; unclear what to do here */\r
1626           v = VariantLoadable;\r
1627           break;\r
1628         case 9:\r
1629           /* Bizarre ICC game */\r
1630           v = VariantTwoKings;\r
1631           break;\r
1632         case 16:\r
1633           v = VariantKriegspiel;\r
1634           break;\r
1635         case 17:\r
1636           v = VariantLosers;\r
1637           break;\r
1638         case 22:\r
1639           v = VariantFischeRandom;\r
1640           break;\r
1641         case 23:\r
1642           v = VariantCrazyhouse;\r
1643           break;\r
1644         case 24:\r
1645           v = VariantBughouse;\r
1646           break;\r
1647         case 25:\r
1648           v = Variant3Check;\r
1649           break;\r
1650         case 26:\r
1651           /* Not quite the same as FICS suicide! */\r
1652           v = VariantGiveaway;\r
1653           break;\r
1654         case 27:\r
1655           v = VariantAtomic;\r
1656           break;\r
1657         case 28:\r
1658           v = VariantShatranj;\r
1659           break;\r
1660 \r
1661         /* Temporary names for future ICC types.  The name *will* change in \r
1662            the next xboard/WinBoard release after ICC defines it. */\r
1663         case 29:\r
1664           v = Variant29;\r
1665           break;\r
1666         case 30:\r
1667           v = Variant30;\r
1668           break;\r
1669         case 31:\r
1670           v = Variant31;\r
1671           break;\r
1672         case 32:\r
1673           v = Variant32;\r
1674           break;\r
1675         case 33:\r
1676           v = Variant33;\r
1677           break;\r
1678         case 34:\r
1679           v = Variant34;\r
1680           break;\r
1681         case 35:\r
1682           v = Variant35;\r
1683           break;\r
1684         case 36:\r
1685           v = Variant36;\r
1686           break;\r
1687         case 37:\r
1688           v = VariantShogi;\r
1689           break;\r
1690         case 38:\r
1691           v = VariantXiangqi;\r
1692           break;\r
1693         case 39:\r
1694           v = VariantCourier;\r
1695           break;\r
1696         case 40:\r
1697           v = VariantGothic;\r
1698           break;\r
1699         case 41:\r
1700           v = VariantCapablanca;\r
1701           break;\r
1702         case 42:\r
1703           v = VariantKnightmate;\r
1704           break;\r
1705         case 43:\r
1706           v = VariantFairy;\r
1707           break;\r
1708         case 44:\r
1709           v = VariantCylinder;\r
1710           break;\r
1711         case 45:\r
1712           v = VariantFalcon;\r
1713           break;\r
1714         case 46:\r
1715           v = VariantCapaRandom;\r
1716           break;\r
1717         case 47:\r
1718           v = VariantBerolina;\r
1719           break;\r
1720         case 48:\r
1721           v = VariantJanus;\r
1722           break;\r
1723         case 49:\r
1724           v = VariantSuper;\r
1725           break;\r
1726         case 50:\r
1727           v = VariantGreat;\r
1728           break;\r
1729         case -1:\r
1730           /* Found "wild" or "w" in the string but no number;\r
1731              must assume it's normal chess. */\r
1732           v = VariantNormal;\r
1733           break;\r
1734         default:\r
1735           sprintf(buf, _("Unknown wild type %d"), wnum);\r
1736           DisplayError(buf, 0);\r
1737           v = VariantUnknown;\r
1738           break;\r
1739         }\r
1740       }\r
1741     }\r
1742     if (appData.debugMode) {\r
1743       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),\r
1744               e, wnum, VariantName(v));\r
1745     }\r
1746     return v;\r
1747 }\r
1748 \r
1749 static int leftover_start = 0, leftover_len = 0;\r
1750 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1751 \r
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1753    advance *index beyond it, and set leftover_start to the new value of\r
1754    *index; else return FALSE.  If pattern contains the character '*', it\r
1755    matches any sequence of characters not containing '\r', '\n', or the\r
1756    character following the '*' (if any), and the matched sequence(s) are\r
1757    copied into star_match.\r
1758    */\r
1759 int\r
1760 looking_at(buf, index, pattern)\r
1761      char *buf;\r
1762      int *index;\r
1763      char *pattern;\r
1764 {\r
1765     char *bufp = &buf[*index], *patternp = pattern;\r
1766     int star_count = 0;\r
1767     char *matchp = star_match[0];\r
1768     \r
1769     for (;;) {\r
1770         if (*patternp == NULLCHAR) {\r
1771             *index = leftover_start = bufp - buf;\r
1772             *matchp = NULLCHAR;\r
1773             return TRUE;\r
1774         }\r
1775         if (*bufp == NULLCHAR) return FALSE;\r
1776         if (*patternp == '*') {\r
1777             if (*bufp == *(patternp + 1)) {\r
1778                 *matchp = NULLCHAR;\r
1779                 matchp = star_match[++star_count];\r
1780                 patternp += 2;\r
1781                 bufp++;\r
1782                 continue;\r
1783             } else if (*bufp == '\n' || *bufp == '\r') {\r
1784                 patternp++;\r
1785                 if (*patternp == NULLCHAR)\r
1786                   continue;\r
1787                 else\r
1788                   return FALSE;\r
1789             } else {\r
1790                 *matchp++ = *bufp++;\r
1791                 continue;\r
1792             }\r
1793         }\r
1794         if (*patternp != *bufp) return FALSE;\r
1795         patternp++;\r
1796         bufp++;\r
1797     }\r
1798 }\r
1799 \r
1800 void\r
1801 SendToPlayer(data, length)\r
1802      char *data;\r
1803      int length;\r
1804 {\r
1805     int error, outCount;\r
1806     outCount = OutputToProcess(NoProc, data, length, &error);\r
1807     if (outCount < length) {\r
1808         DisplayFatalError(_("Error writing to display"), error, 1);\r
1809     }\r
1810 }\r
1811 \r
1812 void\r
1813 PackHolding(packed, holding)\r
1814      char packed[];\r
1815      char *holding;\r
1816 {\r
1817     char *p = holding;\r
1818     char *q = packed;\r
1819     int runlength = 0;\r
1820     int curr = 9999;\r
1821     do {\r
1822         if (*p == curr) {\r
1823             runlength++;\r
1824         } else {\r
1825             switch (runlength) {\r
1826               case 0:\r
1827                 break;\r
1828               case 1:\r
1829                 *q++ = curr;\r
1830                 break;\r
1831               case 2:\r
1832                 *q++ = curr;\r
1833                 *q++ = curr;\r
1834                 break;\r
1835               default:\r
1836                 sprintf(q, "%d", runlength);\r
1837                 while (*q) q++;\r
1838                 *q++ = curr;\r
1839                 break;\r
1840             }\r
1841             runlength = 1;\r
1842             curr = *p;\r
1843         }\r
1844     } while (*p++);\r
1845     *q = NULLCHAR;\r
1846 }\r
1847 \r
1848 /* Telnet protocol requests from the front end */\r
1849 void\r
1850 TelnetRequest(ddww, option)\r
1851      unsigned char ddww, option;\r
1852 {\r
1853     unsigned char msg[3];\r
1854     int outCount, outError;\r
1855 \r
1856     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1857 \r
1858     if (appData.debugMode) {\r
1859         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1860         switch (ddww) {\r
1861           case TN_DO:\r
1862             ddwwStr = "DO";\r
1863             break;\r
1864           case TN_DONT:\r
1865             ddwwStr = "DONT";\r
1866             break;\r
1867           case TN_WILL:\r
1868             ddwwStr = "WILL";\r
1869             break;\r
1870           case TN_WONT:\r
1871             ddwwStr = "WONT";\r
1872             break;\r
1873           default:\r
1874             ddwwStr = buf1;\r
1875             sprintf(buf1, "%d", ddww);\r
1876             break;\r
1877         }\r
1878         switch (option) {\r
1879           case TN_ECHO:\r
1880             optionStr = "ECHO";\r
1881             break;\r
1882           default:\r
1883             optionStr = buf2;\r
1884             sprintf(buf2, "%d", option);\r
1885             break;\r
1886         }\r
1887         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1888     }\r
1889     msg[0] = TN_IAC;\r
1890     msg[1] = ddww;\r
1891     msg[2] = option;\r
1892     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1893     if (outCount < 3) {\r
1894         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1895     }\r
1896 }\r
1897 \r
1898 void\r
1899 DoEcho()\r
1900 {\r
1901     if (!appData.icsActive) return;\r
1902     TelnetRequest(TN_DO, TN_ECHO);\r
1903 }\r
1904 \r
1905 void\r
1906 DontEcho()\r
1907 {\r
1908     if (!appData.icsActive) return;\r
1909     TelnetRequest(TN_DONT, TN_ECHO);\r
1910 }\r
1911 \r
1912 void\r
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
1914 {\r
1915     /* put the holdings sent to us by the server on the board holdings area */\r
1916     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
1917     char p;\r
1918     ChessSquare piece;\r
1919 \r
1920     if(gameInfo.holdingsWidth < 2)  return;\r
1921 \r
1922     if( (int)lowestPiece >= BlackPawn ) {\r
1923         holdingsColumn = 0;\r
1924         countsColumn = 1;\r
1925         holdingsStartRow = BOARD_HEIGHT-1;\r
1926         direction = -1;\r
1927     } else {\r
1928         holdingsColumn = BOARD_WIDTH-1;\r
1929         countsColumn = BOARD_WIDTH-2;\r
1930         holdingsStartRow = 0;\r
1931         direction = 1;\r
1932     }\r
1933 \r
1934     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
1935         board[i][holdingsColumn] = EmptySquare;\r
1936         board[i][countsColumn]   = (ChessSquare) 0;\r
1937     }\r
1938     while( (p=*holdings++) != NULLCHAR ) {\r
1939         piece = CharToPiece( ToUpper(p) );\r
1940         if(piece == EmptySquare) continue;\r
1941         /*j = (int) piece - (int) WhitePawn;*/\r
1942         j = PieceToNumber(piece);\r
1943         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1944         if(j < 0) continue;               /* should not happen */\r
1945         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );\r
1946         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
1947         board[holdingsStartRow+j*direction][countsColumn]++;\r
1948     }\r
1949 \r
1950 }\r
1951 \r
1952 \r
1953 void\r
1954 VariantSwitch(Board board, VariantClass newVariant)\r
1955 {\r
1956    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;\r
1957    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;\r
1958 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;\r
1959 \r
1960    startedFromPositionFile = FALSE;\r
1961    if(gameInfo.variant == newVariant) return;\r
1962 \r
1963    /* [HGM] This routine is called each time an assignment is made to\r
1964     * gameInfo.variant during a game, to make sure the board sizes\r
1965     * are set to match the new variant. If that means adding or deleting\r
1966     * holdings, we shift the playing board accordingly\r
1967     * This kludge is needed because in ICS observe mode, we get boards\r
1968     * of an ongoing game without knowing the variant, and learn about the\r
1969     * latter only later. This can be because of the move list we requested,\r
1970     * in which case the game history is refilled from the beginning anyway,\r
1971     * but also when receiving holdings of a crazyhouse game. In the latter\r
1972     * case we want to add those holdings to the already received position.\r
1973     */\r
1974 \r
1975 \r
1976   if (appData.debugMode) {\r
1977     fprintf(debugFP, "Switch board from %s to %s\n",\r
1978                VariantName(gameInfo.variant), VariantName(newVariant));\r
1979     setbuf(debugFP, NULL);\r
1980   }\r
1981     shuffleOpenings = 0;       /* [HGM] shuffle */\r
1982     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
1983     switch(newVariant) {\r
1984             case VariantShogi:\r
1985               newWidth = 9;  newHeight = 9;\r
1986               gameInfo.holdingsSize = 7;\r
1987             case VariantBughouse:\r
1988             case VariantCrazyhouse:\r
1989               newHoldingsWidth = 2; break;\r
1990             default:\r
1991               newHoldingsWidth = gameInfo.holdingsSize = 0;\r
1992     }\r
1993 \r
1994     if(newWidth  != gameInfo.boardWidth  ||\r
1995        newHeight != gameInfo.boardHeight ||\r
1996        newHoldingsWidth != gameInfo.holdingsWidth ) {\r
1997 \r
1998         /* shift position to new playing area, if needed */\r
1999         if(newHoldingsWidth > gameInfo.holdingsWidth) {\r
2000            for(i=0; i<BOARD_HEIGHT; i++) \r
2001                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)\r
2002                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
2003                                                      board[i][j];\r
2004            for(i=0; i<newHeight; i++) {\r
2005                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;\r
2006                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;\r
2007            }\r
2008         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {\r
2009            for(i=0; i<BOARD_HEIGHT; i++)\r
2010                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
2011                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
2012                                                  board[i][j];\r
2013         }\r
2014 \r
2015         gameInfo.boardWidth  = newWidth;\r
2016         gameInfo.boardHeight = newHeight;\r
2017         gameInfo.holdingsWidth = newHoldingsWidth;\r
2018         gameInfo.variant = newVariant;\r
2019         InitDrawingSizes(-2, 0);\r
2020 \r
2021         /* [HGM] The following should definitely be solved in a better way */\r
2022 #if 0\r
2023         CopyBoard(board, tempBoard); /* save position in case it is board[0] */\r
2024         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];\r
2025         saveEP = epStatus[0];\r
2026 #endif\r
2027         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */\r
2028 #if 0\r
2029         epStatus[0] = saveEP;\r
2030         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];\r
2031         CopyBoard(tempBoard, board); /* restore position received from ICS   */\r
2032 #endif\r
2033     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }\r
2034 \r
2035     forwardMostMove = oldForwardMostMove;\r
2036     backwardMostMove = oldBackwardMostMove;\r
2037     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */\r
2038 }\r
2039 \r
2040 static int loggedOn = FALSE;\r
2041 \r
2042 /*-- Game start info cache: --*/\r
2043 int gs_gamenum;\r
2044 char gs_kind[MSG_SIZ];\r
2045 static char player1Name[128] = "";\r
2046 static char player2Name[128] = "";\r
2047 static int player1Rating = -1;\r
2048 static int player2Rating = -1;\r
2049 /*----------------------------*/\r
2050 \r
2051 ColorClass curColor = ColorNormal;\r
2052 int suppressKibitz = 0;\r
2053 \r
2054 void\r
2055 read_from_ics(isr, closure, data, count, error)\r
2056      InputSourceRef isr;\r
2057      VOIDSTAR closure;\r
2058      char *data;\r
2059      int count;\r
2060      int error;\r
2061 {\r
2062 #define BUF_SIZE 8192\r
2063 #define STARTED_NONE 0\r
2064 #define STARTED_MOVES 1\r
2065 #define STARTED_BOARD 2\r
2066 #define STARTED_OBSERVE 3\r
2067 #define STARTED_HOLDINGS 4\r
2068 #define STARTED_CHATTER 5\r
2069 #define STARTED_COMMENT 6\r
2070 #define STARTED_MOVES_NOHIDE 7\r
2071     \r
2072     static int started = STARTED_NONE;\r
2073     static char parse[20000];\r
2074     static int parse_pos = 0;\r
2075     static char buf[BUF_SIZE + 1];\r
2076     static int firstTime = TRUE, intfSet = FALSE;\r
2077     static ColorClass prevColor = ColorNormal;\r
2078     static int savingComment = FALSE;\r
2079     char str[500];\r
2080     int i, oldi;\r
2081     int buf_len;\r
2082     int next_out;\r
2083     int tkind;\r
2084     int backup;    /* [DM] For zippy color lines */\r
2085     char *p;\r
2086 \r
2087     if (appData.debugMode) {\r
2088       if (!error) {\r
2089         fprintf(debugFP, "<ICS: ");\r
2090         show_bytes(debugFP, data, count);\r
2091         fprintf(debugFP, "\n");\r
2092       }\r
2093     }\r
2094 \r
2095     if (appData.debugMode) { int f = forwardMostMove;\r
2096         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,\r
2097                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
2098     }\r
2099     if (count > 0) {\r
2100         /* If last read ended with a partial line that we couldn't parse,\r
2101            prepend it to the new read and try again. */\r
2102         if (leftover_len > 0) {\r
2103             for (i=0; i<leftover_len; i++)\r
2104               buf[i] = buf[leftover_start + i];\r
2105         }\r
2106 \r
2107         /* Copy in new characters, removing nulls and \r's */\r
2108         buf_len = leftover_len;\r
2109         for (i = 0; i < count; i++) {\r
2110             if (data[i] != NULLCHAR && data[i] != '\r')\r
2111               buf[buf_len++] = data[i];\r
2112             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && \r
2113                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') \r
2114                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous\r
2115         }\r
2116 \r
2117         buf[buf_len] = NULLCHAR;\r
2118         next_out = leftover_len;\r
2119         leftover_start = 0;\r
2120         \r
2121         i = 0;\r
2122         while (i < buf_len) {\r
2123             /* Deal with part of the TELNET option negotiation\r
2124                protocol.  We refuse to do anything beyond the\r
2125                defaults, except that we allow the WILL ECHO option,\r
2126                which ICS uses to turn off password echoing when we are\r
2127                directly connected to it.  We reject this option\r
2128                if localLineEditing mode is on (always on in xboard)\r
2129                and we are talking to port 23, which might be a real\r
2130                telnet server that will try to keep WILL ECHO on permanently.\r
2131              */\r
2132             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
2133                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
2134                 unsigned char option;\r
2135                 oldi = i;\r
2136                 switch ((unsigned char) buf[++i]) {\r
2137                   case TN_WILL:\r
2138                     if (appData.debugMode)\r
2139                       fprintf(debugFP, "\n<WILL ");\r
2140                     switch (option = (unsigned char) buf[++i]) {\r
2141                       case TN_ECHO:\r
2142                         if (appData.debugMode)\r
2143                           fprintf(debugFP, "ECHO ");\r
2144                         /* Reply only if this is a change, according\r
2145                            to the protocol rules. */\r
2146                         if (remoteEchoOption) break;\r
2147                         if (appData.localLineEditing &&\r
2148                             atoi(appData.icsPort) == TN_PORT) {\r
2149                             TelnetRequest(TN_DONT, TN_ECHO);\r
2150                         } else {\r
2151                             EchoOff();\r
2152                             TelnetRequest(TN_DO, TN_ECHO);\r
2153                             remoteEchoOption = TRUE;\r
2154                         }\r
2155                         break;\r
2156                       default:\r
2157                         if (appData.debugMode)\r
2158                           fprintf(debugFP, "%d ", option);\r
2159                         /* Whatever this is, we don't want it. */\r
2160                         TelnetRequest(TN_DONT, option);\r
2161                         break;\r
2162                     }\r
2163                     break;\r
2164                   case TN_WONT:\r
2165                     if (appData.debugMode)\r
2166                       fprintf(debugFP, "\n<WONT ");\r
2167                     switch (option = (unsigned char) buf[++i]) {\r
2168                       case TN_ECHO:\r
2169                         if (appData.debugMode)\r
2170                           fprintf(debugFP, "ECHO ");\r
2171                         /* Reply only if this is a change, according\r
2172                            to the protocol rules. */\r
2173                         if (!remoteEchoOption) break;\r
2174                         EchoOn();\r
2175                         TelnetRequest(TN_DONT, TN_ECHO);\r
2176                         remoteEchoOption = FALSE;\r
2177                         break;\r
2178                       default:\r
2179                         if (appData.debugMode)\r
2180                           fprintf(debugFP, "%d ", (unsigned char) option);\r
2181                         /* Whatever this is, it must already be turned\r
2182                            off, because we never agree to turn on\r
2183                            anything non-default, so according to the\r
2184                            protocol rules, we don't reply. */\r
2185                         break;\r
2186                     }\r
2187                     break;\r
2188                   case TN_DO:\r
2189                     if (appData.debugMode)\r
2190                       fprintf(debugFP, "\n<DO ");\r
2191                     switch (option = (unsigned char) buf[++i]) {\r
2192                       default:\r
2193                         /* Whatever this is, we refuse to do it. */\r
2194                         if (appData.debugMode)\r
2195                           fprintf(debugFP, "%d ", option);\r
2196                         TelnetRequest(TN_WONT, option);\r
2197                         break;\r
2198                     }\r
2199                     break;\r
2200                   case TN_DONT:\r
2201                     if (appData.debugMode)\r
2202                       fprintf(debugFP, "\n<DONT ");\r
2203                     switch (option = (unsigned char) buf[++i]) {\r
2204                       default:\r
2205                         if (appData.debugMode)\r
2206                           fprintf(debugFP, "%d ", option);\r
2207                         /* Whatever this is, we are already not doing\r
2208                            it, because we never agree to do anything\r
2209                            non-default, so according to the protocol\r
2210                            rules, we don't reply. */\r
2211                         break;\r
2212                     }\r
2213                     break;\r
2214                   case TN_IAC:\r
2215                     if (appData.debugMode)\r
2216                       fprintf(debugFP, "\n<IAC ");\r
2217                     /* Doubled IAC; pass it through */\r
2218                     i--;\r
2219                     break;\r
2220                   default:\r
2221                     if (appData.debugMode)\r
2222                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
2223                     /* Drop all other telnet commands on the floor */\r
2224                     break;\r
2225                 }\r
2226                 if (oldi > next_out)\r
2227                   SendToPlayer(&buf[next_out], oldi - next_out);\r
2228                 if (++i > next_out)\r
2229                   next_out = i;\r
2230                 continue;\r
2231             }\r
2232                 \r
2233             /* OK, this at least will *usually* work */\r
2234             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
2235                 loggedOn = TRUE;\r
2236             }\r
2237             \r
2238             if (loggedOn && !intfSet) {\r
2239                 if (ics_type == ICS_ICC) {\r
2240                   sprintf(str,\r
2241                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
2242                           programVersion);\r
2243 \r
2244                 } else if (ics_type == ICS_CHESSNET) {\r
2245                   sprintf(str, "/style 12\n");\r
2246                 } else {\r
2247                   strcpy(str, "alias $ @\n$set interface ");\r
2248                   strcat(str, programVersion);\r
2249                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
2250 #ifdef WIN32\r
2251                   strcat(str, "$iset nohighlight 1\n");\r
2252 #endif\r
2253                   strcat(str, "$iset lock 1\n$style 12\n");\r
2254                 }\r
2255                 SendToICS(str);\r
2256                 intfSet = TRUE;\r
2257             }\r
2258 \r
2259             if (started == STARTED_COMMENT) {\r
2260                 /* Accumulate characters in comment */\r
2261                 parse[parse_pos++] = buf[i];\r
2262                 if (buf[i] == '\n') {\r
2263                     parse[parse_pos] = NULLCHAR;\r
2264                     if(!suppressKibitz) // [HGM] kibitz\r
2265                         AppendComment(forwardMostMove, StripHighlight(parse));\r
2266                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window\r
2267                         int nrDigit = 0, nrAlph = 0, i;\r
2268                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input\r
2269                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }\r
2270                         parse[parse_pos] = NULLCHAR;\r
2271                         // try to be smart: if it does not look like search info, it should go to\r
2272                         // ICS interaction window after all, not to engine-output window.\r
2273                         for(i=0; i<parse_pos; i++) { // count letters and digits\r
2274                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');\r
2275                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');\r
2276                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');\r
2277                         }\r
2278                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info\r
2279                             int depth=0; float score;\r
2280                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {\r
2281                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph\r
2282                                 pvInfoList[forwardMostMove-1].depth = depth;\r
2283                                 pvInfoList[forwardMostMove-1].score = 100*score;\r
2284                             }\r
2285                             OutputKibitz(suppressKibitz, parse);\r
2286                         } else {\r
2287                             char tmp[MSG_SIZ];\r
2288                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);\r
2289                             SendToPlayer(tmp, strlen(tmp));\r
2290                         }\r
2291                     }\r
2292                     started = STARTED_NONE;\r
2293                 } else {\r
2294                     /* Don't match patterns against characters in chatter */\r
2295                     i++;\r
2296                     continue;\r
2297                 }\r
2298             }\r
2299             if (started == STARTED_CHATTER) {\r
2300                 if (buf[i] != '\n') {\r
2301                     /* Don't match patterns against characters in chatter */\r
2302                     i++;\r
2303                     continue;\r
2304                 }\r
2305                 started = STARTED_NONE;\r
2306             }\r
2307 \r
2308             /* Kludge to deal with rcmd protocol */\r
2309             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2310                 DisplayFatalError(&buf[1], 0, 1);\r
2311                 continue;\r
2312             } else {\r
2313                 firstTime = FALSE;\r
2314             }\r
2315 \r
2316             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2317                 ics_type = ICS_ICC;\r
2318                 ics_prefix = "/";\r
2319                 if (appData.debugMode)\r
2320                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2321                 continue;\r
2322             }\r
2323             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2324                 ics_type = ICS_FICS;\r
2325                 ics_prefix = "$";\r
2326                 if (appData.debugMode)\r
2327                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2328                 continue;\r
2329             }\r
2330             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2331                 ics_type = ICS_CHESSNET;\r
2332                 ics_prefix = "/";\r
2333                 if (appData.debugMode)\r
2334                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2335                 continue;\r
2336             }\r
2337 \r
2338             if (!loggedOn &&\r
2339                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2340                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2341                  looking_at(buf, &i, "will be \"*\""))) {\r
2342               strcpy(ics_handle, star_match[0]);\r
2343               continue;\r
2344             }\r
2345 \r
2346             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2347               char buf[MSG_SIZ];\r
2348               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2349               DisplayIcsInteractionTitle(buf);\r
2350               have_set_title = TRUE;\r
2351             }\r
2352 \r
2353             /* skip finger notes */\r
2354             if (started == STARTED_NONE &&\r
2355                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2356                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2357                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2358               started = STARTED_CHATTER;\r
2359               i += 3;\r
2360               continue;\r
2361             }\r
2362 \r
2363             /* skip formula vars */\r
2364             if (started == STARTED_NONE &&\r
2365                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2366               started = STARTED_CHATTER;\r
2367               i += 3;\r
2368               continue;\r
2369             }\r
2370 \r
2371             oldi = i;\r
2372             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window\r
2373             if (appData.autoKibitz && started == STARTED_NONE && \r
2374                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze\r
2375                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {\r
2376                 if(looking_at(buf, &i, "* kibitzes: ") &&\r
2377                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || \r
2378                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent\r
2379                         suppressKibitz = TRUE;\r
2380                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]\r
2381                                 && (gameMode == IcsPlayingWhite)) ||\r
2382                            (StrStr(star_match[0], gameInfo.black) == star_match[0]\r
2383                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz\r
2384                             started = STARTED_CHATTER; // own kibitz we simply discard\r
2385                         else {\r
2386                             started = STARTED_COMMENT; // make sure it will be collected in parse[]\r
2387                             parse_pos = 0; parse[0] = NULLCHAR;\r
2388                             savingComment = TRUE;\r
2389                             suppressKibitz = gameMode != IcsObserving ? 2 :\r
2390                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;\r
2391                         } \r
2392                         continue;\r
2393                 } else\r
2394                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz\r
2395                     started = STARTED_CHATTER;\r
2396                     suppressKibitz = TRUE;\r
2397                 }\r
2398             } // [HGM] kibitz: end of patch\r
2399 \r
2400             if (appData.zippyTalk || appData.zippyPlay) {\r
2401                 /* [DM] Backup address for color zippy lines */\r
2402                 backup = i;\r
2403 #if ZIPPY\r
2404        #ifdef WIN32\r
2405                if (loggedOn == TRUE)\r
2406                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||\r
2407                           (appData.zippyPlay && ZippyMatch(buf, &backup)));\r
2408        #else\r
2409                 if (ZippyControl(buf, &i) ||\r
2410                     ZippyConverse(buf, &i) ||\r
2411                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2412                       loggedOn = TRUE;\r
2413                       if (!appData.colorize) continue;\r
2414                 }\r
2415        #endif\r
2416 #endif\r
2417             } // [DM] 'else { ' deleted\r
2418                 if (/* Don't color "message" or "messages" output */\r
2419                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2420                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2421                     looking_at(buf, &i, "--* (*:*): ") ||\r
2422                     /* Regular tells and says */\r
2423                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2424                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2425                     looking_at(buf, &i, "* says: ") ||\r
2426                     /* Message notifications (same color as tells) */\r
2427                     looking_at(buf, &i, "* has left a message ") ||\r
2428                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2429                     /* Whispers and kibitzes */\r
2430                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2431                     looking_at(buf, &i, "* kibitzes: ") ||\r
2432                     /* Channel tells */\r
2433                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2434 \r
2435                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2436                       /* Avoid "tells you:" spoofs in channels */\r
2437                      tkind = 3;\r
2438                   }\r
2439                   if (star_match[0][0] == NULLCHAR ||\r
2440                       strchr(star_match[0], ' ') ||\r
2441                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2442                     /* Reject bogus matches */\r
2443                     i = oldi;\r
2444                   } else {\r
2445                     if (appData.colorize) {\r
2446                       if (oldi > next_out) {\r
2447                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2448                         next_out = oldi;\r
2449                       }\r
2450                       switch (tkind) {\r
2451                       case 1:\r
2452                         Colorize(ColorTell, FALSE);\r
2453                         curColor = ColorTell;\r
2454                         break;\r
2455                       case 2:\r
2456                         Colorize(ColorKibitz, FALSE);\r
2457                         curColor = ColorKibitz;\r
2458                         break;\r
2459                       case 3:\r
2460                         p = strrchr(star_match[1], '(');\r
2461                         if (p == NULL) {\r
2462                           p = star_match[1];\r
2463                         } else {\r
2464                           p++;\r
2465                         }\r
2466                         if (atoi(p) == 1) {\r
2467                           Colorize(ColorChannel1, FALSE);\r
2468                           curColor = ColorChannel1;\r
2469                         } else {\r
2470                           Colorize(ColorChannel, FALSE);\r
2471                           curColor = ColorChannel;\r
2472                         }\r
2473                         break;\r
2474                       case 5:\r
2475                         curColor = ColorNormal;\r
2476                         break;\r
2477                       }\r
2478                     }\r
2479                     if (started == STARTED_NONE && appData.autoComment &&\r
2480                         (gameMode == IcsObserving ||\r
2481                          gameMode == IcsPlayingWhite ||\r
2482                          gameMode == IcsPlayingBlack)) {\r
2483                       parse_pos = i - oldi;\r
2484                       memcpy(parse, &buf[oldi], parse_pos);\r
2485                       parse[parse_pos] = NULLCHAR;\r
2486                       started = STARTED_COMMENT;\r
2487                       savingComment = TRUE;\r
2488                     } else {\r
2489                       started = STARTED_CHATTER;\r
2490                       savingComment = FALSE;\r
2491                     }\r
2492                     loggedOn = TRUE;\r
2493                     continue;\r
2494                   }\r
2495                 }\r
2496 \r
2497                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2498                     looking_at(buf, &i, "* c-shouts: ")) {\r
2499                     if (appData.colorize) {\r
2500                         if (oldi > next_out) {\r
2501                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2502                             next_out = oldi;\r
2503                         }\r
2504                         Colorize(ColorSShout, FALSE);\r
2505                         curColor = ColorSShout;\r
2506                     }\r
2507                     loggedOn = TRUE;\r
2508                     started = STARTED_CHATTER;\r
2509                     continue;\r
2510                 }\r
2511 \r
2512                 if (looking_at(buf, &i, "--->")) {\r
2513                     loggedOn = TRUE;\r
2514                     continue;\r
2515                 }\r
2516 \r
2517                 if (looking_at(buf, &i, "* shouts: ") ||\r
2518                     looking_at(buf, &i, "--> ")) {\r
2519                     if (appData.colorize) {\r
2520                         if (oldi > next_out) {\r
2521                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2522                             next_out = oldi;\r
2523                         }\r
2524                         Colorize(ColorShout, FALSE);\r
2525                         curColor = ColorShout;\r
2526                     }\r
2527                     loggedOn = TRUE;\r
2528                     started = STARTED_CHATTER;\r
2529                     continue;\r
2530                 }\r
2531 \r
2532                 if (looking_at( buf, &i, "Challenge:")) {\r
2533                     if (appData.colorize) {\r
2534                         if (oldi > next_out) {\r
2535                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2536                             next_out = oldi;\r
2537                         }\r
2538                         Colorize(ColorChallenge, FALSE);\r
2539                         curColor = ColorChallenge;\r
2540                     }\r
2541                     loggedOn = TRUE;\r
2542                     continue;\r
2543                 }\r
2544 \r
2545                 if (looking_at(buf, &i, "* offers you") ||\r
2546                     looking_at(buf, &i, "* offers to be") ||\r
2547                     looking_at(buf, &i, "* would like to") ||\r
2548                     looking_at(buf, &i, "* requests to") ||\r
2549                     looking_at(buf, &i, "Your opponent offers") ||\r
2550                     looking_at(buf, &i, "Your opponent requests")) {\r
2551 \r
2552                     if (appData.colorize) {\r
2553                         if (oldi > next_out) {\r
2554                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2555                             next_out = oldi;\r
2556                         }\r
2557                         Colorize(ColorRequest, FALSE);\r
2558                         curColor = ColorRequest;\r
2559                     }\r
2560                     continue;\r
2561                 }\r
2562 \r
2563                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2564                     if (appData.colorize) {\r
2565                         if (oldi > next_out) {\r
2566                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2567                             next_out = oldi;\r
2568                         }\r
2569                         Colorize(ColorSeek, FALSE);\r
2570                         curColor = ColorSeek;\r
2571                     }\r
2572                     continue;\r
2573             }\r
2574 \r
2575             if (looking_at(buf, &i, "\\   ")) {\r
2576                 if (prevColor != ColorNormal) {\r
2577                     if (oldi > next_out) {\r
2578                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2579                         next_out = oldi;\r
2580                     }\r
2581                     Colorize(prevColor, TRUE);\r
2582                     curColor = prevColor;\r
2583                 }\r
2584                 if (savingComment) {\r
2585                     parse_pos = i - oldi;\r
2586                     memcpy(parse, &buf[oldi], parse_pos);\r
2587                     parse[parse_pos] = NULLCHAR;\r
2588                     started = STARTED_COMMENT;\r
2589                 } else {\r
2590                     started = STARTED_CHATTER;\r
2591                 }\r
2592                 continue;\r
2593             }\r
2594 \r
2595             if (looking_at(buf, &i, "Black Strength :") ||\r
2596                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2597                 looking_at(buf, &i, "<10>") ||\r
2598                 looking_at(buf, &i, "#@#")) {\r
2599                 /* Wrong board style */\r
2600                 loggedOn = TRUE;\r
2601                 SendToICS(ics_prefix);\r
2602                 SendToICS("set style 12\n");\r
2603                 SendToICS(ics_prefix);\r
2604                 SendToICS("refresh\n");\r
2605                 continue;\r
2606             }\r
2607             \r
2608             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2609                 ICSInitScript();\r
2610                 have_sent_ICS_logon = 1;\r
2611                 continue;\r
2612             }\r
2613               \r
2614             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2615                 (looking_at(buf, &i, "\n<12> ") ||\r
2616                  looking_at(buf, &i, "<12> "))) {\r
2617                 loggedOn = TRUE;\r
2618                 if (oldi > next_out) {\r
2619                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2620                 }\r
2621                 next_out = i;\r
2622                 started = STARTED_BOARD;\r
2623                 parse_pos = 0;\r
2624                 continue;\r
2625             }\r
2626 \r
2627             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2628                 looking_at(buf, &i, "<b1> ")) {\r
2629                 if (oldi > next_out) {\r
2630                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2631                 }\r
2632                 next_out = i;\r
2633                 started = STARTED_HOLDINGS;\r
2634                 parse_pos = 0;\r
2635                 continue;\r
2636             }\r
2637 \r
2638             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2639                 loggedOn = TRUE;\r
2640                 /* Header for a move list -- first line */\r
2641 \r
2642                 switch (ics_getting_history) {\r
2643                   case H_FALSE:\r
2644                     switch (gameMode) {\r
2645                       case IcsIdle:\r
2646                       case BeginningOfGame:\r
2647                         /* User typed "moves" or "oldmoves" while we\r
2648                            were idle.  Pretend we asked for these\r
2649                            moves and soak them up so user can step\r
2650                            through them and/or save them.\r
2651                            */\r
2652                         Reset(FALSE, TRUE);\r
2653                         gameMode = IcsObserving;\r
2654                         ModeHighlight();\r
2655                         ics_gamenum = -1;\r
2656                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2657                         break;\r
2658                       case EditGame: /*?*/\r
2659                       case EditPosition: /*?*/\r
2660                         /* Should above feature work in these modes too? */\r
2661                         /* For now it doesn't */\r
2662                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2663                         break;\r
2664                       default:\r
2665                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2666                         break;\r
2667                     }\r
2668                     break;\r
2669                   case H_REQUESTED:\r
2670                     /* Is this the right one? */\r
2671                     if (gameInfo.white && gameInfo.black &&\r
2672                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2673                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2674                         /* All is well */\r
2675                         ics_getting_history = H_GOT_REQ_HEADER;\r
2676                     }\r
2677                     break;\r
2678                   case H_GOT_REQ_HEADER:\r
2679                   case H_GOT_UNREQ_HEADER:\r
2680                   case H_GOT_UNWANTED_HEADER:\r
2681                   case H_GETTING_MOVES:\r
2682                     /* Should not happen */\r
2683                     DisplayError(_("Error gathering move list: two headers"), 0);\r
2684                     ics_getting_history = H_FALSE;\r
2685                     break;\r
2686                 }\r
2687 \r
2688                 /* Save player ratings into gameInfo if needed */\r
2689                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2690                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2691                     (gameInfo.whiteRating == -1 ||\r
2692                      gameInfo.blackRating == -1)) {\r
2693 \r
2694                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2695                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2696                     if (appData.debugMode)\r
2697                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), \r
2698                               gameInfo.whiteRating, gameInfo.blackRating);\r
2699                 }\r
2700                 continue;\r
2701             }\r
2702 \r
2703             if (looking_at(buf, &i,\r
2704               "* * match, initial time: * minute*, increment: * second")) {\r
2705                 /* Header for a move list -- second line */\r
2706                 /* Initial board will follow if this is a wild game */\r
2707                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2708                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2709                 gameInfo.event = StrSave(str);\r
2710                 /* [HGM] we switched variant. Translate boards if needed. */\r
2711                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2712                 continue;\r
2713             }\r
2714 \r
2715             if (looking_at(buf, &i, "Move  ")) {\r
2716                 /* Beginning of a move list */\r
2717                 switch (ics_getting_history) {\r
2718                   case H_FALSE:\r
2719                     /* Normally should not happen */\r
2720                     /* Maybe user hit reset while we were parsing */\r
2721                     break;\r
2722                   case H_REQUESTED:\r
2723                     /* Happens if we are ignoring a move list that is not\r
2724                      * the one we just requested.  Common if the user\r
2725                      * tries to observe two games without turning off\r
2726                      * getMoveList */\r
2727                     break;\r
2728                   case H_GETTING_MOVES:\r
2729                     /* Should not happen */\r
2730                     DisplayError(_("Error gathering move list: nested"), 0);\r
2731                     ics_getting_history = H_FALSE;\r
2732                     break;\r
2733                   case H_GOT_REQ_HEADER:\r
2734                     ics_getting_history = H_GETTING_MOVES;\r
2735                     started = STARTED_MOVES;\r
2736                     parse_pos = 0;\r
2737                     if (oldi > next_out) {\r
2738                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2739                     }\r
2740                     break;\r
2741                   case H_GOT_UNREQ_HEADER:\r
2742                     ics_getting_history = H_GETTING_MOVES;\r
2743                     started = STARTED_MOVES_NOHIDE;\r
2744                     parse_pos = 0;\r
2745                     break;\r
2746                   case H_GOT_UNWANTED_HEADER:\r
2747                     ics_getting_history = H_FALSE;\r
2748                     break;\r
2749                 }\r
2750                 continue;\r
2751             }                           \r
2752             \r
2753             if (looking_at(buf, &i, "% ") ||\r
2754                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2755                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book\r
2756                 savingComment = FALSE;\r
2757                 switch (started) {\r
2758                   case STARTED_MOVES:\r
2759                   case STARTED_MOVES_NOHIDE:\r
2760                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2761                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2762                     ParseGameHistory(parse);\r
2763 #if ZIPPY\r
2764                     if (appData.zippyPlay && first.initDone) {\r
2765                         FeedMovesToProgram(&first, forwardMostMove);\r
2766                         if (gameMode == IcsPlayingWhite) {\r
2767                             if (WhiteOnMove(forwardMostMove)) {\r
2768                                 if (first.sendTime) {\r
2769                                   if (first.useColors) {\r
2770                                     SendToProgram("black\n", &first); \r
2771                                   }\r
2772                                   SendTimeRemaining(&first, TRUE);\r
2773                                 }\r
2774 #if 0\r
2775                                 if (first.useColors) {\r
2776                                   SendToProgram("white\ngo\n", &first);\r
2777                                 } else {\r
2778                                   SendToProgram("go\n", &first);\r
2779                                 }\r
2780 #else\r
2781                                 if (first.useColors) {\r
2782                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent\r
2783                                 }\r
2784                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos\r
2785 #endif\r
2786                                 first.maybeThinking = TRUE;\r
2787                             } else {\r
2788                                 if (first.usePlayother) {\r
2789                                   if (first.sendTime) {\r
2790                                     SendTimeRemaining(&first, TRUE);\r
2791                                   }\r
2792                                   SendToProgram("playother\n", &first);\r
2793                                   firstMove = FALSE;\r
2794                                 } else {\r
2795                                   firstMove = TRUE;\r
2796                                 }\r
2797                             }\r
2798                         } else if (gameMode == IcsPlayingBlack) {\r
2799                             if (!WhiteOnMove(forwardMostMove)) {\r
2800                                 if (first.sendTime) {\r
2801                                   if (first.useColors) {\r
2802                                     SendToProgram("white\n", &first);\r
2803                                   }\r
2804                                   SendTimeRemaining(&first, FALSE);\r
2805                                 }\r
2806 #if 0\r
2807                                 if (first.useColors) {\r
2808                                   SendToProgram("black\ngo\n", &first);\r
2809                                 } else {\r
2810                                   SendToProgram("go\n", &first);\r
2811                                 }\r
2812 #else\r
2813                                 if (first.useColors) {\r
2814                                   SendToProgram("black\n", &first);\r
2815                                 }\r
2816                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);\r
2817 #endif\r
2818                                 first.maybeThinking = TRUE;\r
2819                             } else {\r
2820                                 if (first.usePlayother) {\r
2821                                   if (first.sendTime) {\r
2822                                     SendTimeRemaining(&first, FALSE);\r
2823                                   }\r
2824                                   SendToProgram("playother\n", &first);\r
2825                                   firstMove = FALSE;\r
2826                                 } else {\r
2827                                   firstMove = TRUE;\r
2828                                 }\r
2829                             }\r
2830                         }                       \r
2831                     }\r
2832 #endif\r
2833                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2834                         /* Moves came from oldmoves or moves command\r
2835                            while we weren't doing anything else.\r
2836                            */\r
2837                         currentMove = forwardMostMove;\r
2838                         ClearHighlights();/*!!could figure this out*/\r
2839                         flipView = appData.flipView;\r
2840                         DrawPosition(FALSE, boards[currentMove]);\r
2841                         DisplayBothClocks();\r
2842                         sprintf(str, "%s vs. %s",\r
2843                                 gameInfo.white, gameInfo.black);\r
2844                         DisplayTitle(str);\r
2845                         gameMode = IcsIdle;\r
2846                     } else {\r
2847                         /* Moves were history of an active game */\r
2848                         if (gameInfo.resultDetails != NULL) {\r
2849                             free(gameInfo.resultDetails);\r
2850                             gameInfo.resultDetails = NULL;\r
2851                         }\r
2852                     }\r
2853                     HistorySet(parseList, backwardMostMove,\r
2854                                forwardMostMove, currentMove-1);\r
2855                     DisplayMove(currentMove - 1);\r
2856                     if (started == STARTED_MOVES) next_out = i;\r
2857                     started = STARTED_NONE;\r
2858                     ics_getting_history = H_FALSE;\r
2859                     break;\r
2860 \r
2861                   case STARTED_OBSERVE:\r
2862                     started = STARTED_NONE;\r
2863                     SendToICS(ics_prefix);\r
2864                     SendToICS("refresh\n");\r
2865                     break;\r
2866 \r
2867                   default:\r
2868                     break;\r
2869                 }\r
2870                 if(bookHit) { // [HGM] book: simulate book reply\r
2871                     static char bookMove[MSG_SIZ]; // a bit generous?\r
2872 \r
2873                     programStats.nodes = programStats.depth = programStats.time = \r
2874                     programStats.score = programStats.got_only_move = 0;\r
2875                     sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
2876 \r
2877                     strcpy(bookMove, "move ");\r
2878                     strcat(bookMove, bookHit);\r
2879                     HandleMachineMove(bookMove, &first);\r
2880                 }\r
2881                 continue;\r
2882             }\r
2883             \r
2884             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2885                  started == STARTED_HOLDINGS ||\r
2886                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2887                 /* Accumulate characters in move list or board */\r
2888                 parse[parse_pos++] = buf[i];\r
2889             }\r
2890             \r
2891             /* Start of game messages.  Mostly we detect start of game\r
2892                when the first board image arrives.  On some versions\r
2893                of the ICS, though, we need to do a "refresh" after starting\r
2894                to observe in order to get the current board right away. */\r
2895             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2896                 started = STARTED_OBSERVE;\r
2897                 continue;\r
2898             }\r
2899 \r
2900             /* Handle auto-observe */\r
2901             if (appData.autoObserve &&\r
2902                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2903                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2904                 char *player;\r
2905                 /* Choose the player that was highlighted, if any. */\r
2906                 if (star_match[0][0] == '\033' ||\r
2907                     star_match[1][0] != '\033') {\r
2908                     player = star_match[0];\r
2909                 } else {\r
2910                     player = star_match[2];\r
2911                 }\r
2912                 sprintf(str, "%sobserve %s\n",\r
2913                         ics_prefix, StripHighlightAndTitle(player));\r
2914                 SendToICS(str);\r
2915 \r
2916                 /* Save ratings from notify string */\r
2917                 strcpy(player1Name, star_match[0]);\r
2918                 player1Rating = string_to_rating(star_match[1]);\r
2919                 strcpy(player2Name, star_match[2]);\r
2920                 player2Rating = string_to_rating(star_match[3]);\r
2921 \r
2922                 if (appData.debugMode)\r
2923                   fprintf(debugFP, \r
2924                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2925                           player1Name, player1Rating,\r
2926                           player2Name, player2Rating);\r
2927 \r
2928                 continue;\r
2929             }\r
2930 \r
2931             /* Deal with automatic examine mode after a game,\r
2932                and with IcsObserving -> IcsExamining transition */\r
2933             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2934                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2935 \r
2936                 int gamenum = atoi(star_match[0]);\r
2937                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2938                     gamenum == ics_gamenum) {\r
2939                     /* We were already playing or observing this game;\r
2940                        no need to refetch history */\r
2941                     gameMode = IcsExamining;\r
2942                     if (pausing) {\r
2943                         pauseExamForwardMostMove = forwardMostMove;\r
2944                     } else if (currentMove < forwardMostMove) {\r
2945                         ForwardInner(forwardMostMove);\r
2946                     }\r
2947                 } else {\r
2948                     /* I don't think this case really can happen */\r
2949                     SendToICS(ics_prefix);\r
2950                     SendToICS("refresh\n");\r
2951                 }\r
2952                 continue;\r
2953             }    \r
2954             \r
2955             /* Error messages */\r
2956             if (ics_user_moved) {\r
2957                 if (looking_at(buf, &i, "Illegal move") ||\r
2958                     looking_at(buf, &i, "Not a legal move") ||\r
2959                     looking_at(buf, &i, "Your king is in check") ||\r
2960                     looking_at(buf, &i, "It isn't your turn") ||\r
2961                     looking_at(buf, &i, "It is not your move")) {\r
2962                     /* Illegal move */\r
2963                     ics_user_moved = 0;\r
2964                     if (forwardMostMove > backwardMostMove) {\r
2965                         currentMove = --forwardMostMove;\r
2966                         DisplayMove(currentMove - 1); /* before DMError */\r
2967                         DisplayMoveError(_("Illegal move (rejected by ICS)"));\r
2968                         DrawPosition(FALSE, boards[currentMove]);\r
2969                         SwitchClocks();\r
2970                         DisplayBothClocks();\r
2971                     }\r
2972                     continue;\r
2973                 }\r
2974             }\r
2975 \r
2976             if (looking_at(buf, &i, "still have time") ||\r
2977                 looking_at(buf, &i, "not out of time") ||\r
2978                 looking_at(buf, &i, "either player is out of time") ||\r
2979                 looking_at(buf, &i, "has timeseal; checking")) {\r
2980                 /* We must have called his flag a little too soon */\r
2981                 whiteFlag = blackFlag = FALSE;\r
2982                 continue;\r
2983             }\r
2984 \r
2985             if (looking_at(buf, &i, "added * seconds to") ||\r
2986                 looking_at(buf, &i, "seconds were added to")) {\r
2987                 /* Update the clocks */\r
2988                 SendToICS(ics_prefix);\r
2989                 SendToICS("refresh\n");\r
2990                 continue;\r
2991             }\r
2992 \r
2993             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2994                 ics_clock_paused = TRUE;\r
2995                 StopClocks();\r
2996                 continue;\r
2997             }\r
2998 \r
2999             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
3000                 ics_clock_paused = FALSE;\r
3001                 StartClocks();\r
3002                 continue;\r
3003             }\r
3004 \r
3005             /* Grab player ratings from the Creating: message.\r
3006                Note we have to check for the special case when\r
3007                the ICS inserts things like [white] or [black]. */\r
3008             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
3009                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
3010                 /* star_matches:\r
3011                    0    player 1 name (not necessarily white)\r
3012                    1    player 1 rating\r
3013                    2    empty, white, or black (IGNORED)\r
3014                    3    player 2 name (not necessarily black)\r
3015                    4    player 2 rating\r
3016                    \r
3017                    The names/ratings are sorted out when the game\r
3018                    actually starts (below).\r
3019                 */\r
3020                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
3021                 player1Rating = string_to_rating(star_match[1]);\r
3022                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
3023                 player2Rating = string_to_rating(star_match[4]);\r
3024 \r
3025                 if (appData.debugMode)\r
3026                   fprintf(debugFP, \r
3027                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
3028                           player1Name, player1Rating,\r
3029                           player2Name, player2Rating);\r
3030 \r
3031                 continue;\r
3032             }\r
3033             \r
3034             /* Improved generic start/end-of-game messages */\r
3035             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
3036                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
3037                 /* If tkind == 0: */\r
3038                 /* star_match[0] is the game number */\r
3039                 /*           [1] is the white player's name */\r
3040                 /*           [2] is the black player's name */\r
3041                 /* For end-of-game: */\r
3042                 /*           [3] is the reason for the game end */\r
3043                 /*           [4] is a PGN end game-token, preceded by " " */\r
3044                 /* For start-of-game: */\r
3045                 /*           [3] begins with "Creating" or "Continuing" */\r
3046                 /*           [4] is " *" or empty (don't care). */\r
3047                 int gamenum = atoi(star_match[0]);\r
3048                 char *whitename, *blackname, *why, *endtoken;\r
3049                 ChessMove endtype = (ChessMove) 0;\r
3050 \r
3051                 if (tkind == 0) {\r
3052                   whitename = star_match[1];\r
3053                   blackname = star_match[2];\r
3054                   why = star_match[3];\r
3055                   endtoken = star_match[4];\r
3056                 } else {\r
3057                   whitename = star_match[1];\r
3058                   blackname = star_match[3];\r
3059                   why = star_match[5];\r
3060                   endtoken = star_match[6];\r
3061                 }\r
3062 \r
3063                 /* Game start messages */\r
3064                 if (strncmp(why, "Creating ", 9) == 0 ||\r
3065                     strncmp(why, "Continuing ", 11) == 0) {\r
3066                     gs_gamenum = gamenum;\r
3067                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
3068 #if ZIPPY\r
3069                     if (appData.zippyPlay) {\r
3070                         ZippyGameStart(whitename, blackname);\r
3071                     }\r
3072 #endif /*ZIPPY*/\r
3073                     continue;\r
3074                 }\r
3075 \r
3076                 /* Game end messages */\r
3077                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
3078                     ics_gamenum != gamenum) {\r
3079                     continue;\r
3080                 }\r
3081                 while (endtoken[0] == ' ') endtoken++;\r
3082                 switch (endtoken[0]) {\r
3083                   case '*':\r
3084                   default:\r
3085                     endtype = GameUnfinished;\r
3086                     break;\r
3087                   case '0':\r
3088                     endtype = BlackWins;\r
3089                     break;\r
3090                   case '1':\r
3091                     if (endtoken[1] == '/')\r
3092                       endtype = GameIsDrawn;\r
3093                     else\r
3094                       endtype = WhiteWins;\r
3095                     break;\r
3096                 }\r
3097                 GameEnds(endtype, why, GE_ICS);\r
3098 #if ZIPPY\r
3099                 if (appData.zippyPlay && first.initDone) {\r
3100                     ZippyGameEnd(endtype, why);\r
3101                     if (first.pr == NULL) {\r
3102                       /* Start the next process early so that we'll\r
3103                          be ready for the next challenge */\r
3104                       StartChessProgram(&first);\r
3105                     }\r
3106                     /* Send "new" early, in case this command takes\r
3107                        a long time to finish, so that we'll be ready\r
3108                        for the next challenge. */\r
3109                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'\r
3110                     Reset(TRUE, TRUE);\r
3111                 }\r
3112 #endif /*ZIPPY*/\r
3113                 continue;\r
3114             }\r
3115 \r
3116             if (looking_at(buf, &i, "Removing game * from observation") ||\r
3117                 looking_at(buf, &i, "no longer observing game *") ||\r
3118                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
3119                 if (gameMode == IcsObserving &&\r
3120                     atoi(star_match[0]) == ics_gamenum)\r
3121                   {\r
3122                       /* icsEngineAnalyze */\r
3123                       if (appData.icsEngineAnalyze) {\r
3124                             ExitAnalyzeMode();\r
3125                             ModeHighlight();\r
3126                       }\r
3127                       StopClocks();\r
3128                       gameMode = IcsIdle;\r
3129                       ics_gamenum = -1;\r
3130                       ics_user_moved = FALSE;\r
3131                   }\r
3132                 continue;\r
3133             }\r
3134 \r
3135             if (looking_at(buf, &i, "no longer examining game *")) {\r
3136                 if (gameMode == IcsExamining &&\r
3137                     atoi(star_match[0]) == ics_gamenum)\r
3138                   {\r
3139                       gameMode = IcsIdle;\r
3140                       ics_gamenum = -1;\r
3141                       ics_user_moved = FALSE;\r
3142                   }\r
3143                 continue;\r
3144             }\r
3145 \r
3146             /* Advance leftover_start past any newlines we find,\r
3147                so only partial lines can get reparsed */\r
3148             if (looking_at(buf, &i, "\n")) {\r
3149                 prevColor = curColor;\r
3150                 if (curColor != ColorNormal) {\r
3151                     if (oldi > next_out) {\r
3152                         SendToPlayer(&buf[next_out], oldi - next_out);\r
3153                         next_out = oldi;\r
3154                     }\r
3155                     Colorize(ColorNormal, FALSE);\r
3156                     curColor = ColorNormal;\r
3157                 }\r
3158                 if (started == STARTED_BOARD) {\r
3159                     started = STARTED_NONE;\r
3160                     parse[parse_pos] = NULLCHAR;\r
3161                     ParseBoard12(parse);\r
3162                     ics_user_moved = 0;\r
3163 \r
3164                     /* Send premove here */\r
3165                     if (appData.premove) {\r
3166                       char str[MSG_SIZ];\r
3167                       if (currentMove == 0 &&\r
3168                           gameMode == IcsPlayingWhite &&\r
3169                           appData.premoveWhite) {\r
3170                         sprintf(str, "%s%s\n", ics_prefix,\r
3171                                 appData.premoveWhiteText);\r
3172                         if (appData.debugMode)\r
3173                           fprintf(debugFP, "Sending premove:\n");\r
3174                         SendToICS(str);\r
3175                       } else if (currentMove == 1 &&\r
3176                                  gameMode == IcsPlayingBlack &&\r
3177                                  appData.premoveBlack) {\r
3178                         sprintf(str, "%s%s\n", ics_prefix,\r
3179                                 appData.premoveBlackText);\r
3180                         if (appData.debugMode)\r
3181                           fprintf(debugFP, "Sending premove:\n");\r
3182                         SendToICS(str);\r
3183                       } else if (gotPremove) {\r
3184                         gotPremove = 0;\r
3185                         ClearPremoveHighlights();\r
3186                         if (appData.debugMode)\r
3187                           fprintf(debugFP, "Sending premove:\n");\r
3188                           UserMoveEvent(premoveFromX, premoveFromY, \r
3189                                         premoveToX, premoveToY, \r
3190                                         premovePromoChar);\r
3191                       }\r
3192                     }\r
3193 \r
3194                     /* Usually suppress following prompt */\r
3195                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
3196                         if (looking_at(buf, &i, "*% ")) {\r
3197                             savingComment = FALSE;\r
3198                         }\r
3199                     }\r
3200                     next_out = i;\r
3201                 } else if (started == STARTED_HOLDINGS) {\r
3202                     int gamenum;\r
3203                     char new_piece[MSG_SIZ];\r
3204                     started = STARTED_NONE;\r
3205                     parse[parse_pos] = NULLCHAR;\r
3206                     if (appData.debugMode)\r
3207                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
3208                                                         parse, currentMove);\r
3209                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
3210                         gamenum == ics_gamenum) {\r
3211                         if (gameInfo.variant == VariantNormal) {\r
3212                           /* [HGM] We seem to switch variant during a game!\r
3213                            * Presumably no holdings were displayed, so we have\r
3214                            * to move the position two files to the right to\r
3215                            * create room for them!\r
3216                            */\r
3217                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
3218                           /* Get a move list just to see the header, which\r
3219                              will tell us whether this is really bug or zh */\r
3220                           if (ics_getting_history == H_FALSE) {\r
3221                             ics_getting_history = H_REQUESTED;\r
3222                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3223                             SendToICS(str);\r
3224                           }\r
3225                         }\r
3226                         new_piece[0] = NULLCHAR;\r
3227                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
3228                                &gamenum, white_holding, black_holding,\r
3229                                new_piece);\r
3230                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
3231                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
3232                         /* [HGM] copy holdings to board holdings area */\r
3233                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
3234                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
3235 #if ZIPPY\r
3236                         if (appData.zippyPlay && first.initDone) {\r
3237                             ZippyHoldings(white_holding, black_holding,\r
3238                                           new_piece);\r
3239                         }\r
3240 #endif /*ZIPPY*/\r
3241                         if (tinyLayout || smallLayout) {\r
3242                             char wh[16], bh[16];\r
3243                             PackHolding(wh, white_holding);\r
3244                             PackHolding(bh, black_holding);\r
3245                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
3246                                     gameInfo.white, gameInfo.black);\r
3247                         } else {\r
3248                             sprintf(str, "%s [%s] vs. %s [%s]",\r
3249                                     gameInfo.white, white_holding,\r
3250                                     gameInfo.black, black_holding);\r
3251                         }\r
3252 \r
3253                         DrawPosition(FALSE, boards[currentMove]);\r
3254                         DisplayTitle(str);\r
3255                     }\r
3256                     /* Suppress following prompt */\r
3257                     if (looking_at(buf, &i, "*% ")) {\r
3258                         savingComment = FALSE;\r
3259                     }\r
3260                     next_out = i;\r
3261                 }\r
3262                 continue;\r
3263             }\r
3264 \r
3265             i++;                /* skip unparsed character and loop back */\r
3266         }\r
3267         \r
3268         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window\r
3269             started != STARTED_HOLDINGS && i > next_out) {\r
3270             SendToPlayer(&buf[next_out], i - next_out);\r
3271             next_out = i;\r
3272         }\r
3273         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above\r
3274         \r
3275         leftover_len = buf_len - leftover_start;\r
3276         /* if buffer ends with something we couldn't parse,\r
3277            reparse it after appending the next read */\r
3278         \r
3279     } else if (count == 0) {\r
3280         RemoveInputSource(isr);\r
3281         DisplayFatalError(_("Connection closed by ICS"), 0, 0);\r
3282     } else {\r
3283         DisplayFatalError(_("Error reading from ICS"), error, 1);\r
3284     }\r
3285 }\r
3286 \r
3287 \r
3288 /* Board style 12 looks like this:\r
3289    \r
3290    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0\r
3291    \r
3292  * The "<12> " is stripped before it gets to this routine.  The two\r
3293  * trailing 0's (flip state and clock ticking) are later addition, and\r
3294  * some chess servers may not have them, or may have only the first.\r
3295  * Additional trailing fields may be added in the future.  \r
3296  */\r
3297 \r
3298 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"\r
3299 \r
3300 #define RELATION_OBSERVING_PLAYED    0\r
3301 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
3302 #define RELATION_PLAYING_MYMOVE      1\r
3303 #define RELATION_PLAYING_NOTMYMOVE  -1\r
3304 #define RELATION_EXAMINING           2\r
3305 #define RELATION_ISOLATED_BOARD     -3\r
3306 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
3307 \r
3308 void\r
3309 ParseBoard12(string)\r
3310      char *string;\r
3311\r
3312     GameMode newGameMode;\r
3313     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;\r
3314     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;\r
3315     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
3316     char to_play, board_chars[200];\r
3317     char move_str[500], str[500], elapsed_time[500];\r
3318     char black[32], white[32];\r
3319     Board board;\r
3320     int prevMove = currentMove;\r
3321     int ticking = 2;\r
3322     ChessMove moveType;\r
3323     int fromX, fromY, toX, toY;\r
3324     char promoChar;\r
3325     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */\r
3326     char *bookHit = NULL; // [HGM] book\r
3327 \r
3328     fromX = fromY = toX = toY = -1;\r
3329     \r
3330     newGame = FALSE;\r
3331 \r
3332     if (appData.debugMode)\r
3333       fprintf(debugFP, _("Parsing board: %s\n"), string);\r
3334 \r
3335     move_str[0] = NULLCHAR;\r
3336     elapsed_time[0] = NULLCHAR;\r
3337     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */\r
3338         int  i = 0, j;\r
3339         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {\r
3340             if(string[i] == ' ') { ranks++; files = 0; }\r
3341             else files++;\r
3342             i++;\r
3343         }\r
3344         for(j = 0; j <i; j++) board_chars[j] = string[j];\r
3345         board_chars[i] = '\0';\r
3346         string += i + 1;\r
3347     }\r
3348     n = sscanf(string, PATTERN, &to_play, &double_push,\r
3349                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
3350                &gamenum, white, black, &relation, &basetime, &increment,\r
3351                &white_stren, &black_stren, &white_time, &black_time,\r
3352                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
3353                &ticking);\r
3354 \r
3355     if (n < 21) {\r
3356         sprintf(str, _("Failed to parse board string:\n\"%s\""), string);\r
3357         DisplayError(str, 0);\r
3358         return;\r
3359     }\r
3360 \r
3361     /* Convert the move number to internal form */\r
3362     moveNum = (moveNum - 1) * 2;\r
3363     if (to_play == 'B') moveNum++;\r
3364     if (moveNum >= MAX_MOVES) {\r
3365       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
3366                         0, 1);\r
3367       return;\r
3368     }\r
3369     \r
3370     switch (relation) {\r
3371       case RELATION_OBSERVING_PLAYED:\r
3372       case RELATION_OBSERVING_STATIC:\r
3373         if (gamenum == -1) {\r
3374             /* Old ICC buglet */\r
3375             relation = RELATION_OBSERVING_STATIC;\r
3376         }\r
3377         newGameMode = IcsObserving;\r
3378         break;\r
3379       case RELATION_PLAYING_MYMOVE:\r
3380       case RELATION_PLAYING_NOTMYMOVE:\r
3381         newGameMode =\r
3382           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3383             IcsPlayingWhite : IcsPlayingBlack;\r
3384         break;\r
3385       case RELATION_EXAMINING:\r
3386         newGameMode = IcsExamining;\r
3387         break;\r
3388       case RELATION_ISOLATED_BOARD:\r
3389       default:\r
3390         /* Just display this board.  If user was doing something else,\r
3391            we will forget about it until the next board comes. */ \r
3392         newGameMode = IcsIdle;\r
3393         break;\r
3394       case RELATION_STARTING_POSITION:\r
3395         newGameMode = gameMode;\r
3396         break;\r
3397     }\r
3398     \r
3399     /* Modify behavior for initial board display on move listing\r
3400        of wild games.\r
3401        */\r
3402     switch (ics_getting_history) {\r
3403       case H_FALSE:\r
3404       case H_REQUESTED:\r
3405         break;\r
3406       case H_GOT_REQ_HEADER:\r
3407       case H_GOT_UNREQ_HEADER:\r
3408         /* This is the initial position of the current game */\r
3409         gamenum = ics_gamenum;\r
3410         moveNum = 0;            /* old ICS bug workaround */\r
3411         if (to_play == 'B') {\r
3412           startedFromSetupPosition = TRUE;\r
3413           blackPlaysFirst = TRUE;\r
3414           moveNum = 1;\r
3415           if (forwardMostMove == 0) forwardMostMove = 1;\r
3416           if (backwardMostMove == 0) backwardMostMove = 1;\r
3417           if (currentMove == 0) currentMove = 1;\r
3418         }\r
3419         newGameMode = gameMode;\r
3420         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3421         break;\r
3422       case H_GOT_UNWANTED_HEADER:\r
3423         /* This is an initial board that we don't want */\r
3424         return;\r
3425       case H_GETTING_MOVES:\r
3426         /* Should not happen */\r
3427         DisplayError(_("Error gathering move list: extra board"), 0);\r
3428         ics_getting_history = H_FALSE;\r
3429         return;\r
3430     }\r
3431     \r
3432     /* Take action if this is the first board of a new game, or of a\r
3433        different game than is currently being displayed.  */\r
3434     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3435         relation == RELATION_ISOLATED_BOARD) {\r
3436         \r
3437         /* Forget the old game and get the history (if any) of the new one */\r
3438         if (gameMode != BeginningOfGame) {\r
3439           Reset(FALSE, TRUE);\r
3440         }\r
3441         newGame = TRUE;\r
3442         if (appData.autoRaiseBoard) BoardToTop();\r
3443         prevMove = -3;\r
3444         if (gamenum == -1) {\r
3445             newGameMode = IcsIdle;\r
3446         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3447                    appData.getMoveList) {\r
3448             /* Need to get game history */\r
3449             ics_getting_history = H_REQUESTED;\r
3450             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3451             SendToICS(str);\r
3452         }\r
3453         \r
3454         /* Initially flip the board to have black on the bottom if playing\r
3455            black or if the ICS flip flag is set, but let the user change\r
3456            it with the Flip View button. */\r
3457         flipView = appData.autoFlipView ? \r
3458           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3459           appData.flipView;\r
3460         \r
3461         /* Done with values from previous mode; copy in new ones */\r
3462         gameMode = newGameMode;\r
3463         ModeHighlight();\r
3464         ics_gamenum = gamenum;\r
3465         if (gamenum == gs_gamenum) {\r
3466             int klen = strlen(gs_kind);\r
3467             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3468             sprintf(str, "ICS %s", gs_kind);\r
3469             gameInfo.event = StrSave(str);\r
3470         } else {\r
3471             gameInfo.event = StrSave("ICS game");\r
3472         }\r
3473         gameInfo.site = StrSave(appData.icsHost);\r
3474         gameInfo.date = PGNDate();\r
3475         gameInfo.round = StrSave("-");\r
3476         gameInfo.white = StrSave(white);\r
3477         gameInfo.black = StrSave(black);\r
3478         timeControl = basetime * 60 * 1000;\r
3479         timeControl_2 = 0;\r
3480         timeIncrement = increment * 1000;\r
3481         movesPerSession = 0;\r
3482         gameInfo.timeControl = TimeControlTagValue();\r
3483         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3484   if (appData.debugMode) {\r
3485     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3486     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3487     setbuf(debugFP, NULL);\r
3488   }\r
3489 \r
3490         gameInfo.outOfBook = NULL;\r
3491         \r
3492         /* Do we have the ratings? */\r
3493         if (strcmp(player1Name, white) == 0 &&\r
3494             strcmp(player2Name, black) == 0) {\r
3495             if (appData.debugMode)\r
3496               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3497                       player1Rating, player2Rating);\r
3498             gameInfo.whiteRating = player1Rating;\r
3499             gameInfo.blackRating = player2Rating;\r
3500         } else if (strcmp(player2Name, white) == 0 &&\r
3501                    strcmp(player1Name, black) == 0) {\r
3502             if (appData.debugMode)\r
3503               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3504                       player2Rating, player1Rating);\r
3505             gameInfo.whiteRating = player2Rating;\r
3506             gameInfo.blackRating = player1Rating;\r
3507         }\r
3508         player1Name[0] = player2Name[0] = NULLCHAR;\r
3509 \r
3510         /* Silence shouts if requested */\r
3511         if (appData.quietPlay &&\r
3512             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3513             SendToICS(ics_prefix);\r
3514             SendToICS("set shout 0\n");\r
3515         }\r
3516     }\r
3517     \r
3518     /* Deal with midgame name changes */\r
3519     if (!newGame) {\r
3520         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3521             if (gameInfo.white) free(gameInfo.white);\r
3522             gameInfo.white = StrSave(white);\r
3523         }\r
3524         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3525             if (gameInfo.black) free(gameInfo.black);\r
3526             gameInfo.black = StrSave(black);\r
3527         }\r
3528     }\r
3529     \r
3530     /* Throw away game result if anything actually changes in examine mode */\r
3531     if (gameMode == IcsExamining && !newGame) {\r
3532         gameInfo.result = GameUnfinished;\r
3533         if (gameInfo.resultDetails != NULL) {\r
3534             free(gameInfo.resultDetails);\r
3535             gameInfo.resultDetails = NULL;\r
3536         }\r
3537     }\r
3538     \r
3539     /* In pausing && IcsExamining mode, we ignore boards coming\r
3540        in if they are in a different variation than we are. */\r
3541     if (pauseExamInvalid) return;\r
3542     if (pausing && gameMode == IcsExamining) {\r
3543         if (moveNum <= pauseExamForwardMostMove) {\r
3544             pauseExamInvalid = TRUE;\r
3545             forwardMostMove = pauseExamForwardMostMove;\r
3546             return;\r
3547         }\r
3548     }\r
3549     \r
3550   if (appData.debugMode) {\r
3551     fprintf(debugFP, "load %dx%d board\n", files, ranks);\r
3552   }\r
3553     /* Parse the board */\r
3554     for (k = 0; k < ranks; k++) {\r
3555       for (j = 0; j < files; j++)\r
3556         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);\r
3557       if(gameInfo.holdingsWidth > 1) {\r
3558            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3559            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3560       }\r
3561     }\r
3562     CopyBoard(boards[moveNum], board);\r
3563     if (moveNum == 0) {\r
3564         startedFromSetupPosition =\r
3565           !CompareBoards(board, initialPosition);\r
3566         if(startedFromSetupPosition)\r
3567             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */\r
3568     }\r
3569 \r
3570     /* [HGM] Set castling rights. Take the outermost Rooks,\r
3571        to make it also work for FRC opening positions. Note that board12\r
3572        is really defective for later FRC positions, as it has no way to\r
3573        indicate which Rook can castle if they are on the same side of King.\r
3574        For the initial position we grant rights to the outermost Rooks,\r
3575        and remember thos rights, and we then copy them on positions\r
3576        later in an FRC game. This means WB might not recognize castlings with\r
3577        Rooks that have moved back to their original position as illegal,\r
3578        but in ICS mode that is not its job anyway.\r
3579     */\r
3580     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)\r
3581     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;\r
3582 \r
3583         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3584             if(board[0][i] == WhiteRook) j = i;\r
3585         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3586         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3587             if(board[0][i] == WhiteRook) j = i;\r
3588         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3589         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3590             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3591         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3592         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3593             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3594         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3595 \r
3596         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }\r
3597         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3598             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;\r
3599         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3600             if(board[BOARD_HEIGHT-1][k] == bKing)\r
3601                 initialRights[5] = castlingRights[moveNum][5] = k;\r
3602     } else { int r;\r
3603         r = castlingRights[moveNum][0] = initialRights[0];\r
3604         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;\r
3605         r = castlingRights[moveNum][1] = initialRights[1];\r
3606         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;\r
3607         r = castlingRights[moveNum][3] = initialRights[3];\r
3608         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;\r
3609         r = castlingRights[moveNum][4] = initialRights[4];\r
3610         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;\r
3611         /* wildcastle kludge: always assume King has rights */\r
3612         r = castlingRights[moveNum][2] = initialRights[2];\r
3613         r = castlingRights[moveNum][5] = initialRights[5];\r
3614     }\r
3615     /* [HGM] e.p. rights. Assume that ICS sends file number here? */\r
3616     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;\r
3617 \r
3618     \r
3619     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3620         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3621         /* This was an initial position from a move list, not\r
3622            the current position */\r
3623         return;\r
3624     }\r
3625     \r
3626     /* Update currentMove and known move number limits */\r
3627     newMove = newGame || moveNum > forwardMostMove;\r
3628 \r
3629     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */\r
3630     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {\r
3631         takeback = forwardMostMove - moveNum;\r
3632         for (i = 0; i < takeback; i++) {\r
3633              if (appData.debugMode) fprintf(debugFP, "take back move\n");\r
3634              SendToProgram("undo\n", &first);\r
3635         }\r
3636     }\r
3637 \r
3638     if (newGame) {\r
3639         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3640         if (gameMode == IcsExamining && moveNum == 0) {\r
3641           /* Workaround for ICS limitation: we are not told the wild\r
3642              type when starting to examine a game.  But if we ask for\r
3643              the move list, the move list header will tell us */\r
3644             ics_getting_history = H_REQUESTED;\r
3645             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3646             SendToICS(str);\r
3647         }\r
3648     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3649                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3650         forwardMostMove = moveNum;\r
3651         if (!pausing || currentMove > forwardMostMove)\r
3652           currentMove = forwardMostMove;\r
3653     } else {\r
3654         /* New part of history that is not contiguous with old part */ \r
3655         if (pausing && gameMode == IcsExamining) {\r
3656             pauseExamInvalid = TRUE;\r
3657             forwardMostMove = pauseExamForwardMostMove;\r
3658             return;\r
3659         }\r
3660         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3661         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3662             ics_getting_history = H_REQUESTED;\r
3663             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3664             SendToICS(str);\r
3665         }\r
3666     }\r
3667     \r
3668     /* Update the clocks */\r
3669     if (strchr(elapsed_time, '.')) {\r
3670       /* Time is in ms */\r
3671       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3672       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3673     } else {\r
3674       /* Time is in seconds */\r
3675       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3676       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3677     }\r
3678       \r
3679 \r
3680 #if ZIPPY\r
3681     if (appData.zippyPlay && newGame &&\r
3682         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3683         gameMode != IcsExamining)\r
3684       ZippyFirstBoard(moveNum, basetime, increment);\r
3685 #endif\r
3686     \r
3687     /* Put the move on the move list, first converting\r
3688        to canonical algebraic form. */\r
3689     if (moveNum > 0) {\r
3690   if (appData.debugMode) {\r
3691     if (appData.debugMode) { int f = forwardMostMove;\r
3692         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,\r
3693                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
3694     }\r
3695     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3696     fprintf(debugFP, "moveNum = %d\n", moveNum);\r
3697     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3698     setbuf(debugFP, NULL);\r
3699   }\r
3700         if (moveNum <= backwardMostMove) {\r
3701             /* We don't know what the board looked like before\r
3702                this move.  Punt. */\r
3703             strcpy(parseList[moveNum - 1], move_str);\r
3704             strcat(parseList[moveNum - 1], " ");\r
3705             strcat(parseList[moveNum - 1], elapsed_time);\r
3706             moveList[moveNum - 1][0] = NULLCHAR;\r
3707         } else if (strcmp(move_str, "none") == 0) {\r
3708             // [HGM] long SAN: swapped order; test for 'none' before parsing move\r
3709             /* Again, we don't know what the board looked like;\r
3710                this is really the start of the game. */\r
3711             parseList[moveNum - 1][0] = NULLCHAR;\r
3712             moveList[moveNum - 1][0] = NULLCHAR;\r
3713             backwardMostMove = moveNum;\r
3714             startedFromSetupPosition = TRUE;\r
3715             fromX = fromY = toX = toY = -1;\r
3716         } else {\r
3717           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. \r
3718           //                 So we parse the long-algebraic move string in stead of the SAN move\r
3719           int valid; char buf[MSG_SIZ], *prom;\r
3720 \r
3721           // str looks something like "Q/a1-a2"; kill the slash\r
3722           if(str[1] == '/') \r
3723                 sprintf(buf, "%c%s", str[0], str+2);\r
3724           else  strcpy(buf, str); // might be castling\r
3725           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) \r
3726                 strcat(buf, prom); // long move lacks promo specification!\r
3727           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)\r
3728                 if(appData.debugMode) \r
3729                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);\r
3730                 strcpy(move_str, buf);\r
3731           }\r
3732           valid = ParseOneMove(move_str, moveNum - 1, &moveType,\r
3733                                 &fromX, &fromY, &toX, &toY, &promoChar)\r
3734                || ParseOneMove(buf, moveNum - 1, &moveType,\r
3735                                 &fromX, &fromY, &toX, &toY, &promoChar);\r
3736           // end of long SAN patch\r
3737           if (valid) {\r
3738             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3739                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3740                                      fromY, fromX, toY, toX, promoChar,\r
3741                                      parseList[moveNum-1]);\r
3742             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3743                              castlingRights[moveNum]) ) {\r
3744               case MT_NONE:\r
3745               case MT_STALEMATE:\r
3746               default:\r
3747                 break;\r
3748               case MT_CHECK:\r
3749                 if(gameInfo.variant != VariantShogi)\r
3750                     strcat(parseList[moveNum - 1], "+");\r
3751                 break;\r
3752               case MT_CHECKMATE:\r
3753               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate\r
3754                 strcat(parseList[moveNum - 1], "#");\r
3755                 break;\r
3756             }\r
3757             strcat(parseList[moveNum - 1], " ");\r
3758             strcat(parseList[moveNum - 1], elapsed_time);\r
3759             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3760             strcpy(moveList[moveNum - 1], currentMoveString);\r
3761             strcat(moveList[moveNum - 1], "\n");\r
3762           } else {\r
3763             /* Move from ICS was illegal!?  Punt. */\r
3764   if (appData.debugMode) {\r
3765     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3766     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3767   }\r
3768 #if 0\r
3769             if (appData.testLegality && appData.debugMode) {\r
3770                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3771                 DisplayError(str, 0);\r
3772             }\r
3773 #endif\r
3774             strcpy(parseList[moveNum - 1], move_str);\r
3775             strcat(parseList[moveNum - 1], " ");\r
3776             strcat(parseList[moveNum - 1], elapsed_time);\r
3777             moveList[moveNum - 1][0] = NULLCHAR;\r
3778             fromX = fromY = toX = toY = -1;\r
3779           }\r
3780         }\r
3781   if (appData.debugMode) {\r
3782     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3783     setbuf(debugFP, NULL);\r
3784   }\r
3785 \r
3786 #if ZIPPY\r
3787         /* Send move to chess program (BEFORE animating it). */\r
3788         if (appData.zippyPlay && !newGame && newMove && \r
3789            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3790 \r
3791             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3792                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3793                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3794                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),\r
3795                             move_str);\r
3796                     DisplayError(str, 0);\r
3797                 } else {\r
3798                     if (first.sendTime) {\r
3799                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3800                     }\r
3801                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book\r
3802                     if (firstMove && !bookHit) {\r
3803                         firstMove = FALSE;\r
3804                         if (first.useColors) {\r
3805                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3806                                         "white\ngo\n" :\r
3807                                         "black\ngo\n", &first);\r
3808                         } else {\r
3809                           SendToProgram("go\n", &first);\r
3810                         }\r
3811                         first.maybeThinking = TRUE;\r
3812                     }\r
3813                 }\r
3814             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3815               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3816                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);\r
3817                 DisplayError(str, 0);\r
3818               } else {\r
3819                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!\r
3820                 SendMoveToProgram(moveNum - 1, &first);\r
3821               }\r
3822             }\r
3823         }\r
3824 #endif\r
3825     }\r
3826 \r
3827     if (moveNum > 0 && !gotPremove) {\r
3828         /* If move comes from a remote source, animate it.  If it\r
3829            isn't remote, it will have already been animated. */\r
3830         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3831             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3832         }\r
3833         if (!pausing && appData.highlightLastMove) {\r
3834             SetHighlights(fromX, fromY, toX, toY);\r
3835         }\r
3836     }\r
3837     \r
3838     /* Start the clocks */\r
3839     whiteFlag = blackFlag = FALSE;\r
3840     appData.clockMode = !(basetime == 0 && increment == 0);\r
3841     if (ticking == 0) {\r
3842       ics_clock_paused = TRUE;\r
3843       StopClocks();\r
3844     } else if (ticking == 1) {\r
3845       ics_clock_paused = FALSE;\r
3846     }\r
3847     if (gameMode == IcsIdle ||\r
3848         relation == RELATION_OBSERVING_STATIC ||\r
3849         relation == RELATION_EXAMINING ||\r
3850         ics_clock_paused)\r
3851       DisplayBothClocks();\r
3852     else\r
3853       StartClocks();\r
3854     \r
3855     /* Display opponents and material strengths */\r
3856     if (gameInfo.variant != VariantBughouse &&\r
3857         gameInfo.variant != VariantCrazyhouse) {\r
3858         if (tinyLayout || smallLayout) {\r
3859             if(gameInfo.variant == VariantNormal)\r
3860                 sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3861                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3862                     basetime, increment);\r
3863             else\r
3864                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", \r
3865                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3866                     basetime, increment, (int) gameInfo.variant);\r
3867         } else {\r
3868             if(gameInfo.variant == VariantNormal)\r
3869                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3870                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3871                     basetime, increment);\r
3872             else\r
3873                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", \r
3874                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3875                     basetime, increment, VariantName(gameInfo.variant));\r
3876         }\r
3877         DisplayTitle(str);\r
3878   if (appData.debugMode) {\r
3879     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);\r
3880   }\r
3881     }\r
3882 \r
3883    \r
3884     /* Display the board */\r
3885     if (!pausing) {\r
3886       \r
3887       if (appData.premove)\r
3888           if (!gotPremove || \r
3889              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3890              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3891               ClearPremoveHighlights();\r
3892 \r
3893       DrawPosition(FALSE, boards[currentMove]);\r
3894       DisplayMove(moveNum - 1);\r
3895       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3896         RingBell();\r
3897     }\r
3898 \r
3899     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3900 #if ZIPPY\r
3901     if(bookHit) { // [HGM] book: simulate book reply\r
3902         static char bookMove[MSG_SIZ]; // a bit generous?\r
3903 \r
3904         programStats.nodes = programStats.depth = programStats.time = \r
3905         programStats.score = programStats.got_only_move = 0;\r
3906         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
3907 \r
3908         strcpy(bookMove, "move ");\r
3909         strcat(bookMove, bookHit);\r
3910         HandleMachineMove(bookMove, &first);\r
3911     }\r
3912 #endif\r
3913 }\r
3914 \r
3915 void\r
3916 GetMoveListEvent()\r
3917 {\r
3918     char buf[MSG_SIZ];\r
3919     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3920         ics_getting_history = H_REQUESTED;\r
3921         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3922         SendToICS(buf);\r
3923     }\r
3924 }\r
3925 \r
3926 void\r
3927 AnalysisPeriodicEvent(force)\r
3928      int force;\r
3929 {\r
3930     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3931          && !force) || !appData.periodicUpdates)\r
3932       return;\r
3933 \r
3934     /* Send . command to Crafty to collect stats */\r
3935     SendToProgram(".\n", &first);\r
3936 \r
3937     /* Don't send another until we get a response (this makes\r
3938        us stop sending to old Crafty's which don't understand\r
3939        the "." command (sending illegal cmds resets node count & time,\r
3940        which looks bad)) */\r
3941     programStats.ok_to_send = 0;\r
3942 }\r
3943 \r
3944 void\r
3945 SendMoveToProgram(moveNum, cps)\r
3946      int moveNum;\r
3947      ChessProgramState *cps;\r
3948 {\r
3949     char buf[MSG_SIZ];\r
3950 \r
3951     if (cps->useUsermove) {\r
3952       SendToProgram("usermove ", cps);\r
3953     }\r
3954     if (cps->useSAN) {\r
3955       char *space;\r
3956       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3957         int len = space - parseList[moveNum];\r
3958         memcpy(buf, parseList[moveNum], len);\r
3959         buf[len++] = '\n';\r
3960         buf[len] = NULLCHAR;\r
3961       } else {\r
3962         sprintf(buf, "%s\n", parseList[moveNum]);\r
3963       }\r
3964       SendToProgram(buf, cps);\r
3965     } else {\r
3966       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */\r
3967         AlphaRank(moveList[moveNum], 4);\r
3968         SendToProgram(moveList[moveNum], cps);\r
3969         AlphaRank(moveList[moveNum], 4); // and back\r
3970       } else\r
3971       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3972        * the engine. It would be nice to have a better way to identify castle \r
3973        * moves here. */\r
3974       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)\r
3975                                                                          && cps->useOOCastle) {\r
3976         int fromX = moveList[moveNum][0] - AAA; \r
3977         int fromY = moveList[moveNum][1] - ONE;\r
3978         int toX = moveList[moveNum][2] - AAA; \r
3979         int toY = moveList[moveNum][3] - ONE;\r
3980         if((boards[moveNum][fromY][fromX] == WhiteKing \r
3981             && boards[moveNum][toY][toX] == WhiteRook)\r
3982            || (boards[moveNum][fromY][fromX] == BlackKing \r
3983                && boards[moveNum][toY][toX] == BlackRook)) {\r
3984           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3985           else SendToProgram("O-O-O\n", cps);\r
3986         }\r
3987         else SendToProgram(moveList[moveNum], cps);\r
3988       }\r
3989       else SendToProgram(moveList[moveNum], cps);\r
3990       /* End of additions by Tord */\r
3991     }\r
3992 \r
3993     /* [HGM] setting up the opening has brought engine in force mode! */\r
3994     /*       Send 'go' if we are in a mode where machine should play. */\r
3995     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
3996         (gameMode == TwoMachinesPlay   ||\r
3997 #ifdef ZIPPY\r
3998          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||\r
3999 #endif\r
4000          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
4001         SendToProgram("go\n", cps);\r
4002   if (appData.debugMode) {\r
4003     fprintf(debugFP, "(extra)\n");\r
4004   }\r
4005     }\r
4006     setboardSpoiledMachineBlack = 0;\r
4007 }\r
4008 \r
4009 void\r
4010 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
4011      ChessMove moveType;\r
4012      int fromX, fromY, toX, toY;\r
4013 {\r
4014     char user_move[MSG_SIZ];\r
4015 \r
4016     switch (moveType) {\r
4017       default:\r
4018         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),\r
4019                 (int)moveType, fromX, fromY, toX, toY);\r
4020         DisplayError(user_move + strlen("say "), 0);\r
4021         break;\r
4022       case WhiteKingSideCastle:\r
4023       case BlackKingSideCastle:\r
4024       case WhiteQueenSideCastleWild:\r
4025       case BlackQueenSideCastleWild:\r
4026       /* PUSH Fabien */\r
4027       case WhiteHSideCastleFR:\r
4028       case BlackHSideCastleFR:\r
4029       /* POP Fabien */\r
4030         sprintf(user_move, "o-o\n");\r
4031         break;\r
4032       case WhiteQueenSideCastle:\r
4033       case BlackQueenSideCastle:\r
4034       case WhiteKingSideCastleWild:\r
4035       case BlackKingSideCastleWild:\r
4036       /* PUSH Fabien */\r
4037       case WhiteASideCastleFR:\r
4038       case BlackASideCastleFR:\r
4039       /* POP Fabien */\r
4040         sprintf(user_move, "o-o-o\n");\r
4041         break;\r
4042       case WhitePromotionQueen:\r
4043       case BlackPromotionQueen:\r
4044       case WhitePromotionRook:\r
4045       case BlackPromotionRook:\r
4046       case WhitePromotionBishop:\r
4047       case BlackPromotionBishop:\r
4048       case WhitePromotionKnight:\r
4049       case BlackPromotionKnight:\r
4050       case WhitePromotionKing:\r
4051       case BlackPromotionKing:\r
4052       case WhitePromotionChancellor:\r
4053       case BlackPromotionChancellor:\r
4054       case WhitePromotionArchbishop:\r
4055       case BlackPromotionArchbishop:\r
4056         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)\r
4057             sprintf(user_move, "%c%c%c%c=%c\n",\r
4058                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4059                 PieceToChar(WhiteFerz));\r
4060         else if(gameInfo.variant == VariantGreat)\r
4061             sprintf(user_move, "%c%c%c%c=%c\n",\r
4062                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4063                 PieceToChar(WhiteMan));\r
4064         else\r
4065             sprintf(user_move, "%c%c%c%c=%c\n",\r
4066                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4067                 PieceToChar(PromoPiece(moveType)));\r
4068         break;\r
4069       case WhiteDrop:\r
4070       case BlackDrop:\r
4071         sprintf(user_move, "%c@%c%c\n",\r
4072                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
4073                 AAA + toX, ONE + toY);\r
4074         break;\r
4075       case NormalMove:\r
4076       case WhiteCapturesEnPassant:\r
4077       case BlackCapturesEnPassant:\r
4078       case IllegalMove:  /* could be a variant we don't quite understand */\r
4079         sprintf(user_move, "%c%c%c%c\n",\r
4080                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
4081         break;\r
4082     }\r
4083     SendToICS(user_move);\r
4084 }\r
4085 \r
4086 void\r
4087 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
4088      int rf, ff, rt, ft;\r
4089      char promoChar;\r
4090      char move[7];\r
4091 {\r
4092     if (rf == DROP_RANK) {\r
4093         sprintf(move, "%c@%c%c\n",\r
4094                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
4095     } else {\r
4096         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
4097             sprintf(move, "%c%c%c%c\n",\r
4098                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
4099         } else {\r
4100             sprintf(move, "%c%c%c%c%c\n",\r
4101                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
4102         }\r
4103     }\r
4104 }\r
4105 \r
4106 void\r
4107 ProcessICSInitScript(f)\r
4108      FILE *f;\r
4109 {\r
4110     char buf[MSG_SIZ];\r
4111 \r
4112     while (fgets(buf, MSG_SIZ, f)) {\r
4113         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
4114     }\r
4115 \r
4116     fclose(f);\r
4117 }\r
4118 \r
4119 \r
4120 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
4121 void\r
4122 AlphaRank(char *move, int n)\r
4123 {\r
4124 //    char *p = move, c; int x, y;\r
4125 \r
4126     if (appData.debugMode) {\r
4127         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
4128     }\r
4129 \r
4130     if(move[1]=='*' && \r
4131        move[2]>='0' && move[2]<='9' &&\r
4132        move[3]>='a' && move[3]<='x'    ) {\r
4133         move[1] = '@';\r
4134         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4135         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4136     } else\r
4137     if(move[0]>='0' && move[0]<='9' &&\r
4138        move[1]>='a' && move[1]<='x' &&\r
4139        move[2]>='0' && move[2]<='9' &&\r
4140        move[3]>='a' && move[3]<='x'    ) {\r
4141         /* input move, Shogi -> normal */\r
4142         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;\r
4143         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\r
4144         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4145         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4146     } else\r
4147     if(move[1]=='@' &&\r
4148        move[3]>='0' && move[3]<='9' &&\r
4149        move[2]>='a' && move[2]<='x'    ) {\r
4150         move[1] = '*';\r
4151         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4152         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4153     } else\r
4154     if(\r
4155        move[0]>='a' && move[0]<='x' &&\r
4156        move[3]>='0' && move[3]<='9' &&\r
4157        move[2]>='a' && move[2]<='x'    ) {\r
4158          /* output move, normal -> Shogi */\r
4159         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
4160         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
4161         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4162         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4163         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
4164     }\r
4165     if (appData.debugMode) {\r
4166         fprintf(debugFP, "   out = '%s'\n", move);\r
4167     }\r
4168 }\r
4169 \r
4170 /* Parser for moves from gnuchess, ICS, or user typein box */\r
4171 Boolean\r
4172 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
4173      char *move;\r
4174      int moveNum;\r
4175      ChessMove *moveType;\r
4176      int *fromX, *fromY, *toX, *toY;\r
4177      char *promoChar;\r
4178 {       \r
4179     if (appData.debugMode) {\r
4180         fprintf(debugFP, "move to parse: %s\n", move);\r
4181     }\r
4182     *moveType = yylexstr(moveNum, move);\r
4183 \r
4184     switch (*moveType) {\r
4185       case WhitePromotionChancellor:\r
4186       case BlackPromotionChancellor:\r
4187       case WhitePromotionArchbishop:\r
4188       case BlackPromotionArchbishop:\r
4189       case WhitePromotionQueen:\r
4190       case BlackPromotionQueen:\r
4191       case WhitePromotionRook:\r
4192       case BlackPromotionRook:\r
4193       case WhitePromotionBishop:\r
4194       case BlackPromotionBishop:\r
4195       case WhitePromotionKnight:\r
4196       case BlackPromotionKnight:\r
4197       case WhitePromotionKing:\r
4198       case BlackPromotionKing:\r
4199       case NormalMove:\r
4200       case WhiteCapturesEnPassant:\r
4201       case BlackCapturesEnPassant:\r
4202       case WhiteKingSideCastle:\r
4203       case WhiteQueenSideCastle:\r
4204       case BlackKingSideCastle:\r
4205       case BlackQueenSideCastle:\r
4206       case WhiteKingSideCastleWild:\r
4207       case WhiteQueenSideCastleWild:\r
4208       case BlackKingSideCastleWild:\r
4209       case BlackQueenSideCastleWild:\r
4210       /* Code added by Tord: */\r
4211       case WhiteHSideCastleFR:\r
4212       case WhiteASideCastleFR:\r
4213       case BlackHSideCastleFR:\r
4214       case BlackASideCastleFR:\r
4215       /* End of code added by Tord */\r
4216       case IllegalMove:         /* bug or odd chess variant */\r
4217         *fromX = currentMoveString[0] - AAA;\r
4218         *fromY = currentMoveString[1] - ONE;\r
4219         *toX = currentMoveString[2] - AAA;\r
4220         *toY = currentMoveString[3] - ONE;\r
4221         *promoChar = currentMoveString[4];\r
4222         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
4223             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
4224     if (appData.debugMode) {\r
4225         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
4226     }\r
4227             *fromX = *fromY = *toX = *toY = 0;\r
4228             return FALSE;\r
4229         }\r
4230         if (appData.testLegality) {\r
4231           return (*moveType != IllegalMove);\r
4232         } else {\r
4233           return !(fromX == fromY && toX == toY);\r
4234         }\r
4235 \r
4236       case WhiteDrop:\r
4237       case BlackDrop:\r
4238         *fromX = *moveType == WhiteDrop ?\r
4239           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
4240           (int) CharToPiece(ToLower(currentMoveString[0]));\r
4241         *fromY = DROP_RANK;\r
4242         *toX = currentMoveString[2] - AAA;\r
4243         *toY = currentMoveString[3] - ONE;\r
4244         *promoChar = NULLCHAR;\r
4245         return TRUE;\r
4246 \r
4247       case AmbiguousMove:\r
4248       case ImpossibleMove:\r
4249       case (ChessMove) 0:       /* end of file */\r
4250       case ElapsedTime:\r
4251       case Comment:\r
4252       case PGNTag:\r
4253       case NAG:\r
4254       case WhiteWins:\r
4255       case BlackWins:\r
4256       case GameIsDrawn:\r
4257       default:\r
4258     if (appData.debugMode) {\r
4259         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
4260     }\r
4261         /* bug? */\r
4262         *fromX = *fromY = *toX = *toY = 0;\r
4263         *promoChar = NULLCHAR;\r
4264         return FALSE;\r
4265     }\r
4266 }\r
4267 \r
4268 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.\r
4269 // All positions will have equal probability, but the current method will not provide a unique\r
4270 // numbering scheme for arrays that contain 3 or more pieces of the same kind.\r
4271 #define DARK 1\r
4272 #define LITE 2\r
4273 #define ANY 3\r
4274 \r
4275 int squaresLeft[4];\r
4276 int piecesLeft[(int)BlackPawn];\r
4277 int seed, nrOfShuffles;\r
4278 \r
4279 void GetPositionNumber()\r
4280 {       // sets global variable seed\r
4281         int i;\r
4282 \r
4283         seed = appData.defaultFrcPosition;\r
4284         if(seed < 0) { // randomize based on time for negative FRC position numbers\r
4285                 for(i=0; i<50; i++) seed += random();\r
4286                 seed = random() ^ random() >> 8 ^ random() << 8;\r
4287                 if(seed<0) seed = -seed;\r
4288         }\r
4289 }\r
4290 \r
4291 int put(Board board, int pieceType, int rank, int n, int shade)\r
4292 // put the piece on the (n-1)-th empty squares of the given shade\r
4293 {\r
4294         int i;\r
4295 \r
4296         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
4297                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {\r
4298                         board[rank][i] = (ChessSquare) pieceType;\r
4299                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;\r
4300                         squaresLeft[ANY]--;\r
4301                         piecesLeft[pieceType]--; \r
4302                         return i;\r
4303                 }\r
4304         }\r
4305         return -1;\r
4306 }\r
4307 \r
4308 \r
4309 void AddOnePiece(Board board, int pieceType, int rank, int shade)\r
4310 // calculate where the next piece goes, (any empty square), and put it there\r
4311 {\r
4312         int i;\r
4313 \r
4314         i = seed % squaresLeft[shade];\r
4315         nrOfShuffles *= squaresLeft[shade];\r
4316         seed /= squaresLeft[shade];\r
4317         put(board, pieceType, rank, i, shade);\r
4318 }\r
4319 \r
4320 void AddTwoPieces(Board board, int pieceType, int rank)\r
4321 // calculate where the next 2 identical pieces go, (any empty square), and put it there\r
4322 {\r
4323         int i, n=squaresLeft[ANY], j=n-1, k;\r
4324 \r
4325         k = n*(n-1)/2; // nr of possibilities, not counting permutations\r
4326         i = seed % k;  // pick one\r
4327         nrOfShuffles *= k;\r
4328         seed /= k;\r
4329         while(i >= j) i -= j--;\r
4330         j = n - 1 - j; i += j;\r
4331         put(board, pieceType, rank, j, ANY);\r
4332         put(board, pieceType, rank, i, ANY);\r
4333 }\r
4334 \r
4335 void SetUpShuffle(Board board, int number)\r
4336 {\r
4337         int i, p, first=1;\r
4338 \r
4339         GetPositionNumber(); nrOfShuffles = 1;\r
4340 \r
4341         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;\r
4342         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;\r
4343         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];\r
4344 \r
4345         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;\r
4346 \r
4347         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board\r
4348             p = (int) board[0][i];\r
4349             if(p < (int) BlackPawn) piecesLeft[p] ++;\r
4350             board[0][i] = EmptySquare;\r
4351         }\r
4352 \r
4353         if(PosFlags(0) & F_ALL_CASTLE_OK) {\r
4354             // shuffles restricted to allow normal castling put KRR first\r
4355             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle\r
4356                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4357             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles\r
4358                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4359             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling\r
4360                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);\r
4361             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling\r
4362                 put(board, WhiteRook, 0, 0, ANY);\r
4363             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle\r
4364         }\r
4365 \r
4366         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)\r
4367             // only for even boards make effort to put pairs of colorbound pieces on opposite colors\r
4368             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {\r
4369                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;\r
4370                 while(piecesLeft[p] >= 2) {\r
4371                     AddOnePiece(board, p, 0, LITE);\r
4372                     AddOnePiece(board, p, 0, DARK);\r
4373                 }\r
4374                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)\r
4375             }\r
4376 \r
4377         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {\r
4378             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere\r
4379             // but we leave King and Rooks for last, to possibly obey FRC restriction\r
4380             if(p == (int)WhiteRook) continue;\r
4381             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations\r
4382             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece\r
4383         }\r
4384 \r
4385         // now everything is placed, except perhaps King (Unicorn) and Rooks\r
4386 \r
4387         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {\r
4388             // Last King gets castling rights\r
4389             while(piecesLeft[(int)WhiteUnicorn]) {\r
4390                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4391                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4392             }\r
4393 \r
4394             while(piecesLeft[(int)WhiteKing]) {\r
4395                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4396                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4397             }\r
4398 \r
4399 \r
4400         } else {\r
4401             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);\r
4402             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);\r
4403         }\r
4404 \r
4405         // Only Rooks can be left; simply place them all\r
4406         while(piecesLeft[(int)WhiteRook]) {\r
4407                 i = put(board, WhiteRook, 0, 0, ANY);\r
4408                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights\r
4409                         if(first) {\r
4410                                 first=0;\r
4411                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;\r
4412                         }\r
4413                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;\r
4414                 }\r
4415         }\r
4416         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white\r
4417             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;\r
4418         }\r
4419 \r
4420         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize\r
4421 }\r
4422 \r
4423 int SetCharTable( char *table, const char * map )\r
4424 /* [HGM] moved here from winboard.c because of its general usefulness */\r
4425 /*       Basically a safe strcpy that uses the last character as King */\r
4426 {\r
4427     int result = FALSE; int NrPieces;\r
4428 \r
4429     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
4430                     && NrPieces >= 12 && !(NrPieces&1)) {\r
4431         int i; /* [HGM] Accept even length from 12 to 34 */\r
4432 \r
4433         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
4434         for( i=0; i<NrPieces/2-1; i++ ) {\r
4435             table[i] = map[i];\r
4436             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
4437         }\r
4438         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
4439         table[(int) BlackKing]  = map[NrPieces-1];\r
4440 \r
4441         result = TRUE;\r
4442     }\r
4443 \r
4444     return result;\r
4445 }\r
4446 \r
4447 void Prelude(Board board)\r
4448 {       // [HGM] superchess: random selection of exo-pieces\r
4449         int i, j, k; ChessSquare p; \r
4450         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };\r
4451 \r
4452         GetPositionNumber(); // use FRC position number\r
4453 \r
4454         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table\r
4455             SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4456             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) \r
4457                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;\r
4458         }\r
4459 \r
4460         j = seed%4;                 seed /= 4; \r
4461         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4462         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4463         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4464         j = seed%3 + (seed%3 >= j); seed /= 3; \r
4465         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4466         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4467         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4468         j = seed%3;                 seed /= 3; \r
4469         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4470         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4471         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4472         j = seed%2 + (seed%2 >= j); seed /= 2; \r
4473         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4474         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4475         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4476         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);\r
4477         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);\r
4478         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);\r
4479         put(board, exoPieces[0],    0, 0, ANY);\r
4480         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];\r
4481 }\r
4482 \r
4483 void\r
4484 InitPosition(redraw)\r
4485      int redraw;\r
4486 {\r
4487     ChessSquare (* pieces)[BOARD_SIZE];\r
4488     int i, j, pawnRow, overrule,\r
4489     oldx = gameInfo.boardWidth,\r
4490     oldy = gameInfo.boardHeight,\r
4491     oldh = gameInfo.holdingsWidth,\r
4492     oldv = gameInfo.variant;\r
4493 \r
4494     currentMove = forwardMostMove = backwardMostMove = 0;\r
4495     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request\r
4496 \r
4497     /* [AS] Initialize pv info list [HGM] and game status */\r
4498     {\r
4499         for( i=0; i<MAX_MOVES; i++ ) {\r
4500             pvInfoList[i].depth = 0;\r
4501             epStatus[i]=EP_NONE;\r
4502             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
4503         }\r
4504 \r
4505         initialRulePlies = 0; /* 50-move counter start */\r
4506 \r
4507         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4508         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4509     }\r
4510 \r
4511     \r
4512     /* [HGM] logic here is completely changed. In stead of full positions */\r
4513     /* the initialized data only consist of the two backranks. The switch */\r
4514     /* selects which one we will use, which is than copied to the Board   */\r
4515     /* initialPosition, which for the rest is initialized by Pawns and    */\r
4516     /* empty squares. This initial position is then copied to boards[0],  */\r
4517     /* possibly after shuffling, so that it remains available.            */\r
4518 \r
4519     gameInfo.holdingsWidth = 0; /* default board sizes */\r
4520     gameInfo.boardWidth    = 8;\r
4521     gameInfo.boardHeight   = 8;\r
4522     gameInfo.holdingsSize  = 0;\r
4523     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
4524     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
4525     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
4526 \r
4527     switch (gameInfo.variant) {\r
4528     case VariantFischeRandom:\r
4529       shuffleOpenings = TRUE;\r
4530     default:\r
4531       pieces = FIDEArray;\r
4532       break;\r
4533     case VariantShatranj:\r
4534       pieces = ShatranjArray;\r
4535       nrCastlingRights = 0;\r
4536       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
4537       break;\r
4538     case VariantTwoKings:\r
4539       pieces = twoKingsArray;\r
4540       break;\r
4541     case VariantCapaRandom:\r
4542       shuffleOpenings = TRUE;\r
4543     case VariantCapablanca:\r
4544       pieces = CapablancaArray;\r
4545       gameInfo.boardWidth = 10;\r
4546       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4547       break;\r
4548     case VariantGothic:\r
4549       pieces = GothicArray;\r
4550       gameInfo.boardWidth = 10;\r
4551       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4552       break;\r
4553     case VariantJanus:\r
4554       pieces = JanusArray;\r
4555       gameInfo.boardWidth = 10;\r
4556       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
4557       nrCastlingRights = 6;\r
4558         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4559         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4560         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;\r
4561         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4562         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4563         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;\r
4564       break;\r
4565     case VariantFalcon:\r
4566       pieces = FalconArray;\r
4567       gameInfo.boardWidth = 10;\r
4568       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4569       break;\r
4570     case VariantXiangqi:\r
4571       pieces = XiangqiArray;\r
4572       gameInfo.boardWidth  = 9;\r
4573       gameInfo.boardHeight = 10;\r
4574       nrCastlingRights = 0;\r
4575       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4576       break;\r
4577     case VariantShogi:\r
4578       pieces = ShogiArray;\r
4579       gameInfo.boardWidth  = 9;\r
4580       gameInfo.boardHeight = 9;\r
4581       gameInfo.holdingsSize = 7;\r
4582       nrCastlingRights = 0;\r
4583       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4584       break;\r
4585     case VariantCourier:\r
4586       pieces = CourierArray;\r
4587       gameInfo.boardWidth  = 12;\r
4588       nrCastlingRights = 0;\r
4589       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4590       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4591       break;\r
4592     case VariantKnightmate:\r
4593       pieces = KnightmateArray;\r
4594       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4595       break;\r
4596     case VariantFairy:\r
4597       pieces = fairyArray;\r
4598       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4599       break;\r
4600     case VariantGreat:\r
4601       pieces = GreatArray;\r
4602       gameInfo.boardWidth = 10;\r
4603       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");\r
4604       gameInfo.holdingsSize = 8;\r
4605       break;\r
4606     case VariantSuper:\r
4607       pieces = FIDEArray;\r
4608       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
4609       gameInfo.holdingsSize = 8;\r
4610       startedFromSetupPosition = TRUE;\r
4611       break;\r
4612     case VariantCrazyhouse:\r
4613     case VariantBughouse:\r
4614       pieces = FIDEArray;\r
4615       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4616       gameInfo.holdingsSize = 5;\r
4617       break;\r
4618     case VariantWildCastle:\r
4619       pieces = FIDEArray;\r
4620       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4621       shuffleOpenings = 1;\r
4622       break;\r
4623     case VariantNoCastle:\r
4624       pieces = FIDEArray;\r
4625       nrCastlingRights = 0;\r
4626       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4627       /* !!?unconstrained back-rank shuffle */\r
4628       shuffleOpenings = 1;\r
4629       break;\r
4630     }\r
4631 \r
4632     overrule = 0;\r
4633     if(appData.NrFiles >= 0) {\r
4634         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4635         gameInfo.boardWidth = appData.NrFiles;\r
4636     }\r
4637     if(appData.NrRanks >= 0) {\r
4638         gameInfo.boardHeight = appData.NrRanks;\r
4639     }\r
4640     if(appData.holdingsSize >= 0) {\r
4641         i = appData.holdingsSize;\r
4642         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4643         gameInfo.holdingsSize = i;\r
4644     }\r
4645     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4646     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4647         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);\r
4648 \r
4649     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4650     if(pawnRow < 1) pawnRow = 1;\r
4651 \r
4652     /* User pieceToChar list overrules defaults */\r
4653     if(appData.pieceToCharTable != NULL)\r
4654         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4655 \r
4656     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4657 \r
4658         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4659             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4660         for( i=0; i<BOARD_HEIGHT; i++ )\r
4661             initialPosition[i][j] = s;\r
4662 \r
4663         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4664         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4665         initialPosition[pawnRow][j] = WhitePawn;\r
4666         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4667         if(gameInfo.variant == VariantXiangqi) {\r
4668             if(j&1) {\r
4669                 initialPosition[pawnRow][j] = \r
4670                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4671                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4672                    initialPosition[2][j] = WhiteCannon;\r
4673                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4674                 }\r
4675             }\r
4676         }\r
4677         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4678     }\r
4679     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4680 \r
4681             j=BOARD_LEFT+1;\r
4682             initialPosition[1][j] = WhiteBishop;\r
4683             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4684             j=BOARD_RGHT-2;\r
4685             initialPosition[1][j] = WhiteRook;\r
4686             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4687     }\r
4688 \r
4689     if( nrCastlingRights == -1) {\r
4690         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4691         /*       This sets default castling rights from none to normal corners   */\r
4692         /* Variants with other castling rights must set them themselves above    */\r
4693         nrCastlingRights = 6;\r
4694        \r
4695         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4696         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4697         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4698         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4699         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4700         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4701      }\r
4702 \r
4703      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
4704      if(gameInfo.variant == VariantGreat) { // promotion commoners\r
4705         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;\r
4706         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;\r
4707         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;\r
4708         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;\r
4709      }\r
4710 #if 0\r
4711     if(gameInfo.variant == VariantFischeRandom) {\r
4712       if( appData.defaultFrcPosition < 0 ) {\r
4713         ShuffleFRC( initialPosition );\r
4714       }\r
4715       else {\r
4716         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4717       }\r
4718       startedFromSetupPosition = TRUE;\r
4719     } else \r
4720 #else\r
4721   if (appData.debugMode) {\r
4722     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
4723   }\r
4724     if(shuffleOpenings) {\r
4725         SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
4726         startedFromSetupPosition = TRUE;\r
4727     }\r
4728 #endif\r
4729     if(startedFromPositionFile) {\r
4730       /* [HGM] loadPos: use PositionFile for every new game */\r
4731       CopyBoard(initialPosition, filePosition);\r
4732       for(i=0; i<nrCastlingRights; i++)\r
4733           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4734       startedFromSetupPosition = TRUE;\r
4735     }\r
4736 \r
4737     CopyBoard(boards[0], initialPosition);\r
4738 \r
4739     if(oldx != gameInfo.boardWidth ||\r
4740        oldy != gameInfo.boardHeight ||\r
4741        oldh != gameInfo.holdingsWidth\r
4742 #ifdef GOTHIC\r
4743        || oldv == VariantGothic ||        // For licensing popups\r
4744        gameInfo.variant == VariantGothic\r
4745 #endif\r
4746 #ifdef FALCON\r
4747        || oldv == VariantFalcon ||\r
4748        gameInfo.variant == VariantFalcon\r
4749 #endif\r
4750                                          )\r
4751             InitDrawingSizes(-2 ,0);\r
4752 \r
4753     if (redraw)\r
4754       DrawPosition(TRUE, boards[currentMove]);\r
4755 }\r
4756 \r
4757 void\r
4758 SendBoard(cps, moveNum)\r
4759      ChessProgramState *cps;\r
4760      int moveNum;\r
4761 {\r
4762     char message[MSG_SIZ];\r
4763     \r
4764     if (cps->useSetboard) {\r
4765       char* fen = PositionToFEN(moveNum, cps->fenOverride);\r
4766       sprintf(message, "setboard %s\n", fen);\r
4767       SendToProgram(message, cps);\r
4768       free(fen);\r
4769 \r
4770     } else {\r
4771       ChessSquare *bp;\r
4772       int i, j;\r
4773       /* Kludge to set black to move, avoiding the troublesome and now\r
4774        * deprecated "black" command.\r
4775        */\r
4776       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4777 \r
4778       SendToProgram("edit\n", cps);\r
4779       SendToProgram("#\n", cps);\r
4780       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4781         bp = &boards[moveNum][i][BOARD_LEFT];\r
4782         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4783           if ((int) *bp < (int) BlackPawn) {\r
4784             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4785                     AAA + j, ONE + i);\r
4786             if(message[0] == '+' || message[0] == '~') {\r
4787                 sprintf(message, "%c%c%c+\n",\r
4788                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4789                         AAA + j, ONE + i);\r
4790             }\r
4791             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4792                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4793                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4794             }\r
4795             SendToProgram(message, cps);\r
4796           }\r
4797         }\r
4798       }\r
4799     \r
4800       SendToProgram("c\n", cps);\r
4801       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4802         bp = &boards[moveNum][i][BOARD_LEFT];\r
4803         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4804           if (((int) *bp != (int) EmptySquare)\r
4805               && ((int) *bp >= (int) BlackPawn)) {\r
4806             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4807                     AAA + j, ONE + i);\r
4808             if(message[0] == '+' || message[0] == '~') {\r
4809                 sprintf(message, "%c%c%c+\n",\r
4810                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4811                         AAA + j, ONE + i);\r
4812             }\r
4813             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4814                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4815                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4816             }\r
4817             SendToProgram(message, cps);\r
4818           }\r
4819         }\r
4820       }\r
4821     \r
4822       SendToProgram(".\n", cps);\r
4823     }\r
4824     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4825 }\r
4826 \r
4827 int\r
4828 IsPromotion(fromX, fromY, toX, toY)\r
4829      int fromX, fromY, toX, toY;\r
4830 {\r
4831     /* [HGM] add Shogi promotions */\r
4832     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4833     ChessSquare piece;\r
4834 \r
4835     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4836       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4837    /* [HGM] Note to self: line above also weeds out drops */\r
4838     piece = boards[currentMove][fromY][fromX];\r
4839     if(gameInfo.variant == VariantShogi) {\r
4840         promotionZoneSize = 3;\r
4841         highestPromotingPiece = (int)WhiteKing;\r
4842         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4843            and if in normal chess we then allow promotion to King, why not\r
4844            allow promotion of other piece in Shogi?                         */\r
4845     }\r
4846     if((int)piece >= BlackPawn) {\r
4847         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4848              return FALSE;\r
4849         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4850     } else {\r
4851         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4852            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4853     }\r
4854     return ( (int)piece <= highestPromotingPiece );\r
4855 }\r
4856 \r
4857 int\r
4858 InPalace(row, column)\r
4859      int row, column;\r
4860 {   /* [HGM] for Xiangqi */\r
4861     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4862          column < (BOARD_WIDTH + 4)/2 &&\r
4863          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4864     return FALSE;\r
4865 }\r
4866 \r
4867 int\r
4868 PieceForSquare (x, y)\r
4869      int x;\r
4870      int y;\r
4871 {\r
4872   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
4873      return -1;\r
4874   else\r
4875      return boards[currentMove][y][x];\r
4876 }\r
4877 \r
4878 int\r
4879 OKToStartUserMove(x, y)\r
4880      int x, y;\r
4881 {\r
4882     ChessSquare from_piece;\r
4883     int white_piece;\r
4884 \r
4885     if (matchMode) return FALSE;\r
4886     if (gameMode == EditPosition) return TRUE;\r
4887 \r
4888     if (x >= 0 && y >= 0)\r
4889       from_piece = boards[currentMove][y][x];\r
4890     else\r
4891       from_piece = EmptySquare;\r
4892 \r
4893     if (from_piece == EmptySquare) return FALSE;\r
4894 \r
4895     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4896       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4897 \r
4898     switch (gameMode) {\r
4899       case PlayFromGameFile:\r
4900       case AnalyzeFile:\r
4901       case TwoMachinesPlay:\r
4902       case EndOfGame:\r
4903         return FALSE;\r
4904 \r
4905       case IcsObserving:\r
4906       case IcsIdle:\r
4907         return FALSE;\r
4908 \r
4909       case MachinePlaysWhite:\r
4910       case IcsPlayingBlack:\r
4911         if (appData.zippyPlay) return FALSE;\r
4912         if (white_piece) {\r
4913             DisplayMoveError(_("You are playing Black"));\r
4914             return FALSE;\r
4915         }\r
4916         break;\r
4917 \r
4918       case MachinePlaysBlack:\r
4919       case IcsPlayingWhite:\r
4920         if (appData.zippyPlay) return FALSE;\r
4921         if (!white_piece) {\r
4922             DisplayMoveError(_("You are playing White"));\r
4923             return FALSE;\r
4924         }\r
4925         break;\r
4926 \r
4927       case EditGame:\r
4928         if (!white_piece && WhiteOnMove(currentMove)) {\r
4929             DisplayMoveError(_("It is White's turn"));\r
4930             return FALSE;\r
4931         }           \r
4932         if (white_piece && !WhiteOnMove(currentMove)) {\r
4933             DisplayMoveError(_("It is Black's turn"));\r
4934             return FALSE;\r
4935         }           \r
4936         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
4937             /* Editing correspondence game history */\r
4938             /* Could disallow this or prompt for confirmation */\r
4939             cmailOldMove = -1;\r
4940         }\r
4941         if (currentMove < forwardMostMove) {\r
4942             /* Discarding moves */\r
4943             /* Could prompt for confirmation here,\r
4944                but I don't think that's such a good idea */\r
4945             forwardMostMove = currentMove;\r
4946         }\r
4947         break;\r
4948 \r
4949       case BeginningOfGame:\r
4950         if (appData.icsActive) return FALSE;\r
4951         if (!appData.noChessProgram) {\r
4952             if (!white_piece) {\r
4953                 DisplayMoveError(_("You are playing White"));\r
4954                 return FALSE;\r
4955             }\r
4956         }\r
4957         break;\r
4958         \r
4959       case Training:\r
4960         if (!white_piece && WhiteOnMove(currentMove)) {\r
4961             DisplayMoveError(_("It is White's turn"));\r
4962             return FALSE;\r
4963         }           \r
4964         if (white_piece && !WhiteOnMove(currentMove)) {\r
4965             DisplayMoveError(_("It is Black's turn"));\r
4966             return FALSE;\r
4967         }           \r
4968         break;\r
4969 \r
4970       default:\r
4971       case IcsExamining:\r
4972         break;\r
4973     }\r
4974     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
4975         && gameMode != AnalyzeFile && gameMode != Training) {\r
4976         DisplayMoveError(_("Displayed position is not current"));\r
4977         return FALSE;\r
4978     }\r
4979     return TRUE;\r
4980 }\r
4981 \r
4982 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
4983 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
4984 int lastLoadGameUseList = FALSE;\r
4985 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
4986 ChessMove lastLoadGameStart = (ChessMove) 0;\r
4987 \r
4988 \r
4989 ChessMove\r
4990 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
4991      int fromX, fromY, toX, toY;\r
4992      int promoChar;\r
4993 {\r
4994     ChessMove moveType;\r
4995     ChessSquare pdown, pup;\r
4996 \r
4997     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
4998     if ((fromX == toX) && (fromY == toY)) {\r
4999         return ImpossibleMove;\r
5000     }\r
5001 \r
5002     /* [HGM] suppress all moves into holdings area and guard band */\r
5003     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
5004             return ImpossibleMove;\r
5005 \r
5006     /* [HGM] <sameColor> moved to here from winboard.c */\r
5007     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
5008     pdown = boards[currentMove][fromY][fromX];\r
5009     pup = boards[currentMove][toY][toX];\r
5010     if (    gameMode != EditPosition &&\r
5011             (WhitePawn <= pdown && pdown < BlackPawn &&\r
5012              WhitePawn <= pup && pup < BlackPawn  ||\r
5013              BlackPawn <= pdown && pdown < EmptySquare &&\r
5014              BlackPawn <= pup && pup < EmptySquare \r
5015             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
5016                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
5017                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
5018         )           )\r
5019          return ImpossibleMove;\r
5020 \r
5021     /* Check if the user is playing in turn.  This is complicated because we\r
5022        let the user "pick up" a piece before it is his turn.  So the piece he\r
5023        tried to pick up may have been captured by the time he puts it down!\r
5024        Therefore we use the color the user is supposed to be playing in this\r
5025        test, not the color of the piece that is currently on the starting\r
5026        square---except in EditGame mode, where the user is playing both\r
5027        sides; fortunately there the capture race can't happen.  (It can\r
5028        now happen in IcsExamining mode, but that's just too bad.  The user\r
5029        will get a somewhat confusing message in that case.)\r
5030        */\r
5031 \r
5032     switch (gameMode) {\r
5033       case PlayFromGameFile:\r
5034       case AnalyzeFile:\r
5035       case TwoMachinesPlay:\r
5036       case EndOfGame:\r
5037       case IcsObserving:\r
5038       case IcsIdle:\r
5039         /* We switched into a game mode where moves are not accepted,\r
5040            perhaps while the mouse button was down. */\r
5041         return ImpossibleMove;\r
5042 \r
5043       case MachinePlaysWhite:\r
5044         /* User is moving for Black */\r
5045         if (WhiteOnMove(currentMove)) {\r
5046             DisplayMoveError(_("It is White's turn"));\r
5047             return ImpossibleMove;\r
5048         }\r
5049         break;\r
5050 \r
5051       case MachinePlaysBlack:\r
5052         /* User is moving for White */\r
5053         if (!WhiteOnMove(currentMove)) {\r
5054             DisplayMoveError(_("It is Black's turn"));\r
5055             return ImpossibleMove;\r
5056         }\r
5057         break;\r
5058 \r
5059       case EditGame:\r
5060       case IcsExamining:\r
5061       case BeginningOfGame:\r
5062       case AnalyzeMode:\r
5063       case Training:\r
5064         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
5065             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
5066             /* User is moving for Black */\r
5067             if (WhiteOnMove(currentMove)) {\r
5068                 DisplayMoveError(_("It is White's turn"));\r
5069                 return ImpossibleMove;\r
5070             }\r
5071         } else {\r
5072             /* User is moving for White */\r
5073             if (!WhiteOnMove(currentMove)) {\r
5074                 DisplayMoveError(_("It is Black's turn"));\r
5075                 return ImpossibleMove;\r
5076             }\r
5077         }\r
5078         break;\r
5079 \r
5080       case IcsPlayingBlack:\r
5081         /* User is moving for Black */\r
5082         if (WhiteOnMove(currentMove)) {\r
5083             if (!appData.premove) {\r
5084                 DisplayMoveError(_("It is White's turn"));\r
5085             } else if (toX >= 0 && toY >= 0) {\r
5086                 premoveToX = toX;\r
5087                 premoveToY = toY;\r
5088                 premoveFromX = fromX;\r
5089                 premoveFromY = fromY;\r
5090                 premovePromoChar = promoChar;\r
5091                 gotPremove = 1;\r
5092                 if (appData.debugMode) \r
5093                     fprintf(debugFP, "Got premove: fromX %d,"\r
5094                             "fromY %d, toX %d, toY %d\n",\r
5095                             fromX, fromY, toX, toY);\r
5096             }\r
5097             return ImpossibleMove;\r
5098         }\r
5099         break;\r
5100 \r
5101       case IcsPlayingWhite:\r
5102         /* User is moving for White */\r
5103         if (!WhiteOnMove(currentMove)) {\r
5104             if (!appData.premove) {\r
5105                 DisplayMoveError(_("It is Black's turn"));\r
5106             } else if (toX >= 0 && toY >= 0) {\r
5107                 premoveToX = toX;\r
5108                 premoveToY = toY;\r
5109                 premoveFromX = fromX;\r
5110                 premoveFromY = fromY;\r
5111                 premovePromoChar = promoChar;\r
5112                 gotPremove = 1;\r
5113                 if (appData.debugMode) \r
5114                     fprintf(debugFP, "Got premove: fromX %d,"\r
5115                             "fromY %d, toX %d, toY %d\n",\r
5116                             fromX, fromY, toX, toY);\r
5117             }\r
5118             return ImpossibleMove;\r
5119         }\r
5120         break;\r
5121 \r
5122       default:\r
5123         break;\r
5124 \r
5125       case EditPosition:\r
5126         /* EditPosition, empty square, or different color piece;\r
5127            click-click move is possible */\r
5128         if (toX == -2 || toY == -2) {\r
5129             boards[0][fromY][fromX] = EmptySquare;\r
5130             return AmbiguousMove;\r
5131         } else if (toX >= 0 && toY >= 0) {\r
5132             boards[0][toY][toX] = boards[0][fromY][fromX];\r
5133             boards[0][fromY][fromX] = EmptySquare;\r
5134             return AmbiguousMove;\r
5135         }\r
5136         return ImpossibleMove;\r
5137     }\r
5138 \r
5139     /* [HGM] If move started in holdings, it means a drop */\r
5140     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
5141          if( pup != EmptySquare ) return ImpossibleMove;\r
5142          if(appData.testLegality) {\r
5143              /* it would be more logical if LegalityTest() also figured out\r
5144               * which drops are legal. For now we forbid pawns on back rank.\r
5145               * Shogi is on its own here...\r
5146               */\r
5147              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
5148                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
5149                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
5150          }\r
5151          return WhiteDrop; /* Not needed to specify white or black yet */\r
5152     }\r
5153 \r
5154     userOfferedDraw = FALSE;\r
5155         \r
5156     /* [HGM] always test for legality, to get promotion info */\r
5157     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
5158                           epStatus[currentMove], castlingRights[currentMove],\r
5159                                          fromY, fromX, toY, toX, promoChar);\r
5160 \r
5161     /* [HGM] but possibly ignore an IllegalMove result */\r
5162     if (appData.testLegality) {\r
5163         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
5164             DisplayMoveError(_("Illegal move"));\r
5165             return ImpossibleMove;\r
5166         }\r
5167     }\r
5168 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);\r
5169     return moveType;\r
5170     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
5171        function is made into one that returns an OK move type if FinishMove\r
5172        should be called. This to give the calling driver routine the\r
5173        opportunity to finish the userMove input with a promotion popup,\r
5174        without bothering the user with this for invalid or illegal moves */\r
5175 \r
5176 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
5177 }\r
5178 \r
5179 /* Common tail of UserMoveEvent and DropMenuEvent */\r
5180 int\r
5181 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
5182      ChessMove moveType;\r
5183      int fromX, fromY, toX, toY;\r
5184      /*char*/int promoChar;\r
5185 {\r
5186     char *bookHit = 0;\r
5187 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);\r
5188     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { \r
5189         // [HGM] superchess: suppress promotions to non-available piece\r
5190         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5191         if(WhiteOnMove(currentMove)) {\r
5192             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;\r
5193         } else {\r
5194             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;\r
5195         }\r
5196     }\r
5197 \r
5198     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion\r
5199        move type in caller when we know the move is a legal promotion */\r
5200     if(moveType == NormalMove && promoChar)\r
5201         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
5202 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);\r
5203     /* [HGM] convert drag-and-drop piece drops to standard form */\r
5204     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
5205          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
5206          fromX = boards[currentMove][fromY][fromX];\r
5207          fromY = DROP_RANK;\r
5208     }\r
5209 \r
5210     /* [HGM] <popupFix> The following if has been moved here from\r
5211        UserMoveEvent(). Because it seemed to belon here (why not allow\r
5212        piece drops in training games?), and because it can only be\r
5213        performed after it is known to what we promote. */\r
5214     if (gameMode == Training) {\r
5215       /* compare the move played on the board to the next move in the\r
5216        * game. If they match, display the move and the opponent's response. \r
5217        * If they don't match, display an error message.\r
5218        */\r
5219       int saveAnimate;\r
5220       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;\r
5221       CopyBoard(testBoard, boards[currentMove]);\r
5222       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);\r
5223 \r
5224       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
5225         ForwardInner(currentMove+1);\r
5226 \r
5227         /* Autoplay the opponent's response.\r
5228          * if appData.animate was TRUE when Training mode was entered,\r
5229          * the response will be animated.\r
5230          */\r
5231         saveAnimate = appData.animate;\r
5232         appData.animate = animateTraining;\r
5233         ForwardInner(currentMove+1);\r
5234         appData.animate = saveAnimate;\r
5235 \r
5236         /* check for the end of the game */\r
5237         if (currentMove >= forwardMostMove) {\r
5238           gameMode = PlayFromGameFile;\r
5239           ModeHighlight();\r
5240           SetTrainingModeOff();\r
5241           DisplayInformation(_("End of game"));\r
5242         }\r
5243       } else {\r
5244         DisplayError(_("Incorrect move"), 0);\r
5245       }\r
5246       return 1;\r
5247     }\r
5248 \r
5249   /* Ok, now we know that the move is good, so we can kill\r
5250      the previous line in Analysis Mode */\r
5251   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
5252     forwardMostMove = currentMove;\r
5253   }\r
5254 \r
5255   /* If we need the chess program but it's dead, restart it */\r
5256   ResurrectChessProgram();\r
5257 \r
5258   /* A user move restarts a paused game*/\r
5259   if (pausing)\r
5260     PauseEvent();\r
5261 \r
5262   thinkOutput[0] = NULLCHAR;\r
5263 \r
5264   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
5265 \r
5266   if (gameMode == BeginningOfGame) {\r
5267     if (appData.noChessProgram) {\r
5268       gameMode = EditGame;\r
5269       SetGameInfo();\r
5270     } else {\r
5271       char buf[MSG_SIZ];\r
5272       gameMode = MachinePlaysBlack;\r
5273       StartClocks();\r
5274       SetGameInfo();\r
5275       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
5276       DisplayTitle(buf);\r
5277       if (first.sendName) {\r
5278         sprintf(buf, "name %s\n", gameInfo.white);\r
5279         SendToProgram(buf, &first);\r
5280       }\r
5281       StartClocks();\r
5282     }\r
5283     ModeHighlight();\r
5284   }\r
5285 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);\r
5286   /* Relay move to ICS or chess engine */\r
5287   if (appData.icsActive) {\r
5288     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
5289         gameMode == IcsExamining) {\r
5290       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5291       ics_user_moved = 1;\r
5292     }\r
5293   } else {\r
5294     if (first.sendTime && (gameMode == BeginningOfGame ||\r
5295                            gameMode == MachinePlaysWhite ||\r
5296                            gameMode == MachinePlaysBlack)) {\r
5297       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
5298     }\r
5299     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
5300          // [HGM] book: if program might be playing, let it use book\r
5301         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);\r
5302         first.maybeThinking = TRUE;\r
5303     } else SendMoveToProgram(forwardMostMove-1, &first);\r
5304     if (currentMove == cmailOldMove + 1) {\r
5305       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
5306     }\r
5307   }\r
5308 \r
5309   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5310 \r
5311   switch (gameMode) {\r
5312   case EditGame:\r
5313     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
5314                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
5315     case MT_NONE:\r
5316     case MT_CHECK:\r
5317       break;\r
5318     case MT_CHECKMATE:\r
5319     case MT_STAINMATE:\r
5320       if (WhiteOnMove(currentMove)) {\r
5321         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
5322       } else {\r
5323         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
5324       }\r
5325       break;\r
5326     case MT_STALEMATE:\r
5327       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
5328       break;\r
5329     }\r
5330     break;\r
5331     \r
5332   case MachinePlaysBlack:\r
5333   case MachinePlaysWhite:\r
5334     /* disable certain menu options while machine is thinking */\r
5335     SetMachineThinkingEnables();\r
5336     break;\r
5337 \r
5338   default:\r
5339     break;\r
5340   }\r
5341 \r
5342   if(bookHit) { // [HGM] book: simulate book reply\r
5343         static char bookMove[MSG_SIZ]; // a bit generous?\r
5344 \r
5345         programStats.nodes = programStats.depth = programStats.time = \r
5346         programStats.score = programStats.got_only_move = 0;\r
5347         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
5348 \r
5349         strcpy(bookMove, "move ");\r
5350         strcat(bookMove, bookHit);\r
5351         HandleMachineMove(bookMove, &first);\r
5352   }\r
5353   return 1;\r
5354 }\r
5355 \r
5356 void\r
5357 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
5358      int fromX, fromY, toX, toY;\r
5359      int promoChar;\r
5360 {\r
5361     /* [HGM] This routine was added to allow calling of its two logical\r
5362        parts from other modules in the old way. Before, UserMoveEvent()\r
5363        automatically called FinishMove() if the move was OK, and returned\r
5364        otherwise. I separated the two, in order to make it possible to\r
5365        slip a promotion popup in between. But that it always needs two\r
5366        calls, to the first part, (now called UserMoveTest() ), and to\r
5367        FinishMove if the first part succeeded. Calls that do not need\r
5368        to do anything in between, can call this routine the old way. \r
5369     */\r
5370     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
5371 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);\r
5372     if(moveType != ImpossibleMove)\r
5373         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
5374 }\r
5375 \r
5376 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
5377 {\r
5378 //    char * hint = lastHint;\r
5379     FrontEndProgramStats stats;\r
5380 \r
5381     stats.which = cps == &first ? 0 : 1;\r
5382     stats.depth = cpstats->depth;\r
5383     stats.nodes = cpstats->nodes;\r
5384     stats.score = cpstats->score;\r
5385     stats.time = cpstats->time;\r
5386     stats.pv = cpstats->movelist;\r
5387     stats.hint = lastHint;\r
5388     stats.an_move_index = 0;\r
5389     stats.an_move_count = 0;\r
5390 \r
5391     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
5392         stats.hint = cpstats->move_name;\r
5393         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
5394         stats.an_move_count = cpstats->nr_moves;\r
5395     }\r
5396 \r
5397     SetProgramStats( &stats );\r
5398 }\r
5399 \r
5400 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)\r
5401 {   // [HGM] book: this routine intercepts moves to simulate book replies\r
5402     char *bookHit = NULL;\r
5403 \r
5404     //first determine if the incoming move brings opponent into his book\r
5405     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
5406         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
5407     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");\r
5408     if(bookHit != NULL && !cps->bookSuspend) {\r
5409         // make sure opponent is not going to reply after receiving move to book position\r
5410         SendToProgram("force\n", cps);\r
5411         cps->bookSuspend = TRUE; // flag indicating it has to be restarted\r
5412     }\r
5413     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move\r
5414     // now arrange restart after book miss\r
5415     if(bookHit) {\r
5416         // after a book hit we never send 'go', and the code after the call to this routine\r
5417         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').\r
5418         char buf[MSG_SIZ];\r
5419         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(\r
5420         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it\r
5421         SendToProgram(buf, cps);\r
5422         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'\r
5423     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine\r
5424         SendToProgram("go\n", cps);\r
5425         cps->bookSuspend = FALSE; // after a 'go' we are never suspended\r
5426     } else { // 'go' might be sent based on 'firstMove' after this routine returns\r
5427         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return\r
5428             SendToProgram("go\n", cps); \r
5429         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss\r
5430     }\r
5431     return bookHit; // notify caller of hit, so it can take action to send move to opponent\r
5432 }\r
5433 \r
5434 char *savedMessage;\r
5435 ChessProgramState *savedState;\r
5436 void DeferredBookMove(void)\r
5437 {\r
5438         if(savedState->lastPing != savedState->lastPong)\r
5439                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
5440         else\r
5441         HandleMachineMove(savedMessage, savedState);\r
5442 }\r
5443 \r
5444 void\r
5445 HandleMachineMove(message, cps)\r
5446      char *message;\r
5447      ChessProgramState *cps;\r
5448 {\r
5449     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
5450     char realname[MSG_SIZ];\r
5451     int fromX, fromY, toX, toY;\r
5452     ChessMove moveType;\r
5453     char promoChar;\r
5454     char *p;\r
5455     int machineWhite;\r
5456     char *bookHit;\r
5457 \r
5458 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit\r
5459     /*\r
5460      * Kludge to ignore BEL characters\r
5461      */\r
5462     while (*message == '\007') message++;\r
5463 \r
5464     /*\r
5465      * [HGM] engine debug message: ignore lines starting with '#' character\r
5466      */\r
5467     if(cps->debug && *message == '#') return;\r
5468 \r
5469     /*\r
5470      * Look for book output\r
5471      */\r
5472     if (cps == &first && bookRequested) {\r
5473         if (message[0] == '\t' || message[0] == ' ') {\r
5474             /* Part of the book output is here; append it */\r
5475             strcat(bookOutput, message);\r
5476             strcat(bookOutput, "  \n");\r
5477             return;\r
5478         } else if (bookOutput[0] != NULLCHAR) {\r
5479             /* All of book output has arrived; display it */\r
5480             char *p = bookOutput;\r
5481             while (*p != NULLCHAR) {\r
5482                 if (*p == '\t') *p = ' ';\r
5483                 p++;\r
5484             }\r
5485             DisplayInformation(bookOutput);\r
5486             bookRequested = FALSE;\r
5487             /* Fall through to parse the current output */\r
5488         }\r
5489     }\r
5490 \r
5491     /*\r
5492      * Look for machine move.\r
5493      */\r
5494     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
5495         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
5496     {\r
5497         /* This method is only useful on engines that support ping */\r
5498         if (cps->lastPing != cps->lastPong) {\r
5499           if (gameMode == BeginningOfGame) {\r
5500             /* Extra move from before last new; ignore */\r
5501             if (appData.debugMode) {\r
5502                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5503             }\r
5504           } else {\r
5505             if (appData.debugMode) {\r
5506                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5507                         cps->which, gameMode);\r
5508             }\r
5509 \r
5510             SendToProgram("undo\n", cps);\r
5511           }\r
5512           return;\r
5513         }\r
5514 \r
5515         switch (gameMode) {\r
5516           case BeginningOfGame:\r
5517             /* Extra move from before last reset; ignore */\r
5518             if (appData.debugMode) {\r
5519                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5520             }\r
5521             return;\r
5522 \r
5523           case EndOfGame:\r
5524           case IcsIdle:\r
5525           default:\r
5526             /* Extra move after we tried to stop.  The mode test is\r
5527                not a reliable way of detecting this problem, but it's\r
5528                the best we can do on engines that don't support ping.\r
5529             */\r
5530             if (appData.debugMode) {\r
5531                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5532                         cps->which, gameMode);\r
5533             }\r
5534             SendToProgram("undo\n", cps);\r
5535             return;\r
5536 \r
5537           case MachinePlaysWhite:\r
5538           case IcsPlayingWhite:\r
5539             machineWhite = TRUE;\r
5540             break;\r
5541 \r
5542           case MachinePlaysBlack:\r
5543           case IcsPlayingBlack:\r
5544             machineWhite = FALSE;\r
5545             break;\r
5546 \r
5547           case TwoMachinesPlay:\r
5548             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5549             break;\r
5550         }\r
5551         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5552             if (appData.debugMode) {\r
5553                 fprintf(debugFP,\r
5554                         "Ignoring move out of turn by %s, gameMode %d"\r
5555                         ", forwardMost %d\n",\r
5556                         cps->which, gameMode, forwardMostMove);\r
5557             }\r
5558             return;\r
5559         }\r
5560 \r
5561     if (appData.debugMode) { int f = forwardMostMove;\r
5562         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5563                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5564     }\r
5565         if(cps->alphaRank) AlphaRank(machineMove, 4);\r
5566         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5567                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5568             /* Machine move could not be parsed; ignore it. */\r
5569             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),\r
5570                     machineMove, cps->which);\r
5571             DisplayError(buf1, 0);\r
5572             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",\r
5573                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);\r
5574             if (gameMode == TwoMachinesPlay) {\r
5575               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5576                        buf1, GE_XBOARD);\r
5577             }\r
5578             return;\r
5579         }\r
5580 \r
5581         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5582         /* So we have to redo legality test with true e.p. status here,  */\r
5583         /* to make sure an illegal e.p. capture does not slip through,   */\r
5584         /* to cause a forfeit on a justified illegal-move complaint      */\r
5585         /* of the opponent.                                              */\r
5586         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5587             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5588                                                               ) {\r
5589            ChessMove moveType;\r
5590            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5591                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5592                              fromY, fromX, toY, toX, promoChar);\r
5593             if (appData.debugMode) {\r
5594                 int i;\r
5595                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5596                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5597                 fprintf(debugFP, "castling rights\n");\r
5598             }\r
5599             if(moveType == IllegalMove) {\r
5600                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5601                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5602                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5603                            buf1, GE_XBOARD);\r
5604                 return;\r
5605            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
5606            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5607               when they shouldn't (like TSCP-Gothic) */\r
5608            switch(moveType) {\r
5609              case WhiteASideCastleFR:\r
5610              case BlackASideCastleFR:\r
5611                toX+=2;\r
5612                currentMoveString[2]++;\r
5613                break;\r
5614              case WhiteHSideCastleFR:\r
5615              case BlackHSideCastleFR:\r
5616                toX--;\r
5617                currentMoveString[2]--;\r
5618                break;\r
5619              default: ; // nothing to do, but suppresses warning of pedantic compilers\r
5620            }\r
5621         }\r
5622         hintRequested = FALSE;\r
5623         lastHint[0] = NULLCHAR;\r
5624         bookRequested = FALSE;\r
5625         /* Program may be pondering now */\r
5626         cps->maybeThinking = TRUE;\r
5627         if (cps->sendTime == 2) cps->sendTime = 1;\r
5628         if (cps->offeredDraw) cps->offeredDraw--;\r
5629 \r
5630 #if ZIPPY\r
5631         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5632             first.initDone) {\r
5633           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5634           ics_user_moved = 1;\r
5635           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
5636                 char buf[3*MSG_SIZ];\r
5637 \r
5638                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",\r
5639                         programStats.score / 100.,\r
5640                         programStats.depth,\r
5641                         programStats.time / 100.,\r
5642                         u64ToDouble(programStats.nodes),\r
5643                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),\r
5644                         programStats.movelist);\r
5645                 SendToICS(buf);\r
5646           }\r
5647         }\r
5648 #endif\r
5649         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5650         strcpy(machineMove, currentMoveString);\r
5651         strcat(machineMove, "\n");\r
5652         strcpy(moveList[forwardMostMove], machineMove);\r
5653 \r
5654         /* [AS] Save move info and clear stats for next move */\r
5655         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5656         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5657         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5658         ClearProgramStats();\r
5659         thinkOutput[0] = NULLCHAR;\r
5660         hiddenThinkOutputState = 0;\r
5661 \r
5662         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5663 \r
5664         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5665         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5666             int count = 0;\r
5667 \r
5668             while( count < adjudicateLossPlies ) {\r
5669                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5670 \r
5671                 if( count & 1 ) {\r
5672                     score = -score; /* Flip score for winning side */\r
5673                 }\r
5674 \r
5675                 if( score > adjudicateLossThreshold ) {\r
5676                     break;\r
5677                 }\r
5678 \r
5679                 count++;\r
5680             }\r
5681 \r
5682             if( count >= adjudicateLossPlies ) {\r
5683                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5684 \r
5685                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5686                     "Xboard adjudication", \r
5687                     GE_XBOARD );\r
5688 \r
5689                 return;\r
5690             }\r
5691         }\r
5692 \r
5693         if( gameMode == TwoMachinesPlay ) {\r
5694           // [HGM] some adjudications useful with buggy engines\r
5695             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
5696           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {\r
5697 \r
5698 \r
5699             if( appData.testLegality )\r
5700             {   /* [HGM] Some more adjudications for obstinate engines */\r
5701                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5702                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,\r
5703                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;\r
5704                 static int moveCount = 6;\r
5705                 ChessMove result;\r
5706                 char *reason = NULL;\r
5707 \r
5708                 /* Count what is on board. */\r
5709                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5710                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5711                     int m=i;\r
5712 \r
5713                     switch((int) p)\r
5714                     {   /* count B,N,R and other of each side */\r
5715                         case WhiteKing:\r
5716                         case BlackKing:\r
5717                              NrK++; break; // [HGM] atomic: count Kings\r
5718                         case WhiteKnight:\r
5719                              NrWN++; break;\r
5720                         case WhiteBishop:\r
5721                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5722                              bishopsColor |= 1 << ((i^j)&1);\r
5723                              NrWB++; break;\r
5724                         case BlackKnight:\r
5725                              NrBN++; break;\r
5726                         case BlackBishop:\r
5727                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5728                              bishopsColor |= 1 << ((i^j)&1);\r
5729                              NrBB++; break;\r
5730                         case WhiteRook:\r
5731                              NrWR++; break;\r
5732                         case BlackRook:\r
5733                              NrBR++; break;\r
5734                         case WhiteQueen:\r
5735                              NrWQ++; break;\r
5736                         case BlackQueen:\r
5737                              NrBQ++; break;\r
5738                         case EmptySquare: \r
5739                              break;\r
5740                         case BlackPawn:\r
5741                              m = 7-i;\r
5742                         case WhitePawn:\r
5743                              PawnAdvance += m; NrPawns++;\r
5744                     }\r
5745                     NrPieces += (p != EmptySquare);\r
5746                     NrW += ((int)p < (int)BlackPawn);\r
5747                     if(gameInfo.variant == VariantXiangqi && \r
5748                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {\r
5749                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces\r
5750                         NrW -= ((int)p < (int)BlackPawn);\r
5751                     }\r
5752                 }\r
5753 \r
5754                 /* Some material-based adjudications that have to be made before stalemate test */\r
5755                 if(gameInfo.variant == VariantAtomic && NrK < 2) {\r
5756                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal\r
5757                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated\r
5758                      if(appData.checkMates) {\r
5759                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move\r
5760                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5761                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5762                                                         "Xboard adjudication: King destroyed", GE_XBOARD );\r
5763                          return;\r
5764                      }\r
5765                 }\r
5766 \r
5767                 /* Bare King in Shatranj (loses) or Losers (wins) */\r
5768                 if( NrW == 1 || NrPieces - NrW == 1) {\r
5769                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)\r
5770                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable\r
5771                      if(appData.checkMates) {\r
5772                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move\r
5773                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5774                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5775                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5776                          return;\r
5777                      }\r
5778                   } else\r
5779                   if( gameInfo.variant == VariantShatranj && --bare < 0)\r
5780                   {    /* bare King */\r
5781                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm\r
5782                         if(appData.checkMates) {\r
5783                             /* but only adjudicate if adjudication enabled */\r
5784                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move\r
5785                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5786                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
5787                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5788                             return;\r
5789                         }\r
5790                   }\r
5791                 } else bare = 1;\r
5792 \r
5793 \r
5794             // don't wait for engine to announce game end if we can judge ourselves\r
5795             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,\r
5796                                        castlingRights[forwardMostMove]) ) {\r
5797               case MT_CHECK:\r
5798                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time\r
5799                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)\r
5800                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {\r
5801                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)\r
5802                             checkCnt++;\r
5803                         if(checkCnt >= 2) {\r
5804                             reason = "Xboard adjudication: 3rd check";\r
5805                             epStatus[forwardMostMove] = EP_CHECKMATE;\r
5806                             break;\r
5807                         }\r
5808                     }\r
5809                 }\r
5810               case MT_NONE:\r
5811               default:\r
5812                 break;\r
5813               case MT_STALEMATE:\r
5814               case MT_STAINMATE:\r
5815                 reason = "Xboard adjudication: Stalemate";\r
5816                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt\r
5817                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw\r
5818                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:\r
5819                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win\r
5820                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends\r
5821                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :\r
5822                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?\r
5823                                                                         EP_CHECKMATE : EP_WINS);\r
5824                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)\r
5825                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses\r
5826                 }\r
5827                 break;\r
5828               case MT_CHECKMATE:\r
5829                 reason = "Xboard adjudication: Checkmate";\r
5830                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);\r
5831                 break;\r
5832             }\r
5833 \r
5834                 switch(i = epStatus[forwardMostMove]) {\r
5835                     case EP_STALEMATE:\r
5836                         result = GameIsDrawn; break;\r
5837                     case EP_CHECKMATE:\r
5838                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;\r
5839                     case EP_WINS:\r
5840                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;\r
5841                     default:\r
5842                         result = (ChessMove) 0;\r
5843                 }\r
5844                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested\r
5845                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5846                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5847                     GameEnds( result, reason, GE_XBOARD );\r
5848                     return;\r
5849                 }\r
5850 \r
5851                 /* Next absolutely insufficient mating material. */\r
5852                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && \r
5853                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible\r
5854                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||\r
5855                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color\r
5856                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5857 \r
5858                      /* always flag draws, for judging claims */\r
5859                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5860 \r
5861                      if(appData.materialDraws) {\r
5862                          /* but only adjudicate them if adjudication enabled */\r
5863                          SendToProgram("force\n", cps->other); // suppress reply\r
5864                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */\r
5865                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5866                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5867                          return;\r
5868                      }\r
5869                 }\r
5870 \r
5871                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5872                 if(NrPieces == 4 && \r
5873                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5874                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5875                    || NrWN==2 || NrBN==2     /* KNNK */\r
5876                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5877                   ) ) {\r
5878                      if(--moveCount < 0 && appData.trivialDraws)\r
5879                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5880                           SendToProgram("force\n", cps->other); // suppress reply\r
5881                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5882                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5883                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5884                           return;\r
5885                      }\r
5886                 } else moveCount = 6;\r
5887             }\r
5888           }\r
5889 #if 1\r
5890     if (appData.debugMode) { int i;\r
5891       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5892               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5893               appData.drawRepeats);\r
5894       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5895            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5896 \r
5897     }\r
5898 #endif\r
5899                 /* Check for rep-draws */\r
5900                 count = 0;\r
5901                 for(k = forwardMostMove-2;\r
5902                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5903                         epStatus[k] < EP_UNKNOWN &&\r
5904                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5905                     k-=2)\r
5906                 {   int rights=0;\r
5907 #if 0\r
5908     if (appData.debugMode) {\r
5909       fprintf(debugFP, " loop\n");\r
5910     }\r
5911 #endif\r
5912                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5913 #if 0\r
5914     if (appData.debugMode) {\r
5915       fprintf(debugFP, "match\n");\r
5916     }\r
5917 #endif\r
5918                         /* compare castling rights */\r
5919                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5920                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5921                                 rights++; /* King lost rights, while rook still had them */\r
5922                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5923                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5924                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5925                                    rights++; /* but at least one rook lost them */\r
5926                         }\r
5927                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5928                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5929                                 rights++; \r
5930                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5931                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5932                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5933                                    rights++;\r
5934                         }\r
5935 #if 0\r
5936     if (appData.debugMode) {\r
5937       for(i=0; i<nrCastlingRights; i++)\r
5938       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5939     }\r
5940 \r
5941     if (appData.debugMode) {\r
5942       fprintf(debugFP, " %d %d\n", rights, k);\r
5943     }\r
5944 #endif\r
5945                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5946                             && appData.drawRepeats > 1) {\r
5947                              /* adjudicate after user-specified nr of repeats */\r
5948                              SendToProgram("force\n", cps->other); // suppress reply\r
5949                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5950                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5951                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { \r
5952                                 // [HGM] xiangqi: check for forbidden perpetuals\r
5953                                 int m, ourPerpetual = 1, hisPerpetual = 1;\r
5954                                 for(m=forwardMostMove; m>k; m-=2) {\r
5955                                     if(MateTest(boards[m], PosFlags(m), \r
5956                                                         EP_NONE, castlingRights[m]) != MT_CHECK)\r
5957                                         ourPerpetual = 0; // the current mover did not always check\r
5958                                     if(MateTest(boards[m-1], PosFlags(m-1), \r
5959                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)\r
5960                                         hisPerpetual = 0; // the opponent did not always check\r
5961                                 }\r
5962                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",\r
5963                                                                         ourPerpetual, hisPerpetual);\r
5964                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
5965                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5966                                            "Xboard adjudication: perpetual checking", GE_XBOARD );\r
5967                                     return;\r
5968                                 }\r
5969                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet\r
5970                                     break; // (or we would have caught him before). Abort repetition-checking loop.\r
5971                                 // Now check for perpetual chases\r
5972                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase\r
5973                                     hisPerpetual = PerpetualChase(k, forwardMostMove);\r
5974                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);\r
5975                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit\r
5976                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5977                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );\r
5978                                         return;\r
5979                                     }\r
5980                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet\r
5981                                         break; // Abort repetition-checking loop.\r
5982                                 }\r
5983                                 // if neither of us is checking or chasing all the time, or both are, it is draw\r
5984                              }\r
5985                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
5986                              return;\r
5987                         }\r
5988                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
5989                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
5990                     }\r
5991                 }\r
5992 \r
5993                 /* Now we test for 50-move draws. Determine ply count */\r
5994                 count = forwardMostMove;\r
5995                 /* look for last irreversble move */\r
5996                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
5997                     count--;\r
5998                 /* if we hit starting position, add initial plies */\r
5999                 if( count == backwardMostMove )\r
6000                     count -= initialRulePlies;\r
6001                 count = forwardMostMove - count; \r
6002                 if( count >= 100)\r
6003                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
6004                          /* this is used to judge if draw claims are legal */\r
6005                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
6006                          SendToProgram("force\n", cps->other); // suppress reply\r
6007                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6008                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6009                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
6010                          return;\r
6011                 }\r
6012 \r
6013                 /* if draw offer is pending, treat it as a draw claim\r
6014                  * when draw condition present, to allow engines a way to\r
6015                  * claim draws before making their move to avoid a race\r
6016                  * condition occurring after their move\r
6017                  */\r
6018                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
6019                          char *p = NULL;\r
6020                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
6021                              p = "Draw claim: 50-move rule";\r
6022                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
6023                              p = "Draw claim: 3-fold repetition";\r
6024                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
6025                              p = "Draw claim: insufficient mating material";\r
6026                          if( p != NULL ) {\r
6027                              SendToProgram("force\n", cps->other); // suppress reply\r
6028                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6029                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
6030                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6031                              return;\r
6032                          }\r
6033                 }\r
6034 \r
6035 \r
6036                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
6037                     SendToProgram("force\n", cps->other); // suppress reply\r
6038                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6039                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6040 \r
6041                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
6042 \r
6043                     return;\r
6044                 }\r
6045         }\r
6046 \r
6047         bookHit = NULL;\r
6048         if (gameMode == TwoMachinesPlay) {\r
6049             /* [HGM] relaying draw offers moved to after reception of move */\r
6050             /* and interpreting offer as claim if it brings draw condition */\r
6051             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
6052                 SendToProgram("draw\n", cps->other);\r
6053             }\r
6054             if (cps->other->sendTime) {\r
6055                 SendTimeRemaining(cps->other,\r
6056                                   cps->other->twoMachinesColor[0] == 'w');\r
6057             }\r
6058             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);\r
6059             if (firstMove && !bookHit) {\r
6060                 firstMove = FALSE;\r
6061                 if (cps->other->useColors) {\r
6062                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
6063                 }\r
6064                 SendToProgram("go\n", cps->other);\r
6065             }\r
6066             cps->other->maybeThinking = TRUE;\r
6067         }\r
6068 \r
6069         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6070         \r
6071         if (!pausing && appData.ringBellAfterMoves) {\r
6072             RingBell();\r
6073         }\r
6074 \r
6075         /* \r
6076          * Reenable menu items that were disabled while\r
6077          * machine was thinking\r
6078          */\r
6079         if (gameMode != TwoMachinesPlay)\r
6080             SetUserThinkingEnables();\r
6081 \r
6082         // [HGM] book: after book hit opponent has received move and is now in force mode\r
6083         // force the book reply into it, and then fake that it outputted this move by jumping\r
6084         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move\r
6085         if(bookHit) {\r
6086                 static char bookMove[MSG_SIZ]; // a bit generous?\r
6087 \r
6088                 strcpy(bookMove, "move ");\r
6089                 strcat(bookMove, bookHit);\r
6090                 message = bookMove;\r
6091                 cps = cps->other;\r
6092                 programStats.nodes = programStats.depth = programStats.time = \r
6093                 programStats.score = programStats.got_only_move = 0;\r
6094                 sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
6095 \r
6096                 if(cps->lastPing != cps->lastPong) {\r
6097                     savedMessage = message; // args for deferred call\r
6098                     savedState = cps;\r
6099                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
6100                     return;\r
6101                 }\r
6102                 goto FakeBookMove;\r
6103         }\r
6104 \r
6105         return;\r
6106     }\r
6107 \r
6108     /* Set special modes for chess engines.  Later something general\r
6109      *  could be added here; for now there is just one kludge feature,\r
6110      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
6111      *  when "xboard" is given as an interactive command.\r
6112      */\r
6113     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
6114         cps->useSigint = FALSE;\r
6115         cps->useSigterm = FALSE;\r
6116     }\r
6117 \r
6118     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
6119      * want this, I was asked to put it in, and obliged.\r
6120      */\r
6121     if (!strncmp(message, "setboard ", 9)) {\r
6122         Board initial_position; int i;\r
6123 \r
6124         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
6125 \r
6126         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
6127             DisplayError(_("Bad FEN received from engine"), 0);\r
6128             return ;\r
6129         } else {\r
6130            Reset(FALSE, FALSE);\r
6131            CopyBoard(boards[0], initial_position);\r
6132            initialRulePlies = FENrulePlies;\r
6133            epStatus[0] = FENepStatus;\r
6134            for( i=0; i<nrCastlingRights; i++ )\r
6135                 castlingRights[0][i] = FENcastlingRights[i];\r
6136            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
6137            else gameMode = MachinePlaysBlack;                 \r
6138            DrawPosition(FALSE, boards[currentMove]);\r
6139         }\r
6140         return;\r
6141     }\r
6142 \r
6143     /*\r
6144      * Look for communication commands\r
6145      */\r
6146     if (!strncmp(message, "telluser ", 9)) {\r
6147         DisplayNote(message + 9);\r
6148         return;\r
6149     }\r
6150     if (!strncmp(message, "tellusererror ", 14)) {\r
6151         DisplayError(message + 14, 0);\r
6152         return;\r
6153     }\r
6154     if (!strncmp(message, "tellopponent ", 13)) {\r
6155       if (appData.icsActive) {\r
6156         if (loggedOn) {\r
6157           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
6158           SendToICS(buf1);\r
6159         }\r
6160       } else {\r
6161         DisplayNote(message + 13);\r
6162       }\r
6163       return;\r
6164     }\r
6165     if (!strncmp(message, "tellothers ", 11)) {\r
6166       if (appData.icsActive) {\r
6167         if (loggedOn) {\r
6168           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
6169           SendToICS(buf1);\r
6170         }\r
6171       }\r
6172       return;\r
6173     }\r
6174     if (!strncmp(message, "tellall ", 8)) {\r
6175       if (appData.icsActive) {\r
6176         if (loggedOn) {\r
6177           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
6178           SendToICS(buf1);\r
6179         }\r
6180       } else {\r
6181         DisplayNote(message + 8);\r
6182       }\r
6183       return;\r
6184     }\r
6185     if (strncmp(message, "warning", 7) == 0) {\r
6186         /* Undocumented feature, use tellusererror in new code */\r
6187         DisplayError(message, 0);\r
6188         return;\r
6189     }\r
6190     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
6191         strcpy(realname, cps->tidy);\r
6192         strcat(realname, " query");\r
6193         AskQuestion(realname, buf2, buf1, cps->pr);\r
6194         return;\r
6195     }\r
6196     /* Commands from the engine directly to ICS.  We don't allow these to be \r
6197      *  sent until we are logged on. Crafty kibitzes have been known to \r
6198      *  interfere with the login process.\r
6199      */\r
6200     if (loggedOn) {\r
6201         if (!strncmp(message, "tellics ", 8)) {\r
6202             SendToICS(message + 8);\r
6203             SendToICS("\n");\r
6204             return;\r
6205         }\r
6206         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
6207             SendToICS(ics_prefix);\r
6208             SendToICS(message + 15);\r
6209             SendToICS("\n");\r
6210             return;\r
6211         }\r
6212         /* The following are for backward compatibility only */\r
6213         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
6214             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
6215             SendToICS(ics_prefix);\r
6216             SendToICS(message);\r
6217             SendToICS("\n");\r
6218             return;\r
6219         }\r
6220     }\r
6221     if (strncmp(message, "feature ", 8) == 0) {\r
6222       ParseFeatures(message+8, cps);\r
6223     }\r
6224     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
6225         return;\r
6226     }\r
6227     /*\r
6228      * If the move is illegal, cancel it and redraw the board.\r
6229      * Also deal with other error cases.  Matching is rather loose\r
6230      * here to accommodate engines written before the spec.\r
6231      */\r
6232     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
6233         strncmp(message, "Error", 5) == 0) {\r
6234         if (StrStr(message, "name") || \r
6235             StrStr(message, "rating") || StrStr(message, "?") ||\r
6236             StrStr(message, "result") || StrStr(message, "board") ||\r
6237             StrStr(message, "bk") || StrStr(message, "computer") ||\r
6238             StrStr(message, "variant") || StrStr(message, "hint") ||\r
6239             StrStr(message, "random") || StrStr(message, "depth") ||\r
6240             StrStr(message, "accepted")) {\r
6241             return;\r
6242         }\r
6243         if (StrStr(message, "protover")) {\r
6244           /* Program is responding to input, so it's apparently done\r
6245              initializing, and this error message indicates it is\r
6246              protocol version 1.  So we don't need to wait any longer\r
6247              for it to initialize and send feature commands. */\r
6248           FeatureDone(cps, 1);\r
6249           cps->protocolVersion = 1;\r
6250           return;\r
6251         }\r
6252         cps->maybeThinking = FALSE;\r
6253 \r
6254         if (StrStr(message, "draw")) {\r
6255             /* Program doesn't have "draw" command */\r
6256             cps->sendDrawOffers = 0;\r
6257             return;\r
6258         }\r
6259         if (cps->sendTime != 1 &&\r
6260             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
6261           /* Program apparently doesn't have "time" or "otim" command */\r
6262           cps->sendTime = 0;\r
6263           return;\r
6264         }\r
6265         if (StrStr(message, "analyze")) {\r
6266             cps->analysisSupport = FALSE;\r
6267             cps->analyzing = FALSE;\r
6268             Reset(FALSE, TRUE);\r
6269             sprintf(buf2, _("%s does not support analysis"), cps->tidy);\r
6270             DisplayError(buf2, 0);\r
6271             return;\r
6272         }\r
6273         if (StrStr(message, "(no matching move)st")) {\r
6274           /* Special kludge for GNU Chess 4 only */\r
6275           cps->stKludge = TRUE;\r
6276           SendTimeControl(cps, movesPerSession, timeControl,\r
6277                           timeIncrement, appData.searchDepth,\r
6278                           searchTime);\r
6279           return;\r
6280         }\r
6281         if (StrStr(message, "(no matching move)sd")) {\r
6282           /* Special kludge for GNU Chess 4 only */\r
6283           cps->sdKludge = TRUE;\r
6284           SendTimeControl(cps, movesPerSession, timeControl,\r
6285                           timeIncrement, appData.searchDepth,\r
6286                           searchTime);\r
6287           return;\r
6288         }\r
6289         if (!StrStr(message, "llegal")) {\r
6290             return;\r
6291         }\r
6292         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6293             gameMode == IcsIdle) return;\r
6294         if (forwardMostMove <= backwardMostMove) return;\r
6295 #if 0\r
6296         /* Following removed: it caused a bug where a real illegal move\r
6297            message in analyze mored would be ignored. */\r
6298         if (cps == &first && programStats.ok_to_send == 0) {\r
6299             /* Bogus message from Crafty responding to "."  This filtering\r
6300                can miss some of the bad messages, but fortunately the bug \r
6301                is fixed in current Crafty versions, so it doesn't matter. */\r
6302             return;\r
6303         }\r
6304 #endif\r
6305         if (pausing) PauseEvent();\r
6306         if (gameMode == PlayFromGameFile) {\r
6307             /* Stop reading this game file */\r
6308             gameMode = EditGame;\r
6309             ModeHighlight();\r
6310         }\r
6311         currentMove = --forwardMostMove;\r
6312         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
6313         SwitchClocks();\r
6314         DisplayBothClocks();\r
6315         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),\r
6316                 parseList[currentMove], cps->which);\r
6317         DisplayMoveError(buf1);\r
6318         DrawPosition(FALSE, boards[currentMove]);\r
6319 \r
6320         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
6321         /* only passes fully legal moves                            */\r
6322         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
6323             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
6324                                 "False illegal-move claim", GE_XBOARD );\r
6325         }\r
6326         return;\r
6327     }\r
6328     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
6329         /* Program has a broken "time" command that\r
6330            outputs a string not ending in newline.\r
6331            Don't use it. */\r
6332         cps->sendTime = 0;\r
6333     }\r
6334     \r
6335     /*\r
6336      * If chess program startup fails, exit with an error message.\r
6337      * Attempts to recover here are futile.\r
6338      */\r
6339     if ((StrStr(message, "unknown host") != NULL)\r
6340         || (StrStr(message, "No remote directory") != NULL)\r
6341         || (StrStr(message, "not found") != NULL)\r
6342         || (StrStr(message, "No such file") != NULL)\r
6343         || (StrStr(message, "can't alloc") != NULL)\r
6344         || (StrStr(message, "Permission denied") != NULL)) {\r
6345 \r
6346         cps->maybeThinking = FALSE;\r
6347         sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),\r
6348                 cps->which, cps->program, cps->host, message);\r
6349         RemoveInputSource(cps->isr);\r
6350         DisplayFatalError(buf1, 0, 1);\r
6351         return;\r
6352     }\r
6353     \r
6354     /* \r
6355      * Look for hint output\r
6356      */\r
6357     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
6358         if (cps == &first && hintRequested) {\r
6359             hintRequested = FALSE;\r
6360             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
6361                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
6362                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
6363                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
6364                                     fromY, fromX, toY, toX, promoChar, buf1);\r
6365                 sprintf(buf2, _("Hint: %s"), buf1);\r
6366                 DisplayInformation(buf2);\r
6367             } else {\r
6368                 /* Hint move could not be parsed!? */\r
6369                 sprintf(buf2,\r
6370                         _("Illegal hint move \"%s\"\nfrom %s chess program"),\r
6371                         buf1, cps->which);\r
6372                 DisplayError(buf2, 0);\r
6373             }\r
6374         } else {\r
6375             strcpy(lastHint, buf1);\r
6376         }\r
6377         return;\r
6378     }\r
6379 \r
6380     /*\r
6381      * Ignore other messages if game is not in progress\r
6382      */\r
6383     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6384         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
6385 \r
6386     /*\r
6387      * look for win, lose, draw, or draw offer\r
6388      */\r
6389     if (strncmp(message, "1-0", 3) == 0) {\r
6390         char *p, *q, *r = "";\r
6391         p = strchr(message, '{');\r
6392         if (p) {\r
6393             q = strchr(p, '}');\r
6394             if (q) {\r
6395                 *q = NULLCHAR;\r
6396                 r = p + 1;\r
6397             }\r
6398         }\r
6399         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
6400         return;\r
6401     } else if (strncmp(message, "0-1", 3) == 0) {\r
6402         char *p, *q, *r = "";\r
6403         p = strchr(message, '{');\r
6404         if (p) {\r
6405             q = strchr(p, '}');\r
6406             if (q) {\r
6407                 *q = NULLCHAR;\r
6408                 r = p + 1;\r
6409             }\r
6410         }\r
6411         /* Kludge for Arasan 4.1 bug */\r
6412         if (strcmp(r, "Black resigns") == 0) {\r
6413             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
6414             return;\r
6415         }\r
6416         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
6417         return;\r
6418     } else if (strncmp(message, "1/2", 3) == 0) {\r
6419         char *p, *q, *r = "";\r
6420         p = strchr(message, '{');\r
6421         if (p) {\r
6422             q = strchr(p, '}');\r
6423             if (q) {\r
6424                 *q = NULLCHAR;\r
6425                 r = p + 1;\r
6426             }\r
6427         }\r
6428             \r
6429         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
6430         return;\r
6431 \r
6432     } else if (strncmp(message, "White resign", 12) == 0) {\r
6433         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6434         return;\r
6435     } else if (strncmp(message, "Black resign", 12) == 0) {\r
6436         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6437         return;\r
6438     } else if (strncmp(message, "White matches", 13) == 0 ||\r
6439                strncmp(message, "Black matches", 13) == 0   ) {\r
6440         /* [HGM] ignore GNUShogi noises */\r
6441         return;\r
6442     } else if (strncmp(message, "White", 5) == 0 &&\r
6443                message[5] != '(' &&\r
6444                StrStr(message, "Black") == NULL) {\r
6445         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6446         return;\r
6447     } else if (strncmp(message, "Black", 5) == 0 &&\r
6448                message[5] != '(') {\r
6449         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6450         return;\r
6451     } else if (strcmp(message, "resign") == 0 ||\r
6452                strcmp(message, "computer resigns") == 0) {\r
6453         switch (gameMode) {\r
6454           case MachinePlaysBlack:\r
6455           case IcsPlayingBlack:\r
6456             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
6457             break;\r
6458           case MachinePlaysWhite:\r
6459           case IcsPlayingWhite:\r
6460             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
6461             break;\r
6462           case TwoMachinesPlay:\r
6463             if (cps->twoMachinesColor[0] == 'w')\r
6464               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6465             else\r
6466               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6467             break;\r
6468           default:\r
6469             /* can't happen */\r
6470             break;\r
6471         }\r
6472         return;\r
6473     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
6474         switch (gameMode) {\r
6475           case MachinePlaysBlack:\r
6476           case IcsPlayingBlack:\r
6477             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6478             break;\r
6479           case MachinePlaysWhite:\r
6480           case IcsPlayingWhite:\r
6481             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
6482             break;\r
6483           case TwoMachinesPlay:\r
6484             if (cps->twoMachinesColor[0] == 'w')\r
6485               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6486             else\r
6487               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6488             break;\r
6489           default:\r
6490             /* can't happen */\r
6491             break;\r
6492         }\r
6493         return;\r
6494     } else if (strncmp(message, "computer mates", 14) == 0) {\r
6495         switch (gameMode) {\r
6496           case MachinePlaysBlack:\r
6497           case IcsPlayingBlack:\r
6498             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
6499             break;\r
6500           case MachinePlaysWhite:\r
6501           case IcsPlayingWhite:\r
6502             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6503             break;\r
6504           case TwoMachinesPlay:\r
6505             if (cps->twoMachinesColor[0] == 'w')\r
6506               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6507             else\r
6508               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6509             break;\r
6510           default:\r
6511             /* can't happen */\r
6512             break;\r
6513         }\r
6514         return;\r
6515     } else if (strncmp(message, "checkmate", 9) == 0) {\r
6516         if (WhiteOnMove(forwardMostMove)) {\r
6517             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6518         } else {\r
6519             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6520         }\r
6521         return;\r
6522     } else if (strstr(message, "Draw") != NULL ||\r
6523                strstr(message, "game is a draw") != NULL) {\r
6524         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
6525         return;\r
6526     } else if (strstr(message, "offer") != NULL &&\r
6527                strstr(message, "draw") != NULL) {\r
6528 #if ZIPPY\r
6529         if (appData.zippyPlay && first.initDone) {\r
6530             /* Relay offer to ICS */\r
6531             SendToICS(ics_prefix);\r
6532             SendToICS("draw\n");\r
6533         }\r
6534 #endif\r
6535         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
6536         if (gameMode == TwoMachinesPlay) {\r
6537             if (cps->other->offeredDraw) {\r
6538                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6539             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
6540             /* until after we also have move, to see if it is really claim */\r
6541             }\r
6542 #if 0\r
6543               else {\r
6544                 if (cps->other->sendDrawOffers) {\r
6545                     SendToProgram("draw\n", cps->other);\r
6546                 }\r
6547             }\r
6548 #endif\r
6549         } else if (gameMode == MachinePlaysWhite ||\r
6550                    gameMode == MachinePlaysBlack) {\r
6551           if (userOfferedDraw) {\r
6552             DisplayInformation(_("Machine accepts your draw offer"));\r
6553             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6554           } else {\r
6555             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));\r
6556           }\r
6557         }\r
6558     }\r
6559 \r
6560     \r
6561     /*\r
6562      * Look for thinking output\r
6563      */\r
6564     if ( appData.showThinking // [HGM] thinking: test all options that cause this output\r
6565           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
6566                                 ) {\r
6567         int plylev, mvleft, mvtot, curscore, time;\r
6568         char mvname[MOVE_LEN];\r
6569         u64 nodes; // [DM]\r
6570         char plyext;\r
6571         int ignore = FALSE;\r
6572         int prefixHint = FALSE;\r
6573         mvname[0] = NULLCHAR;\r
6574 \r
6575         switch (gameMode) {\r
6576           case MachinePlaysBlack:\r
6577           case IcsPlayingBlack:\r
6578             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6579             break;\r
6580           case MachinePlaysWhite:\r
6581           case IcsPlayingWhite:\r
6582             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6583             break;\r
6584           case AnalyzeMode:\r
6585           case AnalyzeFile:\r
6586             break;\r
6587           case IcsObserving: /* [DM] icsEngineAnalyze */\r
6588             if (!appData.icsEngineAnalyze) ignore = TRUE;\r
6589             break;\r
6590           case TwoMachinesPlay:\r
6591             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
6592                 ignore = TRUE;\r
6593             }\r
6594             break;\r
6595           default:\r
6596             ignore = TRUE;\r
6597             break;\r
6598         }\r
6599 \r
6600         if (!ignore) {\r
6601             buf1[0] = NULLCHAR;\r
6602             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6603                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
6604 \r
6605                 if (plyext != ' ' && plyext != '\t') {\r
6606                     time *= 100;\r
6607                 }\r
6608 \r
6609                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6610                 if( cps->scoreIsAbsolute && \r
6611                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
6612                 {\r
6613                     curscore = -curscore;\r
6614                 }\r
6615 \r
6616 \r
6617                 programStats.depth = plylev;\r
6618                 programStats.nodes = nodes;\r
6619                 programStats.time = time;\r
6620                 programStats.score = curscore;\r
6621                 programStats.got_only_move = 0;\r
6622 \r
6623                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
6624                         int ticklen;\r
6625 \r
6626                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time\r
6627                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time\r
6628                         if(WhiteOnMove(forwardMostMove)) \r
6629                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
6630                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
6631                 }\r
6632 \r
6633                 /* Buffer overflow protection */\r
6634                 if (buf1[0] != NULLCHAR) {\r
6635                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
6636                         && appData.debugMode) {\r
6637                         fprintf(debugFP,\r
6638                                 "PV is too long; using the first %d bytes.\n",\r
6639                                 sizeof(programStats.movelist) - 1);\r
6640                     }\r
6641 \r
6642                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
6643                 } else {\r
6644                     sprintf(programStats.movelist, " no PV\n");\r
6645                 }\r
6646 \r
6647                 if (programStats.seen_stat) {\r
6648                     programStats.ok_to_send = 1;\r
6649                 }\r
6650 \r
6651                 if (strchr(programStats.movelist, '(') != NULL) {\r
6652                     programStats.line_is_book = 1;\r
6653                     programStats.nr_moves = 0;\r
6654                     programStats.moves_left = 0;\r
6655                 } else {\r
6656                     programStats.line_is_book = 0;\r
6657                 }\r
6658 \r
6659                 SendProgramStatsToFrontend( cps, &programStats );\r
6660 \r
6661                 /* \r
6662                     [AS] Protect the thinkOutput buffer from overflow... this\r
6663                     is only useful if buf1 hasn't overflowed first!\r
6664                 */\r
6665                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
6666                         plylev, \r
6667                         (gameMode == TwoMachinesPlay ?\r
6668                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
6669                         ((double) curscore) / 100.0,\r
6670                         prefixHint ? lastHint : "",\r
6671                         prefixHint ? " " : "" );\r
6672 \r
6673                 if( buf1[0] != NULLCHAR ) {\r
6674                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
6675 \r
6676                     if( strlen(buf1) > max_len ) {\r
6677                         if( appData.debugMode) {\r
6678                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
6679                         }\r
6680                         buf1[max_len+1] = '\0';\r
6681                     }\r
6682 \r
6683                     strcat( thinkOutput, buf1 );\r
6684                 }\r
6685 \r
6686                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode\r
6687                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6688                     DisplayMove(currentMove - 1);\r
6689                     DisplayAnalysis();\r
6690                 }\r
6691                 return;\r
6692 \r
6693             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6694                 /* crafty (9.25+) says "(only move) <move>"\r
6695                  * if there is only 1 legal move\r
6696                  */\r
6697                 sscanf(p, "(only move) %s", buf1);\r
6698                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6699                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6700                 programStats.depth = 1;\r
6701                 programStats.nr_moves = 1;\r
6702                 programStats.moves_left = 1;\r
6703                 programStats.nodes = 1;\r
6704                 programStats.time = 1;\r
6705                 programStats.got_only_move = 1;\r
6706 \r
6707                 /* Not really, but we also use this member to\r
6708                    mean "line isn't going to change" (Crafty\r
6709                    isn't searching, so stats won't change) */\r
6710                 programStats.line_is_book = 1;\r
6711 \r
6712                 SendProgramStatsToFrontend( cps, &programStats );\r
6713                 \r
6714                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || \r
6715                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6716                     DisplayMove(currentMove - 1);\r
6717                     DisplayAnalysis();\r
6718                 }\r
6719                 return;\r
6720             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",\r
6721                               &time, &nodes, &plylev, &mvleft,\r
6722                               &mvtot, mvname) >= 5) {\r
6723                 /* The stat01: line is from Crafty (9.29+) in response\r
6724                    to the "." command */\r
6725                 programStats.seen_stat = 1;\r
6726                 cps->maybeThinking = TRUE;\r
6727 \r
6728                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6729                   return;\r
6730 \r
6731                 programStats.depth = plylev;\r
6732                 programStats.time = time;\r
6733                 programStats.nodes = nodes;\r
6734                 programStats.moves_left = mvleft;\r
6735                 programStats.nr_moves = mvtot;\r
6736                 strcpy(programStats.move_name, mvname);\r
6737                 programStats.ok_to_send = 1;\r
6738                 programStats.movelist[0] = '\0';\r
6739 \r
6740                 SendProgramStatsToFrontend( cps, &programStats );\r
6741 \r
6742                 DisplayAnalysis();\r
6743                 return;\r
6744 \r
6745             } else if (strncmp(message,"++",2) == 0) {\r
6746                 /* Crafty 9.29+ outputs this */\r
6747                 programStats.got_fail = 2;\r
6748                 return;\r
6749 \r
6750             } else if (strncmp(message,"--",2) == 0) {\r
6751                 /* Crafty 9.29+ outputs this */\r
6752                 programStats.got_fail = 1;\r
6753                 return;\r
6754 \r
6755             } else if (thinkOutput[0] != NULLCHAR &&\r
6756                        strncmp(message, "    ", 4) == 0) {\r
6757                 unsigned message_len;\r
6758 \r
6759                 p = message;\r
6760                 while (*p && *p == ' ') p++;\r
6761 \r
6762                 message_len = strlen( p );\r
6763 \r
6764                 /* [AS] Avoid buffer overflow */\r
6765                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6766                     strcat(thinkOutput, " ");\r
6767                     strcat(thinkOutput, p);\r
6768                 }\r
6769 \r
6770                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6771                     strcat(programStats.movelist, " ");\r
6772                     strcat(programStats.movelist, p);\r
6773                 }\r
6774 \r
6775                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||\r
6776                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6777                     DisplayMove(currentMove - 1);\r
6778                     DisplayAnalysis();\r
6779                 }\r
6780                 return;\r
6781             }\r
6782         }\r
6783         else {\r
6784             buf1[0] = NULLCHAR;\r
6785 \r
6786             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6787                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6788             {\r
6789                 ChessProgramStats cpstats;\r
6790 \r
6791                 if (plyext != ' ' && plyext != '\t') {\r
6792                     time *= 100;\r
6793                 }\r
6794 \r
6795                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6796                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6797                     curscore = -curscore;\r
6798                 }\r
6799 \r
6800                 cpstats.depth = plylev;\r
6801                 cpstats.nodes = nodes;\r
6802                 cpstats.time = time;\r
6803                 cpstats.score = curscore;\r
6804                 cpstats.got_only_move = 0;\r
6805                 cpstats.movelist[0] = '\0';\r
6806 \r
6807                 if (buf1[0] != NULLCHAR) {\r
6808                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6809                 }\r
6810 \r
6811                 cpstats.ok_to_send = 0;\r
6812                 cpstats.line_is_book = 0;\r
6813                 cpstats.nr_moves = 0;\r
6814                 cpstats.moves_left = 0;\r
6815 \r
6816                 SendProgramStatsToFrontend( cps, &cpstats );\r
6817             }\r
6818         }\r
6819     }\r
6820 }\r
6821 \r
6822 \r
6823 /* Parse a game score from the character string "game", and\r
6824    record it as the history of the current game.  The game\r
6825    score is NOT assumed to start from the standard position. \r
6826    The display is not updated in any way.\r
6827    */\r
6828 void\r
6829 ParseGameHistory(game)\r
6830      char *game;\r
6831 {\r
6832     ChessMove moveType;\r
6833     int fromX, fromY, toX, toY, boardIndex;\r
6834     char promoChar;\r
6835     char *p, *q;\r
6836     char buf[MSG_SIZ];\r
6837 \r
6838     if (appData.debugMode)\r
6839       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6840 \r
6841     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6842     gameInfo.site = StrSave(appData.icsHost);\r
6843     gameInfo.date = PGNDate();\r
6844     gameInfo.round = StrSave("-");\r
6845 \r
6846     /* Parse out names of players */\r
6847     while (*game == ' ') game++;\r
6848     p = buf;\r
6849     while (*game != ' ') *p++ = *game++;\r
6850     *p = NULLCHAR;\r
6851     gameInfo.white = StrSave(buf);\r
6852     while (*game == ' ') game++;\r
6853     p = buf;\r
6854     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6855     *p = NULLCHAR;\r
6856     gameInfo.black = StrSave(buf);\r
6857 \r
6858     /* Parse moves */\r
6859     boardIndex = blackPlaysFirst ? 1 : 0;\r
6860     yynewstr(game);\r
6861     for (;;) {\r
6862         yyboardindex = boardIndex;\r
6863         moveType = (ChessMove) yylex();\r
6864         switch (moveType) {\r
6865           case IllegalMove:             /* maybe suicide chess, etc. */\r
6866   if (appData.debugMode) {\r
6867     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6868     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6869     setbuf(debugFP, NULL);\r
6870   }\r
6871           case WhitePromotionChancellor:\r
6872           case BlackPromotionChancellor:\r
6873           case WhitePromotionArchbishop:\r
6874           case BlackPromotionArchbishop:\r
6875           case WhitePromotionQueen:\r
6876           case BlackPromotionQueen:\r
6877           case WhitePromotionRook:\r
6878           case BlackPromotionRook:\r
6879           case WhitePromotionBishop:\r
6880           case BlackPromotionBishop:\r
6881           case WhitePromotionKnight:\r
6882           case BlackPromotionKnight:\r
6883           case WhitePromotionKing:\r
6884           case BlackPromotionKing:\r
6885           case NormalMove:\r
6886           case WhiteCapturesEnPassant:\r
6887           case BlackCapturesEnPassant:\r
6888           case WhiteKingSideCastle:\r
6889           case WhiteQueenSideCastle:\r
6890           case BlackKingSideCastle:\r
6891           case BlackQueenSideCastle:\r
6892           case WhiteKingSideCastleWild:\r
6893           case WhiteQueenSideCastleWild:\r
6894           case BlackKingSideCastleWild:\r
6895           case BlackQueenSideCastleWild:\r
6896           /* PUSH Fabien */\r
6897           case WhiteHSideCastleFR:\r
6898           case WhiteASideCastleFR:\r
6899           case BlackHSideCastleFR:\r
6900           case BlackASideCastleFR:\r
6901           /* POP Fabien */\r
6902             fromX = currentMoveString[0] - AAA;\r
6903             fromY = currentMoveString[1] - ONE;\r
6904             toX = currentMoveString[2] - AAA;\r
6905             toY = currentMoveString[3] - ONE;\r
6906             promoChar = currentMoveString[4];\r
6907             break;\r
6908           case WhiteDrop:\r
6909           case BlackDrop:\r
6910             fromX = moveType == WhiteDrop ?\r
6911               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6912             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6913             fromY = DROP_RANK;\r
6914             toX = currentMoveString[2] - AAA;\r
6915             toY = currentMoveString[3] - ONE;\r
6916             promoChar = NULLCHAR;\r
6917             break;\r
6918           case AmbiguousMove:\r
6919             /* bug? */\r
6920             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);\r
6921   if (appData.debugMode) {\r
6922     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
6923     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6924     setbuf(debugFP, NULL);\r
6925   }\r
6926             DisplayError(buf, 0);\r
6927             return;\r
6928           case ImpossibleMove:\r
6929             /* bug? */\r
6930             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);\r
6931   if (appData.debugMode) {\r
6932     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
6933     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6934     setbuf(debugFP, NULL);\r
6935   }\r
6936             DisplayError(buf, 0);\r
6937             return;\r
6938           case (ChessMove) 0:   /* end of file */\r
6939             if (boardIndex < backwardMostMove) {\r
6940                 /* Oops, gap.  How did that happen? */\r
6941                 DisplayError(_("Gap in move list"), 0);\r
6942                 return;\r
6943             }\r
6944             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
6945             if (boardIndex > forwardMostMove) {\r
6946                 forwardMostMove = boardIndex;\r
6947             }\r
6948             return;\r
6949           case ElapsedTime:\r
6950             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
6951                 strcat(parseList[boardIndex-1], " ");\r
6952                 strcat(parseList[boardIndex-1], yy_text);\r
6953             }\r
6954             continue;\r
6955           case Comment:\r
6956           case PGNTag:\r
6957           case NAG:\r
6958           default:\r
6959             /* ignore */\r
6960             continue;\r
6961           case WhiteWins:\r
6962           case BlackWins:\r
6963           case GameIsDrawn:\r
6964           case GameUnfinished:\r
6965             if (gameMode == IcsExamining) {\r
6966                 if (boardIndex < backwardMostMove) {\r
6967                     /* Oops, gap.  How did that happen? */\r
6968                     return;\r
6969                 }\r
6970                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6971                 return;\r
6972             }\r
6973             gameInfo.result = moveType;\r
6974             p = strchr(yy_text, '{');\r
6975             if (p == NULL) p = strchr(yy_text, '(');\r
6976             if (p == NULL) {\r
6977                 p = yy_text;\r
6978                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
6979             } else {\r
6980                 q = strchr(p, *p == '{' ? '}' : ')');\r
6981                 if (q != NULL) *q = NULLCHAR;\r
6982                 p++;\r
6983             }\r
6984             gameInfo.resultDetails = StrSave(p);\r
6985             continue;\r
6986         }\r
6987         if (boardIndex >= forwardMostMove &&\r
6988             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
6989             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6990             return;\r
6991         }\r
6992         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
6993                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
6994                                  parseList[boardIndex]);\r
6995         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
6996         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}\r
6997         /* currentMoveString is set as a side-effect of yylex */\r
6998         strcpy(moveList[boardIndex], currentMoveString);\r
6999         strcat(moveList[boardIndex], "\n");\r
7000         boardIndex++;\r
7001         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], \r
7002                                         castlingRights[boardIndex], &epStatus[boardIndex]);\r
7003         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
7004                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
7005           case MT_NONE:\r
7006           case MT_STALEMATE:\r
7007           default:\r
7008             break;\r
7009           case MT_CHECK:\r
7010             if(gameInfo.variant != VariantShogi)\r
7011                 strcat(parseList[boardIndex - 1], "+");\r
7012             break;\r
7013           case MT_CHECKMATE:\r
7014           case MT_STAINMATE:\r
7015             strcat(parseList[boardIndex - 1], "#");\r
7016             break;\r
7017         }\r
7018     }\r
7019 }\r
7020 \r
7021 \r
7022 /* Apply a move to the given board  */\r
7023 void\r
7024 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)\r
7025      int fromX, fromY, toX, toY;\r
7026      int promoChar;\r
7027      Board board;\r
7028      char *castling;\r
7029      char *ep;\r
7030 {\r
7031   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
7032 \r
7033     /* [HGM] compute & store e.p. status and castling rights for new position */\r
7034     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */\r
7035     { int i;\r
7036 \r
7037       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
7038       oldEP = *ep;\r
7039       *ep = EP_NONE;\r
7040 \r
7041       if( board[toY][toX] != EmptySquare ) \r
7042            *ep = EP_CAPTURE;  \r
7043 \r
7044       if( board[fromY][fromX] == WhitePawn ) {\r
7045            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7046                *ep = EP_PAWN_MOVE;\r
7047            if( toY-fromY==2) {\r
7048                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
7049                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7050                       *ep = toX | berolina;\r
7051                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
7052                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7053                       *ep = toX;\r
7054            }\r
7055       } else \r
7056       if( board[fromY][fromX] == BlackPawn ) {\r
7057            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7058                *ep = EP_PAWN_MOVE; \r
7059            if( toY-fromY== -2) {\r
7060                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
7061                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7062                       *ep = toX | berolina;\r
7063                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
7064                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7065                       *ep = toX;\r
7066            }\r
7067        }\r
7068 \r
7069        for(i=0; i<nrCastlingRights; i++) {\r
7070            if(castling[i] == fromX && castlingRank[i] == fromY ||\r
7071               castling[i] == toX   && castlingRank[i] == toY   \r
7072              ) castling[i] = -1; // revoke for moved or captured piece\r
7073        }\r
7074 \r
7075     }\r
7076 \r
7077   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
7078   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
7079        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
7080          \r
7081   if (fromX == toX && fromY == toY) return;\r
7082 \r
7083   if (fromY == DROP_RANK) {\r
7084         /* must be first */\r
7085         piece = board[toY][toX] = (ChessSquare) fromX;\r
7086   } else {\r
7087      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
7088      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
7089      if(gameInfo.variant == VariantKnightmate)\r
7090          king += (int) WhiteUnicorn - (int) WhiteKing;\r
7091 \r
7092     /* Code added by Tord: */\r
7093     /* FRC castling assumed when king captures friendly rook. */\r
7094     if (board[fromY][fromX] == WhiteKing &&\r
7095              board[toY][toX] == WhiteRook) {\r
7096       board[fromY][fromX] = EmptySquare;\r
7097       board[toY][toX] = EmptySquare;\r
7098       if(toX > fromX) {\r
7099         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
7100       } else {\r
7101         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
7102       }\r
7103     } else if (board[fromY][fromX] == BlackKing &&\r
7104                board[toY][toX] == BlackRook) {\r
7105       board[fromY][fromX] = EmptySquare;\r
7106       board[toY][toX] = EmptySquare;\r
7107       if(toX > fromX) {\r
7108         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
7109       } else {\r
7110         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
7111       }\r
7112     /* End of code added by Tord */\r
7113 \r
7114     } else if (board[fromY][fromX] == king\r
7115         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7116         && toY == fromY && toX > fromX+1) {\r
7117         board[fromY][fromX] = EmptySquare;\r
7118         board[toY][toX] = king;\r
7119         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7120         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7121     } else if (board[fromY][fromX] == king\r
7122         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7123                && toY == fromY && toX < fromX-1) {\r
7124         board[fromY][fromX] = EmptySquare;\r
7125         board[toY][toX] = king;\r
7126         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7127         board[fromY][BOARD_LEFT] = EmptySquare;\r
7128     } else if (board[fromY][fromX] == WhitePawn\r
7129                && toY == BOARD_HEIGHT-1\r
7130                && gameInfo.variant != VariantXiangqi\r
7131                ) {\r
7132         /* white pawn promotion */\r
7133         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
7134         if (board[toY][toX] == EmptySquare) {\r
7135             board[toY][toX] = WhiteQueen;\r
7136         }\r
7137         if(gameInfo.variant==VariantBughouse ||\r
7138            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7139             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7140         board[fromY][fromX] = EmptySquare;\r
7141     } else if ((fromY == BOARD_HEIGHT-4)\r
7142                && (toX != fromX)\r
7143                && gameInfo.variant != VariantXiangqi\r
7144                && gameInfo.variant != VariantBerolina\r
7145                && (board[fromY][fromX] == WhitePawn)\r
7146                && (board[toY][toX] == EmptySquare)) {\r
7147         board[fromY][fromX] = EmptySquare;\r
7148         board[toY][toX] = WhitePawn;\r
7149         captured = board[toY - 1][toX];\r
7150         board[toY - 1][toX] = EmptySquare;\r
7151     } else if ((fromY == BOARD_HEIGHT-4)\r
7152                && (toX == fromX)\r
7153                && gameInfo.variant == VariantBerolina\r
7154                && (board[fromY][fromX] == WhitePawn)\r
7155                && (board[toY][toX] == EmptySquare)) {\r
7156         board[fromY][fromX] = EmptySquare;\r
7157         board[toY][toX] = WhitePawn;\r
7158         if(oldEP & EP_BEROLIN_A) {\r
7159                 captured = board[fromY][fromX-1];\r
7160                 board[fromY][fromX-1] = EmptySquare;\r
7161         }else{  captured = board[fromY][fromX+1];\r
7162                 board[fromY][fromX+1] = EmptySquare;\r
7163         }\r
7164     } else if (board[fromY][fromX] == king\r
7165         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7166                && toY == fromY && toX > fromX+1) {\r
7167         board[fromY][fromX] = EmptySquare;\r
7168         board[toY][toX] = king;\r
7169         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7170         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7171     } else if (board[fromY][fromX] == king\r
7172         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7173                && toY == fromY && toX < fromX-1) {\r
7174         board[fromY][fromX] = EmptySquare;\r
7175         board[toY][toX] = king;\r
7176         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7177         board[fromY][BOARD_LEFT] = EmptySquare;\r
7178     } else if (fromY == 7 && fromX == 3\r
7179                && board[fromY][fromX] == BlackKing\r
7180                && toY == 7 && toX == 5) {\r
7181         board[fromY][fromX] = EmptySquare;\r
7182         board[toY][toX] = BlackKing;\r
7183         board[fromY][7] = EmptySquare;\r
7184         board[toY][4] = BlackRook;\r
7185     } else if (fromY == 7 && fromX == 3\r
7186                && board[fromY][fromX] == BlackKing\r
7187                && toY == 7 && toX == 1) {\r
7188         board[fromY][fromX] = EmptySquare;\r
7189         board[toY][toX] = BlackKing;\r
7190         board[fromY][0] = EmptySquare;\r
7191         board[toY][2] = BlackRook;\r
7192     } else if (board[fromY][fromX] == BlackPawn\r
7193                && toY == 0\r
7194                && gameInfo.variant != VariantXiangqi\r
7195                ) {\r
7196         /* black pawn promotion */\r
7197         board[0][toX] = CharToPiece(ToLower(promoChar));\r
7198         if (board[0][toX] == EmptySquare) {\r
7199             board[0][toX] = BlackQueen;\r
7200         }\r
7201         if(gameInfo.variant==VariantBughouse ||\r
7202            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7203             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7204         board[fromY][fromX] = EmptySquare;\r
7205     } else if ((fromY == 3)\r
7206                && (toX != fromX)\r
7207                && gameInfo.variant != VariantXiangqi\r
7208                && gameInfo.variant != VariantBerolina\r
7209                && (board[fromY][fromX] == BlackPawn)\r
7210                && (board[toY][toX] == EmptySquare)) {\r
7211         board[fromY][fromX] = EmptySquare;\r
7212         board[toY][toX] = BlackPawn;\r
7213         captured = board[toY + 1][toX];\r
7214         board[toY + 1][toX] = EmptySquare;\r
7215     } else if ((fromY == 3)\r
7216                && (toX == fromX)\r
7217                && gameInfo.variant == VariantBerolina\r
7218                && (board[fromY][fromX] == BlackPawn)\r
7219                && (board[toY][toX] == EmptySquare)) {\r
7220         board[fromY][fromX] = EmptySquare;\r
7221         board[toY][toX] = BlackPawn;\r
7222         if(oldEP & EP_BEROLIN_A) {\r
7223                 captured = board[fromY][fromX-1];\r
7224                 board[fromY][fromX-1] = EmptySquare;\r
7225         }else{  captured = board[fromY][fromX+1];\r
7226                 board[fromY][fromX+1] = EmptySquare;\r
7227         }\r
7228     } else {\r
7229         board[toY][toX] = board[fromY][fromX];\r
7230         board[fromY][fromX] = EmptySquare;\r
7231     }\r
7232 \r
7233     /* [HGM] now we promote for Shogi, if needed */\r
7234     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
7235         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7236   }\r
7237 \r
7238     if (gameInfo.holdingsWidth != 0) {\r
7239 \r
7240       /* !!A lot more code needs to be written to support holdings  */\r
7241       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
7242       /* penultimate board files, so they are automaticlly stored   */\r
7243       /* in the game history.                                       */\r
7244       if (fromY == DROP_RANK) {\r
7245         /* Delete from holdings, by decreasing count */\r
7246         /* and erasing image if necessary            */\r
7247         p = (int) fromX;\r
7248         if(p < (int) BlackPawn) { /* white drop */\r
7249              p -= (int)WhitePawn;\r
7250              if(p >= gameInfo.holdingsSize) p = 0;\r
7251              if(--board[p][BOARD_WIDTH-2] == 0)\r
7252                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
7253         } else {                  /* black drop */\r
7254              p -= (int)BlackPawn;\r
7255              if(p >= gameInfo.holdingsSize) p = 0;\r
7256              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
7257                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
7258         }\r
7259       }\r
7260       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
7261           && gameInfo.variant != VariantBughouse        ) {\r
7262         /* [HGM] holdings: Add to holdings, if holdings exist */\r
7263         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { \r
7264                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
7265                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
7266         }\r
7267         p = (int) captured;\r
7268         if (p >= (int) BlackPawn) {\r
7269           p -= (int)BlackPawn;\r
7270           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7271                   /* in Shogi restore piece to its original  first */\r
7272                   captured = (ChessSquare) (DEMOTED captured);\r
7273                   p = DEMOTED p;\r
7274           }\r
7275           p = PieceToNumber((ChessSquare)p);\r
7276           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
7277           board[p][BOARD_WIDTH-2]++;\r
7278           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;\r
7279         } else {\r
7280           p -= (int)WhitePawn;\r
7281           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7282                   captured = (ChessSquare) (DEMOTED captured);\r
7283                   p = DEMOTED p;\r
7284           }\r
7285           p = PieceToNumber((ChessSquare)p);\r
7286           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
7287           board[BOARD_HEIGHT-1-p][1]++;\r
7288           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;\r
7289         }\r
7290       }\r
7291 \r
7292     } else if (gameInfo.variant == VariantAtomic) {\r
7293       if (captured != EmptySquare) {\r
7294         int y, x;\r
7295         for (y = toY-1; y <= toY+1; y++) {\r
7296           for (x = toX-1; x <= toX+1; x++) {\r
7297             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
7298                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
7299               board[y][x] = EmptySquare;\r
7300             }\r
7301           }\r
7302         }\r
7303         board[toY][toX] = EmptySquare;\r
7304       }\r
7305     }\r
7306     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
7307         /* [HGM] Shogi promotions */\r
7308         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7309     }\r
7310 \r
7311     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) \r
7312                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
7313         // [HGM] superchess: take promotion piece out of holdings\r
7314         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
7315         if((int)piece < (int)BlackPawn) { // determine stm from piece color\r
7316             if(!--board[k][BOARD_WIDTH-2])\r
7317                 board[k][BOARD_WIDTH-1] = EmptySquare;\r
7318         } else {\r
7319             if(!--board[BOARD_HEIGHT-1-k][1])\r
7320                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;\r
7321         }\r
7322     }\r
7323 \r
7324 }\r
7325 \r
7326 /* Updates forwardMostMove */\r
7327 void\r
7328 MakeMove(fromX, fromY, toX, toY, promoChar)\r
7329      int fromX, fromY, toX, toY;\r
7330      int promoChar;\r
7331 {\r
7332 //    forwardMostMove++; // [HGM] bare: moved downstream\r
7333 \r
7334     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */\r
7335         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
7336         piece = boards[forwardMostMove][fromY][fromX];\r
7337         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
7338         if(gameInfo.variant == VariantKnightmate)\r
7339             king += (int) WhiteUnicorn - (int) WhiteKing;\r
7340         if(forwardMostMove == 0) {\r
7341             if(blackPlaysFirst) \r
7342                 fprintf(serverMoves, "%s;", second.tidy);\r
7343             fprintf(serverMoves, "%s;", first.tidy);\r
7344             if(!blackPlaysFirst) \r
7345                 fprintf(serverMoves, "%s;", second.tidy);\r
7346         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
7347         lastLoadFlag = loadFlag;\r
7348         // print base move\r
7349         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
7350         // print castling suffix\r
7351         if( toY == fromY && piece == king ) {\r
7352             if(toX-fromX > 1)\r
7353                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
7354             if(fromX-toX >1)\r
7355                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
7356         }\r
7357         // e.p. suffix\r
7358         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||\r
7359              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&\r
7360              boards[forwardMostMove][toY][toX] == EmptySquare\r
7361              && fromX != toX )\r
7362                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
7363         // promotion suffix\r
7364         if(promoChar != NULLCHAR)\r
7365                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
7366         if(!loadFlag) {\r
7367             fprintf(serverMoves, "/%d/%d",\r
7368                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);\r
7369             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;\r
7370             else                      timeLeft = blackTimeRemaining/1000;\r
7371             fprintf(serverMoves, "/%d", timeLeft);\r
7372         }\r
7373         fflush(serverMoves);\r
7374     }\r
7375 \r
7376     if (forwardMostMove+1 >= MAX_MOVES) {\r
7377       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
7378                         0, 1);\r
7379       return;\r
7380     }\r
7381     SwitchClocks();\r
7382     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;\r
7383     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;\r
7384     if (commentList[forwardMostMove+1] != NULL) {\r
7385         free(commentList[forwardMostMove+1]);\r
7386         commentList[forwardMostMove+1] = NULL;\r
7387     }\r
7388     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);\r
7389     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}\r
7390     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], \r
7391                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);\r
7392     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board\r
7393     gameInfo.result = GameUnfinished;\r
7394     if (gameInfo.resultDetails != NULL) {\r
7395         free(gameInfo.resultDetails);\r
7396         gameInfo.resultDetails = NULL;\r
7397     }\r
7398     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
7399                               moveList[forwardMostMove - 1]);\r
7400     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
7401                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
7402                              fromY, fromX, toY, toX, promoChar,\r
7403                              parseList[forwardMostMove - 1]);\r
7404     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
7405                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
7406                             castlingRights[forwardMostMove]) ) {\r
7407       case MT_NONE:\r
7408       case MT_STALEMATE:\r
7409       default:\r
7410         break;\r
7411       case MT_CHECK:\r
7412         if(gameInfo.variant != VariantShogi)\r
7413             strcat(parseList[forwardMostMove - 1], "+");\r
7414         break;\r
7415       case MT_CHECKMATE:\r
7416       case MT_STAINMATE:\r
7417         strcat(parseList[forwardMostMove - 1], "#");\r
7418         break;\r
7419     }\r
7420     if (appData.debugMode) {\r
7421         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
7422     }\r
7423 \r
7424 }\r
7425 \r
7426 /* Updates currentMove if not pausing */\r
7427 void\r
7428 ShowMove(fromX, fromY, toX, toY)\r
7429 {\r
7430     int instant = (gameMode == PlayFromGameFile) ?\r
7431         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
7432     if(appData.noGUI) return;\r
7433     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
7434         if (!instant) {\r
7435             if (forwardMostMove == currentMove + 1) {\r
7436                 AnimateMove(boards[forwardMostMove - 1],\r
7437                             fromX, fromY, toX, toY);\r
7438             }\r
7439             if (appData.highlightLastMove) {\r
7440                 SetHighlights(fromX, fromY, toX, toY);\r
7441             }\r
7442         }\r
7443         currentMove = forwardMostMove;\r
7444     }\r
7445 \r
7446     if (instant) return;\r
7447 \r
7448     DisplayMove(currentMove - 1);\r
7449     DrawPosition(FALSE, boards[currentMove]);\r
7450     DisplayBothClocks();\r
7451     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
7452 }\r
7453 \r
7454 void SendEgtPath(ChessProgramState *cps)\r
7455 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */\r
7456         char buf[MSG_SIZ], name[MSG_SIZ], *p;\r
7457 \r
7458         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;\r
7459 \r
7460         while(*p) {\r
7461             char c, *q = name+1, *r, *s;\r
7462 \r
7463             name[0] = ','; // extract next format name from feature and copy with prefixed ','\r
7464             while(*p && *p != ',') *q++ = *p++;\r
7465             *q++ = ':'; *q = 0;\r
7466             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && \r
7467                 strcmp(name, ",nalimov:") == 0 ) {\r
7468                 // take nalimov path from the menu-changeable option first, if it is defined\r
7469                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);\r
7470                 SendToProgram(buf,cps);     // send egtbpath command for nalimov\r
7471             } else\r
7472             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||\r
7473                 (s = StrStr(appData.egtFormats, name)) != NULL) {\r
7474                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma\r
7475                 s = r = StrStr(s, ":") + 1; // beginning of path info\r
7476                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string\r
7477                 c = *r; *r = 0;             // temporarily null-terminate path info\r
7478                     *--q = 0;               // strip of trailig ':' from name\r
7479                     sprintf(buf, "egtbpath %s %s\n", name+1, s);\r
7480                 *r = c;\r
7481                 SendToProgram(buf,cps);     // send egtbpath command for this format\r
7482             }\r
7483             if(*p == ',') p++; // read away comma to position for next format name\r
7484         }\r
7485 }\r
7486 \r
7487 void\r
7488 InitChessProgram(cps, setup)\r
7489      ChessProgramState *cps;\r
7490      int setup; /* [HGM] needed to setup FRC opening position */\r
7491 {\r
7492     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
7493     if (appData.noChessProgram) return;\r
7494     hintRequested = FALSE;\r
7495     bookRequested = FALSE;\r
7496 \r
7497     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */\r
7498     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */\r
7499     if(cps->memSize) { /* [HGM] memory */\r
7500         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);\r
7501         SendToProgram(buf, cps);\r
7502     }\r
7503     SendEgtPath(cps); /* [HGM] EGT */\r
7504     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */\r
7505         sprintf(buf, "cores %d\n", appData.smpCores);\r
7506         SendToProgram(buf, cps);\r
7507     }\r
7508 \r
7509     SendToProgram(cps->initString, cps);\r
7510     if (gameInfo.variant != VariantNormal &&\r
7511         gameInfo.variant != VariantLoadable\r
7512         /* [HGM] also send variant if board size non-standard */\r
7513         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
7514                                             ) {\r
7515       char *v = VariantName(gameInfo.variant);\r
7516       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
7517         /* [HGM] in protocol 1 we have to assume all variants valid */\r
7518         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);\r
7519         DisplayFatalError(buf, 0, 1);\r
7520         return;\r
7521       }\r
7522 \r
7523       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
7524       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7525       if( gameInfo.variant == VariantXiangqi )\r
7526            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
7527       if( gameInfo.variant == VariantShogi )\r
7528            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
7529       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
7530            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
7531       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
7532                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
7533            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7534       if( gameInfo.variant == VariantCourier )\r
7535            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7536       if( gameInfo.variant == VariantSuper )\r
7537            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7538       if( gameInfo.variant == VariantGreat )\r
7539            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7540 \r
7541       if(overruled) {\r
7542            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
7543                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
7544            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
7545            if(StrStr(cps->variants, b) == NULL) { \r
7546                // specific sized variant not known, check if general sizing allowed\r
7547                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
7548                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
7549                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
7550                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
7551                        DisplayFatalError(buf, 0, 1);\r
7552                        return;\r
7553                    }\r
7554                    /* [HGM] here we really should compare with the maximum supported board size */\r
7555                }\r
7556            }\r
7557       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
7558       sprintf(buf, "variant %s\n", b);\r
7559       SendToProgram(buf, cps);\r
7560     }\r
7561     currentlyInitializedVariant = gameInfo.variant;\r
7562 \r
7563     /* [HGM] send opening position in FRC to first engine */\r
7564     if(setup) {\r
7565           SendToProgram("force\n", cps);\r
7566           SendBoard(cps, 0);\r
7567           /* engine is now in force mode! Set flag to wake it up after first move. */\r
7568           setboardSpoiledMachineBlack = 1;\r
7569     }\r
7570 \r
7571     if (cps->sendICS) {\r
7572       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
7573       SendToProgram(buf, cps);\r
7574     }\r
7575     cps->maybeThinking = FALSE;\r
7576     cps->offeredDraw = 0;\r
7577     if (!appData.icsActive) {\r
7578         SendTimeControl(cps, movesPerSession, timeControl,\r
7579                         timeIncrement, appData.searchDepth,\r
7580                         searchTime);\r
7581     }\r
7582     if (appData.showThinking \r
7583         // [HGM] thinking: four options require thinking output to be sent\r
7584         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
7585                                 ) {\r
7586         SendToProgram("post\n", cps);\r
7587     }\r
7588     SendToProgram("hard\n", cps);\r
7589     if (!appData.ponderNextMove) {\r
7590         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
7591            it without being sure what state we are in first.  "hard"\r
7592            is not a toggle, so that one is OK.\r
7593          */\r
7594         SendToProgram("easy\n", cps);\r
7595     }\r
7596     if (cps->usePing) {\r
7597       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
7598       SendToProgram(buf, cps);\r
7599     }\r
7600     cps->initDone = TRUE;\r
7601 }   \r
7602 \r
7603 \r
7604 void\r
7605 StartChessProgram(cps)\r
7606      ChessProgramState *cps;\r
7607 {\r
7608     char buf[MSG_SIZ];\r
7609     int err;\r
7610 \r
7611     if (appData.noChessProgram) return;\r
7612     cps->initDone = FALSE;\r
7613 \r
7614     if (strcmp(cps->host, "localhost") == 0) {\r
7615         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
7616     } else if (*appData.remoteShell == NULLCHAR) {\r
7617         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
7618     } else {\r
7619         if (*appData.remoteUser == NULLCHAR) {\r
7620             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
7621                     cps->program);\r
7622         } else {\r
7623             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
7624                     cps->host, appData.remoteUser, cps->program);\r
7625         }\r
7626         err = StartChildProcess(buf, "", &cps->pr);\r
7627     }\r
7628     \r
7629     if (err != 0) {\r
7630         sprintf(buf, _("Startup failure on '%s'"), cps->program);\r
7631         DisplayFatalError(buf, err, 1);\r
7632         cps->pr = NoProc;\r
7633         cps->isr = NULL;\r
7634         return;\r
7635     }\r
7636     \r
7637     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
7638     if (cps->protocolVersion > 1) {\r
7639       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
7640       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options\r
7641       cps->comboCnt = 0;  //                and values of combo boxes\r
7642       SendToProgram(buf, cps);\r
7643     } else {\r
7644       SendToProgram("xboard\n", cps);\r
7645     }\r
7646 }\r
7647 \r
7648 \r
7649 void\r
7650 TwoMachinesEventIfReady P((void))\r
7651 {\r
7652   if (first.lastPing != first.lastPong) {\r
7653     DisplayMessage("", _("Waiting for first chess program"));\r
7654     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7655     return;\r
7656   }\r
7657   if (second.lastPing != second.lastPong) {\r
7658     DisplayMessage("", _("Waiting for second chess program"));\r
7659     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7660     return;\r
7661   }\r
7662   ThawUI();\r
7663   TwoMachinesEvent();\r
7664 }\r
7665 \r
7666 void\r
7667 NextMatchGame P((void))\r
7668 {\r
7669     int index; /* [HGM] autoinc: step lod index during match */\r
7670     Reset(FALSE, TRUE);\r
7671     if (*appData.loadGameFile != NULLCHAR) {\r
7672         index = appData.loadGameIndex;\r
7673         if(index < 0) { // [HGM] autoinc\r
7674             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7675             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7676         } \r
7677         LoadGameFromFile(appData.loadGameFile,\r
7678                          index,\r
7679                          appData.loadGameFile, FALSE);\r
7680     } else if (*appData.loadPositionFile != NULLCHAR) {\r
7681         index = appData.loadPositionIndex;\r
7682         if(index < 0) { // [HGM] autoinc\r
7683             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7684             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7685         } \r
7686         LoadPositionFromFile(appData.loadPositionFile,\r
7687                              index,\r
7688                              appData.loadPositionFile);\r
7689     }\r
7690     TwoMachinesEventIfReady();\r
7691 }\r
7692 \r
7693 void UserAdjudicationEvent( int result )\r
7694 {\r
7695     ChessMove gameResult = GameIsDrawn;\r
7696 \r
7697     if( result > 0 ) {\r
7698         gameResult = WhiteWins;\r
7699     }\r
7700     else if( result < 0 ) {\r
7701         gameResult = BlackWins;\r
7702     }\r
7703 \r
7704     if( gameMode == TwoMachinesPlay ) {\r
7705         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
7706     }\r
7707 }\r
7708 \r
7709 \r
7710 void\r
7711 GameEnds(result, resultDetails, whosays)\r
7712      ChessMove result;\r
7713      char *resultDetails;\r
7714      int whosays;\r
7715 {\r
7716     GameMode nextGameMode;\r
7717     int isIcsGame;\r
7718     char buf[MSG_SIZ];\r
7719 \r
7720     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
7721     endingGame = 1;\r
7722 \r
7723     if (appData.debugMode) {\r
7724       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
7725               result, resultDetails ? resultDetails : "(null)", whosays);\r
7726     }\r
7727 \r
7728     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
7729         /* If we are playing on ICS, the server decides when the\r
7730            game is over, but the engine can offer to draw, claim \r
7731            a draw, or resign. \r
7732          */\r
7733 #if ZIPPY\r
7734         if (appData.zippyPlay && first.initDone) {\r
7735             if (result == GameIsDrawn) {\r
7736                 /* In case draw still needs to be claimed */\r
7737                 SendToICS(ics_prefix);\r
7738                 SendToICS("draw\n");\r
7739             } else if (StrCaseStr(resultDetails, "resign")) {\r
7740                 SendToICS(ics_prefix);\r
7741                 SendToICS("resign\n");\r
7742             }\r
7743         }\r
7744 #endif\r
7745         endingGame = 0; /* [HGM] crash */\r
7746         return;\r
7747     }\r
7748 \r
7749     /* If we're loading the game from a file, stop */\r
7750     if (whosays == GE_FILE) {\r
7751       (void) StopLoadGameTimer();\r
7752       gameFileFP = NULL;\r
7753     }\r
7754 \r
7755     /* Cancel draw offers */\r
7756     first.offeredDraw = second.offeredDraw = 0;\r
7757 \r
7758     /* If this is an ICS game, only ICS can really say it's done;\r
7759        if not, anyone can. */\r
7760     isIcsGame = (gameMode == IcsPlayingWhite || \r
7761                  gameMode == IcsPlayingBlack || \r
7762                  gameMode == IcsObserving    || \r
7763                  gameMode == IcsExamining);\r
7764 \r
7765     if (!isIcsGame || whosays == GE_ICS) {\r
7766         /* OK -- not an ICS game, or ICS said it was done */\r
7767         StopClocks();\r
7768         if (!isIcsGame && !appData.noChessProgram) \r
7769           SetUserThinkingEnables();\r
7770     \r
7771         /* [HGM] if a machine claims the game end we verify this claim */\r
7772         if(gameMode == TwoMachinesPlay && appData.testClaims) {\r
7773             if(appData.testLegality && whosays >= GE_ENGINE1 ) {\r
7774                 char claimer;\r
7775                 ChessMove trueResult = (ChessMove) -1;\r
7776 \r
7777                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
7778                                             first.twoMachinesColor[0] :\r
7779                                             second.twoMachinesColor[0] ;\r
7780 \r
7781                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first\r
7782                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {\r
7783                     /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7784                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;\r
7785                 } else\r
7786                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win\r
7787                     /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7788                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;\r
7789                 } else\r
7790                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now\r
7791                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE\r
7792                 }\r
7793 \r
7794                 // now verify win claims, but not in drop games, as we don't understand those yet\r
7795                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper\r
7796                                                  || gameInfo.variant == VariantGreat) &&\r
7797                     (result == WhiteWins && claimer == 'w' ||\r
7798                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win\r
7799                       if (appData.debugMode) {\r
7800                         fprintf(debugFP, "result=%d sp=%d move=%d\n",\r
7801                                 result, epStatus[forwardMostMove], forwardMostMove);\r
7802                       }\r
7803                       if(result != trueResult) {\r
7804                               sprintf(buf, "False win claim: '%s'", resultDetails);\r
7805                               result = claimer == 'w' ? BlackWins : WhiteWins;\r
7806                               resultDetails = buf;\r
7807                       }\r
7808                 } else\r
7809                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
7810                     && (forwardMostMove <= backwardMostMove ||\r
7811                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
7812                         (claimer=='b')==(forwardMostMove&1))\r
7813                                                                                   ) {\r
7814                       /* [HGM] verify: draws that were not flagged are false claims */\r
7815                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
7816                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7817                       resultDetails = buf;\r
7818                 }\r
7819                 /* (Claiming a loss is accepted no questions asked!) */\r
7820             }\r
7821             /* [HGM] bare: don't allow bare King to win */\r
7822             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)\r
7823                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway \r
7824                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...\r
7825                && result != GameIsDrawn)\r
7826             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
7827                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
7828                         int p = (int)boards[forwardMostMove][i][j] - color;\r
7829                         if(p >= 0 && p <= (int)WhiteKing) k++;\r
7830                 }\r
7831                 if (appData.debugMode) {\r
7832                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",\r
7833                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);\r
7834                 }\r
7835                 if(k <= 1) {\r
7836                         result = GameIsDrawn;\r
7837                         sprintf(buf, "%s but bare king", resultDetails);\r
7838                         resultDetails = buf;\r
7839                 }\r
7840             }\r
7841         }\r
7842 \r
7843 \r
7844         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
7845             if(result==WhiteWins) c = '+';\r
7846             if(result==BlackWins) c = '-';\r
7847             if(resultDetails != NULL)\r
7848                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
7849         }\r
7850         if (resultDetails != NULL) {\r
7851             gameInfo.result = result;\r
7852             gameInfo.resultDetails = StrSave(resultDetails);\r
7853 \r
7854             /* display last move only if game was not loaded from file */\r
7855             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7856                 DisplayMove(currentMove - 1);\r
7857     \r
7858             if (forwardMostMove != 0) {\r
7859                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7860                     if (*appData.saveGameFile != NULLCHAR) {\r
7861                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7862                     } else if (appData.autoSaveGames) {\r
7863                         AutoSaveGame();\r
7864                     }\r
7865                     if (*appData.savePositionFile != NULLCHAR) {\r
7866                         SavePositionToFile(appData.savePositionFile);\r
7867                     }\r
7868                 }\r
7869             }\r
7870 \r
7871             /* Tell program how game ended in case it is learning */\r
7872             /* [HGM] Moved this to after saving the PGN, just in case */\r
7873             /* engine died and we got here through time loss. In that */\r
7874             /* case we will get a fatal error writing the pipe, which */\r
7875             /* would otherwise lose us the PGN.                       */\r
7876             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7877             /* output during GameEnds should never be fatal anymore   */\r
7878             if (gameMode == MachinePlaysWhite ||\r
7879                 gameMode == MachinePlaysBlack ||\r
7880                 gameMode == TwoMachinesPlay ||\r
7881                 gameMode == IcsPlayingWhite ||\r
7882                 gameMode == IcsPlayingBlack ||\r
7883                 gameMode == BeginningOfGame) {\r
7884                 char buf[MSG_SIZ];\r
7885                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7886                         resultDetails);\r
7887                 if (first.pr != NoProc) {\r
7888                     SendToProgram(buf, &first);\r
7889                 }\r
7890                 if (second.pr != NoProc &&\r
7891                     gameMode == TwoMachinesPlay) {\r
7892                     SendToProgram(buf, &second);\r
7893                 }\r
7894             }\r
7895         }\r
7896 \r
7897         if (appData.icsActive) {\r
7898             if (appData.quietPlay &&\r
7899                 (gameMode == IcsPlayingWhite ||\r
7900                  gameMode == IcsPlayingBlack)) {\r
7901                 SendToICS(ics_prefix);\r
7902                 SendToICS("set shout 1\n");\r
7903             }\r
7904             nextGameMode = IcsIdle;\r
7905             ics_user_moved = FALSE;\r
7906             /* clean up premove.  It's ugly when the game has ended and the\r
7907              * premove highlights are still on the board.\r
7908              */\r
7909             if (gotPremove) {\r
7910               gotPremove = FALSE;\r
7911               ClearPremoveHighlights();\r
7912               DrawPosition(FALSE, boards[currentMove]);\r
7913             }\r
7914             if (whosays == GE_ICS) {\r
7915                 switch (result) {\r
7916                 case WhiteWins:\r
7917                     if (gameMode == IcsPlayingWhite)\r
7918                         PlayIcsWinSound();\r
7919                     else if(gameMode == IcsPlayingBlack)\r
7920                         PlayIcsLossSound();\r
7921                     break;\r
7922                 case BlackWins:\r
7923                     if (gameMode == IcsPlayingBlack)\r
7924                         PlayIcsWinSound();\r
7925                     else if(gameMode == IcsPlayingWhite)\r
7926                         PlayIcsLossSound();\r
7927                     break;\r
7928                 case GameIsDrawn:\r
7929                     PlayIcsDrawSound();\r
7930                     break;\r
7931                 default:\r
7932                     PlayIcsUnfinishedSound();\r
7933                 }\r
7934             }\r
7935         } else if (gameMode == EditGame ||\r
7936                    gameMode == PlayFromGameFile || \r
7937                    gameMode == AnalyzeMode || \r
7938                    gameMode == AnalyzeFile) {\r
7939             nextGameMode = gameMode;\r
7940         } else {\r
7941             nextGameMode = EndOfGame;\r
7942         }\r
7943         pausing = FALSE;\r
7944         ModeHighlight();\r
7945     } else {\r
7946         nextGameMode = gameMode;\r
7947     }\r
7948 \r
7949     if (appData.noChessProgram) {\r
7950         gameMode = nextGameMode;\r
7951         ModeHighlight();\r
7952         endingGame = 0; /* [HGM] crash */\r
7953         return;\r
7954     }\r
7955 \r
7956     if (first.reuse) {\r
7957         /* Put first chess program into idle state */\r
7958         if (first.pr != NoProc &&\r
7959             (gameMode == MachinePlaysWhite ||\r
7960              gameMode == MachinePlaysBlack ||\r
7961              gameMode == TwoMachinesPlay ||\r
7962              gameMode == IcsPlayingWhite ||\r
7963              gameMode == IcsPlayingBlack ||\r
7964              gameMode == BeginningOfGame)) {\r
7965             SendToProgram("force\n", &first);\r
7966             if (first.usePing) {\r
7967               char buf[MSG_SIZ];\r
7968               sprintf(buf, "ping %d\n", ++first.lastPing);\r
7969               SendToProgram(buf, &first);\r
7970             }\r
7971         }\r
7972     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7973         /* Kill off first chess program */\r
7974         if (first.isr != NULL)\r
7975           RemoveInputSource(first.isr);\r
7976         first.isr = NULL;\r
7977     \r
7978         if (first.pr != NoProc) {\r
7979             ExitAnalyzeMode();\r
7980             DoSleep( appData.delayBeforeQuit );\r
7981             SendToProgram("quit\n", &first);\r
7982             DoSleep( appData.delayAfterQuit );\r
7983             DestroyChildProcess(first.pr, first.useSigterm);\r
7984         }\r
7985         first.pr = NoProc;\r
7986     }\r
7987     if (second.reuse) {\r
7988         /* Put second chess program into idle state */\r
7989         if (second.pr != NoProc &&\r
7990             gameMode == TwoMachinesPlay) {\r
7991             SendToProgram("force\n", &second);\r
7992             if (second.usePing) {\r
7993               char buf[MSG_SIZ];\r
7994               sprintf(buf, "ping %d\n", ++second.lastPing);\r
7995               SendToProgram(buf, &second);\r
7996             }\r
7997         }\r
7998     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7999         /* Kill off second chess program */\r
8000         if (second.isr != NULL)\r
8001           RemoveInputSource(second.isr);\r
8002         second.isr = NULL;\r
8003     \r
8004         if (second.pr != NoProc) {\r
8005             DoSleep( appData.delayBeforeQuit );\r
8006             SendToProgram("quit\n", &second);\r
8007             DoSleep( appData.delayAfterQuit );\r
8008             DestroyChildProcess(second.pr, second.useSigterm);\r
8009         }\r
8010         second.pr = NoProc;\r
8011     }\r
8012 \r
8013     if (matchMode && gameMode == TwoMachinesPlay) {\r
8014         switch (result) {\r
8015         case WhiteWins:\r
8016           if (first.twoMachinesColor[0] == 'w') {\r
8017             first.matchWins++;\r
8018           } else {\r
8019             second.matchWins++;\r
8020           }\r
8021           break;\r
8022         case BlackWins:\r
8023           if (first.twoMachinesColor[0] == 'b') {\r
8024             first.matchWins++;\r
8025           } else {\r
8026             second.matchWins++;\r
8027           }\r
8028           break;\r
8029         default:\r
8030           break;\r
8031         }\r
8032         if (matchGame < appData.matchGames) {\r
8033             char *tmp;\r
8034             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */\r
8035                 tmp = first.twoMachinesColor;\r
8036                 first.twoMachinesColor = second.twoMachinesColor;\r
8037                 second.twoMachinesColor = tmp;\r
8038             }\r
8039             gameMode = nextGameMode;\r
8040             matchGame++;\r
8041             if(appData.matchPause>10000 || appData.matchPause<10)\r
8042                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
8043             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
8044             endingGame = 0; /* [HGM] crash */\r
8045             return;\r
8046         } else {\r
8047             char buf[MSG_SIZ];\r
8048             gameMode = nextGameMode;\r
8049             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),\r
8050                     first.tidy, second.tidy,\r
8051                     first.matchWins, second.matchWins,\r
8052                     appData.matchGames - (first.matchWins + second.matchWins));\r
8053             DisplayFatalError(buf, 0, 0);\r
8054         }\r
8055     }\r
8056     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
8057         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
8058       ExitAnalyzeMode();\r
8059     gameMode = nextGameMode;\r
8060     ModeHighlight();\r
8061     endingGame = 0;  /* [HGM] crash */\r
8062 }\r
8063 \r
8064 /* Assumes program was just initialized (initString sent).\r
8065    Leaves program in force mode. */\r
8066 void\r
8067 FeedMovesToProgram(cps, upto) \r
8068      ChessProgramState *cps;\r
8069      int upto;\r
8070 {\r
8071     int i;\r
8072     \r
8073     if (appData.debugMode)\r
8074       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
8075               startedFromSetupPosition ? "position and " : "",\r
8076               backwardMostMove, upto, cps->which);\r
8077     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
8078         // [HGM] variantswitch: make engine aware of new variant\r
8079         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
8080                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
8081         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
8082         SendToProgram(buf, cps);\r
8083         currentlyInitializedVariant = gameInfo.variant;\r
8084     }\r
8085     SendToProgram("force\n", cps);\r
8086     if (startedFromSetupPosition) {\r
8087         SendBoard(cps, backwardMostMove);\r
8088     if (appData.debugMode) {\r
8089         fprintf(debugFP, "feedMoves\n");\r
8090     }\r
8091     }\r
8092     for (i = backwardMostMove; i < upto; i++) {\r
8093         SendMoveToProgram(i, cps);\r
8094     }\r
8095 }\r
8096 \r
8097 \r
8098 void\r
8099 ResurrectChessProgram()\r
8100 {\r
8101      /* The chess program may have exited.\r
8102         If so, restart it and feed it all the moves made so far. */\r
8103 \r
8104     if (appData.noChessProgram || first.pr != NoProc) return;\r
8105     \r
8106     StartChessProgram(&first);\r
8107     InitChessProgram(&first, FALSE);\r
8108     FeedMovesToProgram(&first, currentMove);\r
8109 \r
8110     if (!first.sendTime) {\r
8111         /* can't tell gnuchess what its clock should read,\r
8112            so we bow to its notion. */\r
8113         ResetClocks();\r
8114         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
8115         timeRemaining[1][currentMove] = blackTimeRemaining;\r
8116     }\r
8117 \r
8118     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||\r
8119                 appData.icsEngineAnalyze) && first.analysisSupport) {\r
8120       SendToProgram("analyze\n", &first);\r
8121       first.analyzing = TRUE;\r
8122     }\r
8123 }\r
8124 \r
8125 /*\r
8126  * Button procedures\r
8127  */\r
8128 void\r
8129 Reset(redraw, init)\r
8130      int redraw, init;\r
8131 {\r
8132     int i;\r
8133 \r
8134     if (appData.debugMode) {\r
8135         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
8136                 redraw, init, gameMode);\r
8137     }\r
8138     pausing = pauseExamInvalid = FALSE;\r
8139     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
8140     firstMove = TRUE;\r
8141     whiteFlag = blackFlag = FALSE;\r
8142     userOfferedDraw = FALSE;\r
8143     hintRequested = bookRequested = FALSE;\r
8144     first.maybeThinking = FALSE;\r
8145     second.maybeThinking = FALSE;\r
8146     first.bookSuspend = FALSE; // [HGM] book\r
8147     second.bookSuspend = FALSE;\r
8148     thinkOutput[0] = NULLCHAR;\r
8149     lastHint[0] = NULLCHAR;\r
8150     ClearGameInfo(&gameInfo);\r
8151     gameInfo.variant = StringToVariant(appData.variant);\r
8152     ics_user_moved = ics_clock_paused = FALSE;\r
8153     ics_getting_history = H_FALSE;\r
8154     ics_gamenum = -1;\r
8155     white_holding[0] = black_holding[0] = NULLCHAR;\r
8156     ClearProgramStats();\r
8157     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode\r
8158     \r
8159     ResetFrontEnd();\r
8160     ClearHighlights();\r
8161     flipView = appData.flipView;\r
8162     ClearPremoveHighlights();\r
8163     gotPremove = FALSE;\r
8164     alarmSounded = FALSE;\r
8165 \r
8166     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8167     if(appData.serverMovesName != NULL) {\r
8168         /* [HGM] prepare to make moves file for broadcasting */\r
8169         clock_t t = clock();\r
8170         if(serverMoves != NULL) fclose(serverMoves);\r
8171         serverMoves = fopen(appData.serverMovesName, "r");\r
8172         if(serverMoves != NULL) {\r
8173             fclose(serverMoves);\r
8174             /* delay 15 sec before overwriting, so all clients can see end */\r
8175             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
8176         }\r
8177         serverMoves = fopen(appData.serverMovesName, "w");\r
8178     }\r
8179 \r
8180     ExitAnalyzeMode();\r
8181     gameMode = BeginningOfGame;\r
8182     ModeHighlight();\r
8183     if(appData.icsActive) gameInfo.variant = VariantNormal;\r
8184     InitPosition(redraw);\r
8185     for (i = 0; i < MAX_MOVES; i++) {\r
8186         if (commentList[i] != NULL) {\r
8187             free(commentList[i]);\r
8188             commentList[i] = NULL;\r
8189         }\r
8190     }\r
8191     ResetClocks();\r
8192     timeRemaining[0][0] = whiteTimeRemaining;\r
8193     timeRemaining[1][0] = blackTimeRemaining;\r
8194     if (first.pr == NULL) {\r
8195         StartChessProgram(&first);\r
8196     }\r
8197     if (init) {\r
8198             InitChessProgram(&first, startedFromSetupPosition);\r
8199     }\r
8200     DisplayTitle("");\r
8201     DisplayMessage("", "");\r
8202     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8203 }\r
8204 \r
8205 void\r
8206 AutoPlayGameLoop()\r
8207 {\r
8208     for (;;) {\r
8209         if (!AutoPlayOneMove())\r
8210           return;\r
8211         if (matchMode || appData.timeDelay == 0)\r
8212           continue;\r
8213         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
8214           return;\r
8215         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
8216         break;\r
8217     }\r
8218 }\r
8219 \r
8220 \r
8221 int\r
8222 AutoPlayOneMove()\r
8223 {\r
8224     int fromX, fromY, toX, toY;\r
8225 \r
8226     if (appData.debugMode) {\r
8227       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
8228     }\r
8229 \r
8230     if (gameMode != PlayFromGameFile)\r
8231       return FALSE;\r
8232 \r
8233     if (currentMove >= forwardMostMove) {\r
8234       gameMode = EditGame;\r
8235       ModeHighlight();\r
8236 \r
8237       /* [AS] Clear current move marker at the end of a game */\r
8238       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
8239 \r
8240       return FALSE;\r
8241     }\r
8242     \r
8243     toX = moveList[currentMove][2] - AAA;\r
8244     toY = moveList[currentMove][3] - ONE;\r
8245 \r
8246     if (moveList[currentMove][1] == '@') {\r
8247         if (appData.highlightLastMove) {\r
8248             SetHighlights(-1, -1, toX, toY);\r
8249         }\r
8250     } else {\r
8251         fromX = moveList[currentMove][0] - AAA;\r
8252         fromY = moveList[currentMove][1] - ONE;\r
8253 \r
8254         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
8255 \r
8256         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
8257 \r
8258         if (appData.highlightLastMove) {\r
8259             SetHighlights(fromX, fromY, toX, toY);\r
8260         }\r
8261     }\r
8262     DisplayMove(currentMove);\r
8263     SendMoveToProgram(currentMove++, &first);\r
8264     DisplayBothClocks();\r
8265     DrawPosition(FALSE, boards[currentMove]);\r
8266     // [HGM] PV info: always display, routine tests if empty\r
8267     DisplayComment(currentMove - 1, commentList[currentMove]);\r
8268     return TRUE;\r
8269 }\r
8270 \r
8271 \r
8272 int\r
8273 LoadGameOneMove(readAhead)\r
8274      ChessMove readAhead;\r
8275 {\r
8276     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
8277     char promoChar = NULLCHAR;\r
8278     ChessMove moveType;\r
8279     char move[MSG_SIZ];\r
8280     char *p, *q;\r
8281     \r
8282     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
8283         gameMode != AnalyzeMode && gameMode != Training) {\r
8284         gameFileFP = NULL;\r
8285         return FALSE;\r
8286     }\r
8287     \r
8288     yyboardindex = forwardMostMove;\r
8289     if (readAhead != (ChessMove)0) {\r
8290       moveType = readAhead;\r
8291     } else {\r
8292       if (gameFileFP == NULL)\r
8293           return FALSE;\r
8294       moveType = (ChessMove) yylex();\r
8295     }\r
8296     \r
8297     done = FALSE;\r
8298     switch (moveType) {\r
8299       case Comment:\r
8300         if (appData.debugMode) \r
8301           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8302         p = yy_text;\r
8303         if (*p == '{' || *p == '[' || *p == '(') {\r
8304             p[strlen(p) - 1] = NULLCHAR;\r
8305             p++;\r
8306         }\r
8307 \r
8308         /* append the comment but don't display it */\r
8309         while (*p == '\n') p++;\r
8310         AppendComment(currentMove, p);\r
8311         return TRUE;\r
8312 \r
8313       case WhiteCapturesEnPassant:\r
8314       case BlackCapturesEnPassant:\r
8315       case WhitePromotionChancellor:\r
8316       case BlackPromotionChancellor:\r
8317       case WhitePromotionArchbishop:\r
8318       case BlackPromotionArchbishop:\r
8319       case WhitePromotionCentaur:\r
8320       case BlackPromotionCentaur:\r
8321       case WhitePromotionQueen:\r
8322       case BlackPromotionQueen:\r
8323       case WhitePromotionRook:\r
8324       case BlackPromotionRook:\r
8325       case WhitePromotionBishop:\r
8326       case BlackPromotionBishop:\r
8327       case WhitePromotionKnight:\r
8328       case BlackPromotionKnight:\r
8329       case WhitePromotionKing:\r
8330       case BlackPromotionKing:\r
8331       case NormalMove:\r
8332       case WhiteKingSideCastle:\r
8333       case WhiteQueenSideCastle:\r
8334       case BlackKingSideCastle:\r
8335       case BlackQueenSideCastle:\r
8336       case WhiteKingSideCastleWild:\r
8337       case WhiteQueenSideCastleWild:\r
8338       case BlackKingSideCastleWild:\r
8339       case BlackQueenSideCastleWild:\r
8340       /* PUSH Fabien */\r
8341       case WhiteHSideCastleFR:\r
8342       case WhiteASideCastleFR:\r
8343       case BlackHSideCastleFR:\r
8344       case BlackASideCastleFR:\r
8345       /* POP Fabien */\r
8346         if (appData.debugMode)\r
8347           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8348         fromX = currentMoveString[0] - AAA;\r
8349         fromY = currentMoveString[1] - ONE;\r
8350         toX = currentMoveString[2] - AAA;\r
8351         toY = currentMoveString[3] - ONE;\r
8352         promoChar = currentMoveString[4];\r
8353         break;\r
8354 \r
8355       case WhiteDrop:\r
8356       case BlackDrop:\r
8357         if (appData.debugMode)\r
8358           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8359         fromX = moveType == WhiteDrop ?\r
8360           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
8361         (int) CharToPiece(ToLower(currentMoveString[0]));\r
8362         fromY = DROP_RANK;\r
8363         toX = currentMoveString[2] - AAA;\r
8364         toY = currentMoveString[3] - ONE;\r
8365         break;\r
8366 \r
8367       case WhiteWins:\r
8368       case BlackWins:\r
8369       case GameIsDrawn:\r
8370       case GameUnfinished:\r
8371         if (appData.debugMode)\r
8372           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
8373         p = strchr(yy_text, '{');\r
8374         if (p == NULL) p = strchr(yy_text, '(');\r
8375         if (p == NULL) {\r
8376             p = yy_text;\r
8377             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
8378         } else {\r
8379             q = strchr(p, *p == '{' ? '}' : ')');\r
8380             if (q != NULL) *q = NULLCHAR;\r
8381             p++;\r
8382         }\r
8383         GameEnds(moveType, p, GE_FILE);\r
8384         done = TRUE;\r
8385         if (cmailMsgLoaded) {\r
8386             ClearHighlights();\r
8387             flipView = WhiteOnMove(currentMove);\r
8388             if (moveType == GameUnfinished) flipView = !flipView;\r
8389             if (appData.debugMode)\r
8390               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
8391         }\r
8392         break;\r
8393 \r
8394       case (ChessMove) 0:       /* end of file */\r
8395         if (appData.debugMode)\r
8396           fprintf(debugFP, "Parser hit end of file\n");\r
8397         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8398                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8399           case MT_NONE:\r
8400           case MT_CHECK:\r
8401             break;\r
8402           case MT_CHECKMATE:\r
8403           case MT_STAINMATE:\r
8404             if (WhiteOnMove(currentMove)) {\r
8405                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8406             } else {\r
8407                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8408             }\r
8409             break;\r
8410           case MT_STALEMATE:\r
8411             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8412             break;\r
8413         }\r
8414         done = TRUE;\r
8415         break;\r
8416 \r
8417       case MoveNumberOne:\r
8418         if (lastLoadGameStart == GNUChessGame) {\r
8419             /* GNUChessGames have numbers, but they aren't move numbers */\r
8420             if (appData.debugMode)\r
8421               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8422                       yy_text, (int) moveType);\r
8423             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8424         }\r
8425         /* else fall thru */\r
8426 \r
8427       case XBoardGame:\r
8428       case GNUChessGame:\r
8429       case PGNTag:\r
8430         /* Reached start of next game in file */\r
8431         if (appData.debugMode)\r
8432           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
8433         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8434                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8435           case MT_NONE:\r
8436           case MT_CHECK:\r
8437             break;\r
8438           case MT_CHECKMATE:\r
8439           case MT_STAINMATE:\r
8440             if (WhiteOnMove(currentMove)) {\r
8441                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8442             } else {\r
8443                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8444             }\r
8445             break;\r
8446           case MT_STALEMATE:\r
8447             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8448             break;\r
8449         }\r
8450         done = TRUE;\r
8451         break;\r
8452 \r
8453       case PositionDiagram:     /* should not happen; ignore */\r
8454       case ElapsedTime:         /* ignore */\r
8455       case NAG:                 /* ignore */\r
8456         if (appData.debugMode)\r
8457           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8458                   yy_text, (int) moveType);\r
8459         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8460 \r
8461       case IllegalMove:\r
8462         if (appData.testLegality) {\r
8463             if (appData.debugMode)\r
8464               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
8465             sprintf(move, _("Illegal move: %d.%s%s"),\r
8466                     (forwardMostMove / 2) + 1,\r
8467                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8468             DisplayError(move, 0);\r
8469             done = TRUE;\r
8470         } else {\r
8471             if (appData.debugMode)\r
8472               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
8473                       yy_text, currentMoveString);\r
8474             fromX = currentMoveString[0] - AAA;\r
8475             fromY = currentMoveString[1] - ONE;\r
8476             toX = currentMoveString[2] - AAA;\r
8477             toY = currentMoveString[3] - ONE;\r
8478             promoChar = currentMoveString[4];\r
8479         }\r
8480         break;\r
8481 \r
8482       case AmbiguousMove:\r
8483         if (appData.debugMode)\r
8484           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
8485         sprintf(move, _("Ambiguous move: %d.%s%s"),\r
8486                 (forwardMostMove / 2) + 1,\r
8487                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8488         DisplayError(move, 0);\r
8489         done = TRUE;\r
8490         break;\r
8491 \r
8492       default:\r
8493       case ImpossibleMove:\r
8494         if (appData.debugMode)\r
8495           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);\r
8496         sprintf(move, _("Illegal move: %d.%s%s"),\r
8497                 (forwardMostMove / 2) + 1,\r
8498                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8499         DisplayError(move, 0);\r
8500         done = TRUE;\r
8501         break;\r
8502     }\r
8503 \r
8504     if (done) {\r
8505         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
8506             DrawPosition(FALSE, boards[currentMove]);\r
8507             DisplayBothClocks();\r
8508             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
8509               DisplayComment(currentMove - 1, commentList[currentMove]);\r
8510         }\r
8511         (void) StopLoadGameTimer();\r
8512         gameFileFP = NULL;\r
8513         cmailOldMove = forwardMostMove;\r
8514         return FALSE;\r
8515     } else {\r
8516         /* currentMoveString is set as a side-effect of yylex */\r
8517         strcat(currentMoveString, "\n");\r
8518         strcpy(moveList[forwardMostMove], currentMoveString);\r
8519         \r
8520         thinkOutput[0] = NULLCHAR;\r
8521         MakeMove(fromX, fromY, toX, toY, promoChar);\r
8522         currentMove = forwardMostMove;\r
8523         return TRUE;\r
8524     }\r
8525 }\r
8526 \r
8527 /* Load the nth game from the given file */\r
8528 int\r
8529 LoadGameFromFile(filename, n, title, useList)\r
8530      char *filename;\r
8531      int n;\r
8532      char *title;\r
8533      /*Boolean*/ int useList;\r
8534 {\r
8535     FILE *f;\r
8536     char buf[MSG_SIZ];\r
8537 \r
8538     if (strcmp(filename, "-") == 0) {\r
8539         f = stdin;\r
8540         title = "stdin";\r
8541     } else {\r
8542         f = fopen(filename, "rb");\r
8543         if (f == NULL) {\r
8544             sprintf(buf, _("Can't open \"%s\""), filename);\r
8545             DisplayError(buf, errno);\r
8546             return FALSE;\r
8547         }\r
8548     }\r
8549     if (fseek(f, 0, 0) == -1) {\r
8550         /* f is not seekable; probably a pipe */\r
8551         useList = FALSE;\r
8552     }\r
8553     if (useList && n == 0) {\r
8554         int error = GameListBuild(f);\r
8555         if (error) {\r
8556             DisplayError(_("Cannot build game list"), error);\r
8557         } else if (!ListEmpty(&gameList) &&\r
8558                    ((ListGame *) gameList.tailPred)->number > 1) {\r
8559             GameListPopUp(f, title);\r
8560             return TRUE;\r
8561         }\r
8562         GameListDestroy();\r
8563         n = 1;\r
8564     }\r
8565     if (n == 0) n = 1;\r
8566     return LoadGame(f, n, title, FALSE);\r
8567 }\r
8568 \r
8569 \r
8570 void\r
8571 MakeRegisteredMove()\r
8572 {\r
8573     int fromX, fromY, toX, toY;\r
8574     char promoChar;\r
8575     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8576         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
8577           case CMAIL_MOVE:\r
8578           case CMAIL_DRAW:\r
8579             if (appData.debugMode)\r
8580               fprintf(debugFP, "Restoring %s for game %d\n",\r
8581                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8582     \r
8583             thinkOutput[0] = NULLCHAR;\r
8584             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
8585             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
8586             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
8587             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
8588             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
8589             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
8590             MakeMove(fromX, fromY, toX, toY, promoChar);\r
8591             ShowMove(fromX, fromY, toX, toY);\r
8592               \r
8593             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8594                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8595               case MT_NONE:\r
8596               case MT_CHECK:\r
8597                 break;\r
8598                 \r
8599               case MT_CHECKMATE:\r
8600               case MT_STAINMATE:\r
8601                 if (WhiteOnMove(currentMove)) {\r
8602                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
8603                 } else {\r
8604                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
8605                 }\r
8606                 break;\r
8607                 \r
8608               case MT_STALEMATE:\r
8609                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
8610                 break;\r
8611             }\r
8612 \r
8613             break;\r
8614             \r
8615           case CMAIL_RESIGN:\r
8616             if (WhiteOnMove(currentMove)) {\r
8617                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
8618             } else {\r
8619                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
8620             }\r
8621             break;\r
8622             \r
8623           case CMAIL_ACCEPT:\r
8624             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
8625             break;\r
8626               \r
8627           default:\r
8628             break;\r
8629         }\r
8630     }\r
8631 \r
8632     return;\r
8633 }\r
8634 \r
8635 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
8636 int\r
8637 CmailLoadGame(f, gameNumber, title, useList)\r
8638      FILE *f;\r
8639      int gameNumber;\r
8640      char *title;\r
8641      int useList;\r
8642 {\r
8643     int retVal;\r
8644 \r
8645     if (gameNumber > nCmailGames) {\r
8646         DisplayError(_("No more games in this message"), 0);\r
8647         return FALSE;\r
8648     }\r
8649     if (f == lastLoadGameFP) {\r
8650         int offset = gameNumber - lastLoadGameNumber;\r
8651         if (offset == 0) {\r
8652             cmailMsg[0] = NULLCHAR;\r
8653             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8654                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8655                 nCmailMovesRegistered--;\r
8656             }\r
8657             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
8658             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
8659                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
8660             }\r
8661         } else {\r
8662             if (! RegisterMove()) return FALSE;\r
8663         }\r
8664     }\r
8665 \r
8666     retVal = LoadGame(f, gameNumber, title, useList);\r
8667 \r
8668     /* Make move registered during previous look at this game, if any */\r
8669     MakeRegisteredMove();\r
8670 \r
8671     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
8672         commentList[currentMove]\r
8673           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
8674         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8675     }\r
8676 \r
8677     return retVal;\r
8678 }\r
8679 \r
8680 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
8681 int\r
8682 ReloadGame(offset)\r
8683      int offset;\r
8684 {\r
8685     int gameNumber = lastLoadGameNumber + offset;\r
8686     if (lastLoadGameFP == NULL) {\r
8687         DisplayError(_("No game has been loaded yet"), 0);\r
8688         return FALSE;\r
8689     }\r
8690     if (gameNumber <= 0) {\r
8691         DisplayError(_("Can't back up any further"), 0);\r
8692         return FALSE;\r
8693     }\r
8694     if (cmailMsgLoaded) {\r
8695         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
8696                              lastLoadGameTitle, lastLoadGameUseList);\r
8697     } else {\r
8698         return LoadGame(lastLoadGameFP, gameNumber,\r
8699                         lastLoadGameTitle, lastLoadGameUseList);\r
8700     }\r
8701 }\r
8702 \r
8703 \r
8704 \r
8705 /* Load the nth game from open file f */\r
8706 int\r
8707 LoadGame(f, gameNumber, title, useList)\r
8708      FILE *f;\r
8709      int gameNumber;\r
8710      char *title;\r
8711      int useList;\r
8712 {\r
8713     ChessMove cm;\r
8714     char buf[MSG_SIZ];\r
8715     int gn = gameNumber;\r
8716     ListGame *lg = NULL;\r
8717     int numPGNTags = 0;\r
8718     int err;\r
8719     GameMode oldGameMode;\r
8720     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
8721 \r
8722     if (appData.debugMode) \r
8723         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
8724 \r
8725     if (gameMode == Training )\r
8726         SetTrainingModeOff();\r
8727 \r
8728     oldGameMode = gameMode;\r
8729     if (gameMode != BeginningOfGame) {\r
8730       Reset(FALSE, TRUE);\r
8731     }\r
8732 \r
8733     gameFileFP = f;\r
8734     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
8735         fclose(lastLoadGameFP);\r
8736     }\r
8737 \r
8738     if (useList) {\r
8739         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
8740         \r
8741         if (lg) {\r
8742             fseek(f, lg->offset, 0);\r
8743             GameListHighlight(gameNumber);\r
8744             gn = 1;\r
8745         }\r
8746         else {\r
8747             DisplayError(_("Game number out of range"), 0);\r
8748             return FALSE;\r
8749         }\r
8750     } else {\r
8751         GameListDestroy();\r
8752         if (fseek(f, 0, 0) == -1) {\r
8753             if (f == lastLoadGameFP ?\r
8754                 gameNumber == lastLoadGameNumber + 1 :\r
8755                 gameNumber == 1) {\r
8756                 gn = 1;\r
8757             } else {\r
8758                 DisplayError(_("Can't seek on game file"), 0);\r
8759                 return FALSE;\r
8760             }\r
8761         }\r
8762     }\r
8763     lastLoadGameFP = f;\r
8764     lastLoadGameNumber = gameNumber;\r
8765     strcpy(lastLoadGameTitle, title);\r
8766     lastLoadGameUseList = useList;\r
8767 \r
8768     yynewfile(f);\r
8769 \r
8770     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
8771         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
8772                 lg->gameInfo.black);\r
8773             DisplayTitle(buf);\r
8774     } else if (*title != NULLCHAR) {\r
8775         if (gameNumber > 1) {\r
8776             sprintf(buf, "%s %d", title, gameNumber);\r
8777             DisplayTitle(buf);\r
8778         } else {\r
8779             DisplayTitle(title);\r
8780         }\r
8781     }\r
8782 \r
8783     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
8784         gameMode = PlayFromGameFile;\r
8785         ModeHighlight();\r
8786     }\r
8787 \r
8788     currentMove = forwardMostMove = backwardMostMove = 0;\r
8789     CopyBoard(boards[0], initialPosition);\r
8790     StopClocks();\r
8791 \r
8792     /*\r
8793      * Skip the first gn-1 games in the file.\r
8794      * Also skip over anything that precedes an identifiable \r
8795      * start of game marker, to avoid being confused by \r
8796      * garbage at the start of the file.  Currently \r
8797      * recognized start of game markers are the move number "1",\r
8798      * the pattern "gnuchess .* game", the pattern\r
8799      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
8800      * A game that starts with one of the latter two patterns\r
8801      * will also have a move number 1, possibly\r
8802      * following a position diagram.\r
8803      * 5-4-02: Let's try being more lenient and allowing a game to\r
8804      * start with an unnumbered move.  Does that break anything?\r
8805      */\r
8806     cm = lastLoadGameStart = (ChessMove) 0;\r
8807     while (gn > 0) {\r
8808         yyboardindex = forwardMostMove;\r
8809         cm = (ChessMove) yylex();\r
8810         switch (cm) {\r
8811           case (ChessMove) 0:\r
8812             if (cmailMsgLoaded) {\r
8813                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
8814             } else {\r
8815                 Reset(TRUE, TRUE);\r
8816                 DisplayError(_("Game not found in file"), 0);\r
8817             }\r
8818             return FALSE;\r
8819 \r
8820           case GNUChessGame:\r
8821           case XBoardGame:\r
8822             gn--;\r
8823             lastLoadGameStart = cm;\r
8824             break;\r
8825             \r
8826           case MoveNumberOne:\r
8827             switch (lastLoadGameStart) {\r
8828               case GNUChessGame:\r
8829               case XBoardGame:\r
8830               case PGNTag:\r
8831                 break;\r
8832               case MoveNumberOne:\r
8833               case (ChessMove) 0:\r
8834                 gn--;           /* count this game */\r
8835                 lastLoadGameStart = cm;\r
8836                 break;\r
8837               default:\r
8838                 /* impossible */\r
8839                 break;\r
8840             }\r
8841             break;\r
8842 \r
8843           case PGNTag:\r
8844             switch (lastLoadGameStart) {\r
8845               case GNUChessGame:\r
8846               case PGNTag:\r
8847               case MoveNumberOne:\r
8848               case (ChessMove) 0:\r
8849                 gn--;           /* count this game */\r
8850                 lastLoadGameStart = cm;\r
8851                 break;\r
8852               case XBoardGame:\r
8853                 lastLoadGameStart = cm; /* game counted already */\r
8854                 break;\r
8855               default:\r
8856                 /* impossible */\r
8857                 break;\r
8858             }\r
8859             if (gn > 0) {\r
8860                 do {\r
8861                     yyboardindex = forwardMostMove;\r
8862                     cm = (ChessMove) yylex();\r
8863                 } while (cm == PGNTag || cm == Comment);\r
8864             }\r
8865             break;\r
8866 \r
8867           case WhiteWins:\r
8868           case BlackWins:\r
8869           case GameIsDrawn:\r
8870             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8871                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8872                     != CMAIL_OLD_RESULT) {\r
8873                     nCmailResults ++ ;\r
8874                     cmailResult[  CMAIL_MAX_GAMES\r
8875                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8876                 }\r
8877             }\r
8878             break;\r
8879 \r
8880           case NormalMove:\r
8881             /* Only a NormalMove can be at the start of a game\r
8882              * without a position diagram. */\r
8883             if (lastLoadGameStart == (ChessMove) 0) {\r
8884               gn--;\r
8885               lastLoadGameStart = MoveNumberOne;\r
8886             }\r
8887             break;\r
8888 \r
8889           default:\r
8890             break;\r
8891         }\r
8892     }\r
8893     \r
8894     if (appData.debugMode)\r
8895       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8896 \r
8897     if (cm == XBoardGame) {\r
8898         /* Skip any header junk before position diagram and/or move 1 */\r
8899         for (;;) {\r
8900             yyboardindex = forwardMostMove;\r
8901             cm = (ChessMove) yylex();\r
8902 \r
8903             if (cm == (ChessMove) 0 ||\r
8904                 cm == GNUChessGame || cm == XBoardGame) {\r
8905                 /* Empty game; pretend end-of-file and handle later */\r
8906                 cm = (ChessMove) 0;\r
8907                 break;\r
8908             }\r
8909 \r
8910             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8911                 cm == PGNTag || cm == Comment)\r
8912               break;\r
8913         }\r
8914     } else if (cm == GNUChessGame) {\r
8915         if (gameInfo.event != NULL) {\r
8916             free(gameInfo.event);\r
8917         }\r
8918         gameInfo.event = StrSave(yy_text);\r
8919     }   \r
8920 \r
8921     startedFromSetupPosition = FALSE;\r
8922     while (cm == PGNTag) {\r
8923         if (appData.debugMode) \r
8924           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8925         err = ParsePGNTag(yy_text, &gameInfo);\r
8926         if (!err) numPGNTags++;\r
8927 \r
8928         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8929         if(gameInfo.variant != oldVariant) {\r
8930             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8931             InitPosition(TRUE);\r
8932             oldVariant = gameInfo.variant;\r
8933             if (appData.debugMode) \r
8934               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8935         }\r
8936 \r
8937 \r
8938         if (gameInfo.fen != NULL) {\r
8939           Board initial_position;\r
8940           startedFromSetupPosition = TRUE;\r
8941           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8942             Reset(TRUE, TRUE);\r
8943             DisplayError(_("Bad FEN position in file"), 0);\r
8944             return FALSE;\r
8945           }\r
8946           CopyBoard(boards[0], initial_position);\r
8947           if (blackPlaysFirst) {\r
8948             currentMove = forwardMostMove = backwardMostMove = 1;\r
8949             CopyBoard(boards[1], initial_position);\r
8950             strcpy(moveList[0], "");\r
8951             strcpy(parseList[0], "");\r
8952             timeRemaining[0][1] = whiteTimeRemaining;\r
8953             timeRemaining[1][1] = blackTimeRemaining;\r
8954             if (commentList[0] != NULL) {\r
8955               commentList[1] = commentList[0];\r
8956               commentList[0] = NULL;\r
8957             }\r
8958           } else {\r
8959             currentMove = forwardMostMove = backwardMostMove = 0;\r
8960           }\r
8961           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */\r
8962           {   int i;\r
8963               initialRulePlies = FENrulePlies;\r
8964               epStatus[forwardMostMove] = FENepStatus;\r
8965               for( i=0; i< nrCastlingRights; i++ )\r
8966                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
8967           }\r
8968           yyboardindex = forwardMostMove;\r
8969           free(gameInfo.fen);\r
8970           gameInfo.fen = NULL;\r
8971         }\r
8972 \r
8973         yyboardindex = forwardMostMove;\r
8974         cm = (ChessMove) yylex();\r
8975 \r
8976         /* Handle comments interspersed among the tags */\r
8977         while (cm == Comment) {\r
8978             char *p;\r
8979             if (appData.debugMode) \r
8980               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8981             p = yy_text;\r
8982             if (*p == '{' || *p == '[' || *p == '(') {\r
8983                 p[strlen(p) - 1] = NULLCHAR;\r
8984                 p++;\r
8985             }\r
8986             while (*p == '\n') p++;\r
8987             AppendComment(currentMove, p);\r
8988             yyboardindex = forwardMostMove;\r
8989             cm = (ChessMove) yylex();\r
8990         }\r
8991     }\r
8992 \r
8993     /* don't rely on existence of Event tag since if game was\r
8994      * pasted from clipboard the Event tag may not exist\r
8995      */\r
8996     if (numPGNTags > 0){\r
8997         char *tags;\r
8998         if (gameInfo.variant == VariantNormal) {\r
8999           gameInfo.variant = StringToVariant(gameInfo.event);\r
9000         }\r
9001         if (!matchMode) {\r
9002           if( appData.autoDisplayTags ) {\r
9003             tags = PGNTags(&gameInfo);\r
9004             TagsPopUp(tags, CmailMsg());\r
9005             free(tags);\r
9006           }\r
9007         }\r
9008     } else {\r
9009         /* Make something up, but don't display it now */\r
9010         SetGameInfo();\r
9011         TagsPopDown();\r
9012     }\r
9013 \r
9014     if (cm == PositionDiagram) {\r
9015         int i, j;\r
9016         char *p;\r
9017         Board initial_position;\r
9018 \r
9019         if (appData.debugMode)\r
9020           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
9021 \r
9022         if (!startedFromSetupPosition) {\r
9023             p = yy_text;\r
9024             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
9025               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
9026                 switch (*p) {\r
9027                   case '[':\r
9028                   case '-':\r
9029                   case ' ':\r
9030                   case '\t':\r
9031                   case '\n':\r
9032                   case '\r':\r
9033                     break;\r
9034                   default:\r
9035                     initial_position[i][j++] = CharToPiece(*p);\r
9036                     break;\r
9037                 }\r
9038             while (*p == ' ' || *p == '\t' ||\r
9039                    *p == '\n' || *p == '\r') p++;\r
9040         \r
9041             if (strncmp(p, "black", strlen("black"))==0)\r
9042               blackPlaysFirst = TRUE;\r
9043             else\r
9044               blackPlaysFirst = FALSE;\r
9045             startedFromSetupPosition = TRUE;\r
9046         \r
9047             CopyBoard(boards[0], initial_position);\r
9048             if (blackPlaysFirst) {\r
9049                 currentMove = forwardMostMove = backwardMostMove = 1;\r
9050                 CopyBoard(boards[1], initial_position);\r
9051                 strcpy(moveList[0], "");\r
9052                 strcpy(parseList[0], "");\r
9053                 timeRemaining[0][1] = whiteTimeRemaining;\r
9054                 timeRemaining[1][1] = blackTimeRemaining;\r
9055                 if (commentList[0] != NULL) {\r
9056                     commentList[1] = commentList[0];\r
9057                     commentList[0] = NULL;\r
9058                 }\r
9059             } else {\r
9060                 currentMove = forwardMostMove = backwardMostMove = 0;\r
9061             }\r
9062         }\r
9063         yyboardindex = forwardMostMove;\r
9064         cm = (ChessMove) yylex();\r
9065     }\r
9066 \r
9067     if (first.pr == NoProc) {\r
9068         StartChessProgram(&first);\r
9069     }\r
9070     InitChessProgram(&first, FALSE);\r
9071     SendToProgram("force\n", &first);\r
9072     if (startedFromSetupPosition) {\r
9073         SendBoard(&first, forwardMostMove);\r
9074     if (appData.debugMode) {\r
9075         fprintf(debugFP, "Load Game\n");\r
9076     }\r
9077         DisplayBothClocks();\r
9078     }      \r
9079 \r
9080     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
9081     loadFlag = appData.suppressLoadMoves;\r
9082 \r
9083     while (cm == Comment) {\r
9084         char *p;\r
9085         if (appData.debugMode) \r
9086           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
9087         p = yy_text;\r
9088         if (*p == '{' || *p == '[' || *p == '(') {\r
9089             p[strlen(p) - 1] = NULLCHAR;\r
9090             p++;\r
9091         }\r
9092         while (*p == '\n') p++;\r
9093         AppendComment(currentMove, p);\r
9094         yyboardindex = forwardMostMove;\r
9095         cm = (ChessMove) yylex();\r
9096     }\r
9097 \r
9098     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
9099         cm == WhiteWins || cm == BlackWins ||\r
9100         cm == GameIsDrawn || cm == GameUnfinished) {\r
9101         DisplayMessage("", _("No moves in game"));\r
9102         if (cmailMsgLoaded) {\r
9103             if (appData.debugMode)\r
9104               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
9105             ClearHighlights();\r
9106             flipView = FALSE;\r
9107         }\r
9108         DrawPosition(FALSE, boards[currentMove]);\r
9109         DisplayBothClocks();\r
9110         gameMode = EditGame;\r
9111         ModeHighlight();\r
9112         gameFileFP = NULL;\r
9113         cmailOldMove = 0;\r
9114         return TRUE;\r
9115     }\r
9116 \r
9117     // [HGM] PV info: routine tests if comment empty\r
9118     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
9119         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9120     }\r
9121     if (!matchMode && appData.timeDelay != 0) \r
9122       DrawPosition(FALSE, boards[currentMove]);\r
9123 \r
9124     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
9125       programStats.ok_to_send = 1;\r
9126     }\r
9127 \r
9128     /* if the first token after the PGN tags is a move\r
9129      * and not move number 1, retrieve it from the parser \r
9130      */\r
9131     if (cm != MoveNumberOne)\r
9132         LoadGameOneMove(cm);\r
9133 \r
9134     /* load the remaining moves from the file */\r
9135     while (LoadGameOneMove((ChessMove)0)) {\r
9136       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9137       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9138     }\r
9139 \r
9140     /* rewind to the start of the game */\r
9141     currentMove = backwardMostMove;\r
9142 \r
9143     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9144 \r
9145     if (oldGameMode == AnalyzeFile ||\r
9146         oldGameMode == AnalyzeMode) {\r
9147       AnalyzeFileEvent();\r
9148     }\r
9149 \r
9150     if (matchMode || appData.timeDelay == 0) {\r
9151       ToEndEvent();\r
9152       gameMode = EditGame;\r
9153       ModeHighlight();\r
9154     } else if (appData.timeDelay > 0) {\r
9155       AutoPlayGameLoop();\r
9156     }\r
9157 \r
9158     if (appData.debugMode) \r
9159         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
9160 \r
9161     loadFlag = 0; /* [HGM] true game starts */\r
9162     return TRUE;\r
9163 }\r
9164 \r
9165 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
9166 int\r
9167 ReloadPosition(offset)\r
9168      int offset;\r
9169 {\r
9170     int positionNumber = lastLoadPositionNumber + offset;\r
9171     if (lastLoadPositionFP == NULL) {\r
9172         DisplayError(_("No position has been loaded yet"), 0);\r
9173         return FALSE;\r
9174     }\r
9175     if (positionNumber <= 0) {\r
9176         DisplayError(_("Can't back up any further"), 0);\r
9177         return FALSE;\r
9178     }\r
9179     return LoadPosition(lastLoadPositionFP, positionNumber,\r
9180                         lastLoadPositionTitle);\r
9181 }\r
9182 \r
9183 /* Load the nth position from the given file */\r
9184 int\r
9185 LoadPositionFromFile(filename, n, title)\r
9186      char *filename;\r
9187      int n;\r
9188      char *title;\r
9189 {\r
9190     FILE *f;\r
9191     char buf[MSG_SIZ];\r
9192 \r
9193     if (strcmp(filename, "-") == 0) {\r
9194         return LoadPosition(stdin, n, "stdin");\r
9195     } else {\r
9196         f = fopen(filename, "rb");\r
9197         if (f == NULL) {\r
9198             sprintf(buf, _("Can't open \"%s\""), filename);\r
9199             DisplayError(buf, errno);\r
9200             return FALSE;\r
9201         } else {\r
9202             return LoadPosition(f, n, title);\r
9203         }\r
9204     }\r
9205 }\r
9206 \r
9207 /* Load the nth position from the given open file, and close it */\r
9208 int\r
9209 LoadPosition(f, positionNumber, title)\r
9210      FILE *f;\r
9211      int positionNumber;\r
9212      char *title;\r
9213 {\r
9214     char *p, line[MSG_SIZ];\r
9215     Board initial_position;\r
9216     int i, j, fenMode, pn;\r
9217     \r
9218     if (gameMode == Training )\r
9219         SetTrainingModeOff();\r
9220 \r
9221     if (gameMode != BeginningOfGame) {\r
9222         Reset(FALSE, TRUE);\r
9223     }\r
9224     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
9225         fclose(lastLoadPositionFP);\r
9226     }\r
9227     if (positionNumber == 0) positionNumber = 1;\r
9228     lastLoadPositionFP = f;\r
9229     lastLoadPositionNumber = positionNumber;\r
9230     strcpy(lastLoadPositionTitle, title);\r
9231     if (first.pr == NoProc) {\r
9232       StartChessProgram(&first);\r
9233       InitChessProgram(&first, FALSE);\r
9234     }    \r
9235     pn = positionNumber;\r
9236     if (positionNumber < 0) {\r
9237         /* Negative position number means to seek to that byte offset */\r
9238         if (fseek(f, -positionNumber, 0) == -1) {\r
9239             DisplayError(_("Can't seek on position file"), 0);\r
9240             return FALSE;\r
9241         };\r
9242         pn = 1;\r
9243     } else {\r
9244         if (fseek(f, 0, 0) == -1) {\r
9245             if (f == lastLoadPositionFP ?\r
9246                 positionNumber == lastLoadPositionNumber + 1 :\r
9247                 positionNumber == 1) {\r
9248                 pn = 1;\r
9249             } else {\r
9250                 DisplayError(_("Can't seek on position file"), 0);\r
9251                 return FALSE;\r
9252             }\r
9253         }\r
9254     }\r
9255     /* See if this file is FEN or old-style xboard */\r
9256     if (fgets(line, MSG_SIZ, f) == NULL) {\r
9257         DisplayError(_("Position not found in file"), 0);\r
9258         return FALSE;\r
9259     }\r
9260 #if 0\r
9261     switch (line[0]) {\r
9262       case '#':  case 'x':\r
9263       default:\r
9264         fenMode = FALSE;\r
9265         break;\r
9266       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
9267       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
9268       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
9269       case '7':  case '8':  case '9':\r
9270       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
9271       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
9272       case 'C':  case 'W':             case 'c':  case 'w': \r
9273         fenMode = TRUE;\r
9274         break;\r
9275     }\r
9276 #else\r
9277     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
9278     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
9279 #endif\r
9280 \r
9281     if (pn >= 2) {\r
9282         if (fenMode || line[0] == '#') pn--;\r
9283         while (pn > 0) {\r
9284             /* skip positions before number pn */\r
9285             if (fgets(line, MSG_SIZ, f) == NULL) {\r
9286                 Reset(TRUE, TRUE);\r
9287                 DisplayError(_("Position not found in file"), 0);\r
9288                 return FALSE;\r
9289             }\r
9290             if (fenMode || line[0] == '#') pn--;\r
9291         }\r
9292     }\r
9293 \r
9294     if (fenMode) {\r
9295         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
9296             DisplayError(_("Bad FEN position in file"), 0);\r
9297             return FALSE;\r
9298         }\r
9299     } else {\r
9300         (void) fgets(line, MSG_SIZ, f);\r
9301         (void) fgets(line, MSG_SIZ, f);\r
9302     \r
9303         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
9304             (void) fgets(line, MSG_SIZ, f);\r
9305             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
9306                 if (*p == ' ')\r
9307                   continue;\r
9308                 initial_position[i][j++] = CharToPiece(*p);\r
9309             }\r
9310         }\r
9311     \r
9312         blackPlaysFirst = FALSE;\r
9313         if (!feof(f)) {\r
9314             (void) fgets(line, MSG_SIZ, f);\r
9315             if (strncmp(line, "black", strlen("black"))==0)\r
9316               blackPlaysFirst = TRUE;\r
9317         }\r
9318     }\r
9319     startedFromSetupPosition = TRUE;\r
9320     \r
9321     SendToProgram("force\n", &first);\r
9322     CopyBoard(boards[0], initial_position);\r
9323     if (blackPlaysFirst) {\r
9324         currentMove = forwardMostMove = backwardMostMove = 1;\r
9325         strcpy(moveList[0], "");\r
9326         strcpy(parseList[0], "");\r
9327         CopyBoard(boards[1], initial_position);\r
9328         DisplayMessage("", _("Black to play"));\r
9329     } else {\r
9330         currentMove = forwardMostMove = backwardMostMove = 0;\r
9331         DisplayMessage("", _("White to play"));\r
9332     }\r
9333           /* [HGM] copy FEN attributes as well */\r
9334           {   int i;\r
9335               initialRulePlies = FENrulePlies;\r
9336               epStatus[forwardMostMove] = FENepStatus;\r
9337               for( i=0; i< nrCastlingRights; i++ )\r
9338                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
9339           }\r
9340     SendBoard(&first, forwardMostMove);\r
9341     if (appData.debugMode) {\r
9342 int i, j;\r
9343   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}\r
9344   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");\r
9345         fprintf(debugFP, "Load Position\n");\r
9346     }\r
9347 \r
9348     if (positionNumber > 1) {\r
9349         sprintf(line, "%s %d", title, positionNumber);\r
9350         DisplayTitle(line);\r
9351     } else {\r
9352         DisplayTitle(title);\r
9353     }\r
9354     gameMode = EditGame;\r
9355     ModeHighlight();\r
9356     ResetClocks();\r
9357     timeRemaining[0][1] = whiteTimeRemaining;\r
9358     timeRemaining[1][1] = blackTimeRemaining;\r
9359     DrawPosition(FALSE, boards[currentMove]);\r
9360    \r
9361     return TRUE;\r
9362 }\r
9363 \r
9364 \r
9365 void\r
9366 CopyPlayerNameIntoFileName(dest, src)\r
9367      char **dest, *src;\r
9368 {\r
9369     while (*src != NULLCHAR && *src != ',') {\r
9370         if (*src == ' ') {\r
9371             *(*dest)++ = '_';\r
9372             src++;\r
9373         } else {\r
9374             *(*dest)++ = *src++;\r
9375         }\r
9376     }\r
9377 }\r
9378 \r
9379 char *DefaultFileName(ext)\r
9380      char *ext;\r
9381 {\r
9382     static char def[MSG_SIZ];\r
9383     char *p;\r
9384 \r
9385     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
9386         p = def;\r
9387         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
9388         *p++ = '-';\r
9389         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
9390         *p++ = '.';\r
9391         strcpy(p, ext);\r
9392     } else {\r
9393         def[0] = NULLCHAR;\r
9394     }\r
9395     return def;\r
9396 }\r
9397 \r
9398 /* Save the current game to the given file */\r
9399 int\r
9400 SaveGameToFile(filename, append)\r
9401      char *filename;\r
9402      int append;\r
9403 {\r
9404     FILE *f;\r
9405     char buf[MSG_SIZ];\r
9406 \r
9407     if (strcmp(filename, "-") == 0) {\r
9408         return SaveGame(stdout, 0, NULL);\r
9409     } else {\r
9410         f = fopen(filename, append ? "a" : "w");\r
9411         if (f == NULL) {\r
9412             sprintf(buf, _("Can't open \"%s\""), filename);\r
9413             DisplayError(buf, errno);\r
9414             return FALSE;\r
9415         } else {\r
9416             return SaveGame(f, 0, NULL);\r
9417         }\r
9418     }\r
9419 }\r
9420 \r
9421 char *\r
9422 SavePart(str)\r
9423      char *str;\r
9424 {\r
9425     static char buf[MSG_SIZ];\r
9426     char *p;\r
9427     \r
9428     p = strchr(str, ' ');\r
9429     if (p == NULL) return str;\r
9430     strncpy(buf, str, p - str);\r
9431     buf[p - str] = NULLCHAR;\r
9432     return buf;\r
9433 }\r
9434 \r
9435 #define PGN_MAX_LINE 75\r
9436 \r
9437 #define PGN_SIDE_WHITE  0\r
9438 #define PGN_SIDE_BLACK  1\r
9439 \r
9440 /* [AS] */\r
9441 static int FindFirstMoveOutOfBook( int side )\r
9442 {\r
9443     int result = -1;\r
9444 \r
9445     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
9446         int index = backwardMostMove;\r
9447         int has_book_hit = 0;\r
9448 \r
9449         if( (index % 2) != side ) {\r
9450             index++;\r
9451         }\r
9452 \r
9453         while( index < forwardMostMove ) {\r
9454             /* Check to see if engine is in book */\r
9455             int depth = pvInfoList[index].depth;\r
9456             int score = pvInfoList[index].score;\r
9457             int in_book = 0;\r
9458 \r
9459             if( depth <= 2 ) {\r
9460                 in_book = 1;\r
9461             }\r
9462             else if( score == 0 && depth == 63 ) {\r
9463                 in_book = 1; /* Zappa */\r
9464             }\r
9465             else if( score == 2 && depth == 99 ) {\r
9466                 in_book = 1; /* Abrok */\r
9467             }\r
9468 \r
9469             has_book_hit += in_book;\r
9470 \r
9471             if( ! in_book ) {\r
9472                 result = index;\r
9473 \r
9474                 break;\r
9475             }\r
9476 \r
9477             index += 2;\r
9478         }\r
9479     }\r
9480 \r
9481     return result;\r
9482 }\r
9483 \r
9484 /* [AS] */\r
9485 void GetOutOfBookInfo( char * buf )\r
9486 {\r
9487     int oob[2];\r
9488     int i;\r
9489     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9490 \r
9491     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
9492     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
9493 \r
9494     *buf = '\0';\r
9495 \r
9496     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
9497         for( i=0; i<2; i++ ) {\r
9498             int idx = oob[i];\r
9499 \r
9500             if( idx >= 0 ) {\r
9501                 if( i > 0 && oob[0] >= 0 ) {\r
9502                     strcat( buf, "   " );\r
9503                 }\r
9504 \r
9505                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
9506                 sprintf( buf+strlen(buf), "%s%.2f", \r
9507                     pvInfoList[idx].score >= 0 ? "+" : "",\r
9508                     pvInfoList[idx].score / 100.0 );\r
9509             }\r
9510         }\r
9511     }\r
9512 }\r
9513 \r
9514 /* Save game in PGN style and close the file */\r
9515 int\r
9516 SaveGamePGN(f)\r
9517      FILE *f;\r
9518 {\r
9519     int i, offset, linelen, newblock;\r
9520     time_t tm;\r
9521 //    char *movetext;\r
9522     char numtext[32];\r
9523     int movelen, numlen, blank;\r
9524     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
9525 \r
9526     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9527     \r
9528     tm = time((time_t *) NULL);\r
9529     \r
9530     PrintPGNTags(f, &gameInfo);\r
9531     \r
9532     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9533         char *fen = PositionToFEN(backwardMostMove, NULL);\r
9534         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
9535         fprintf(f, "\n{--------------\n");\r
9536         PrintPosition(f, backwardMostMove);\r
9537         fprintf(f, "--------------}\n");\r
9538         free(fen);\r
9539     }\r
9540     else {\r
9541         /* [AS] Out of book annotation */\r
9542         if( appData.saveOutOfBookInfo ) {\r
9543             char buf[64];\r
9544 \r
9545             GetOutOfBookInfo( buf );\r
9546 \r
9547             if( buf[0] != '\0' ) {\r
9548                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
9549             }\r
9550         }\r
9551 \r
9552         fprintf(f, "\n");\r
9553     }\r
9554 \r
9555     i = backwardMostMove;\r
9556     linelen = 0;\r
9557     newblock = TRUE;\r
9558 \r
9559     while (i < forwardMostMove) {\r
9560         /* Print comments preceding this move */\r
9561         if (commentList[i] != NULL) {\r
9562             if (linelen > 0) fprintf(f, "\n");\r
9563             fprintf(f, "{\n%s}\n", commentList[i]);\r
9564             linelen = 0;\r
9565             newblock = TRUE;\r
9566         }\r
9567 \r
9568         /* Format move number */\r
9569         if ((i % 2) == 0) {\r
9570             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
9571         } else {\r
9572             if (newblock) {\r
9573                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
9574             } else {\r
9575                 numtext[0] = NULLCHAR;\r
9576             }\r
9577         }\r
9578         numlen = strlen(numtext);\r
9579         newblock = FALSE;\r
9580 \r
9581         /* Print move number */\r
9582         blank = linelen > 0 && numlen > 0;\r
9583         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
9584             fprintf(f, "\n");\r
9585             linelen = 0;\r
9586             blank = 0;\r
9587         }\r
9588         if (blank) {\r
9589             fprintf(f, " ");\r
9590             linelen++;\r
9591         }\r
9592         fprintf(f, numtext);\r
9593         linelen += numlen;\r
9594 \r
9595         /* Get move */\r
9596         strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited\r
9597         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */\r
9598         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9599                 int p = movelen - 1;\r
9600                 if(move_buffer[p] == ' ') p--;\r
9601                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info\r
9602                     while(p && move_buffer[--p] != '(');\r
9603                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;\r
9604                 }\r
9605         }\r
9606 \r
9607         /* Print move */\r
9608         blank = linelen > 0 && movelen > 0;\r
9609         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9610             fprintf(f, "\n");\r
9611             linelen = 0;\r
9612             blank = 0;\r
9613         }\r
9614         if (blank) {\r
9615             fprintf(f, " ");\r
9616             linelen++;\r
9617         }\r
9618         fprintf(f, move_buffer);\r
9619         linelen += movelen;\r
9620 \r
9621         /* [AS] Add PV info if present */\r
9622         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9623             /* [HGM] add time */\r
9624             char buf[MSG_SIZ]; int seconds = 0;\r
9625 \r
9626 #if 1\r
9627             if(i >= backwardMostMove) {\r
9628                 if(WhiteOnMove(i))\r
9629                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]\r
9630                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);\r
9631                 else\r
9632                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]\r
9633                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);\r
9634             }\r
9635             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest\r
9636 #else\r
9637             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time\r
9638 #endif\r
9639 \r
9640             if( seconds <= 0) buf[0] = 0; else\r
9641             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {\r
9642                 seconds = (seconds + 4)/10; // round to full seconds\r
9643                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else\r
9644                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
9645             }\r
9646 \r
9647             sprintf( move_buffer, "{%s%.2f/%d%s}", \r
9648                 pvInfoList[i].score >= 0 ? "+" : "",\r
9649                 pvInfoList[i].score / 100.0,\r
9650                 pvInfoList[i].depth,\r
9651                 buf );\r
9652 \r
9653             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */\r
9654 \r
9655             /* Print score/depth */\r
9656             blank = linelen > 0 && movelen > 0;\r
9657             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9658                 fprintf(f, "\n");\r
9659                 linelen = 0;\r
9660                 blank = 0;\r
9661             }\r
9662             if (blank) {\r
9663                 fprintf(f, " ");\r
9664                 linelen++;\r
9665             }\r
9666             fprintf(f, move_buffer);\r
9667             linelen += movelen;\r
9668         }\r
9669 \r
9670         i++;\r
9671     }\r
9672     \r
9673     /* Start a new line */\r
9674     if (linelen > 0) fprintf(f, "\n");\r
9675 \r
9676     /* Print comments after last move */\r
9677     if (commentList[i] != NULL) {\r
9678         fprintf(f, "{\n%s}\n", commentList[i]);\r
9679     }\r
9680 \r
9681     /* Print result */\r
9682     if (gameInfo.resultDetails != NULL &&\r
9683         gameInfo.resultDetails[0] != NULLCHAR) {\r
9684         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
9685                 PGNResult(gameInfo.result));\r
9686     } else {\r
9687         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9688     }\r
9689 \r
9690     fclose(f);\r
9691     return TRUE;\r
9692 }\r
9693 \r
9694 /* Save game in old style and close the file */\r
9695 int\r
9696 SaveGameOldStyle(f)\r
9697      FILE *f;\r
9698 {\r
9699     int i, offset;\r
9700     time_t tm;\r
9701     \r
9702     tm = time((time_t *) NULL);\r
9703     \r
9704     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
9705     PrintOpponents(f);\r
9706     \r
9707     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9708         fprintf(f, "\n[--------------\n");\r
9709         PrintPosition(f, backwardMostMove);\r
9710         fprintf(f, "--------------]\n");\r
9711     } else {\r
9712         fprintf(f, "\n");\r
9713     }\r
9714 \r
9715     i = backwardMostMove;\r
9716     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9717 \r
9718     while (i < forwardMostMove) {\r
9719         if (commentList[i] != NULL) {\r
9720             fprintf(f, "[%s]\n", commentList[i]);\r
9721         }\r
9722 \r
9723         if ((i % 2) == 1) {\r
9724             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
9725             i++;\r
9726         } else {\r
9727             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
9728             i++;\r
9729             if (commentList[i] != NULL) {\r
9730                 fprintf(f, "\n");\r
9731                 continue;\r
9732             }\r
9733             if (i >= forwardMostMove) {\r
9734                 fprintf(f, "\n");\r
9735                 break;\r
9736             }\r
9737             fprintf(f, "%s\n", parseList[i]);\r
9738             i++;\r
9739         }\r
9740     }\r
9741     \r
9742     if (commentList[i] != NULL) {\r
9743         fprintf(f, "[%s]\n", commentList[i]);\r
9744     }\r
9745 \r
9746     /* This isn't really the old style, but it's close enough */\r
9747     if (gameInfo.resultDetails != NULL &&\r
9748         gameInfo.resultDetails[0] != NULLCHAR) {\r
9749         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
9750                 gameInfo.resultDetails);\r
9751     } else {\r
9752         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9753     }\r
9754 \r
9755     fclose(f);\r
9756     return TRUE;\r
9757 }\r
9758 \r
9759 /* Save the current game to open file f and close the file */\r
9760 int\r
9761 SaveGame(f, dummy, dummy2)\r
9762      FILE *f;\r
9763      int dummy;\r
9764      char *dummy2;\r
9765 {\r
9766     if (gameMode == EditPosition) EditPositionDone();\r
9767     if (appData.oldSaveStyle)\r
9768       return SaveGameOldStyle(f);\r
9769     else\r
9770       return SaveGamePGN(f);\r
9771 }\r
9772 \r
9773 /* Save the current position to the given file */\r
9774 int\r
9775 SavePositionToFile(filename)\r
9776      char *filename;\r
9777 {\r
9778     FILE *f;\r
9779     char buf[MSG_SIZ];\r
9780 \r
9781     if (strcmp(filename, "-") == 0) {\r
9782         return SavePosition(stdout, 0, NULL);\r
9783     } else {\r
9784         f = fopen(filename, "a");\r
9785         if (f == NULL) {\r
9786             sprintf(buf, _("Can't open \"%s\""), filename);\r
9787             DisplayError(buf, errno);\r
9788             return FALSE;\r
9789         } else {\r
9790             SavePosition(f, 0, NULL);\r
9791             return TRUE;\r
9792         }\r
9793     }\r
9794 }\r
9795 \r
9796 /* Save the current position to the given open file and close the file */\r
9797 int\r
9798 SavePosition(f, dummy, dummy2)\r
9799      FILE *f;\r
9800      int dummy;\r
9801      char *dummy2;\r
9802 {\r
9803     time_t tm;\r
9804     char *fen;\r
9805     \r
9806     if (appData.oldSaveStyle) {\r
9807         tm = time((time_t *) NULL);\r
9808     \r
9809         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
9810         PrintOpponents(f);\r
9811         fprintf(f, "[--------------\n");\r
9812         PrintPosition(f, currentMove);\r
9813         fprintf(f, "--------------]\n");\r
9814     } else {\r
9815         fen = PositionToFEN(currentMove, NULL);\r
9816         fprintf(f, "%s\n", fen);\r
9817         free(fen);\r
9818     }\r
9819     fclose(f);\r
9820     return TRUE;\r
9821 }\r
9822 \r
9823 void\r
9824 ReloadCmailMsgEvent(unregister)\r
9825      int unregister;\r
9826 {\r
9827 #if !WIN32\r
9828     static char *inFilename = NULL;\r
9829     static char *outFilename;\r
9830     int i;\r
9831     struct stat inbuf, outbuf;\r
9832     int status;\r
9833     \r
9834     /* Any registered moves are unregistered if unregister is set, */\r
9835     /* i.e. invoked by the signal handler */\r
9836     if (unregister) {\r
9837         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9838             cmailMoveRegistered[i] = FALSE;\r
9839             if (cmailCommentList[i] != NULL) {\r
9840                 free(cmailCommentList[i]);\r
9841                 cmailCommentList[i] = NULL;\r
9842             }\r
9843         }\r
9844         nCmailMovesRegistered = 0;\r
9845     }\r
9846 \r
9847     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9848         cmailResult[i] = CMAIL_NOT_RESULT;\r
9849     }\r
9850     nCmailResults = 0;\r
9851 \r
9852     if (inFilename == NULL) {\r
9853         /* Because the filenames are static they only get malloced once  */\r
9854         /* and they never get freed                                      */\r
9855         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
9856         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
9857 \r
9858         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
9859         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
9860     }\r
9861     \r
9862     status = stat(outFilename, &outbuf);\r
9863     if (status < 0) {\r
9864         cmailMailedMove = FALSE;\r
9865     } else {\r
9866         status = stat(inFilename, &inbuf);\r
9867         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
9868     }\r
9869     \r
9870     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
9871        counts the games, notes how each one terminated, etc.\r
9872        \r
9873        It would be nice to remove this kludge and instead gather all\r
9874        the information while building the game list.  (And to keep it\r
9875        in the game list nodes instead of having a bunch of fixed-size\r
9876        parallel arrays.)  Note this will require getting each game's\r
9877        termination from the PGN tags, as the game list builder does\r
9878        not process the game moves.  --mann\r
9879        */\r
9880     cmailMsgLoaded = TRUE;\r
9881     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
9882     \r
9883     /* Load first game in the file or popup game menu */\r
9884     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
9885 \r
9886 #endif /* !WIN32 */\r
9887     return;\r
9888 }\r
9889 \r
9890 int\r
9891 RegisterMove()\r
9892 {\r
9893     FILE *f;\r
9894     char string[MSG_SIZ];\r
9895 \r
9896     if (   cmailMailedMove\r
9897         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9898         return TRUE;            /* Allow free viewing  */\r
9899     }\r
9900 \r
9901     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9902     /* with the move registered when the conditions for registering no   */\r
9903     /* longer hold                                                       */\r
9904     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9905         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9906         nCmailMovesRegistered --;\r
9907 \r
9908         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9909           {\r
9910               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9911               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9912           }\r
9913     }\r
9914 \r
9915     if (cmailOldMove == -1) {\r
9916         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);\r
9917         return FALSE;\r
9918     }\r
9919 \r
9920     if (currentMove > cmailOldMove + 1) {\r
9921         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);\r
9922         return FALSE;\r
9923     }\r
9924 \r
9925     if (currentMove < cmailOldMove) {\r
9926         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);\r
9927         return FALSE;\r
9928     }\r
9929 \r
9930     if (forwardMostMove > currentMove) {\r
9931         /* Silently truncate extra moves */\r
9932         TruncateGame();\r
9933     }\r
9934 \r
9935     if (   (currentMove == cmailOldMove + 1)\r
9936         || (   (currentMove == cmailOldMove)\r
9937             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9938                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9939         if (gameInfo.result != GameUnfinished) {\r
9940             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9941         }\r
9942 \r
9943         if (commentList[currentMove] != NULL) {\r
9944             cmailCommentList[lastLoadGameNumber - 1]\r
9945               = StrSave(commentList[currentMove]);\r
9946         }\r
9947         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9948 \r
9949         if (appData.debugMode)\r
9950           fprintf(debugFP, "Saving %s for game %d\n",\r
9951                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9952 \r
9953         sprintf(string,\r
9954                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
9955         \r
9956         f = fopen(string, "w");\r
9957         if (appData.oldSaveStyle) {\r
9958             SaveGameOldStyle(f); /* also closes the file */\r
9959             \r
9960             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
9961             f = fopen(string, "w");\r
9962             SavePosition(f, 0, NULL); /* also closes the file */\r
9963         } else {\r
9964             fprintf(f, "{--------------\n");\r
9965             PrintPosition(f, currentMove);\r
9966             fprintf(f, "--------------}\n\n");\r
9967             \r
9968             SaveGame(f, 0, NULL); /* also closes the file*/\r
9969         }\r
9970         \r
9971         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
9972         nCmailMovesRegistered ++;\r
9973     } else if (nCmailGames == 1) {\r
9974         DisplayError(_("You have not made a move yet"), 0);\r
9975         return FALSE;\r
9976     }\r
9977 \r
9978     return TRUE;\r
9979 }\r
9980 \r
9981 void\r
9982 MailMoveEvent()\r
9983 {\r
9984 #if !WIN32\r
9985     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
9986     FILE *commandOutput;\r
9987     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
9988     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
9989     int nBuffers;\r
9990     int i;\r
9991     int archived;\r
9992     char *arcDir;\r
9993 \r
9994     if (! cmailMsgLoaded) {\r
9995         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);\r
9996         return;\r
9997     }\r
9998 \r
9999     if (nCmailGames == nCmailResults) {\r
10000         DisplayError(_("No unfinished games"), 0);\r
10001         return;\r
10002     }\r
10003 \r
10004 #if CMAIL_PROHIBIT_REMAIL\r
10005     if (cmailMailedMove) {\r
10006         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
10007         DisplayError(msg, 0);\r
10008         return;\r
10009     }\r
10010 #endif\r
10011 \r
10012     if (! (cmailMailedMove || RegisterMove())) return;\r
10013     \r
10014     if (   cmailMailedMove\r
10015         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
10016         sprintf(string, partCommandString,\r
10017                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
10018         commandOutput = popen(string, "r");\r
10019 \r
10020         if (commandOutput == NULL) {\r
10021             DisplayError(_("Failed to invoke cmail"), 0);\r
10022         } else {\r
10023             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
10024                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
10025             }\r
10026             if (nBuffers > 1) {\r
10027                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
10028                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
10029                 nBytes = MSG_SIZ - 1;\r
10030             } else {\r
10031                 (void) memcpy(msg, buffer, nBytes);\r
10032             }\r
10033             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
10034 \r
10035             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
10036                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
10037 \r
10038                 archived = TRUE;\r
10039                 for (i = 0; i < nCmailGames; i ++) {\r
10040                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
10041                         archived = FALSE;\r
10042                     }\r
10043                 }\r
10044                 if (   archived\r
10045                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
10046                         != NULL)) {\r
10047                     sprintf(buffer, "%s/%s.%s.archive",\r
10048                             arcDir,\r
10049                             appData.cmailGameName,\r
10050                             gameInfo.date);\r
10051                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
10052                     cmailMsgLoaded = FALSE;\r
10053                 }\r
10054             }\r
10055 \r
10056             DisplayInformation(msg);\r
10057             pclose(commandOutput);\r
10058         }\r
10059     } else {\r
10060         if ((*cmailMsg) != '\0') {\r
10061             DisplayInformation(cmailMsg);\r
10062         }\r
10063     }\r
10064 \r
10065     return;\r
10066 #endif /* !WIN32 */\r
10067 }\r
10068 \r
10069 char *\r
10070 CmailMsg()\r
10071 {\r
10072 #if WIN32\r
10073     return NULL;\r
10074 #else\r
10075     int  prependComma = 0;\r
10076     char number[5];\r
10077     char string[MSG_SIZ];       /* Space for game-list */\r
10078     int  i;\r
10079     \r
10080     if (!cmailMsgLoaded) return "";\r
10081 \r
10082     if (cmailMailedMove) {\r
10083         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));\r
10084     } else {\r
10085         /* Create a list of games left */\r
10086         sprintf(string, "[");\r
10087         for (i = 0; i < nCmailGames; i ++) {\r
10088             if (! (   cmailMoveRegistered[i]\r
10089                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
10090                 if (prependComma) {\r
10091                     sprintf(number, ",%d", i + 1);\r
10092                 } else {\r
10093                     sprintf(number, "%d", i + 1);\r
10094                     prependComma = 1;\r
10095                 }\r
10096                 \r
10097                 strcat(string, number);\r
10098             }\r
10099         }\r
10100         strcat(string, "]");\r
10101 \r
10102         if (nCmailMovesRegistered + nCmailResults == 0) {\r
10103             switch (nCmailGames) {\r
10104               case 1:\r
10105                 sprintf(cmailMsg,\r
10106                         _("Still need to make move for game\n"));\r
10107                 break;\r
10108                 \r
10109               case 2:\r
10110                 sprintf(cmailMsg,\r
10111                         _("Still need to make moves for both games\n"));\r
10112                 break;\r
10113                 \r
10114               default:\r
10115                 sprintf(cmailMsg,\r
10116                         _("Still need to make moves for all %d games\n"),\r
10117                         nCmailGames);\r
10118                 break;\r
10119             }\r
10120         } else {\r
10121             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
10122               case 1:\r
10123                 sprintf(cmailMsg,\r
10124                         _("Still need to make a move for game %s\n"),\r
10125                         string);\r
10126                 break;\r
10127                 \r
10128               case 0:\r
10129                 if (nCmailResults == nCmailGames) {\r
10130                     sprintf(cmailMsg, _("No unfinished games\n"));\r
10131                 } else {\r
10132                     sprintf(cmailMsg, _("Ready to send mail\n"));\r
10133                 }\r
10134                 break;\r
10135                 \r
10136               default:\r
10137                 sprintf(cmailMsg,\r
10138                         _("Still need to make moves for games %s\n"),\r
10139                         string);\r
10140             }\r
10141         }\r
10142     }\r
10143     return cmailMsg;\r
10144 #endif /* WIN32 */\r
10145 }\r
10146 \r
10147 void\r
10148 ResetGameEvent()\r
10149 {\r
10150     if (gameMode == Training)\r
10151       SetTrainingModeOff();\r
10152 \r
10153     Reset(TRUE, TRUE);\r
10154     cmailMsgLoaded = FALSE;\r
10155     if (appData.icsActive) {\r
10156       SendToICS(ics_prefix);\r
10157       SendToICS("refresh\n");\r
10158     }\r
10159 }\r
10160 \r
10161 void\r
10162 ExitEvent(status)\r
10163      int status;\r
10164 {\r
10165     exiting++;\r
10166     if (exiting > 2) {\r
10167       /* Give up on clean exit */\r
10168       exit(status);\r
10169     }\r
10170     if (exiting > 1) {\r
10171       /* Keep trying for clean exit */\r
10172       return;\r
10173     }\r
10174 \r
10175     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
10176 \r
10177     if (telnetISR != NULL) {\r
10178       RemoveInputSource(telnetISR);\r
10179     }\r
10180     if (icsPR != NoProc) {\r
10181       DestroyChildProcess(icsPR, TRUE);\r
10182     }\r
10183 #if 0\r
10184     /* Save game if resource set and not already saved by GameEnds() */\r
10185     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
10186                              && forwardMostMove > 0) {\r
10187       if (*appData.saveGameFile != NULLCHAR) {\r
10188         SaveGameToFile(appData.saveGameFile, TRUE);\r
10189       } else if (appData.autoSaveGames) {\r
10190         AutoSaveGame();\r
10191       }\r
10192       if (*appData.savePositionFile != NULLCHAR) {\r
10193         SavePositionToFile(appData.savePositionFile);\r
10194       }\r
10195     }\r
10196     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10197 #else\r
10198     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
10199     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);\r
10200 #endif\r
10201     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
10202     /* make sure this other one finishes before killing it!                  */\r
10203     if(endingGame) { int count = 0;\r
10204         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
10205         while(endingGame && count++ < 10) DoSleep(1);\r
10206         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
10207     }\r
10208 \r
10209     /* Kill off chess programs */\r
10210     if (first.pr != NoProc) {\r
10211         ExitAnalyzeMode();\r
10212         \r
10213         DoSleep( appData.delayBeforeQuit );\r
10214         SendToProgram("quit\n", &first);\r
10215         DoSleep( appData.delayAfterQuit );\r
10216         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
10217     }\r
10218     if (second.pr != NoProc) {\r
10219         DoSleep( appData.delayBeforeQuit );\r
10220         SendToProgram("quit\n", &second);\r
10221         DoSleep( appData.delayAfterQuit );\r
10222         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
10223     }\r
10224     if (first.isr != NULL) {\r
10225         RemoveInputSource(first.isr);\r
10226     }\r
10227     if (second.isr != NULL) {\r
10228         RemoveInputSource(second.isr);\r
10229     }\r
10230 \r
10231     ShutDownFrontEnd();\r
10232     exit(status);\r
10233 }\r
10234 \r
10235 void\r
10236 PauseEvent()\r
10237 {\r
10238     if (appData.debugMode)\r
10239         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
10240     if (pausing) {\r
10241         pausing = FALSE;\r
10242         ModeHighlight();\r
10243         if (gameMode == MachinePlaysWhite ||\r
10244             gameMode == MachinePlaysBlack) {\r
10245             StartClocks();\r
10246         } else {\r
10247             DisplayBothClocks();\r
10248         }\r
10249         if (gameMode == PlayFromGameFile) {\r
10250             if (appData.timeDelay >= 0) \r
10251                 AutoPlayGameLoop();\r
10252         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
10253             Reset(FALSE, TRUE);\r
10254             SendToICS(ics_prefix);\r
10255             SendToICS("refresh\n");\r
10256         } else if (currentMove < forwardMostMove) {\r
10257             ForwardInner(forwardMostMove);\r
10258         }\r
10259         pauseExamInvalid = FALSE;\r
10260     } else {\r
10261         switch (gameMode) {\r
10262           default:\r
10263             return;\r
10264           case IcsExamining:\r
10265             pauseExamForwardMostMove = forwardMostMove;\r
10266             pauseExamInvalid = FALSE;\r
10267             /* fall through */\r
10268           case IcsObserving:\r
10269           case IcsPlayingWhite:\r
10270           case IcsPlayingBlack:\r
10271             pausing = TRUE;\r
10272             ModeHighlight();\r
10273             return;\r
10274           case PlayFromGameFile:\r
10275             (void) StopLoadGameTimer();\r
10276             pausing = TRUE;\r
10277             ModeHighlight();\r
10278             break;\r
10279           case BeginningOfGame:\r
10280             if (appData.icsActive) return;\r
10281             /* else fall through */\r
10282           case MachinePlaysWhite:\r
10283           case MachinePlaysBlack:\r
10284           case TwoMachinesPlay:\r
10285             if (forwardMostMove == 0)\r
10286               return;           /* don't pause if no one has moved */\r
10287             if ((gameMode == MachinePlaysWhite &&\r
10288                  !WhiteOnMove(forwardMostMove)) ||\r
10289                 (gameMode == MachinePlaysBlack &&\r
10290                  WhiteOnMove(forwardMostMove))) {\r
10291                 StopClocks();\r
10292             }\r
10293             pausing = TRUE;\r
10294             ModeHighlight();\r
10295             break;\r
10296         }\r
10297     }\r
10298 }\r
10299 \r
10300 void\r
10301 EditCommentEvent()\r
10302 {\r
10303     char title[MSG_SIZ];\r
10304 \r
10305     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
10306         strcpy(title, _("Edit comment"));\r
10307     } else {\r
10308         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,\r
10309                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
10310                 parseList[currentMove - 1]);\r
10311     }\r
10312 \r
10313     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
10314 }\r
10315 \r
10316 \r
10317 void\r
10318 EditTagsEvent()\r
10319 {\r
10320     char *tags = PGNTags(&gameInfo);\r
10321     EditTagsPopUp(tags);\r
10322     free(tags);\r
10323 }\r
10324 \r
10325 void\r
10326 AnalyzeModeEvent()\r
10327 {\r
10328     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
10329       return;\r
10330 \r
10331     if (gameMode != AnalyzeFile) {\r
10332         if (!appData.icsEngineAnalyze) {\r
10333                EditGameEvent();\r
10334                if (gameMode != EditGame) return;\r
10335         }\r
10336         ResurrectChessProgram();\r
10337         SendToProgram("analyze\n", &first);\r
10338         first.analyzing = TRUE;\r
10339         /*first.maybeThinking = TRUE;*/\r
10340         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10341         AnalysisPopUp(_("Analysis"),\r
10342                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10343     }\r
10344     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;\r
10345     pausing = FALSE;\r
10346     ModeHighlight();\r
10347     SetGameInfo();\r
10348 \r
10349     StartAnalysisClock();\r
10350     GetTimeMark(&lastNodeCountTime);\r
10351     lastNodeCount = 0;\r
10352 }\r
10353 \r
10354 void\r
10355 AnalyzeFileEvent()\r
10356 {\r
10357     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
10358       return;\r
10359 \r
10360     if (gameMode != AnalyzeMode) {\r
10361         EditGameEvent();\r
10362         if (gameMode != EditGame) return;\r
10363         ResurrectChessProgram();\r
10364         SendToProgram("analyze\n", &first);\r
10365         first.analyzing = TRUE;\r
10366         /*first.maybeThinking = TRUE;*/\r
10367         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10368         AnalysisPopUp(_("Analysis"),\r
10369                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10370     }\r
10371     gameMode = AnalyzeFile;\r
10372     pausing = FALSE;\r
10373     ModeHighlight();\r
10374     SetGameInfo();\r
10375 \r
10376     StartAnalysisClock();\r
10377     GetTimeMark(&lastNodeCountTime);\r
10378     lastNodeCount = 0;\r
10379 }\r
10380 \r
10381 void\r
10382 MachineWhiteEvent()\r
10383 {\r
10384     char buf[MSG_SIZ];\r
10385     char *bookHit = NULL;\r
10386 \r
10387     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
10388       return;\r
10389 \r
10390 \r
10391     if (gameMode == PlayFromGameFile || \r
10392         gameMode == TwoMachinesPlay  || \r
10393         gameMode == Training         || \r
10394         gameMode == AnalyzeMode      || \r
10395         gameMode == EndOfGame)\r
10396         EditGameEvent();\r
10397 \r
10398     if (gameMode == EditPosition) \r
10399         EditPositionDone();\r
10400 \r
10401     if (!WhiteOnMove(currentMove)) {\r
10402         DisplayError(_("It is not White's turn"), 0);\r
10403         return;\r
10404     }\r
10405   \r
10406     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10407       ExitAnalyzeMode();\r
10408 \r
10409     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10410         gameMode == AnalyzeFile)\r
10411         TruncateGame();\r
10412 \r
10413     ResurrectChessProgram();    /* in case it isn't running */\r
10414     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */\r
10415         gameMode = MachinePlaysWhite;\r
10416         ResetClocks();\r
10417     } else\r
10418     gameMode = MachinePlaysWhite;\r
10419     pausing = FALSE;\r
10420     ModeHighlight();\r
10421     SetGameInfo();\r
10422     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10423     DisplayTitle(buf);\r
10424     if (first.sendName) {\r
10425       sprintf(buf, "name %s\n", gameInfo.black);\r
10426       SendToProgram(buf, &first);\r
10427     }\r
10428     if (first.sendTime) {\r
10429       if (first.useColors) {\r
10430         SendToProgram("black\n", &first); /*gnu kludge*/\r
10431       }\r
10432       SendTimeRemaining(&first, TRUE);\r
10433     }\r
10434     if (first.useColors) {\r
10435       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately\r
10436     }\r
10437     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10438     SetMachineThinkingEnables();\r
10439     first.maybeThinking = TRUE;\r
10440     StartClocks();\r
10441 \r
10442     if (appData.autoFlipView && !flipView) {\r
10443       flipView = !flipView;\r
10444       DrawPosition(FALSE, NULL);\r
10445       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10446     }\r
10447 \r
10448     if(bookHit) { // [HGM] book: simulate book reply\r
10449         static char bookMove[MSG_SIZ]; // a bit generous?\r
10450 \r
10451         programStats.nodes = programStats.depth = programStats.time = \r
10452         programStats.score = programStats.got_only_move = 0;\r
10453         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10454 \r
10455         strcpy(bookMove, "move ");\r
10456         strcat(bookMove, bookHit);\r
10457         HandleMachineMove(bookMove, &first);\r
10458     }\r
10459 }\r
10460 \r
10461 void\r
10462 MachineBlackEvent()\r
10463 {\r
10464     char buf[MSG_SIZ];\r
10465    char *bookHit = NULL;\r
10466 \r
10467     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
10468         return;\r
10469 \r
10470 \r
10471     if (gameMode == PlayFromGameFile || \r
10472         gameMode == TwoMachinesPlay  || \r
10473         gameMode == Training         || \r
10474         gameMode == AnalyzeMode      || \r
10475         gameMode == EndOfGame)\r
10476         EditGameEvent();\r
10477 \r
10478     if (gameMode == EditPosition) \r
10479         EditPositionDone();\r
10480 \r
10481     if (WhiteOnMove(currentMove)) {\r
10482         DisplayError(_("It is not Black's turn"), 0);\r
10483         return;\r
10484     }\r
10485     \r
10486     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10487       ExitAnalyzeMode();\r
10488 \r
10489     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10490         gameMode == AnalyzeFile)\r
10491         TruncateGame();\r
10492 \r
10493     ResurrectChessProgram();    /* in case it isn't running */\r
10494     gameMode = MachinePlaysBlack;\r
10495     pausing = FALSE;\r
10496     ModeHighlight();\r
10497     SetGameInfo();\r
10498     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10499     DisplayTitle(buf);\r
10500     if (first.sendName) {\r
10501       sprintf(buf, "name %s\n", gameInfo.white);\r
10502       SendToProgram(buf, &first);\r
10503     }\r
10504     if (first.sendTime) {\r
10505       if (first.useColors) {\r
10506         SendToProgram("white\n", &first); /*gnu kludge*/\r
10507       }\r
10508       SendTimeRemaining(&first, FALSE);\r
10509     }\r
10510     if (first.useColors) {\r
10511       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately\r
10512     }\r
10513     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10514     SetMachineThinkingEnables();\r
10515     first.maybeThinking = TRUE;\r
10516     StartClocks();\r
10517 \r
10518     if (appData.autoFlipView && flipView) {\r
10519       flipView = !flipView;\r
10520       DrawPosition(FALSE, NULL);\r
10521       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10522     }\r
10523     if(bookHit) { // [HGM] book: simulate book reply\r
10524         static char bookMove[MSG_SIZ]; // a bit generous?\r
10525 \r
10526         programStats.nodes = programStats.depth = programStats.time = \r
10527         programStats.score = programStats.got_only_move = 0;\r
10528         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10529 \r
10530         strcpy(bookMove, "move ");\r
10531         strcat(bookMove, bookHit);\r
10532         HandleMachineMove(bookMove, &first);\r
10533     }\r
10534 }\r
10535 \r
10536 \r
10537 void\r
10538 DisplayTwoMachinesTitle()\r
10539 {\r
10540     char buf[MSG_SIZ];\r
10541     if (appData.matchGames > 0) {\r
10542         if (first.twoMachinesColor[0] == 'w') {\r
10543             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10544                     gameInfo.white, gameInfo.black,\r
10545                     first.matchWins, second.matchWins,\r
10546                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10547         } else {\r
10548             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10549                     gameInfo.white, gameInfo.black,\r
10550                     second.matchWins, first.matchWins,\r
10551                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10552         }\r
10553     } else {\r
10554         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10555     }\r
10556     DisplayTitle(buf);\r
10557 }\r
10558 \r
10559 void\r
10560 TwoMachinesEvent P((void))\r
10561 {\r
10562     int i;\r
10563     char buf[MSG_SIZ];\r
10564     ChessProgramState *onmove;\r
10565     char *bookHit = NULL;\r
10566     \r
10567     if (appData.noChessProgram) return;\r
10568 \r
10569     switch (gameMode) {\r
10570       case TwoMachinesPlay:\r
10571         return;\r
10572       case MachinePlaysWhite:\r
10573       case MachinePlaysBlack:\r
10574         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10575             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
10576             return;\r
10577         }\r
10578         /* fall through */\r
10579       case BeginningOfGame:\r
10580       case PlayFromGameFile:\r
10581       case EndOfGame:\r
10582         EditGameEvent();\r
10583         if (gameMode != EditGame) return;\r
10584         break;\r
10585       case EditPosition:\r
10586         EditPositionDone();\r
10587         break;\r
10588       case AnalyzeMode:\r
10589       case AnalyzeFile:\r
10590         ExitAnalyzeMode();\r
10591         break;\r
10592       case EditGame:\r
10593       default:\r
10594         break;\r
10595     }\r
10596 \r
10597     forwardMostMove = currentMove;\r
10598     ResurrectChessProgram();    /* in case first program isn't running */\r
10599 \r
10600     if (second.pr == NULL) {\r
10601         StartChessProgram(&second);\r
10602         if (second.protocolVersion == 1) {\r
10603           TwoMachinesEventIfReady();\r
10604         } else {\r
10605           /* kludge: allow timeout for initial "feature" command */\r
10606           FreezeUI();\r
10607           DisplayMessage("", _("Starting second chess program"));\r
10608           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
10609         }\r
10610         return;\r
10611     }\r
10612     DisplayMessage("", "");\r
10613     InitChessProgram(&second, FALSE);\r
10614     SendToProgram("force\n", &second);\r
10615     if (startedFromSetupPosition) {\r
10616         SendBoard(&second, backwardMostMove);\r
10617     if (appData.debugMode) {\r
10618         fprintf(debugFP, "Two Machines\n");\r
10619     }\r
10620     }\r
10621     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
10622         SendMoveToProgram(i, &second);\r
10623     }\r
10624 \r
10625     gameMode = TwoMachinesPlay;\r
10626     pausing = FALSE;\r
10627     ModeHighlight();\r
10628     SetGameInfo();\r
10629     DisplayTwoMachinesTitle();\r
10630     firstMove = TRUE;\r
10631     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
10632         onmove = &first;\r
10633     } else {\r
10634         onmove = &second;\r
10635     }\r
10636 \r
10637     SendToProgram(first.computerString, &first);\r
10638     if (first.sendName) {\r
10639       sprintf(buf, "name %s\n", second.tidy);\r
10640       SendToProgram(buf, &first);\r
10641     }\r
10642     SendToProgram(second.computerString, &second);\r
10643     if (second.sendName) {\r
10644       sprintf(buf, "name %s\n", first.tidy);\r
10645       SendToProgram(buf, &second);\r
10646     }\r
10647 \r
10648     ResetClocks();\r
10649     if (!first.sendTime || !second.sendTime) {\r
10650         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10651         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10652     }\r
10653     if (onmove->sendTime) {\r
10654       if (onmove->useColors) {\r
10655         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
10656       }\r
10657       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
10658     }\r
10659     if (onmove->useColors) {\r
10660       SendToProgram(onmove->twoMachinesColor, onmove);\r
10661     }\r
10662     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move\r
10663 //    SendToProgram("go\n", onmove);\r
10664     onmove->maybeThinking = TRUE;\r
10665     SetMachineThinkingEnables();\r
10666 \r
10667     StartClocks();\r
10668 \r
10669     if(bookHit) { // [HGM] book: simulate book reply\r
10670         static char bookMove[MSG_SIZ]; // a bit generous?\r
10671 \r
10672         programStats.nodes = programStats.depth = programStats.time = \r
10673         programStats.score = programStats.got_only_move = 0;\r
10674         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10675 \r
10676         strcpy(bookMove, "move ");\r
10677         strcat(bookMove, bookHit);\r
10678         HandleMachineMove(bookMove, &first);\r
10679     }\r
10680 }\r
10681 \r
10682 void\r
10683 TrainingEvent()\r
10684 {\r
10685     if (gameMode == Training) {\r
10686       SetTrainingModeOff();\r
10687       gameMode = PlayFromGameFile;\r
10688       DisplayMessage("", _("Training mode off"));\r
10689     } else {\r
10690       gameMode = Training;\r
10691       animateTraining = appData.animate;\r
10692 \r
10693       /* make sure we are not already at the end of the game */\r
10694       if (currentMove < forwardMostMove) {\r
10695         SetTrainingModeOn();\r
10696         DisplayMessage("", _("Training mode on"));\r
10697       } else {\r
10698         gameMode = PlayFromGameFile;\r
10699         DisplayError(_("Already at end of game"), 0);\r
10700       }\r
10701     }\r
10702     ModeHighlight();\r
10703 }\r
10704 \r
10705 void\r
10706 IcsClientEvent()\r
10707 {\r
10708     if (!appData.icsActive) return;\r
10709     switch (gameMode) {\r
10710       case IcsPlayingWhite:\r
10711       case IcsPlayingBlack:\r
10712       case IcsObserving:\r
10713       case IcsIdle:\r
10714       case BeginningOfGame:\r
10715       case IcsExamining:\r
10716         return;\r
10717 \r
10718       case EditGame:\r
10719         break;\r
10720 \r
10721       case EditPosition:\r
10722         EditPositionDone();\r
10723         break;\r
10724 \r
10725       case AnalyzeMode:\r
10726       case AnalyzeFile:\r
10727         ExitAnalyzeMode();\r
10728         break;\r
10729         \r
10730       default:\r
10731         EditGameEvent();\r
10732         break;\r
10733     }\r
10734 \r
10735     gameMode = IcsIdle;\r
10736     ModeHighlight();\r
10737     return;\r
10738 }\r
10739 \r
10740 \r
10741 void\r
10742 EditGameEvent()\r
10743 {\r
10744     int i;\r
10745 \r
10746     switch (gameMode) {\r
10747       case Training:\r
10748         SetTrainingModeOff();\r
10749         break;\r
10750       case MachinePlaysWhite:\r
10751       case MachinePlaysBlack:\r
10752       case BeginningOfGame:\r
10753         SendToProgram("force\n", &first);\r
10754         SetUserThinkingEnables();\r
10755         break;\r
10756       case PlayFromGameFile:\r
10757         (void) StopLoadGameTimer();\r
10758         if (gameFileFP != NULL) {\r
10759             gameFileFP = NULL;\r
10760         }\r
10761         break;\r
10762       case EditPosition:\r
10763         EditPositionDone();\r
10764         break;\r
10765       case AnalyzeMode:\r
10766       case AnalyzeFile:\r
10767         ExitAnalyzeMode();\r
10768         SendToProgram("force\n", &first);\r
10769         break;\r
10770       case TwoMachinesPlay:\r
10771         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10772         ResurrectChessProgram();\r
10773         SetUserThinkingEnables();\r
10774         break;\r
10775       case EndOfGame:\r
10776         ResurrectChessProgram();\r
10777         break;\r
10778       case IcsPlayingBlack:\r
10779       case IcsPlayingWhite:\r
10780         DisplayError(_("Warning: You are still playing a game"), 0);\r
10781         break;\r
10782       case IcsObserving:\r
10783         DisplayError(_("Warning: You are still observing a game"), 0);\r
10784         break;\r
10785       case IcsExamining:\r
10786         DisplayError(_("Warning: You are still examining a game"), 0);\r
10787         break;\r
10788       case IcsIdle:\r
10789         break;\r
10790       case EditGame:\r
10791       default:\r
10792         return;\r
10793     }\r
10794     \r
10795     pausing = FALSE;\r
10796     StopClocks();\r
10797     first.offeredDraw = second.offeredDraw = 0;\r
10798 \r
10799     if (gameMode == PlayFromGameFile) {\r
10800         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10801         blackTimeRemaining = timeRemaining[1][currentMove];\r
10802         DisplayTitle("");\r
10803     }\r
10804 \r
10805     if (gameMode == MachinePlaysWhite ||\r
10806         gameMode == MachinePlaysBlack ||\r
10807         gameMode == TwoMachinesPlay ||\r
10808         gameMode == EndOfGame) {\r
10809         i = forwardMostMove;\r
10810         while (i > currentMove) {\r
10811             SendToProgram("undo\n", &first);\r
10812             i--;\r
10813         }\r
10814         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10815         blackTimeRemaining = timeRemaining[1][currentMove];\r
10816         DisplayBothClocks();\r
10817         if (whiteFlag || blackFlag) {\r
10818             whiteFlag = blackFlag = 0;\r
10819         }\r
10820         DisplayTitle("");\r
10821     }           \r
10822     \r
10823     gameMode = EditGame;\r
10824     ModeHighlight();\r
10825     SetGameInfo();\r
10826 }\r
10827 \r
10828 \r
10829 void\r
10830 EditPositionEvent()\r
10831 {\r
10832     if (gameMode == EditPosition) {\r
10833         EditGameEvent();\r
10834         return;\r
10835     }\r
10836     \r
10837     EditGameEvent();\r
10838     if (gameMode != EditGame) return;\r
10839     \r
10840     gameMode = EditPosition;\r
10841     ModeHighlight();\r
10842     SetGameInfo();\r
10843     if (currentMove > 0)\r
10844       CopyBoard(boards[0], boards[currentMove]);\r
10845     \r
10846     blackPlaysFirst = !WhiteOnMove(currentMove);\r
10847     ResetClocks();\r
10848     currentMove = forwardMostMove = backwardMostMove = 0;\r
10849     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10850     DisplayMove(-1);\r
10851 }\r
10852 \r
10853 void\r
10854 ExitAnalyzeMode()\r
10855 {\r
10856     /* [DM] icsEngineAnalyze - possible call from other functions */\r
10857     if (appData.icsEngineAnalyze) {\r
10858         appData.icsEngineAnalyze = FALSE;\r
10859 \r
10860         DisplayMessage("",_("Close ICS engine analyze..."));\r
10861     }\r
10862     if (first.analysisSupport && first.analyzing) {\r
10863       SendToProgram("exit\n", &first);\r
10864       first.analyzing = FALSE;\r
10865     }\r
10866     AnalysisPopDown();\r
10867     thinkOutput[0] = NULLCHAR;\r
10868 }\r
10869 \r
10870 void\r
10871 EditPositionDone()\r
10872 {\r
10873     startedFromSetupPosition = TRUE;\r
10874     InitChessProgram(&first, FALSE);\r
10875     SendToProgram("force\n", &first);\r
10876     if (blackPlaysFirst) {\r
10877         strcpy(moveList[0], "");\r
10878         strcpy(parseList[0], "");\r
10879         currentMove = forwardMostMove = backwardMostMove = 1;\r
10880         CopyBoard(boards[1], boards[0]);\r
10881         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */\r
10882         { int i;\r
10883           epStatus[1] = epStatus[0];\r
10884           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];\r
10885         }\r
10886     } else {\r
10887         currentMove = forwardMostMove = backwardMostMove = 0;\r
10888     }\r
10889     SendBoard(&first, forwardMostMove);\r
10890     if (appData.debugMode) {\r
10891         fprintf(debugFP, "EditPosDone\n");\r
10892     }\r
10893     DisplayTitle("");\r
10894     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10895     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10896     gameMode = EditGame;\r
10897     ModeHighlight();\r
10898     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10899     ClearHighlights(); /* [AS] */\r
10900 }\r
10901 \r
10902 /* Pause for `ms' milliseconds */\r
10903 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10904 void\r
10905 TimeDelay(ms)\r
10906      long ms;\r
10907 {\r
10908     TimeMark m1, m2;\r
10909 \r
10910     GetTimeMark(&m1);\r
10911     do {\r
10912         GetTimeMark(&m2);\r
10913     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
10914 }\r
10915 \r
10916 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10917 void\r
10918 SendMultiLineToICS(buf)\r
10919      char *buf;\r
10920 {\r
10921     char temp[MSG_SIZ+1], *p;\r
10922     int len;\r
10923 \r
10924     len = strlen(buf);\r
10925     if (len > MSG_SIZ)\r
10926       len = MSG_SIZ;\r
10927   \r
10928     strncpy(temp, buf, len);\r
10929     temp[len] = 0;\r
10930 \r
10931     p = temp;\r
10932     while (*p) {\r
10933         if (*p == '\n' || *p == '\r')\r
10934           *p = ' ';\r
10935         ++p;\r
10936     }\r
10937 \r
10938     strcat(temp, "\n");\r
10939     SendToICS(temp);\r
10940     SendToPlayer(temp, strlen(temp));\r
10941 }\r
10942 \r
10943 void\r
10944 SetWhiteToPlayEvent()\r
10945 {\r
10946     if (gameMode == EditPosition) {\r
10947         blackPlaysFirst = FALSE;\r
10948         DisplayBothClocks();    /* works because currentMove is 0 */\r
10949     } else if (gameMode == IcsExamining) {\r
10950         SendToICS(ics_prefix);\r
10951         SendToICS("tomove white\n");\r
10952     }\r
10953 }\r
10954 \r
10955 void\r
10956 SetBlackToPlayEvent()\r
10957 {\r
10958     if (gameMode == EditPosition) {\r
10959         blackPlaysFirst = TRUE;\r
10960         currentMove = 1;        /* kludge */\r
10961         DisplayBothClocks();\r
10962         currentMove = 0;\r
10963     } else if (gameMode == IcsExamining) {\r
10964         SendToICS(ics_prefix);\r
10965         SendToICS("tomove black\n");\r
10966     }\r
10967 }\r
10968 \r
10969 void\r
10970 EditPositionMenuEvent(selection, x, y)\r
10971      ChessSquare selection;\r
10972      int x, y;\r
10973 {\r
10974     char buf[MSG_SIZ];\r
10975     ChessSquare piece = boards[0][y][x];\r
10976 \r
10977     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
10978 \r
10979     switch (selection) {\r
10980       case ClearBoard:\r
10981         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
10982             SendToICS(ics_prefix);\r
10983             SendToICS("bsetup clear\n");\r
10984         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
10985             SendToICS(ics_prefix);\r
10986             SendToICS("clearboard\n");\r
10987         } else {\r
10988             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;\r
10989                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */\r
10990                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
10991                     if (gameMode == IcsExamining) {\r
10992                         if (boards[currentMove][y][x] != EmptySquare) {\r
10993                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
10994                                     AAA + x, ONE + y);\r
10995                             SendToICS(buf);\r
10996                         }\r
10997                     } else {\r
10998                         boards[0][y][x] = p;\r
10999                     }\r
11000                 }\r
11001             }\r
11002         }\r
11003         if (gameMode == EditPosition) {\r
11004             DrawPosition(FALSE, boards[0]);\r
11005         }\r
11006         break;\r
11007 \r
11008       case WhitePlay:\r
11009         SetWhiteToPlayEvent();\r
11010         break;\r
11011 \r
11012       case BlackPlay:\r
11013         SetBlackToPlayEvent();\r
11014         break;\r
11015 \r
11016       case EmptySquare:\r
11017         if (gameMode == IcsExamining) {\r
11018             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
11019             SendToICS(buf);\r
11020         } else {\r
11021             boards[0][y][x] = EmptySquare;\r
11022             DrawPosition(FALSE, boards[0]);\r
11023         }\r
11024         break;\r
11025 \r
11026       case PromotePiece:\r
11027         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
11028            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
11029             selection = (ChessSquare) (PROMOTED piece);\r
11030         } else if(piece == EmptySquare) selection = WhiteSilver;\r
11031         else selection = (ChessSquare)((int)piece - 1);\r
11032         goto defaultlabel;\r
11033 \r
11034       case DemotePiece:\r
11035         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
11036            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
11037             selection = (ChessSquare) (DEMOTED piece);\r
11038         } else if(piece == EmptySquare) selection = BlackSilver;\r
11039         else selection = (ChessSquare)((int)piece + 1);       \r
11040         goto defaultlabel;\r
11041 \r
11042       case WhiteQueen:\r
11043       case BlackQueen:\r
11044         if(gameInfo.variant == VariantShatranj ||\r
11045            gameInfo.variant == VariantXiangqi  ||\r
11046            gameInfo.variant == VariantCourier    )\r
11047             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
11048         goto defaultlabel;\r
11049 \r
11050       case WhiteKing:\r
11051       case BlackKing:\r
11052         if(gameInfo.variant == VariantXiangqi)\r
11053             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
11054         if(gameInfo.variant == VariantKnightmate)\r
11055             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
11056       default:\r
11057         defaultlabel:\r
11058         if (gameMode == IcsExamining) {\r
11059             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
11060                     PieceToChar(selection), AAA + x, ONE + y);\r
11061             SendToICS(buf);\r
11062         } else {\r
11063             boards[0][y][x] = selection;\r
11064             DrawPosition(FALSE, boards[0]);\r
11065         }\r
11066         break;\r
11067     }\r
11068 }\r
11069 \r
11070 \r
11071 void\r
11072 DropMenuEvent(selection, x, y)\r
11073      ChessSquare selection;\r
11074      int x, y;\r
11075 {\r
11076     ChessMove moveType;\r
11077 \r
11078     switch (gameMode) {\r
11079       case IcsPlayingWhite:\r
11080       case MachinePlaysBlack:\r
11081         if (!WhiteOnMove(currentMove)) {\r
11082             DisplayMoveError(_("It is Black's turn"));\r
11083             return;\r
11084         }\r
11085         moveType = WhiteDrop;\r
11086         break;\r
11087       case IcsPlayingBlack:\r
11088       case MachinePlaysWhite:\r
11089         if (WhiteOnMove(currentMove)) {\r
11090             DisplayMoveError(_("It is White's turn"));\r
11091             return;\r
11092         }\r
11093         moveType = BlackDrop;\r
11094         break;\r
11095       case EditGame:\r
11096         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
11097         break;\r
11098       default:\r
11099         return;\r
11100     }\r
11101 \r
11102     if (moveType == BlackDrop && selection < BlackPawn) {\r
11103       selection = (ChessSquare) ((int) selection\r
11104                                  + (int) BlackPawn - (int) WhitePawn);\r
11105     }\r
11106     if (boards[currentMove][y][x] != EmptySquare) {\r
11107         DisplayMoveError(_("That square is occupied"));\r
11108         return;\r
11109     }\r
11110 \r
11111     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
11112 }\r
11113 \r
11114 void\r
11115 AcceptEvent()\r
11116 {\r
11117     /* Accept a pending offer of any kind from opponent */\r
11118     \r
11119     if (appData.icsActive) {\r
11120         SendToICS(ics_prefix);\r
11121         SendToICS("accept\n");\r
11122     } else if (cmailMsgLoaded) {\r
11123         if (currentMove == cmailOldMove &&\r
11124             commentList[cmailOldMove] != NULL &&\r
11125             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11126                    "Black offers a draw" : "White offers a draw")) {\r
11127             TruncateGame();\r
11128             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11129             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11130         } else {\r
11131             DisplayError(_("There is no pending offer on this move"), 0);\r
11132             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11133         }\r
11134     } else {\r
11135         /* Not used for offers from chess program */\r
11136     }\r
11137 }\r
11138 \r
11139 void\r
11140 DeclineEvent()\r
11141 {\r
11142     /* Decline a pending offer of any kind from opponent */\r
11143     \r
11144     if (appData.icsActive) {\r
11145         SendToICS(ics_prefix);\r
11146         SendToICS("decline\n");\r
11147     } else if (cmailMsgLoaded) {\r
11148         if (currentMove == cmailOldMove &&\r
11149             commentList[cmailOldMove] != NULL &&\r
11150             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11151                    "Black offers a draw" : "White offers a draw")) {\r
11152 #ifdef NOTDEF\r
11153             AppendComment(cmailOldMove, "Draw declined");\r
11154             DisplayComment(cmailOldMove - 1, "Draw declined");\r
11155 #endif /*NOTDEF*/\r
11156         } else {\r
11157             DisplayError(_("There is no pending offer on this move"), 0);\r
11158         }\r
11159     } else {\r
11160         /* Not used for offers from chess program */\r
11161     }\r
11162 }\r
11163 \r
11164 void\r
11165 RematchEvent()\r
11166 {\r
11167     /* Issue ICS rematch command */\r
11168     if (appData.icsActive) {\r
11169         SendToICS(ics_prefix);\r
11170         SendToICS("rematch\n");\r
11171     }\r
11172 }\r
11173 \r
11174 void\r
11175 CallFlagEvent()\r
11176 {\r
11177     /* Call your opponent's flag (claim a win on time) */\r
11178     if (appData.icsActive) {\r
11179         SendToICS(ics_prefix);\r
11180         SendToICS("flag\n");\r
11181     } else {\r
11182         switch (gameMode) {\r
11183           default:\r
11184             return;\r
11185           case MachinePlaysWhite:\r
11186             if (whiteFlag) {\r
11187                 if (blackFlag)\r
11188                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11189                            GE_PLAYER);\r
11190                 else\r
11191                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
11192             } else {\r
11193                 DisplayError(_("Your opponent is not out of time"), 0);\r
11194             }\r
11195             break;\r
11196           case MachinePlaysBlack:\r
11197             if (blackFlag) {\r
11198                 if (whiteFlag)\r
11199                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11200                            GE_PLAYER);\r
11201                 else\r
11202                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
11203             } else {\r
11204                 DisplayError(_("Your opponent is not out of time"), 0);\r
11205             }\r
11206             break;\r
11207         }\r
11208     }\r
11209 }\r
11210 \r
11211 void\r
11212 DrawEvent()\r
11213 {\r
11214     /* Offer draw or accept pending draw offer from opponent */\r
11215     \r
11216     if (appData.icsActive) {\r
11217         /* Note: tournament rules require draw offers to be\r
11218            made after you make your move but before you punch\r
11219            your clock.  Currently ICS doesn't let you do that;\r
11220            instead, you immediately punch your clock after making\r
11221            a move, but you can offer a draw at any time. */\r
11222         \r
11223         SendToICS(ics_prefix);\r
11224         SendToICS("draw\n");\r
11225     } else if (cmailMsgLoaded) {\r
11226         if (currentMove == cmailOldMove &&\r
11227             commentList[cmailOldMove] != NULL &&\r
11228             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11229                    "Black offers a draw" : "White offers a draw")) {\r
11230             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11231             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11232         } else if (currentMove == cmailOldMove + 1) {\r
11233             char *offer = WhiteOnMove(cmailOldMove) ?\r
11234               "White offers a draw" : "Black offers a draw";\r
11235             AppendComment(currentMove, offer);\r
11236             DisplayComment(currentMove - 1, offer);\r
11237             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
11238         } else {\r
11239             DisplayError(_("You must make your move before offering a draw"), 0);\r
11240             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11241         }\r
11242     } else if (first.offeredDraw) {\r
11243         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
11244     } else {\r
11245         if (first.sendDrawOffers) {\r
11246             SendToProgram("draw\n", &first);\r
11247             userOfferedDraw = TRUE;\r
11248         }\r
11249     }\r
11250 }\r
11251 \r
11252 void\r
11253 AdjournEvent()\r
11254 {\r
11255     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
11256     \r
11257     if (appData.icsActive) {\r
11258         SendToICS(ics_prefix);\r
11259         SendToICS("adjourn\n");\r
11260     } else {\r
11261         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
11262     }\r
11263 }\r
11264 \r
11265 \r
11266 void\r
11267 AbortEvent()\r
11268 {\r
11269     /* Offer Abort or accept pending Abort offer from opponent */\r
11270     \r
11271     if (appData.icsActive) {\r
11272         SendToICS(ics_prefix);\r
11273         SendToICS("abort\n");\r
11274     } else {\r
11275         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
11276     }\r
11277 }\r
11278 \r
11279 void\r
11280 ResignEvent()\r
11281 {\r
11282     /* Resign.  You can do this even if it's not your turn. */\r
11283     \r
11284     if (appData.icsActive) {\r
11285         SendToICS(ics_prefix);\r
11286         SendToICS("resign\n");\r
11287     } else {\r
11288         switch (gameMode) {\r
11289           case MachinePlaysWhite:\r
11290             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11291             break;\r
11292           case MachinePlaysBlack:\r
11293             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11294             break;\r
11295           case EditGame:\r
11296             if (cmailMsgLoaded) {\r
11297                 TruncateGame();\r
11298                 if (WhiteOnMove(cmailOldMove)) {\r
11299                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11300                 } else {\r
11301                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11302                 }\r
11303                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
11304             }\r
11305             break;\r
11306           default:\r
11307             break;\r
11308         }\r
11309     }\r
11310 }\r
11311 \r
11312 \r
11313 void\r
11314 StopObservingEvent()\r
11315 {\r
11316     /* Stop observing current games */\r
11317     SendToICS(ics_prefix);\r
11318     SendToICS("unobserve\n");\r
11319 }\r
11320 \r
11321 void\r
11322 StopExaminingEvent()\r
11323 {\r
11324     /* Stop observing current game */\r
11325     SendToICS(ics_prefix);\r
11326     SendToICS("unexamine\n");\r
11327 }\r
11328 \r
11329 void\r
11330 ForwardInner(target)\r
11331      int target;\r
11332 {\r
11333     int limit;\r
11334 \r
11335     if (appData.debugMode)\r
11336         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
11337                 target, currentMove, forwardMostMove);\r
11338 \r
11339     if (gameMode == EditPosition)\r
11340       return;\r
11341 \r
11342     if (gameMode == PlayFromGameFile && !pausing)\r
11343       PauseEvent();\r
11344     \r
11345     if (gameMode == IcsExamining && pausing)\r
11346       limit = pauseExamForwardMostMove;\r
11347     else\r
11348       limit = forwardMostMove;\r
11349     \r
11350     if (target > limit) target = limit;\r
11351 \r
11352     if (target > 0 && moveList[target - 1][0]) {\r
11353         int fromX, fromY, toX, toY;\r
11354         toX = moveList[target - 1][2] - AAA;\r
11355         toY = moveList[target - 1][3] - ONE;\r
11356         if (moveList[target - 1][1] == '@') {\r
11357             if (appData.highlightLastMove) {\r
11358                 SetHighlights(-1, -1, toX, toY);\r
11359             }\r
11360         } else {\r
11361             fromX = moveList[target - 1][0] - AAA;\r
11362             fromY = moveList[target - 1][1] - ONE;\r
11363             if (target == currentMove + 1) {\r
11364                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
11365             }\r
11366             if (appData.highlightLastMove) {\r
11367                 SetHighlights(fromX, fromY, toX, toY);\r
11368             }\r
11369         }\r
11370     }\r
11371     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
11372         gameMode == Training || gameMode == PlayFromGameFile || \r
11373         gameMode == AnalyzeFile) {\r
11374         while (currentMove < target) {\r
11375             SendMoveToProgram(currentMove++, &first);\r
11376         }\r
11377     } else {\r
11378         currentMove = target;\r
11379     }\r
11380     \r
11381     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11382         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11383         blackTimeRemaining = timeRemaining[1][currentMove];\r
11384     }\r
11385     DisplayBothClocks();\r
11386     DisplayMove(currentMove - 1);\r
11387     DrawPosition(FALSE, boards[currentMove]);\r
11388     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11389     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
11390         DisplayComment(currentMove - 1, commentList[currentMove]);\r
11391     }\r
11392 }\r
11393 \r
11394 \r
11395 void\r
11396 ForwardEvent()\r
11397 {\r
11398     if (gameMode == IcsExamining && !pausing) {\r
11399         SendToICS(ics_prefix);\r
11400         SendToICS("forward\n");\r
11401     } else {\r
11402         ForwardInner(currentMove + 1);\r
11403     }\r
11404 }\r
11405 \r
11406 void\r
11407 ToEndEvent()\r
11408 {\r
11409     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11410         /* to optimze, we temporarily turn off analysis mode while we feed\r
11411          * the remaining moves to the engine. Otherwise we get analysis output\r
11412          * after each move.\r
11413          */ \r
11414         if (first.analysisSupport) {\r
11415           SendToProgram("exit\nforce\n", &first);\r
11416           first.analyzing = FALSE;\r
11417         }\r
11418     }\r
11419         \r
11420     if (gameMode == IcsExamining && !pausing) {\r
11421         SendToICS(ics_prefix);\r
11422         SendToICS("forward 999999\n");\r
11423     } else {\r
11424         ForwardInner(forwardMostMove);\r
11425     }\r
11426 \r
11427     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11428         /* we have fed all the moves, so reactivate analysis mode */\r
11429         SendToProgram("analyze\n", &first);\r
11430         first.analyzing = TRUE;\r
11431         /*first.maybeThinking = TRUE;*/\r
11432         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11433     }\r
11434 }\r
11435 \r
11436 void\r
11437 BackwardInner(target)\r
11438      int target;\r
11439 {\r
11440     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
11441 \r
11442     if (appData.debugMode)\r
11443         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
11444                 target, currentMove, forwardMostMove);\r
11445 \r
11446     if (gameMode == EditPosition) return;\r
11447     if (currentMove <= backwardMostMove) {\r
11448         ClearHighlights();\r
11449         DrawPosition(full_redraw, boards[currentMove]);\r
11450         return;\r
11451     }\r
11452     if (gameMode == PlayFromGameFile && !pausing)\r
11453       PauseEvent();\r
11454     \r
11455     if (moveList[target][0]) {\r
11456         int fromX, fromY, toX, toY;\r
11457         toX = moveList[target][2] - AAA;\r
11458         toY = moveList[target][3] - ONE;\r
11459         if (moveList[target][1] == '@') {\r
11460             if (appData.highlightLastMove) {\r
11461                 SetHighlights(-1, -1, toX, toY);\r
11462             }\r
11463         } else {\r
11464             fromX = moveList[target][0] - AAA;\r
11465             fromY = moveList[target][1] - ONE;\r
11466             if (target == currentMove - 1) {\r
11467                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
11468             }\r
11469             if (appData.highlightLastMove) {\r
11470                 SetHighlights(fromX, fromY, toX, toY);\r
11471             }\r
11472         }\r
11473     }\r
11474     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
11475         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
11476         while (currentMove > target) {\r
11477             SendToProgram("undo\n", &first);\r
11478             currentMove--;\r
11479         }\r
11480     } else {\r
11481         currentMove = target;\r
11482     }\r
11483     \r
11484     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11485         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11486         blackTimeRemaining = timeRemaining[1][currentMove];\r
11487     }\r
11488     DisplayBothClocks();\r
11489     DisplayMove(currentMove - 1);\r
11490     DrawPosition(full_redraw, boards[currentMove]);\r
11491     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11492     // [HGM] PV info: routine tests if comment empty\r
11493     DisplayComment(currentMove - 1, commentList[currentMove]);\r
11494 }\r
11495 \r
11496 void\r
11497 BackwardEvent()\r
11498 {\r
11499     if (gameMode == IcsExamining && !pausing) {\r
11500         SendToICS(ics_prefix);\r
11501         SendToICS("backward\n");\r
11502     } else {\r
11503         BackwardInner(currentMove - 1);\r
11504     }\r
11505 }\r
11506 \r
11507 void\r
11508 ToStartEvent()\r
11509 {\r
11510     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11511         /* to optimze, we temporarily turn off analysis mode while we undo\r
11512          * all the moves. Otherwise we get analysis output after each undo.\r
11513          */ \r
11514         if (first.analysisSupport) {\r
11515           SendToProgram("exit\nforce\n", &first);\r
11516           first.analyzing = FALSE;\r
11517         }\r
11518     }\r
11519 \r
11520     if (gameMode == IcsExamining && !pausing) {\r
11521         SendToICS(ics_prefix);\r
11522         SendToICS("backward 999999\n");\r
11523     } else {\r
11524         BackwardInner(backwardMostMove);\r
11525     }\r
11526 \r
11527     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11528         /* we have fed all the moves, so reactivate analysis mode */\r
11529         SendToProgram("analyze\n", &first);\r
11530         first.analyzing = TRUE;\r
11531         /*first.maybeThinking = TRUE;*/\r
11532         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11533     }\r
11534 }\r
11535 \r
11536 void\r
11537 ToNrEvent(int to)\r
11538 {\r
11539   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
11540   if (to >= forwardMostMove) to = forwardMostMove;\r
11541   if (to <= backwardMostMove) to = backwardMostMove;\r
11542   if (to < currentMove) {\r
11543     BackwardInner(to);\r
11544   } else {\r
11545     ForwardInner(to);\r
11546   }\r
11547 }\r
11548 \r
11549 void\r
11550 RevertEvent()\r
11551 {\r
11552     if (gameMode != IcsExamining) {\r
11553         DisplayError(_("You are not examining a game"), 0);\r
11554         return;\r
11555     }\r
11556     if (pausing) {\r
11557         DisplayError(_("You can't revert while pausing"), 0);\r
11558         return;\r
11559     }\r
11560     SendToICS(ics_prefix);\r
11561     SendToICS("revert\n");\r
11562 }\r
11563 \r
11564 void\r
11565 RetractMoveEvent()\r
11566 {\r
11567     switch (gameMode) {\r
11568       case MachinePlaysWhite:\r
11569       case MachinePlaysBlack:\r
11570         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
11571             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
11572             return;\r
11573         }\r
11574         if (forwardMostMove < 2) return;\r
11575         currentMove = forwardMostMove = forwardMostMove - 2;\r
11576         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11577         blackTimeRemaining = timeRemaining[1][currentMove];\r
11578         DisplayBothClocks();\r
11579         DisplayMove(currentMove - 1);\r
11580         ClearHighlights();/*!! could figure this out*/\r
11581         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
11582         SendToProgram("remove\n", &first);\r
11583         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
11584         break;\r
11585 \r
11586       case BeginningOfGame:\r
11587       default:\r
11588         break;\r
11589 \r
11590       case IcsPlayingWhite:\r
11591       case IcsPlayingBlack:\r
11592         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
11593             SendToICS(ics_prefix);\r
11594             SendToICS("takeback 2\n");\r
11595         } else {\r
11596             SendToICS(ics_prefix);\r
11597             SendToICS("takeback 1\n");\r
11598         }\r
11599         break;\r
11600     }\r
11601 }\r
11602 \r
11603 void\r
11604 MoveNowEvent()\r
11605 {\r
11606     ChessProgramState *cps;\r
11607 \r
11608     switch (gameMode) {\r
11609       case MachinePlaysWhite:\r
11610         if (!WhiteOnMove(forwardMostMove)) {\r
11611             DisplayError(_("It is your turn"), 0);\r
11612             return;\r
11613         }\r
11614         cps = &first;\r
11615         break;\r
11616       case MachinePlaysBlack:\r
11617         if (WhiteOnMove(forwardMostMove)) {\r
11618             DisplayError(_("It is your turn"), 0);\r
11619             return;\r
11620         }\r
11621         cps = &first;\r
11622         break;\r
11623       case TwoMachinesPlay:\r
11624         if (WhiteOnMove(forwardMostMove) ==\r
11625             (first.twoMachinesColor[0] == 'w')) {\r
11626             cps = &first;\r
11627         } else {\r
11628             cps = &second;\r
11629         }\r
11630         break;\r
11631       case BeginningOfGame:\r
11632       default:\r
11633         return;\r
11634     }\r
11635     SendToProgram("?\n", cps);\r
11636 }\r
11637 \r
11638 void\r
11639 TruncateGameEvent()\r
11640 {\r
11641     EditGameEvent();\r
11642     if (gameMode != EditGame) return;\r
11643     TruncateGame();\r
11644 }\r
11645 \r
11646 void\r
11647 TruncateGame()\r
11648 {\r
11649     if (forwardMostMove > currentMove) {\r
11650         if (gameInfo.resultDetails != NULL) {\r
11651             free(gameInfo.resultDetails);\r
11652             gameInfo.resultDetails = NULL;\r
11653             gameInfo.result = GameUnfinished;\r
11654         }\r
11655         forwardMostMove = currentMove;\r
11656         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
11657                    currentMove-1);\r
11658     }\r
11659 }\r
11660 \r
11661 void\r
11662 HintEvent()\r
11663 {\r
11664     if (appData.noChessProgram) return;\r
11665     switch (gameMode) {\r
11666       case MachinePlaysWhite:\r
11667         if (WhiteOnMove(forwardMostMove)) {\r
11668             DisplayError(_("Wait until your turn"), 0);\r
11669             return;\r
11670         }\r
11671         break;\r
11672       case BeginningOfGame:\r
11673       case MachinePlaysBlack:\r
11674         if (!WhiteOnMove(forwardMostMove)) {\r
11675             DisplayError(_("Wait until your turn"), 0);\r
11676             return;\r
11677         }\r
11678         break;\r
11679       default:\r
11680         DisplayError(_("No hint available"), 0);\r
11681         return;\r
11682     }\r
11683     SendToProgram("hint\n", &first);\r
11684     hintRequested = TRUE;\r
11685 }\r
11686 \r
11687 void\r
11688 BookEvent()\r
11689 {\r
11690     if (appData.noChessProgram) return;\r
11691     switch (gameMode) {\r
11692       case MachinePlaysWhite:\r
11693         if (WhiteOnMove(forwardMostMove)) {\r
11694             DisplayError(_("Wait until your turn"), 0);\r
11695             return;\r
11696         }\r
11697         break;\r
11698       case BeginningOfGame:\r
11699       case MachinePlaysBlack:\r
11700         if (!WhiteOnMove(forwardMostMove)) {\r
11701             DisplayError(_("Wait until your turn"), 0);\r
11702             return;\r
11703         }\r
11704         break;\r
11705       case EditPosition:\r
11706         EditPositionDone();\r
11707         break;\r
11708       case TwoMachinesPlay:\r
11709         return;\r
11710       default:\r
11711         break;\r
11712     }\r
11713     SendToProgram("bk\n", &first);\r
11714     bookOutput[0] = NULLCHAR;\r
11715     bookRequested = TRUE;\r
11716 }\r
11717 \r
11718 void\r
11719 AboutGameEvent()\r
11720 {\r
11721     char *tags = PGNTags(&gameInfo);\r
11722     TagsPopUp(tags, CmailMsg());\r
11723     free(tags);\r
11724 }\r
11725 \r
11726 /* end button procedures */\r
11727 \r
11728 void\r
11729 PrintPosition(fp, move)\r
11730      FILE *fp;\r
11731      int move;\r
11732 {\r
11733     int i, j;\r
11734     \r
11735     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11736         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11737             char c = PieceToChar(boards[move][i][j]);\r
11738             fputc(c == 'x' ? '.' : c, fp);\r
11739             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
11740         }\r
11741     }\r
11742     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
11743       fprintf(fp, "white to play\n");\r
11744     else\r
11745       fprintf(fp, "black to play\n");\r
11746 }\r
11747 \r
11748 void\r
11749 PrintOpponents(fp)\r
11750      FILE *fp;\r
11751 {\r
11752     if (gameInfo.white != NULL) {\r
11753         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
11754     } else {\r
11755         fprintf(fp, "\n");\r
11756     }\r
11757 }\r
11758 \r
11759 /* Find last component of program's own name, using some heuristics */\r
11760 void\r
11761 TidyProgramName(prog, host, buf)\r
11762      char *prog, *host, buf[MSG_SIZ];\r
11763 {\r
11764     char *p, *q;\r
11765     int local = (strcmp(host, "localhost") == 0);\r
11766     while (!local && (p = strchr(prog, ';')) != NULL) {\r
11767         p++;\r
11768         while (*p == ' ') p++;\r
11769         prog = p;\r
11770     }\r
11771     if (*prog == '"' || *prog == '\'') {\r
11772         q = strchr(prog + 1, *prog);\r
11773     } else {\r
11774         q = strchr(prog, ' ');\r
11775     }\r
11776     if (q == NULL) q = prog + strlen(prog);\r
11777     p = q;\r
11778     while (p >= prog && *p != '/' && *p != '\\') p--;\r
11779     p++;\r
11780     if(p == prog && *p == '"') p++;\r
11781     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
11782     memcpy(buf, p, q - p);\r
11783     buf[q - p] = NULLCHAR;\r
11784     if (!local) {\r
11785         strcat(buf, "@");\r
11786         strcat(buf, host);\r
11787     }\r
11788 }\r
11789 \r
11790 char *\r
11791 TimeControlTagValue()\r
11792 {\r
11793     char buf[MSG_SIZ];\r
11794     if (!appData.clockMode) {\r
11795         strcpy(buf, "-");\r
11796     } else if (movesPerSession > 0) {\r
11797         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
11798     } else if (timeIncrement == 0) {\r
11799         sprintf(buf, "%ld", timeControl/1000);\r
11800     } else {\r
11801         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
11802     }\r
11803     return StrSave(buf);\r
11804 }\r
11805 \r
11806 void\r
11807 SetGameInfo()\r
11808 {\r
11809     /* This routine is used only for certain modes */\r
11810     VariantClass v = gameInfo.variant;\r
11811     ClearGameInfo(&gameInfo);\r
11812     gameInfo.variant = v;\r
11813 \r
11814     switch (gameMode) {\r
11815       case MachinePlaysWhite:\r
11816         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11817         gameInfo.site = StrSave(HostName());\r
11818         gameInfo.date = PGNDate();\r
11819         gameInfo.round = StrSave("-");\r
11820         gameInfo.white = StrSave(first.tidy);\r
11821         gameInfo.black = StrSave(UserName());\r
11822         gameInfo.timeControl = TimeControlTagValue();\r
11823         break;\r
11824 \r
11825       case MachinePlaysBlack:\r
11826         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11827         gameInfo.site = StrSave(HostName());\r
11828         gameInfo.date = PGNDate();\r
11829         gameInfo.round = StrSave("-");\r
11830         gameInfo.white = StrSave(UserName());\r
11831         gameInfo.black = StrSave(first.tidy);\r
11832         gameInfo.timeControl = TimeControlTagValue();\r
11833         break;\r
11834 \r
11835       case TwoMachinesPlay:\r
11836         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11837         gameInfo.site = StrSave(HostName());\r
11838         gameInfo.date = PGNDate();\r
11839         if (matchGame > 0) {\r
11840             char buf[MSG_SIZ];\r
11841             sprintf(buf, "%d", matchGame);\r
11842             gameInfo.round = StrSave(buf);\r
11843         } else {\r
11844             gameInfo.round = StrSave("-");\r
11845         }\r
11846         if (first.twoMachinesColor[0] == 'w') {\r
11847             gameInfo.white = StrSave(first.tidy);\r
11848             gameInfo.black = StrSave(second.tidy);\r
11849         } else {\r
11850             gameInfo.white = StrSave(second.tidy);\r
11851             gameInfo.black = StrSave(first.tidy);\r
11852         }\r
11853         gameInfo.timeControl = TimeControlTagValue();\r
11854         break;\r
11855 \r
11856       case EditGame:\r
11857         gameInfo.event = StrSave("Edited game");\r
11858         gameInfo.site = StrSave(HostName());\r
11859         gameInfo.date = PGNDate();\r
11860         gameInfo.round = StrSave("-");\r
11861         gameInfo.white = StrSave("-");\r
11862         gameInfo.black = StrSave("-");\r
11863         break;\r
11864 \r
11865       case EditPosition:\r
11866         gameInfo.event = StrSave("Edited position");\r
11867         gameInfo.site = StrSave(HostName());\r
11868         gameInfo.date = PGNDate();\r
11869         gameInfo.round = StrSave("-");\r
11870         gameInfo.white = StrSave("-");\r
11871         gameInfo.black = StrSave("-");\r
11872         break;\r
11873 \r
11874       case IcsPlayingWhite:\r
11875       case IcsPlayingBlack:\r
11876       case IcsObserving:\r
11877       case IcsExamining:\r
11878         break;\r
11879 \r
11880       case PlayFromGameFile:\r
11881         gameInfo.event = StrSave("Game from non-PGN file");\r
11882         gameInfo.site = StrSave(HostName());\r
11883         gameInfo.date = PGNDate();\r
11884         gameInfo.round = StrSave("-");\r
11885         gameInfo.white = StrSave("?");\r
11886         gameInfo.black = StrSave("?");\r
11887         break;\r
11888 \r
11889       default:\r
11890         break;\r
11891     }\r
11892 }\r
11893 \r
11894 void\r
11895 ReplaceComment(index, text)\r
11896      int index;\r
11897      char *text;\r
11898 {\r
11899     int len;\r
11900 \r
11901     while (*text == '\n') text++;\r
11902     len = strlen(text);\r
11903     while (len > 0 && text[len - 1] == '\n') len--;\r
11904 \r
11905     if (commentList[index] != NULL)\r
11906       free(commentList[index]);\r
11907 \r
11908     if (len == 0) {\r
11909         commentList[index] = NULL;\r
11910         return;\r
11911     }\r
11912     commentList[index] = (char *) malloc(len + 2);\r
11913     strncpy(commentList[index], text, len);\r
11914     commentList[index][len] = '\n';\r
11915     commentList[index][len + 1] = NULLCHAR;\r
11916 }\r
11917 \r
11918 void\r
11919 CrushCRs(text)\r
11920      char *text;\r
11921 {\r
11922   char *p = text;\r
11923   char *q = text;\r
11924   char ch;\r
11925 \r
11926   do {\r
11927     ch = *p++;\r
11928     if (ch == '\r') continue;\r
11929     *q++ = ch;\r
11930   } while (ch != '\0');\r
11931 }\r
11932 \r
11933 void\r
11934 AppendComment(index, text)\r
11935      int index;\r
11936      char *text;\r
11937 {\r
11938     int oldlen, len;\r
11939     char *old;\r
11940 \r
11941     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11942 \r
11943     CrushCRs(text);\r
11944     while (*text == '\n') text++;\r
11945     len = strlen(text);\r
11946     while (len > 0 && text[len - 1] == '\n') len--;\r
11947 \r
11948     if (len == 0) return;\r
11949 \r
11950     if (commentList[index] != NULL) {\r
11951         old = commentList[index];\r
11952         oldlen = strlen(old);\r
11953         commentList[index] = (char *) malloc(oldlen + len + 2);\r
11954         strcpy(commentList[index], old);\r
11955         free(old);\r
11956         strncpy(&commentList[index][oldlen], text, len);\r
11957         commentList[index][oldlen + len] = '\n';\r
11958         commentList[index][oldlen + len + 1] = NULLCHAR;\r
11959     } else {\r
11960         commentList[index] = (char *) malloc(len + 2);\r
11961         strncpy(commentList[index], text, len);\r
11962         commentList[index][len] = '\n';\r
11963         commentList[index][len + 1] = NULLCHAR;\r
11964     }\r
11965 }\r
11966 \r
11967 static char * FindStr( char * text, char * sub_text )\r
11968 {\r
11969     char * result = strstr( text, sub_text );\r
11970 \r
11971     if( result != NULL ) {\r
11972         result += strlen( sub_text );\r
11973     }\r
11974 \r
11975     return result;\r
11976 }\r
11977 \r
11978 /* [AS] Try to extract PV info from PGN comment */\r
11979 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
11980 char *GetInfoFromComment( int index, char * text )\r
11981 {\r
11982     char * sep = text;\r
11983 \r
11984     if( text != NULL && index > 0 ) {\r
11985         int score = 0;\r
11986         int depth = 0;\r
11987         int time = -1, sec = 0, deci;\r
11988         char * s_eval = FindStr( text, "[%eval " );\r
11989         char * s_emt = FindStr( text, "[%emt " );\r
11990 \r
11991         if( s_eval != NULL || s_emt != NULL ) {\r
11992             /* New style */\r
11993             char delim;\r
11994 \r
11995             if( s_eval != NULL ) {\r
11996                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
11997                     return text;\r
11998                 }\r
11999 \r
12000                 if( delim != ']' ) {\r
12001                     return text;\r
12002                 }\r
12003             }\r
12004 \r
12005             if( s_emt != NULL ) {\r
12006             }\r
12007         }\r
12008         else {\r
12009             /* We expect something like: [+|-]nnn.nn/dd */\r
12010             int score_lo = 0;\r
12011 \r
12012             sep = strchr( text, '/' );\r
12013             if( sep == NULL || sep < (text+4) ) {\r
12014                 return text;\r
12015             }\r
12016 \r
12017             time = -1; sec = -1; deci = -1;\r
12018             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
12019                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&\r
12020                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
12021                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
12022                 return text;\r
12023             }\r
12024 \r
12025             if( score_lo < 0 || score_lo >= 100 ) {\r
12026                 return text;\r
12027             }\r
12028 \r
12029             if(sec >= 0) time = 600*time + 10*sec; else\r
12030             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec\r
12031 \r
12032             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
12033 \r
12034             /* [HGM] PV time: now locate end of PV info */\r
12035             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
12036             if(time >= 0)\r
12037             while( *++sep >= '0' && *sep <= '9'); // strip time\r
12038             if(sec >= 0)\r
12039             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
12040             if(deci >= 0)\r
12041             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds\r
12042             while(*sep == ' ') sep++;\r
12043         }\r
12044 \r
12045         if( depth <= 0 ) {\r
12046             return text;\r
12047         }\r
12048 \r
12049         if( time < 0 ) {\r
12050             time = -1;\r
12051         }\r
12052 \r
12053         pvInfoList[index-1].depth = depth;\r
12054         pvInfoList[index-1].score = score;\r
12055         pvInfoList[index-1].time  = 10*time; // centi-sec\r
12056     }\r
12057     return sep;\r
12058 }\r
12059 \r
12060 void\r
12061 SendToProgram(message, cps)\r
12062      char *message;\r
12063      ChessProgramState *cps;\r
12064 {\r
12065     int count, outCount, error;\r
12066     char buf[MSG_SIZ];\r
12067 \r
12068     if (cps->pr == NULL) return;\r
12069     Attention(cps);\r
12070     \r
12071     if (appData.debugMode) {\r
12072         TimeMark now;\r
12073         GetTimeMark(&now);\r
12074         fprintf(debugFP, "%ld >%-6s: %s", \r
12075                 SubtractTimeMarks(&now, &programStartTime),\r
12076                 cps->which, message);\r
12077     }\r
12078     \r
12079     count = strlen(message);\r
12080     outCount = OutputToProcess(cps->pr, message, count, &error);\r
12081     if (outCount < count && !exiting \r
12082                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
12083         sprintf(buf, _("Error writing to %s chess program"), cps->which);\r
12084         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12085             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12086                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12087                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
12088             } else {\r
12089                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12090             }\r
12091             gameInfo.resultDetails = buf;\r
12092         }\r
12093         DisplayFatalError(buf, error, 1);\r
12094     }\r
12095 }\r
12096 \r
12097 void\r
12098 ReceiveFromProgram(isr, closure, message, count, error)\r
12099      InputSourceRef isr;\r
12100      VOIDSTAR closure;\r
12101      char *message;\r
12102      int count;\r
12103      int error;\r
12104 {\r
12105     char *end_str;\r
12106     char buf[MSG_SIZ];\r
12107     ChessProgramState *cps = (ChessProgramState *)closure;\r
12108 \r
12109     if (isr != cps->isr) return; /* Killed intentionally */\r
12110     if (count <= 0) {\r
12111         if (count == 0) {\r
12112             sprintf(buf,\r
12113                     _("Error: %s chess program (%s) exited unexpectedly"),\r
12114                     cps->which, cps->program);\r
12115         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12116                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12117                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12118                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);\r
12119                 } else {\r
12120                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12121                 }\r
12122                 gameInfo.resultDetails = buf;\r
12123             }\r
12124             RemoveInputSource(cps->isr);\r
12125             DisplayFatalError(buf, 0, 1);\r
12126         } else {\r
12127             sprintf(buf,\r
12128                     _("Error reading from %s chess program (%s)"),\r
12129                     cps->which, cps->program);\r
12130             RemoveInputSource(cps->isr);\r
12131 \r
12132             /* [AS] Program is misbehaving badly... kill it */\r
12133             if( count == -2 ) {\r
12134                 DestroyChildProcess( cps->pr, 9 );\r
12135                 cps->pr = NoProc;\r
12136             }\r
12137 \r
12138             DisplayFatalError(buf, error, 1);\r
12139         }\r
12140         return;\r
12141     }\r
12142     \r
12143     if ((end_str = strchr(message, '\r')) != NULL)\r
12144       *end_str = NULLCHAR;\r
12145     if ((end_str = strchr(message, '\n')) != NULL)\r
12146       *end_str = NULLCHAR;\r
12147     \r
12148     if (appData.debugMode) {\r
12149         TimeMark now; int print = 1;\r
12150         char *quote = ""; char c; int i;\r
12151 \r
12152         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
12153                 char start = message[0];\r
12154                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
12155                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
12156                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&\r
12157                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
12158                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
12159                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&\r
12160                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
12161                         { quote = "# "; print = (appData.engineComments == 2); }\r
12162                 message[0] = start; // restore original message\r
12163         }\r
12164         if(print) {\r
12165                 GetTimeMark(&now);\r
12166                 fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
12167                         SubtractTimeMarks(&now, &programStartTime), cps->which, \r
12168                         quote,\r
12169                         message);\r
12170         }\r
12171     }\r
12172 \r
12173     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */\r
12174     if (appData.icsEngineAnalyze) {\r
12175         if (strstr(message, "whisper") != NULL ||\r
12176              strstr(message, "kibitz") != NULL || \r
12177             strstr(message, "tellics") != NULL) return;\r
12178     }\r
12179 \r
12180     HandleMachineMove(message, cps);\r
12181 }\r
12182 \r
12183 \r
12184 void\r
12185 SendTimeControl(cps, mps, tc, inc, sd, st)\r
12186      ChessProgramState *cps;\r
12187      int mps, inc, sd, st;\r
12188      long tc;\r
12189 {\r
12190     char buf[MSG_SIZ];\r
12191     int seconds;\r
12192 \r
12193     if( timeControl_2 > 0 ) {\r
12194         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
12195             tc = timeControl_2;\r
12196         }\r
12197     }\r
12198     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
12199     inc /= cps->timeOdds;\r
12200     st  /= cps->timeOdds;\r
12201 \r
12202     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
12203 \r
12204     if (st > 0) {\r
12205       /* Set exact time per move, normally using st command */\r
12206       if (cps->stKludge) {\r
12207         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
12208         seconds = st % 60;\r
12209         if (seconds == 0) {\r
12210           sprintf(buf, "level 1 %d\n", st/60);\r
12211         } else {\r
12212           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
12213         }\r
12214       } else {\r
12215         sprintf(buf, "st %d\n", st);\r
12216       }\r
12217     } else {\r
12218       /* Set conventional or incremental time control, using level command */\r
12219       if (seconds == 0) {\r
12220         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
12221            Fixed in later versions, but still avoid :seconds\r
12222            when seconds is 0. */\r
12223         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
12224       } else {\r
12225         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
12226                 seconds, inc/1000);\r
12227       }\r
12228     }\r
12229     SendToProgram(buf, cps);\r
12230 \r
12231     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
12232     /* Orthogonally, limit search to given depth */\r
12233     if (sd > 0) {\r
12234       if (cps->sdKludge) {\r
12235         sprintf(buf, "depth\n%d\n", sd);\r
12236       } else {\r
12237         sprintf(buf, "sd %d\n", sd);\r
12238       }\r
12239       SendToProgram(buf, cps);\r
12240     }\r
12241 \r
12242     if(cps->nps > 0) { /* [HGM] nps */\r
12243         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
12244         else {\r
12245                 sprintf(buf, "nps %d\n", cps->nps);\r
12246               SendToProgram(buf, cps);\r
12247         }\r
12248     }\r
12249 }\r
12250 \r
12251 ChessProgramState *WhitePlayer()\r
12252 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
12253 {\r
12254     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || \r
12255        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)\r
12256         return &second;\r
12257     return &first;\r
12258 }\r
12259 \r
12260 void\r
12261 SendTimeRemaining(cps, machineWhite)\r
12262      ChessProgramState *cps;\r
12263      int /*boolean*/ machineWhite;\r
12264 {\r
12265     char message[MSG_SIZ];\r
12266     long time, otime;\r
12267 \r
12268     /* Note: this routine must be called when the clocks are stopped\r
12269        or when they have *just* been set or switched; otherwise\r
12270        it will be off by the time since the current tick started.\r
12271     */\r
12272     if (machineWhite) {\r
12273         time = whiteTimeRemaining / 10;\r
12274         otime = blackTimeRemaining / 10;\r
12275     } else {\r
12276         time = blackTimeRemaining / 10;\r
12277         otime = whiteTimeRemaining / 10;\r
12278     }\r
12279     /* [HGM] translate opponent's time by time-odds factor */\r
12280     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
12281     if (appData.debugMode) {\r
12282         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
12283     }\r
12284 \r
12285     if (time <= 0) time = 1;\r
12286     if (otime <= 0) otime = 1;\r
12287     \r
12288     sprintf(message, "time %ld\n", time);\r
12289     SendToProgram(message, cps);\r
12290 \r
12291     sprintf(message, "otim %ld\n", otime);\r
12292     SendToProgram(message, cps);\r
12293 }\r
12294 \r
12295 int\r
12296 BoolFeature(p, name, loc, cps)\r
12297      char **p;\r
12298      char *name;\r
12299      int *loc;\r
12300      ChessProgramState *cps;\r
12301 {\r
12302   char buf[MSG_SIZ];\r
12303   int len = strlen(name);\r
12304   int val;\r
12305   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12306     (*p) += len + 1;\r
12307     sscanf(*p, "%d", &val);\r
12308     *loc = (val != 0);\r
12309     while (**p && **p != ' ') (*p)++;\r
12310     sprintf(buf, "accepted %s\n", name);\r
12311     SendToProgram(buf, cps);\r
12312     return TRUE;\r
12313   }\r
12314   return FALSE;\r
12315 }\r
12316 \r
12317 int\r
12318 IntFeature(p, name, loc, cps)\r
12319      char **p;\r
12320      char *name;\r
12321      int *loc;\r
12322      ChessProgramState *cps;\r
12323 {\r
12324   char buf[MSG_SIZ];\r
12325   int len = strlen(name);\r
12326   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12327     (*p) += len + 1;\r
12328     sscanf(*p, "%d", loc);\r
12329     while (**p && **p != ' ') (*p)++;\r
12330     sprintf(buf, "accepted %s\n", name);\r
12331     SendToProgram(buf, cps);\r
12332     return TRUE;\r
12333   }\r
12334   return FALSE;\r
12335 }\r
12336 \r
12337 int\r
12338 StringFeature(p, name, loc, cps)\r
12339      char **p;\r
12340      char *name;\r
12341      char loc[];\r
12342      ChessProgramState *cps;\r
12343 {\r
12344   char buf[MSG_SIZ];\r
12345   int len = strlen(name);\r
12346   if (strncmp((*p), name, len) == 0\r
12347       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
12348     (*p) += len + 2;\r
12349     sscanf(*p, "%[^\"]", loc);\r
12350     while (**p && **p != '\"') (*p)++;\r
12351     if (**p == '\"') (*p)++;\r
12352     sprintf(buf, "accepted %s\n", name);\r
12353     SendToProgram(buf, cps);\r
12354     return TRUE;\r
12355   }\r
12356   return FALSE;\r
12357 }\r
12358 \r
12359 int \r
12360 ParseOption(Option *opt, ChessProgramState *cps)\r
12361 // [HGM] options: process the string that defines an engine option, and determine\r
12362 // name, type, default value, and allowed value range\r
12363 {\r
12364         char *p, *q, buf[MSG_SIZ];\r
12365         int n, min = (-1)<<31, max = 1<<31, def;\r
12366 \r
12367         if(p = strstr(opt->name, " -spin ")) {\r
12368             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;\r
12369             if(max < min) max = min; // enforce consistency\r
12370             if(def < min) def = min;\r
12371             if(def > max) def = max;\r
12372             opt->value = def;\r
12373             opt->min = min;\r
12374             opt->max = max;\r
12375             opt->type = Spin;\r
12376         } else if(p = strstr(opt->name, " -string ")) {\r
12377             opt->textValue = p+9;\r
12378             opt->type = TextBox;\r
12379         } else if(p = strstr(opt->name, " -check ")) {\r
12380             if(sscanf(p, " -check %d", &def) < 1) return FALSE;\r
12381             opt->value = (def != 0);\r
12382             opt->type = CheckBox;\r
12383         } else if(p = strstr(opt->name, " -combo ")) {\r
12384             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type\r
12385             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices\r
12386             opt->value = n = 0;\r
12387             while(q = StrStr(q, " /// ")) {\r
12388                 n++; *q = 0;    // count choices, and null-terminate each of them\r
12389                 q += 5;\r
12390                 if(*q == '*') { // remember default, which is marked with * prefix\r
12391                     q++;\r
12392                     opt->value = n;\r
12393                 }\r
12394                 cps->comboList[cps->comboCnt++] = q;\r
12395             }\r
12396             cps->comboList[cps->comboCnt++] = NULL;\r
12397             opt->max = n + 1;\r
12398             opt->type = ComboBox;\r
12399         } else if(p = strstr(opt->name, " -button")) {\r
12400             opt->type = Button;\r
12401         } else if(p = strstr(opt->name, " -save")) {\r
12402             opt->type = SaveButton;\r
12403         } else return FALSE;\r
12404         *p = 0; // terminate option name\r
12405         // now look if the command-line options define a setting for this engine option.\r
12406         if(cps->optionSettings && cps->optionSettings[0])\r
12407             p = strstr(cps->optionSettings, opt->name); else p = NULL;\r
12408         if(p && (p == cps->optionSettings || p[-1] == ',')) {\r
12409                 sprintf(buf, "option %s", p);\r
12410                 if(p = strstr(buf, ",")) *p = 0;\r
12411                 strcat(buf, "\n");\r
12412                 SendToProgram(buf, cps);\r
12413         }\r
12414         return TRUE;\r
12415 }\r
12416 \r
12417 void\r
12418 FeatureDone(cps, val)\r
12419      ChessProgramState* cps;\r
12420      int val;\r
12421 {\r
12422   DelayedEventCallback cb = GetDelayedEvent();\r
12423   if ((cb == InitBackEnd3 && cps == &first) ||\r
12424       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
12425     CancelDelayedEvent();\r
12426     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
12427   }\r
12428   cps->initDone = val;\r
12429 }\r
12430 \r
12431 /* Parse feature command from engine */\r
12432 void\r
12433 ParseFeatures(args, cps)\r
12434      char* args;\r
12435      ChessProgramState *cps;  \r
12436 {\r
12437   char *p = args;\r
12438   char *q;\r
12439   int val;\r
12440   char buf[MSG_SIZ];\r
12441 \r
12442   for (;;) {\r
12443     while (*p == ' ') p++;\r
12444     if (*p == NULLCHAR) return;\r
12445 \r
12446     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
12447     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
12448     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
12449     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
12450     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
12451     if (BoolFeature(&p, "reuse", &val, cps)) {\r
12452       /* Engine can disable reuse, but can't enable it if user said no */\r
12453       if (!val) cps->reuse = FALSE;\r
12454       continue;\r
12455     }\r
12456     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
12457     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
12458       if (gameMode == TwoMachinesPlay) {\r
12459         DisplayTwoMachinesTitle();\r
12460       } else {\r
12461         DisplayTitle("");\r
12462       }\r
12463       continue;\r
12464     }\r
12465     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
12466     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
12467     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
12468     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
12469     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
12470     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
12471     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
12472     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
12473     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
12474     if (IntFeature(&p, "done", &val, cps)) {\r
12475       FeatureDone(cps, val);\r
12476       continue;\r
12477     }\r
12478     /* Added by Tord: */\r
12479     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
12480     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
12481     /* End of additions by Tord */\r
12482 \r
12483     /* [HGM] added features: */\r
12484     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
12485     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
12486     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
12487     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
12488     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12489     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
12490     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {\r
12491         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature\r
12492         if(cps->nrOptions >= MAX_OPTIONS) {\r
12493             cps->nrOptions--;\r
12494             sprintf(buf, "%s engine has too many options\n", cps->which);\r
12495             DisplayError(buf, 0);\r
12496         }\r
12497         continue;\r
12498     }\r
12499     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12500     /* End of additions by HGM */\r
12501 \r
12502     /* unknown feature: complain and skip */\r
12503     q = p;\r
12504     while (*q && *q != '=') q++;\r
12505     sprintf(buf, "rejected %.*s\n", q-p, p);\r
12506     SendToProgram(buf, cps);\r
12507     p = q;\r
12508     if (*p == '=') {\r
12509       p++;\r
12510       if (*p == '\"') {\r
12511         p++;\r
12512         while (*p && *p != '\"') p++;\r
12513         if (*p == '\"') p++;\r
12514       } else {\r
12515         while (*p && *p != ' ') p++;\r
12516       }\r
12517     }\r
12518   }\r
12519 \r
12520 }\r
12521 \r
12522 void\r
12523 PeriodicUpdatesEvent(newState)\r
12524      int newState;\r
12525 {\r
12526     if (newState == appData.periodicUpdates)\r
12527       return;\r
12528 \r
12529     appData.periodicUpdates=newState;\r
12530 \r
12531     /* Display type changes, so update it now */\r
12532     DisplayAnalysis();\r
12533 \r
12534     /* Get the ball rolling again... */\r
12535     if (newState) {\r
12536         AnalysisPeriodicEvent(1);\r
12537         StartAnalysisClock();\r
12538     }\r
12539 }\r
12540 \r
12541 void\r
12542 PonderNextMoveEvent(newState)\r
12543      int newState;\r
12544 {\r
12545     if (newState == appData.ponderNextMove) return;\r
12546     if (gameMode == EditPosition) EditPositionDone();\r
12547     if (newState) {\r
12548         SendToProgram("hard\n", &first);\r
12549         if (gameMode == TwoMachinesPlay) {\r
12550             SendToProgram("hard\n", &second);\r
12551         }\r
12552     } else {\r
12553         SendToProgram("easy\n", &first);\r
12554         thinkOutput[0] = NULLCHAR;\r
12555         if (gameMode == TwoMachinesPlay) {\r
12556             SendToProgram("easy\n", &second);\r
12557         }\r
12558     }\r
12559     appData.ponderNextMove = newState;\r
12560 }\r
12561 \r
12562 void\r
12563 NewSettingEvent(option, command, value)\r
12564      char *command;\r
12565      int option, value;\r
12566 {\r
12567     char buf[MSG_SIZ];\r
12568 \r
12569     if (gameMode == EditPosition) EditPositionDone();\r
12570     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);\r
12571     SendToProgram(buf, &first);\r
12572     if (gameMode == TwoMachinesPlay) {\r
12573         SendToProgram(buf, &second);\r
12574     }\r
12575 }\r
12576 \r
12577 void\r
12578 ShowThinkingEvent()\r
12579 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup\r
12580 {\r
12581     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated\r
12582     int newState = appData.showThinking\r
12583         // [HGM] thinking: other features now need thinking output as well\r
12584         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();\r
12585     \r
12586     if (oldState == newState) return;\r
12587     oldState = newState;\r
12588     if (gameMode == EditPosition) EditPositionDone();\r
12589     if (oldState) {\r
12590         SendToProgram("post\n", &first);\r
12591         if (gameMode == TwoMachinesPlay) {\r
12592             SendToProgram("post\n", &second);\r
12593         }\r
12594     } else {\r
12595         SendToProgram("nopost\n", &first);\r
12596         thinkOutput[0] = NULLCHAR;\r
12597         if (gameMode == TwoMachinesPlay) {\r
12598             SendToProgram("nopost\n", &second);\r
12599         }\r
12600     }\r
12601 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!\r
12602 }\r
12603 \r
12604 void\r
12605 AskQuestionEvent(title, question, replyPrefix, which)\r
12606      char *title; char *question; char *replyPrefix; char *which;\r
12607 {\r
12608   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
12609   if (pr == NoProc) return;\r
12610   AskQuestion(title, question, replyPrefix, pr);\r
12611 }\r
12612 \r
12613 void\r
12614 DisplayMove(moveNumber)\r
12615      int moveNumber;\r
12616 {\r
12617     char message[MSG_SIZ];\r
12618     char res[MSG_SIZ];\r
12619     char cpThinkOutput[MSG_SIZ];\r
12620 \r
12621     if(appData.noGUI) return; // [HGM] fast: suppress display of moves\r
12622     \r
12623     if (moveNumber == forwardMostMove - 1 || \r
12624         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12625 \r
12626         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
12627 \r
12628         if (strchr(cpThinkOutput, '\n')) {\r
12629             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
12630         }\r
12631     } else {\r
12632         *cpThinkOutput = NULLCHAR;\r
12633     }\r
12634 \r
12635     /* [AS] Hide thinking from human user */\r
12636     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
12637         *cpThinkOutput = NULLCHAR;\r
12638         if( thinkOutput[0] != NULLCHAR ) {\r
12639             int i;\r
12640 \r
12641             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
12642                 cpThinkOutput[i] = '.';\r
12643             }\r
12644             cpThinkOutput[i] = NULLCHAR;\r
12645             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
12646         }\r
12647     }\r
12648 \r
12649     if (moveNumber == forwardMostMove - 1 &&\r
12650         gameInfo.resultDetails != NULL) {\r
12651         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
12652             sprintf(res, " %s", PGNResult(gameInfo.result));\r
12653         } else {\r
12654             sprintf(res, " {%s} %s",\r
12655                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
12656         }\r
12657     } else {\r
12658         res[0] = NULLCHAR;\r
12659     }\r
12660 \r
12661     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12662         DisplayMessage(res, cpThinkOutput);\r
12663     } else {\r
12664         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
12665                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
12666                 parseList[moveNumber], res);\r
12667         DisplayMessage(message, cpThinkOutput);\r
12668     }\r
12669 }\r
12670 \r
12671 void\r
12672 DisplayAnalysisText(text)\r
12673      char *text;\r
12674 {\r
12675     char buf[MSG_SIZ];\r
12676 \r
12677     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile \r
12678                || appData.icsEngineAnalyze) {\r
12679         sprintf(buf, "Analysis (%s)", first.tidy);\r
12680         AnalysisPopUp(buf, text);\r
12681     }\r
12682 }\r
12683 \r
12684 static int\r
12685 only_one_move(str)\r
12686      char *str;\r
12687 {\r
12688     while (*str && isspace(*str)) ++str;\r
12689     while (*str && !isspace(*str)) ++str;\r
12690     if (!*str) return 1;\r
12691     while (*str && isspace(*str)) ++str;\r
12692     if (!*str) return 1;\r
12693     return 0;\r
12694 }\r
12695 \r
12696 void\r
12697 DisplayAnalysis()\r
12698 {\r
12699     char buf[MSG_SIZ];\r
12700     char lst[MSG_SIZ / 2];\r
12701     double nps;\r
12702     static char *xtra[] = { "", " (--)", " (++)" };\r
12703     int h, m, s, cs;\r
12704   \r
12705     if (programStats.time == 0) {\r
12706         programStats.time = 1;\r
12707     }\r
12708   \r
12709     if (programStats.got_only_move) {\r
12710         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
12711     } else {\r
12712         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
12713 \r
12714         nps = (u64ToDouble(programStats.nodes) /\r
12715              ((double)programStats.time /100.0));\r
12716 \r
12717         cs = programStats.time % 100;\r
12718         s = programStats.time / 100;\r
12719         h = (s / (60*60));\r
12720         s = s - h*60*60;\r
12721         m = (s/60);\r
12722         s = s - m*60;\r
12723 \r
12724         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
12725           if (programStats.move_name[0] != NULLCHAR) {\r
12726             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12727                     programStats.depth,\r
12728                     programStats.nr_moves-programStats.moves_left,\r
12729                     programStats.nr_moves, programStats.move_name,\r
12730                     ((float)programStats.score)/100.0, lst,\r
12731                     only_one_move(lst)?\r
12732                     xtra[programStats.got_fail] : "",\r
12733                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12734           } else {\r
12735             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12736                     programStats.depth,\r
12737                     programStats.nr_moves-programStats.moves_left,\r
12738                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
12739                     lst,\r
12740                     only_one_move(lst)?\r
12741                     xtra[programStats.got_fail] : "",\r
12742                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12743           }\r
12744         } else {\r
12745             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12746                     programStats.depth,\r
12747                     ((float)programStats.score)/100.0,\r
12748                     lst,\r
12749                     only_one_move(lst)?\r
12750                     xtra[programStats.got_fail] : "",\r
12751                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12752         }\r
12753     }\r
12754     DisplayAnalysisText(buf);\r
12755 }\r
12756 \r
12757 void\r
12758 DisplayComment(moveNumber, text)\r
12759      int moveNumber;\r
12760      char *text;\r
12761 {\r
12762     char title[MSG_SIZ];\r
12763     char buf[8000]; // comment can be long!\r
12764     int score, depth;\r
12765 \r
12766     if( appData.autoDisplayComment ) {\r
12767         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12768             strcpy(title, "Comment");\r
12769         } else {\r
12770             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
12771                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
12772                     parseList[moveNumber]);\r
12773         }\r
12774         // [HGM] PV info: display PV info together with (or as) comment\r
12775         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
12776             if(text == NULL) text = "";                                           \r
12777             score = pvInfoList[moveNumber].score;\r
12778             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
12779                               depth, (pvInfoList[moveNumber].time+50)/100, text);\r
12780             text = buf;\r
12781         }\r
12782     } else title[0] = 0;\r
12783 \r
12784     if (text != NULL)\r
12785         CommentPopUp(title, text);\r
12786 }\r
12787 \r
12788 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
12789  * might be busy thinking or pondering.  It can be omitted if your\r
12790  * gnuchess is configured to stop thinking immediately on any user\r
12791  * input.  However, that gnuchess feature depends on the FIONREAD\r
12792  * ioctl, which does not work properly on some flavors of Unix.\r
12793  */\r
12794 void\r
12795 Attention(cps)\r
12796      ChessProgramState *cps;\r
12797 {\r
12798 #if ATTENTION\r
12799     if (!cps->useSigint) return;\r
12800     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
12801     switch (gameMode) {\r
12802       case MachinePlaysWhite:\r
12803       case MachinePlaysBlack:\r
12804       case TwoMachinesPlay:\r
12805       case IcsPlayingWhite:\r
12806       case IcsPlayingBlack:\r
12807       case AnalyzeMode:\r
12808       case AnalyzeFile:\r
12809         /* Skip if we know it isn't thinking */\r
12810         if (!cps->maybeThinking) return;\r
12811         if (appData.debugMode)\r
12812           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
12813         InterruptChildProcess(cps->pr);\r
12814         cps->maybeThinking = FALSE;\r
12815         break;\r
12816       default:\r
12817         break;\r
12818     }\r
12819 #endif /*ATTENTION*/\r
12820 }\r
12821 \r
12822 int\r
12823 CheckFlags()\r
12824 {\r
12825     if (whiteTimeRemaining <= 0) {\r
12826         if (!whiteFlag) {\r
12827             whiteFlag = TRUE;\r
12828             if (appData.icsActive) {\r
12829                 if (appData.autoCallFlag &&\r
12830                     gameMode == IcsPlayingBlack && !blackFlag) {\r
12831                   SendToICS(ics_prefix);\r
12832                   SendToICS("flag\n");\r
12833                 }\r
12834             } else {\r
12835                 if (blackFlag) {\r
12836                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12837                 } else {\r
12838                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));\r
12839                     if (appData.autoCallFlag) {\r
12840                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
12841                         return TRUE;\r
12842                     }\r
12843                 }\r
12844             }\r
12845         }\r
12846     }\r
12847     if (blackTimeRemaining <= 0) {\r
12848         if (!blackFlag) {\r
12849             blackFlag = TRUE;\r
12850             if (appData.icsActive) {\r
12851                 if (appData.autoCallFlag &&\r
12852                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
12853                   SendToICS(ics_prefix);\r
12854                   SendToICS("flag\n");\r
12855                 }\r
12856             } else {\r
12857                 if (whiteFlag) {\r
12858                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12859                 } else {\r
12860                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));\r
12861                     if (appData.autoCallFlag) {\r
12862                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
12863                         return TRUE;\r
12864                     }\r
12865                 }\r
12866             }\r
12867         }\r
12868     }\r
12869     return FALSE;\r
12870 }\r
12871 \r
12872 void\r
12873 CheckTimeControl()\r
12874 {\r
12875     if (!appData.clockMode || appData.icsActive ||\r
12876         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
12877 \r
12878     /*\r
12879      * add time to clocks when time control is achieved ([HGM] now also used for increment)\r
12880      */\r
12881     if ( !WhiteOnMove(forwardMostMove) )\r
12882         /* White made time control */\r
12883         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12884         /* [HGM] time odds: correct new time quota for time odds! */\r
12885                                             / WhitePlayer()->timeOdds;\r
12886       else\r
12887         /* Black made time control */\r
12888         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12889                                             / WhitePlayer()->other->timeOdds;\r
12890 }\r
12891 \r
12892 void\r
12893 DisplayBothClocks()\r
12894 {\r
12895     int wom = gameMode == EditPosition ?\r
12896       !blackPlaysFirst : WhiteOnMove(currentMove);\r
12897     DisplayWhiteClock(whiteTimeRemaining, wom);\r
12898     DisplayBlackClock(blackTimeRemaining, !wom);\r
12899 }\r
12900 \r
12901 \r
12902 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
12903    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
12904    to use other calls if you don't.  Clocks will be less accurate if\r
12905    you have neither ftime nor gettimeofday.\r
12906 */\r
12907 \r
12908 /* VS 2008 requires the #include outside of the function */\r
12909 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME\r
12910 #include <sys/timeb.h>\r
12911 #endif\r
12912 \r
12913 /* Get the current time as a TimeMark */\r
12914 void\r
12915 GetTimeMark(tm)\r
12916      TimeMark *tm;\r
12917 {\r
12918 #if HAVE_GETTIMEOFDAY\r
12919 \r
12920     struct timeval timeVal;\r
12921     struct timezone timeZone;\r
12922 \r
12923     gettimeofday(&timeVal, &timeZone);\r
12924     tm->sec = (long) timeVal.tv_sec; \r
12925     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
12926 \r
12927 #else /*!HAVE_GETTIMEOFDAY*/\r
12928 #if HAVE_FTIME\r
12929 \r
12930 // include <sys/timeb.h> / moved to just above start of function\r
12931     struct timeb timeB;\r
12932 \r
12933     ftime(&timeB);\r
12934     tm->sec = (long) timeB.time;\r
12935     tm->ms = (int) timeB.millitm;\r
12936 \r
12937 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
12938     tm->sec = (long) time(NULL);\r
12939     tm->ms = 0;\r
12940 #endif\r
12941 #endif\r
12942 }\r
12943 \r
12944 /* Return the difference in milliseconds between two\r
12945    time marks.  We assume the difference will fit in a long!\r
12946 */\r
12947 long\r
12948 SubtractTimeMarks(tm2, tm1)\r
12949      TimeMark *tm2, *tm1;\r
12950 {\r
12951     return 1000L*(tm2->sec - tm1->sec) +\r
12952            (long) (tm2->ms - tm1->ms);\r
12953 }\r
12954 \r
12955 \r
12956 /*\r
12957  * Code to manage the game clocks.\r
12958  *\r
12959  * In tournament play, black starts the clock and then white makes a move.\r
12960  * We give the human user a slight advantage if he is playing white---the\r
12961  * clocks don't run until he makes his first move, so it takes zero time.\r
12962  * Also, we don't account for network lag, so we could get out of sync\r
12963  * with GNU Chess's clock -- but then, referees are always right.  \r
12964  */\r
12965 \r
12966 static TimeMark tickStartTM;\r
12967 static long intendedTickLength;\r
12968 \r
12969 long\r
12970 NextTickLength(timeRemaining)\r
12971      long timeRemaining;\r
12972 {\r
12973     long nominalTickLength, nextTickLength;\r
12974 \r
12975     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
12976       nominalTickLength = 100L;\r
12977     else\r
12978       nominalTickLength = 1000L;\r
12979     nextTickLength = timeRemaining % nominalTickLength;\r
12980     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
12981 \r
12982     return nextTickLength;\r
12983 }\r
12984 \r
12985 /* Adjust clock one minute up or down */\r
12986 void\r
12987 AdjustClock(Boolean which, int dir)\r
12988 {\r
12989     if(which) blackTimeRemaining += 60000*dir;\r
12990     else      whiteTimeRemaining += 60000*dir;\r
12991     DisplayBothClocks();\r
12992 }\r
12993 \r
12994 /* Stop clocks and reset to a fresh time control */\r
12995 void\r
12996 ResetClocks() \r
12997 {\r
12998     (void) StopClockTimer();\r
12999     if (appData.icsActive) {\r
13000         whiteTimeRemaining = blackTimeRemaining = 0;\r
13001     } else { /* [HGM] correct new time quote for time odds */\r
13002         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
13003         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
13004     }\r
13005     if (whiteFlag || blackFlag) {\r
13006         DisplayTitle("");\r
13007         whiteFlag = blackFlag = FALSE;\r
13008     }\r
13009     DisplayBothClocks();\r
13010 }\r
13011 \r
13012 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
13013 \r
13014 /* Decrement running clock by amount of time that has passed */\r
13015 void\r
13016 DecrementClocks()\r
13017 {\r
13018     long timeRemaining;\r
13019     long lastTickLength, fudge;\r
13020     TimeMark now;\r
13021 \r
13022     if (!appData.clockMode) return;\r
13023     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
13024         \r
13025     GetTimeMark(&now);\r
13026 \r
13027     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13028 \r
13029     /* Fudge if we woke up a little too soon */\r
13030     fudge = intendedTickLength - lastTickLength;\r
13031     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
13032 \r
13033     if (WhiteOnMove(forwardMostMove)) {\r
13034         if(whiteNPS >= 0) lastTickLength = 0;\r
13035         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
13036         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
13037                           WhiteOnMove(currentMove));\r
13038     } else {\r
13039         if(blackNPS >= 0) lastTickLength = 0;\r
13040         timeRemaining = blackTimeRemaining -= lastTickLength;\r
13041         DisplayBlackClock(blackTimeRemaining - fudge,\r
13042                           !WhiteOnMove(currentMove));\r
13043     }\r
13044 \r
13045     if (CheckFlags()) return;\r
13046         \r
13047     tickStartTM = now;\r
13048     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
13049     StartClockTimer(intendedTickLength);\r
13050 \r
13051     /* if the time remaining has fallen below the alarm threshold, sound the\r
13052      * alarm. if the alarm has sounded and (due to a takeback or time control\r
13053      * with increment) the time remaining has increased to a level above the\r
13054      * threshold, reset the alarm so it can sound again. \r
13055      */\r
13056     \r
13057     if (appData.icsActive && appData.icsAlarm) {\r
13058 \r
13059         /* make sure we are dealing with the user's clock */\r
13060         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
13061                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
13062            )) return;\r
13063 \r
13064         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
13065             alarmSounded = FALSE;\r
13066         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
13067             PlayAlarmSound();\r
13068             alarmSounded = TRUE;\r
13069         }\r
13070     }\r
13071 }\r
13072 \r
13073 \r
13074 /* A player has just moved, so stop the previously running\r
13075    clock and (if in clock mode) start the other one.\r
13076    We redisplay both clocks in case we're in ICS mode, because\r
13077    ICS gives us an update to both clocks after every move.\r
13078    Note that this routine is called *after* forwardMostMove\r
13079    is updated, so the last fractional tick must be subtracted\r
13080    from the color that is *not* on move now.\r
13081 */\r
13082 void\r
13083 SwitchClocks()\r
13084 {\r
13085     long lastTickLength;\r
13086     TimeMark now;\r
13087     int flagged = FALSE;\r
13088 \r
13089     GetTimeMark(&now);\r
13090 \r
13091     if (StopClockTimer() && appData.clockMode) {\r
13092         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13093         if (WhiteOnMove(forwardMostMove)) {\r
13094             if(blackNPS >= 0) lastTickLength = 0;\r
13095             blackTimeRemaining -= lastTickLength;\r
13096            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13097 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13098                  pvInfoList[forwardMostMove-1].time =               // use GUI time\r
13099                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
13100         } else {\r
13101            if(whiteNPS >= 0) lastTickLength = 0;\r
13102            whiteTimeRemaining -= lastTickLength;\r
13103            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13104 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13105                  pvInfoList[forwardMostMove-1].time = \r
13106                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
13107         }\r
13108         flagged = CheckFlags();\r
13109     }\r
13110     CheckTimeControl();\r
13111 \r
13112     if (flagged || !appData.clockMode) return;\r
13113 \r
13114     switch (gameMode) {\r
13115       case MachinePlaysBlack:\r
13116       case MachinePlaysWhite:\r
13117       case BeginningOfGame:\r
13118         if (pausing) return;\r
13119         break;\r
13120 \r
13121       case EditGame:\r
13122       case PlayFromGameFile:\r
13123       case IcsExamining:\r
13124         return;\r
13125 \r
13126       default:\r
13127         break;\r
13128     }\r
13129 \r
13130     tickStartTM = now;\r
13131     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13132       whiteTimeRemaining : blackTimeRemaining);\r
13133     StartClockTimer(intendedTickLength);\r
13134 }\r
13135         \r
13136 \r
13137 /* Stop both clocks */\r
13138 void\r
13139 StopClocks()\r
13140 {       \r
13141     long lastTickLength;\r
13142     TimeMark now;\r
13143 \r
13144     if (!StopClockTimer()) return;\r
13145     if (!appData.clockMode) return;\r
13146 \r
13147     GetTimeMark(&now);\r
13148 \r
13149     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13150     if (WhiteOnMove(forwardMostMove)) {\r
13151         if(whiteNPS >= 0) lastTickLength = 0;\r
13152         whiteTimeRemaining -= lastTickLength;\r
13153         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
13154     } else {\r
13155         if(blackNPS >= 0) lastTickLength = 0;\r
13156         blackTimeRemaining -= lastTickLength;\r
13157         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
13158     }\r
13159     CheckFlags();\r
13160 }\r
13161         \r
13162 /* Start clock of player on move.  Time may have been reset, so\r
13163    if clock is already running, stop and restart it. */\r
13164 void\r
13165 StartClocks()\r
13166 {\r
13167     (void) StopClockTimer(); /* in case it was running already */\r
13168     DisplayBothClocks();\r
13169     if (CheckFlags()) return;\r
13170 \r
13171     if (!appData.clockMode) return;\r
13172     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
13173 \r
13174     GetTimeMark(&tickStartTM);\r
13175     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13176       whiteTimeRemaining : blackTimeRemaining);\r
13177 \r
13178    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
13179     whiteNPS = blackNPS = -1; \r
13180     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
13181        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
13182         whiteNPS = first.nps;\r
13183     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
13184        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
13185         blackNPS = first.nps;\r
13186     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
13187         whiteNPS = second.nps;\r
13188     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
13189         blackNPS = second.nps;\r
13190     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
13191 \r
13192     StartClockTimer(intendedTickLength);\r
13193 }\r
13194 \r
13195 char *\r
13196 TimeString(ms)\r
13197      long ms;\r
13198 {\r
13199     long second, minute, hour, day;\r
13200     char *sign = "";\r
13201     static char buf[32];\r
13202     \r
13203     if (ms > 0 && ms <= 9900) {\r
13204       /* convert milliseconds to tenths, rounding up */\r
13205       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
13206 \r
13207       sprintf(buf, " %03.1f ", tenths/10.0);\r
13208       return buf;\r
13209     }\r
13210 \r
13211     /* convert milliseconds to seconds, rounding up */\r
13212     /* use floating point to avoid strangeness of integer division\r
13213        with negative dividends on many machines */\r
13214     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
13215 \r
13216     if (second < 0) {\r
13217         sign = "-";\r
13218         second = -second;\r
13219     }\r
13220     \r
13221     day = second / (60 * 60 * 24);\r
13222     second = second % (60 * 60 * 24);\r
13223     hour = second / (60 * 60);\r
13224     second = second % (60 * 60);\r
13225     minute = second / 60;\r
13226     second = second % 60;\r
13227     \r
13228     if (day > 0)\r
13229       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
13230               sign, day, hour, minute, second);\r
13231     else if (hour > 0)\r
13232       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
13233     else\r
13234       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
13235     \r
13236     return buf;\r
13237 }\r
13238 \r
13239 \r
13240 /*\r
13241  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
13242  */\r
13243 char *\r
13244 StrStr(string, match)\r
13245      char *string, *match;\r
13246 {\r
13247     int i, length;\r
13248     \r
13249     length = strlen(match);\r
13250     \r
13251     for (i = strlen(string) - length; i >= 0; i--, string++)\r
13252       if (!strncmp(match, string, length))\r
13253         return string;\r
13254     \r
13255     return NULL;\r
13256 }\r
13257 \r
13258 char *\r
13259 StrCaseStr(string, match)\r
13260      char *string, *match;\r
13261 {\r
13262     int i, j, length;\r
13263     \r
13264     length = strlen(match);\r
13265     \r
13266     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
13267         for (j = 0; j < length; j++) {\r
13268             if (ToLower(match[j]) != ToLower(string[j]))\r
13269               break;\r
13270         }\r
13271         if (j == length) return string;\r
13272     }\r
13273 \r
13274     return NULL;\r
13275 }\r
13276 \r
13277 #ifndef _amigados\r
13278 int\r
13279 StrCaseCmp(s1, s2)\r
13280      char *s1, *s2;\r
13281 {\r
13282     char c1, c2;\r
13283     \r
13284     for (;;) {\r
13285         c1 = ToLower(*s1++);\r
13286         c2 = ToLower(*s2++);\r
13287         if (c1 > c2) return 1;\r
13288         if (c1 < c2) return -1;\r
13289         if (c1 == NULLCHAR) return 0;\r
13290     }\r
13291 }\r
13292 \r
13293 \r
13294 int\r
13295 ToLower(c)\r
13296      int c;\r
13297 {\r
13298     return isupper(c) ? tolower(c) : c;\r
13299 }\r
13300 \r
13301 \r
13302 int\r
13303 ToUpper(c)\r
13304      int c;\r
13305 {\r
13306     return islower(c) ? toupper(c) : c;\r
13307 }\r
13308 #endif /* !_amigados    */\r
13309 \r
13310 char *\r
13311 StrSave(s)\r
13312      char *s;\r
13313 {\r
13314     char *ret;\r
13315 \r
13316     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
13317         strcpy(ret, s);\r
13318     }\r
13319     return ret;\r
13320 }\r
13321 \r
13322 char *\r
13323 StrSavePtr(s, savePtr)\r
13324      char *s, **savePtr;\r
13325 {\r
13326     if (*savePtr) {\r
13327         free(*savePtr);\r
13328     }\r
13329     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
13330         strcpy(*savePtr, s);\r
13331     }\r
13332     return(*savePtr);\r
13333 }\r
13334 \r
13335 char *\r
13336 PGNDate()\r
13337 {\r
13338     time_t clock;\r
13339     struct tm *tm;\r
13340     char buf[MSG_SIZ];\r
13341 \r
13342     clock = time((time_t *)NULL);\r
13343     tm = localtime(&clock);\r
13344     sprintf(buf, "%04d.%02d.%02d",\r
13345             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
13346     return StrSave(buf);\r
13347 }\r
13348 \r
13349 \r
13350 char *\r
13351 PositionToFEN(move, overrideCastling)\r
13352      int move;\r
13353      char *overrideCastling;\r
13354 {\r
13355     int i, j, fromX, fromY, toX, toY;\r
13356     int whiteToPlay;\r
13357     char buf[128];\r
13358     char *p, *q;\r
13359     int emptycount;\r
13360     ChessSquare piece;\r
13361 \r
13362     whiteToPlay = (gameMode == EditPosition) ?\r
13363       !blackPlaysFirst : (move % 2 == 0);\r
13364     p = buf;\r
13365 \r
13366     /* Piece placement data */\r
13367     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13368         emptycount = 0;\r
13369         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
13370             if (boards[move][i][j] == EmptySquare) {\r
13371                 emptycount++;\r
13372             } else { ChessSquare piece = boards[move][i][j];\r
13373                 if (emptycount > 0) {\r
13374                     if(emptycount<10) /* [HGM] can be >= 10 */\r
13375                         *p++ = '0' + emptycount;\r
13376                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13377                     emptycount = 0;\r
13378                 }\r
13379                 if(PieceToChar(piece) == '+') {\r
13380                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
13381                     *p++ = '+';\r
13382                     piece = (ChessSquare)(DEMOTED piece);\r
13383                 } \r
13384                 *p++ = PieceToChar(piece);\r
13385                 if(p[-1] == '~') {\r
13386                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
13387                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
13388                     *p++ = '~';\r
13389                 }\r
13390             }\r
13391         }\r
13392         if (emptycount > 0) {\r
13393             if(emptycount<10) /* [HGM] can be >= 10 */\r
13394                 *p++ = '0' + emptycount;\r
13395             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13396             emptycount = 0;\r
13397         }\r
13398         *p++ = '/';\r
13399     }\r
13400     *(p - 1) = ' ';\r
13401 \r
13402     /* [HGM] print Crazyhouse or Shogi holdings */\r
13403     if( gameInfo.holdingsWidth ) {\r
13404         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
13405         q = p;\r
13406         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
13407             piece = boards[move][i][BOARD_WIDTH-1];\r
13408             if( piece != EmptySquare )\r
13409               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
13410                   *p++ = PieceToChar(piece);\r
13411         }\r
13412         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
13413             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
13414             if( piece != EmptySquare )\r
13415               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
13416                   *p++ = PieceToChar(piece);\r
13417         }\r
13418 \r
13419         if( q == p ) *p++ = '-';\r
13420         *p++ = ']';\r
13421         *p++ = ' ';\r
13422     }\r
13423 \r
13424     /* Active color */\r
13425     *p++ = whiteToPlay ? 'w' : 'b';\r
13426     *p++ = ' ';\r
13427 \r
13428   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines\r
13429     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';\r
13430   } else {\r
13431   if(nrCastlingRights) {\r
13432      q = p;\r
13433      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
13434        /* [HGM] write directly from rights */\r
13435            if(castlingRights[move][2] >= 0 &&\r
13436               castlingRights[move][0] >= 0   )\r
13437                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
13438            if(castlingRights[move][2] >= 0 &&\r
13439               castlingRights[move][1] >= 0   )\r
13440                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
13441            if(castlingRights[move][5] >= 0 &&\r
13442               castlingRights[move][3] >= 0   )\r
13443                 *p++ = castlingRights[move][3] + AAA;\r
13444            if(castlingRights[move][5] >= 0 &&\r
13445               castlingRights[move][4] >= 0   )\r
13446                 *p++ = castlingRights[move][4] + AAA;\r
13447      } else {\r
13448 \r
13449         /* [HGM] write true castling rights */\r
13450         if( nrCastlingRights == 6 ) {\r
13451             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
13452                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
13453             if(castlingRights[move][1] == BOARD_LEFT &&\r
13454                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
13455             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
13456                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
13457             if(castlingRights[move][4] == BOARD_LEFT &&\r
13458                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
13459         }\r
13460      }\r
13461      if (q == p) *p++ = '-'; /* No castling rights */\r
13462      *p++ = ' ';\r
13463   }\r
13464 \r
13465   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13466      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13467     /* En passant target square */\r
13468     if (move > backwardMostMove) {\r
13469         fromX = moveList[move - 1][0] - AAA;\r
13470         fromY = moveList[move - 1][1] - ONE;\r
13471         toX = moveList[move - 1][2] - AAA;\r
13472         toY = moveList[move - 1][3] - ONE;\r
13473         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
13474             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
13475             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
13476             fromX == toX) {\r
13477             /* 2-square pawn move just happened */\r
13478             *p++ = toX + AAA;\r
13479             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
13480         } else {\r
13481             *p++ = '-';\r
13482         }\r
13483     } else {\r
13484         *p++ = '-';\r
13485     }\r
13486     *p++ = ' ';\r
13487   }\r
13488   }\r
13489 \r
13490     /* [HGM] find reversible plies */\r
13491     {   int i = 0, j=move;\r
13492 \r
13493         if (appData.debugMode) { int k;\r
13494             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
13495             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
13496                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
13497 \r
13498         }\r
13499 \r
13500         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
13501         if( j == backwardMostMove ) i += initialRulePlies;\r
13502         sprintf(p, "%d ", i);\r
13503         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
13504     }\r
13505     /* Fullmove number */\r
13506     sprintf(p, "%d", (move / 2) + 1);\r
13507     \r
13508     return StrSave(buf);\r
13509 }\r
13510 \r
13511 Boolean\r
13512 ParseFEN(board, blackPlaysFirst, fen)\r
13513     Board board;\r
13514      int *blackPlaysFirst;\r
13515      char *fen;\r
13516 {\r
13517     int i, j;\r
13518     char *p;\r
13519     int emptycount;\r
13520     ChessSquare piece;\r
13521 \r
13522     p = fen;\r
13523 \r
13524     /* [HGM] by default clear Crazyhouse holdings, if present */\r
13525     if(gameInfo.holdingsWidth) {\r
13526        for(i=0; i<BOARD_HEIGHT; i++) {\r
13527            board[i][0]             = EmptySquare; /* black holdings */\r
13528            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
13529            board[i][1]             = (ChessSquare) 0; /* black counts */\r
13530            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
13531        }\r
13532     }\r
13533 \r
13534     /* Piece placement data */\r
13535     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13536         j = 0;\r
13537         for (;;) {\r
13538             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
13539                 if (*p == '/') p++;\r
13540                 emptycount = gameInfo.boardWidth - j;\r
13541                 while (emptycount--)\r
13542                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13543                 break;\r
13544 #if(BOARD_SIZE >= 10)\r
13545             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
13546                 p++; emptycount=10;\r
13547                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13548                 while (emptycount--)\r
13549                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13550 #endif\r
13551             } else if (isdigit(*p)) {\r
13552                 emptycount = *p++ - '0';\r
13553                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
13554                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13555                 while (emptycount--)\r
13556                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13557             } else if (*p == '+' || isalpha(*p)) {\r
13558                 if (j >= gameInfo.boardWidth) return FALSE;\r
13559                 if(*p=='+') {\r
13560                     piece = CharToPiece(*++p);\r
13561                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
13562                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
13563                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
13564                 } else piece = CharToPiece(*p++);\r
13565 \r
13566                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
13567                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
13568                     piece = (ChessSquare) (PROMOTED piece);\r
13569                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
13570                     p++;\r
13571                 }\r
13572                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
13573             } else {\r
13574                 return FALSE;\r
13575             }\r
13576         }\r
13577     }\r
13578     while (*p == '/' || *p == ' ') p++;\r
13579 \r
13580     /* [HGM] look for Crazyhouse holdings here */\r
13581     while(*p==' ') p++;\r
13582     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
13583         if(*p == '[') p++;\r
13584         if(*p == '-' ) *p++; /* empty holdings */ else {\r
13585             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
13586             /* if we would allow FEN reading to set board size, we would   */\r
13587             /* have to add holdings and shift the board read so far here   */\r
13588             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
13589                 *p++;\r
13590                 if((int) piece >= (int) BlackPawn ) {\r
13591                     i = (int)piece - (int)BlackPawn;\r
13592                     i = PieceToNumber((ChessSquare)i);\r
13593                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13594                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
13595                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
13596                 } else {\r
13597                     i = (int)piece - (int)WhitePawn;\r
13598                     i = PieceToNumber((ChessSquare)i);\r
13599                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13600                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
13601                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
13602                 }\r
13603             }\r
13604         }\r
13605         if(*p == ']') *p++;\r
13606     }\r
13607 \r
13608     while(*p == ' ') p++;\r
13609 \r
13610     /* Active color */\r
13611     switch (*p++) {\r
13612       case 'w':\r
13613         *blackPlaysFirst = FALSE;\r
13614         break;\r
13615       case 'b': \r
13616         *blackPlaysFirst = TRUE;\r
13617         break;\r
13618       default:\r
13619         return FALSE;\r
13620     }\r
13621 \r
13622     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
13623     /* return the extra info in global variiables             */\r
13624 \r
13625     /* set defaults in case FEN is incomplete */\r
13626     FENepStatus = EP_UNKNOWN;\r
13627     for(i=0; i<nrCastlingRights; i++ ) {\r
13628         FENcastlingRights[i] =\r
13629             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
13630     }   /* assume possible unless obviously impossible */\r
13631     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
13632     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
13633     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
13634     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
13635     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
13636     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
13637     FENrulePlies = 0;\r
13638 \r
13639     while(*p==' ') p++;\r
13640     if(nrCastlingRights) {\r
13641       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
13642           /* castling indicator present, so default becomes no castlings */\r
13643           for(i=0; i<nrCastlingRights; i++ ) {\r
13644                  FENcastlingRights[i] = -1;\r
13645           }\r
13646       }\r
13647       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
13648              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
13649              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
13650              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
13651         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
13652 \r
13653         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
13654             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
13655             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
13656         }\r
13657         switch(c) {\r
13658           case'K':\r
13659               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
13660               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
13661               FENcastlingRights[2] = whiteKingFile;\r
13662               break;\r
13663           case'Q':\r
13664               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
13665               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
13666               FENcastlingRights[2] = whiteKingFile;\r
13667               break;\r
13668           case'k':\r
13669               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
13670               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
13671               FENcastlingRights[5] = blackKingFile;\r
13672               break;\r
13673           case'q':\r
13674               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
13675               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
13676               FENcastlingRights[5] = blackKingFile;\r
13677           case '-':\r
13678               break;\r
13679           default: /* FRC castlings */\r
13680               if(c >= 'a') { /* black rights */\r
13681                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13682                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
13683                   if(i == BOARD_RGHT) break;\r
13684                   FENcastlingRights[5] = i;\r
13685                   c -= AAA;\r
13686                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
13687                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
13688                   if(c > i)\r
13689                       FENcastlingRights[3] = c;\r
13690                   else\r
13691                       FENcastlingRights[4] = c;\r
13692               } else { /* white rights */\r
13693                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13694                     if(board[0][i] == WhiteKing) break;\r
13695                   if(i == BOARD_RGHT) break;\r
13696                   FENcastlingRights[2] = i;\r
13697                   c -= AAA - 'a' + 'A';\r
13698                   if(board[0][c] >= WhiteKing) break;\r
13699                   if(c > i)\r
13700                       FENcastlingRights[0] = c;\r
13701                   else\r
13702                       FENcastlingRights[1] = c;\r
13703               }\r
13704         }\r
13705       }\r
13706     if (appData.debugMode) {\r
13707         fprintf(debugFP, "FEN castling rights:");\r
13708         for(i=0; i<nrCastlingRights; i++)\r
13709         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
13710         fprintf(debugFP, "\n");\r
13711     }\r
13712 \r
13713       while(*p==' ') p++;\r
13714     }\r
13715 \r
13716     /* read e.p. field in games that know e.p. capture */\r
13717     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13718        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13719       if(*p=='-') {\r
13720         p++; FENepStatus = EP_NONE;\r
13721       } else {\r
13722          char c = *p++ - AAA;\r
13723 \r
13724          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
13725          if(*p >= '0' && *p <='9') *p++;\r
13726          FENepStatus = c;\r
13727       }\r
13728     }\r
13729 \r
13730 \r
13731     if(sscanf(p, "%d", &i) == 1) {\r
13732         FENrulePlies = i; /* 50-move ply counter */\r
13733         /* (The move number is still ignored)    */\r
13734     }\r
13735 \r
13736     return TRUE;\r
13737 }\r
13738       \r
13739 void\r
13740 EditPositionPasteFEN(char *fen)\r
13741 {\r
13742   if (fen != NULL) {\r
13743     Board initial_position;\r
13744 \r
13745     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
13746       DisplayError(_("Bad FEN position in clipboard"), 0);\r
13747       return ;\r
13748     } else {\r
13749       int savedBlackPlaysFirst = blackPlaysFirst;\r
13750       EditPositionEvent();\r
13751       blackPlaysFirst = savedBlackPlaysFirst;\r
13752       CopyBoard(boards[0], initial_position);\r
13753           /* [HGM] copy FEN attributes as well */\r
13754           {   int i;\r
13755               initialRulePlies = FENrulePlies;\r
13756               epStatus[0] = FENepStatus;\r
13757               for( i=0; i<nrCastlingRights; i++ )\r
13758                   castlingRights[0][i] = FENcastlingRights[i];\r
13759           }\r
13760       EditPositionDone();\r
13761       DisplayBothClocks();\r
13762       DrawPosition(FALSE, boards[currentMove]);\r
13763     }\r
13764   }\r
13765 }\r