adding support for different windows compiler
[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 \r
74 #if STDC_HEADERS\r
75 # include <stdlib.h>\r
76 # include <string.h>\r
77 #else /* not STDC_HEADERS */\r
78 # if HAVE_STRING_H\r
79 #  include <string.h>\r
80 # else /* not HAVE_STRING_H */\r
81 #  include <strings.h>\r
82 # endif /* not HAVE_STRING_H */\r
83 #endif /* not STDC_HEADERS */\r
84 \r
85 #if HAVE_SYS_FCNTL_H\r
86 # include <sys/fcntl.h>\r
87 #else /* not HAVE_SYS_FCNTL_H */\r
88 # if HAVE_FCNTL_H\r
89 #  include <fcntl.h>\r
90 # endif /* HAVE_FCNTL_H */\r
91 #endif /* not HAVE_SYS_FCNTL_H */\r
92 \r
93 #if TIME_WITH_SYS_TIME\r
94 # include <sys/time.h>\r
95 # include <time.h>\r
96 #else\r
97 # if HAVE_SYS_TIME_H\r
98 #  include <sys/time.h>\r
99 # else\r
100 #  include <time.h>\r
101 # endif\r
102 #endif\r
103 \r
104 #if defined(_amigados) && !defined(__GNUC__)\r
105 struct timezone {\r
106     int tz_minuteswest;\r
107     int tz_dsttime;\r
108 };\r
109 extern int gettimeofday(struct timeval *, struct timezone *);\r
110 #endif\r
111 \r
112 #if HAVE_UNISTD_H\r
113 # include <unistd.h>\r
114 #endif\r
115 \r
116 #include "common.h"\r
117 #include "frontend.h"\r
118 #include "backend.h"\r
119 #include "parser.h"\r
120 #include "moves.h"\r
121 #if ZIPPY\r
122 # include "zippy.h"\r
123 #endif\r
124 #include "backendz.h"\r
125 #include "gettext.h" \r
126  \r
127 #ifdef ENABLE_NLS \r
128 # define _(s) gettext (s) \r
129 # define N_(s) gettext_noop (s) \r
130 #else \r
131 # define _(s) (s) \r
132 # define N_(s) s \r
133 #endif \r
134 \r
135 \r
136 /* A point in time */\r
137 typedef struct {\r
138     long sec;  /* Assuming this is >= 32 bits */\r
139     int ms;    /* Assuming this is >= 16 bits */\r
140 } TimeMark;\r
141 \r
142 int establish P((void));\r
143 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,\r
144                          char *buf, int count, int error));\r
145 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,\r
146                       char *buf, int count, int error));\r
147 void SendToICS P((char *s));\r
148 void SendToICSDelayed P((char *s, long msdelay));\r
149 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,\r
150                       int toX, int toY));\r
151 void InitPosition P((int redraw));\r
152 void HandleMachineMove P((char *message, ChessProgramState *cps));\r
153 int AutoPlayOneMove P((void));\r
154 int LoadGameOneMove P((ChessMove readAhead));\r
155 int LoadGameFromFile P((char *filename, int n, char *title, int useList));\r
156 int LoadPositionFromFile P((char *filename, int n, char *title));\r
157 int SavePositionToFile P((char *filename));\r
158 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,\r
159                   Board board));\r
160 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));\r
161 void ShowMove P((int fromX, int fromY, int toX, int toY));\r
162 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,\r
163                    /*char*/int promoChar));\r
164 void BackwardInner P((int target));\r
165 void ForwardInner P((int target));\r
166 void GameEnds P((ChessMove result, char *resultDetails, int whosays));\r
167 void EditPositionDone P((void));\r
168 void PrintOpponents P((FILE *fp));\r
169 void PrintPosition P((FILE *fp, int move));\r
170 void StartChessProgram P((ChessProgramState *cps));\r
171 void SendToProgram P((char *message, ChessProgramState *cps));\r
172 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));\r
173 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,\r
174                            char *buf, int count, int error));\r
175 void SendTimeControl P((ChessProgramState *cps,\r
176                         int mps, long tc, int inc, int sd, int st));\r
177 char *TimeControlTagValue P((void));\r
178 void Attention P((ChessProgramState *cps));\r
179 void FeedMovesToProgram P((ChessProgramState *cps, int upto));\r
180 void ResurrectChessProgram P((void));\r
181 void DisplayComment P((int moveNumber, char *text));\r
182 void DisplayMove P((int moveNumber));\r
183 void DisplayAnalysis P((void));\r
184 \r
185 void ParseGameHistory P((char *game));\r
186 void ParseBoard12 P((char *string));\r
187 void StartClocks P((void));\r
188 void SwitchClocks P((void));\r
189 void StopClocks P((void));\r
190 void ResetClocks P((void));\r
191 char *PGNDate P((void));\r
192 void SetGameInfo P((void));\r
193 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));\r
194 int RegisterMove P((void));\r
195 void MakeRegisteredMove P((void));\r
196 void TruncateGame P((void));\r
197 int looking_at P((char *, int *, char *));\r
198 void CopyPlayerNameIntoFileName P((char **, char *));\r
199 char *SavePart P((char *));\r
200 int SaveGameOldStyle P((FILE *));\r
201 int SaveGamePGN P((FILE *));\r
202 void GetTimeMark P((TimeMark *));\r
203 long SubtractTimeMarks P((TimeMark *, TimeMark *));\r
204 int CheckFlags P((void));\r
205 long NextTickLength P((long));\r
206 void CheckTimeControl P((void));\r
207 void show_bytes P((FILE *, char *, int));\r
208 int string_to_rating P((char *str));\r
209 void ParseFeatures P((char* args, ChessProgramState *cps));\r
210 void InitBackEnd3 P((void));\r
211 void FeatureDone P((ChessProgramState* cps, int val));\r
212 void InitChessProgram P((ChessProgramState *cps, int setup));\r
213 \r
214 #ifdef WIN32\r
215        extern void ConsoleCreate();\r
216 #endif\r
217 \r
218 ChessProgramState *WhitePlayer();\r
219 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c\r
220 int VerifyDisplayMode P(());\r
221 \r
222 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment\r
223 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c\r
224 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move\r
225 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book\r
226 extern char installDir[MSG_SIZ];\r
227 \r
228 extern int tinyLayout, smallLayout;\r
229 ChessProgramStats programStats;\r
230 static int exiting = 0; /* [HGM] moved to top */\r
231 static int setboardSpoiledMachineBlack = 0, errorExitFlag = 0;\r
232 extern int startedFromPositionFile;\r
233 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */\r
234 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */\r
235 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */\r
236 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */\r
237 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */\r
238 int opponentKibitzes;\r
239 \r
240 /* States for ics_getting_history */\r
241 #define H_FALSE 0\r
242 #define H_REQUESTED 1\r
243 #define H_GOT_REQ_HEADER 2\r
244 #define H_GOT_UNREQ_HEADER 3\r
245 #define H_GETTING_MOVES 4\r
246 #define H_GOT_UNWANTED_HEADER 5\r
247 \r
248 /* whosays values for GameEnds */\r
249 #define GE_ICS 0\r
250 #define GE_ENGINE 1\r
251 #define GE_PLAYER 2\r
252 #define GE_FILE 3\r
253 #define GE_XBOARD 4\r
254 #define GE_ENGINE1 5\r
255 #define GE_ENGINE2 6\r
256 \r
257 /* Maximum number of games in a cmail message */\r
258 #define CMAIL_MAX_GAMES 20\r
259 \r
260 /* Different types of move when calling RegisterMove */\r
261 #define CMAIL_MOVE   0\r
262 #define CMAIL_RESIGN 1\r
263 #define CMAIL_DRAW   2\r
264 #define CMAIL_ACCEPT 3\r
265 \r
266 /* Different types of result to remember for each game */\r
267 #define CMAIL_NOT_RESULT 0\r
268 #define CMAIL_OLD_RESULT 1\r
269 #define CMAIL_NEW_RESULT 2\r
270 \r
271 /* Telnet protocol constants */\r
272 #define TN_WILL 0373\r
273 #define TN_WONT 0374\r
274 #define TN_DO   0375\r
275 #define TN_DONT 0376\r
276 #define TN_IAC  0377\r
277 #define TN_ECHO 0001\r
278 #define TN_SGA  0003\r
279 #define TN_PORT 23\r
280 \r
281 /* [AS] */\r
282 static char * safeStrCpy( char * dst, const char * src, size_t count )\r
283 {\r
284     assert( dst != NULL );\r
285     assert( src != NULL );\r
286     assert( count > 0 );\r
287 \r
288     strncpy( dst, src, count );\r
289     dst[ count-1 ] = '\0';\r
290     return dst;\r
291 }\r
292 \r
293 static char * safeStrCat( char * dst, const char * src, size_t count )\r
294 {\r
295     size_t  dst_len;\r
296 \r
297     assert( dst != NULL );\r
298     assert( src != NULL );\r
299     assert( count > 0 );\r
300 \r
301     dst_len = strlen(dst);\r
302 \r
303     assert( count > dst_len ); /* Buffer size must be greater than current length */\r
304 \r
305     safeStrCpy( dst + dst_len, src, count - dst_len );\r
306 \r
307     return dst;\r
308 }\r
309 \r
310 /* Some compiler can't cast u64 to double\r
311  * This function do the job for us:\r
312 \r
313  * We use the highest bit for cast, this only\r
314  * works if the highest bit is not\r
315  * in use (This should not happen)\r
316  *\r
317  * We used this for all compiler\r
318  */\r
319 double\r
320 u64ToDouble(u64 value)\r
321 {\r
322   double r;\r
323   u64 tmp = value & u64Const(0x7fffffffffffffff);\r
324   r = (double)(s64)tmp;\r
325   if (value & u64Const(0x8000000000000000))\r
326        r +=  9.2233720368547758080e18; /* 2^63 */\r
327  return r;\r
328 }\r
329 \r
330 /* Fake up flags for now, as we aren't keeping track of castling\r
331    availability yet. [HGM] Change of logic: the flag now only\r
332    indicates the type of castlings allowed by the rule of the game.\r
333    The actual rights themselves are maintained in the array\r
334    castlingRights, as part of the game history, and are not probed\r
335    by this function.\r
336  */\r
337 int\r
338 PosFlags(index)\r
339 {\r
340   int flags = F_ALL_CASTLE_OK;\r
341   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;\r
342   switch (gameInfo.variant) {\r
343   case VariantSuicide:\r
344     flags &= ~F_ALL_CASTLE_OK;\r
345   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!\r
346     flags |= F_IGNORE_CHECK;\r
347     break;\r
348   case VariantAtomic:\r
349     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;\r
350     break;\r
351   case VariantKriegspiel:\r
352     flags |= F_KRIEGSPIEL_CAPTURE;\r
353     break;\r
354   case VariantCapaRandom: \r
355   case VariantFischeRandom:\r
356     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */\r
357   case VariantNoCastle:\r
358   case VariantShatranj:\r
359   case VariantCourier:\r
360     flags &= ~F_ALL_CASTLE_OK;\r
361     break;\r
362   default:\r
363     break;\r
364   }\r
365   return flags;\r
366 }\r
367 \r
368 FILE *gameFileFP, *debugFP;\r
369 \r
370 /* \r
371     [AS] Note: sometimes, the sscanf() function is used to parse the input\r
372     into a fixed-size buffer. Because of this, we must be prepared to\r
373     receive strings as long as the size of the input buffer, which is currently\r
374     set to 4K for Windows and 8K for the rest.\r
375     So, we must either allocate sufficiently large buffers here, or\r
376     reduce the size of the input buffer in the input reading part.\r
377 */\r
378 \r
379 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];\r
380 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];\r
381 char thinkOutput1[MSG_SIZ*10];\r
382 \r
383 ChessProgramState first, second;\r
384 \r
385 /* premove variables */\r
386 int premoveToX = 0;\r
387 int premoveToY = 0;\r
388 int premoveFromX = 0;\r
389 int premoveFromY = 0;\r
390 int premovePromoChar = 0;\r
391 int gotPremove = 0;\r
392 Boolean alarmSounded;\r
393 /* end premove variables */\r
394 \r
395 char *ics_prefix = "$";\r
396 int ics_type = ICS_GENERIC;\r
397 \r
398 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;\r
399 int pauseExamForwardMostMove = 0;\r
400 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;\r
401 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];\r
402 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;\r
403 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;\r
404 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;\r
405 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;\r
406 int whiteFlag = FALSE, blackFlag = FALSE;\r
407 int userOfferedDraw = FALSE;\r
408 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;\r
409 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;\r
410 int cmailMoveType[CMAIL_MAX_GAMES];\r
411 long ics_clock_paused = 0;\r
412 ProcRef icsPR = NoProc, cmailPR = NoProc;\r
413 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;\r
414 GameMode gameMode = BeginningOfGame;\r
415 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];\r
416 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];\r
417 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */\r
418 int hiddenThinkOutputState = 0; /* [AS] */\r
419 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */\r
420 int adjudicateLossPlies = 6;\r
421 char white_holding[64], black_holding[64];\r
422 TimeMark lastNodeCountTime;\r
423 long lastNodeCount=0;\r
424 int have_sent_ICS_logon = 0;\r
425 int movesPerSession;\r
426 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;\r
427 long timeControl_2; /* [AS] Allow separate time controls */\r
428 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */\r
429 long timeRemaining[2][MAX_MOVES];\r
430 int matchGame = 0;\r
431 TimeMark programStartTime;\r
432 char ics_handle[MSG_SIZ];\r
433 int have_set_title = 0;\r
434 \r
435 /* animateTraining preserves the state of appData.animate\r
436  * when Training mode is activated. This allows the\r
437  * response to be animated when appData.animate == TRUE and\r
438  * appData.animateDragging == TRUE.\r
439  */\r
440 Boolean animateTraining;\r
441 \r
442 GameInfo gameInfo;\r
443 \r
444 AppData appData;\r
445 \r
446 Board boards[MAX_MOVES];\r
447 /* [HGM] Following 7 needed for accurate legality tests: */\r
448 char  epStatus[MAX_MOVES];\r
449 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1\r
450 char  castlingRank[BOARD_SIZE]; // and corresponding ranks\r
451 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];\r
452 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status\r
453 int   initialRulePlies, FENrulePlies;\r
454 char  FENepStatus;\r
455 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)\r
456 int loadFlag = 0; \r
457 int shuffleOpenings;\r
458 \r
459 ChessSquare  FIDEArray[2][BOARD_SIZE] = {\r
460     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
461         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
462     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
463         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
464 };\r
465 \r
466 ChessSquare twoKingsArray[2][BOARD_SIZE] = {\r
467     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
468         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },\r
469     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
470         BlackKing, BlackKing, BlackKnight, BlackRook }\r
471 };\r
472 \r
473 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {\r
474     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,\r
475         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },\r
476     { BlackRook, BlackMan, BlackBishop, BlackQueen,\r
477         BlackUnicorn, BlackBishop, BlackMan, BlackRook }\r
478 };\r
479 \r
480 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */\r
481     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,\r
482         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
483     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,\r
484         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
485 };\r
486 \r
487 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */\r
488     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,\r
489         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
490     { BlackRook, BlackKnight, BlackAlfil, BlackKing,\r
491         BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
492 };\r
493 \r
494 \r
495 #if (BOARD_SIZE>=10)\r
496 ChessSquare ShogiArray[2][BOARD_SIZE] = {\r
497     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,\r
498         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },\r
499     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,\r
500         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }\r
501 };\r
502 \r
503 ChessSquare XiangqiArray[2][BOARD_SIZE] = {\r
504     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,\r
505         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
506     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,\r
507         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
508 };\r
509 \r
510 ChessSquare CapablancaArray[2][BOARD_SIZE] = {\r
511     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, \r
512         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },\r
513     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, \r
514         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
515 };\r
516 \r
517 ChessSquare GreatArray[2][BOARD_SIZE] = {\r
518     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, \r
519         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },\r
520     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, \r
521         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },\r
522 };\r
523 \r
524 ChessSquare JanusArray[2][BOARD_SIZE] = {\r
525     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, \r
526         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },\r
527     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, \r
528         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }\r
529 };\r
530 \r
531 #ifdef GOTHIC\r
532 ChessSquare GothicArray[2][BOARD_SIZE] = {\r
533     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, \r
534         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },\r
535     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, \r
536         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }\r
537 };\r
538 #else // !GOTHIC\r
539 #define GothicArray CapablancaArray\r
540 #endif // !GOTHIC\r
541 \r
542 #ifdef FALCON\r
543 ChessSquare FalconArray[2][BOARD_SIZE] = {\r
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, \r
545         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },\r
546     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, \r
547         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }\r
548 };\r
549 #else // !FALCON\r
550 #define FalconArray CapablancaArray\r
551 #endif // !FALCON\r
552 \r
553 #else // !(BOARD_SIZE>=10)\r
554 #define XiangqiPosition FIDEArray\r
555 #define CapablancaArray FIDEArray\r
556 #define GothicArray FIDEArray\r
557 #define GreatArray FIDEArray\r
558 #endif // !(BOARD_SIZE>=10)\r
559 \r
560 #if (BOARD_SIZE>=12)\r
561 ChessSquare CourierArray[2][BOARD_SIZE] = {\r
562     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,\r
563         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },\r
564     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,\r
565         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }\r
566 };\r
567 #else // !(BOARD_SIZE>=12)\r
568 #define CourierArray CapablancaArray\r
569 #endif // !(BOARD_SIZE>=12)\r
570 \r
571 \r
572 Board initialPosition;\r
573 \r
574 \r
575 /* Convert str to a rating. Checks for special cases of "----",\r
576 \r
577    "++++", etc. Also strips ()'s */\r
578 int\r
579 string_to_rating(str)\r
580   char *str;\r
581 {\r
582   while(*str && !isdigit(*str)) ++str;\r
583   if (!*str)\r
584     return 0;   /* One of the special "no rating" cases */\r
585   else\r
586     return atoi(str);\r
587 }\r
588 \r
589 void\r
590 ClearProgramStats()\r
591 {\r
592     /* Init programStats */\r
593     programStats.movelist[0] = 0;\r
594     programStats.depth = 0;\r
595     programStats.nr_moves = 0;\r
596     programStats.moves_left = 0;\r
597     programStats.nodes = 0;\r
598     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output\r
599     programStats.score = 0;\r
600     programStats.got_only_move = 0;\r
601     programStats.got_fail = 0;\r
602     programStats.line_is_book = 0;\r
603 }\r
604 \r
605 void\r
606 InitBackEnd1()\r
607 {\r
608     int matched, min, sec;\r
609 \r
610     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options\r
611 \r
612     GetTimeMark(&programStartTime);\r
613 \r
614     ClearProgramStats();\r
615     programStats.ok_to_send = 1;\r
616     programStats.seen_stat = 0;\r
617 \r
618     /*\r
619      * Initialize game list\r
620      */\r
621     ListNew(&gameList);\r
622 \r
623 \r
624     /*\r
625      * Internet chess server status\r
626      */\r
627     if (appData.icsActive) {\r
628         appData.matchMode = FALSE;\r
629         appData.matchGames = 0;\r
630 #if ZIPPY       \r
631         appData.noChessProgram = !appData.zippyPlay;\r
632 #else\r
633         appData.zippyPlay = FALSE;\r
634         appData.zippyTalk = FALSE;\r
635         appData.noChessProgram = TRUE;\r
636 #endif\r
637         if (*appData.icsHelper != NULLCHAR) {\r
638             appData.useTelnet = TRUE;\r
639             appData.telnetProgram = appData.icsHelper;\r
640         }\r
641     } else {\r
642         appData.zippyTalk = appData.zippyPlay = FALSE;\r
643     }\r
644 \r
645     /* [AS] Initialize pv info list [HGM] and game state */\r
646     {\r
647         int i, j;\r
648 \r
649         for( i=0; i<MAX_MOVES; i++ ) {\r
650             pvInfoList[i].depth = -1;\r
651             epStatus[i]=EP_NONE;\r
652             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
653         }\r
654     }\r
655 \r
656     /*\r
657      * Parse timeControl resource\r
658      */\r
659     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,\r
660                           appData.movesPerSession)) {\r
661         char buf[MSG_SIZ];\r
662         sprintf(buf, _("bad timeControl option %s"), appData.timeControl);\r
663         DisplayFatalError(buf, 0, 2);\r
664     }\r
665 \r
666     /*\r
667      * Parse searchTime resource\r
668      */\r
669     if (*appData.searchTime != NULLCHAR) {\r
670         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);\r
671         if (matched == 1) {\r
672             searchTime = min * 60;\r
673         } else if (matched == 2) {\r
674             searchTime = min * 60 + sec;\r
675         } else {\r
676             char buf[MSG_SIZ];\r
677             sprintf(buf, _("bad searchTime option %s"), appData.searchTime);\r
678             DisplayFatalError(buf, 0, 2);\r
679         }\r
680     }\r
681 \r
682     /* [AS] Adjudication threshold */\r
683     adjudicateLossThreshold = appData.adjudicateLossThreshold;\r
684     \r
685     first.which = "first";\r
686     second.which = "second";\r
687     first.maybeThinking = second.maybeThinking = FALSE;\r
688     first.pr = second.pr = NoProc;\r
689     first.isr = second.isr = NULL;\r
690     first.sendTime = second.sendTime = 2;\r
691     first.sendDrawOffers = 1;\r
692     if (appData.firstPlaysBlack) {\r
693         first.twoMachinesColor = "black\n";\r
694         second.twoMachinesColor = "white\n";\r
695     } else {\r
696         first.twoMachinesColor = "white\n";\r
697         second.twoMachinesColor = "black\n";\r
698     }\r
699     first.program = appData.firstChessProgram;\r
700     second.program = appData.secondChessProgram;\r
701     first.host = appData.firstHost;\r
702     second.host = appData.secondHost;\r
703     first.dir = appData.firstDirectory;\r
704     second.dir = appData.secondDirectory;\r
705     first.other = &second;\r
706     second.other = &first;\r
707     first.initString = appData.initString;\r
708     second.initString = appData.secondInitString;\r
709     first.computerString = appData.firstComputerString;\r
710     second.computerString = appData.secondComputerString;\r
711     first.useSigint = second.useSigint = TRUE;\r
712     first.useSigterm = second.useSigterm = TRUE;\r
713     first.reuse = appData.reuseFirst;\r
714     second.reuse = appData.reuseSecond;\r
715     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second\r
716     second.nps = appData.secondNPS;\r
717     first.useSetboard = second.useSetboard = FALSE;\r
718     first.useSAN = second.useSAN = FALSE;\r
719     first.usePing = second.usePing = FALSE;\r
720     first.lastPing = second.lastPing = 0;\r
721     first.lastPong = second.lastPong = 0;\r
722     first.usePlayother = second.usePlayother = FALSE;\r
723     first.useColors = second.useColors = TRUE;\r
724     first.useUsermove = second.useUsermove = FALSE;\r
725     first.sendICS = second.sendICS = FALSE;\r
726     first.sendName = second.sendName = appData.icsActive;\r
727     first.sdKludge = second.sdKludge = FALSE;\r
728     first.stKludge = second.stKludge = FALSE;\r
729     TidyProgramName(first.program, first.host, first.tidy);\r
730     TidyProgramName(second.program, second.host, second.tidy);\r
731     first.matchWins = second.matchWins = 0;\r
732     strcpy(first.variants, appData.variant);\r
733     strcpy(second.variants, appData.variant);\r
734     first.analysisSupport = second.analysisSupport = 2; /* detect */\r
735     first.analyzing = second.analyzing = FALSE;\r
736     first.initDone = second.initDone = FALSE;\r
737 \r
738     /* New features added by Tord: */\r
739     first.useFEN960 = FALSE; second.useFEN960 = FALSE;\r
740     first.useOOCastle = TRUE; second.useOOCastle = TRUE;\r
741     /* End of new features added by Tord. */\r
742 \r
743     /* [HGM] time odds: set factor for each machine */\r
744     first.timeOdds  = appData.firstTimeOdds;\r
745     second.timeOdds = appData.secondTimeOdds;\r
746     { int norm = 1;\r
747         if(appData.timeOddsMode) {\r
748             norm = first.timeOdds;\r
749             if(norm > second.timeOdds) norm = second.timeOdds;\r
750         }\r
751         first.timeOdds /= norm;\r
752         second.timeOdds /= norm;\r
753     }\r
754 \r
755     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/\r
756     first.accumulateTC = appData.firstAccumulateTC;\r
757     second.accumulateTC = appData.secondAccumulateTC;\r
758     first.maxNrOfSessions = second.maxNrOfSessions = 1;\r
759 \r
760     /* [HGM] debug */\r
761     first.debug = second.debug = FALSE;\r
762     first.supportsNPS = second.supportsNPS = UNKNOWN;\r
763 \r
764     /* [HGM] options */\r
765     first.optionSettings  = appData.firstOptions;\r
766     second.optionSettings = appData.secondOptions;\r
767 \r
768     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
769     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
770     first.isUCI = appData.firstIsUCI; /* [AS] */\r
771     second.isUCI = appData.secondIsUCI; /* [AS] */\r
772     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
773     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
774 \r
775     if (appData.firstProtocolVersion > PROTOVER ||\r
776         appData.firstProtocolVersion < 1) {\r
777       char buf[MSG_SIZ];\r
778       sprintf(buf, _("protocol version %d not supported"),\r
779               appData.firstProtocolVersion);\r
780       DisplayFatalError(buf, 0, 2);\r
781     } else {\r
782       first.protocolVersion = appData.firstProtocolVersion;\r
783     }\r
784 \r
785     if (appData.secondProtocolVersion > PROTOVER ||\r
786         appData.secondProtocolVersion < 1) {\r
787       char buf[MSG_SIZ];\r
788       sprintf(buf, _("protocol version %d not supported"),\r
789               appData.secondProtocolVersion);\r
790       DisplayFatalError(buf, 0, 2);\r
791     } else {\r
792       second.protocolVersion = appData.secondProtocolVersion;\r
793     }\r
794 \r
795     if (appData.icsActive) {\r
796         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */\r
797     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
798         appData.clockMode = FALSE;\r
799         first.sendTime = second.sendTime = 0;\r
800     }\r
801     \r
802 #if ZIPPY\r
803     /* Override some settings from environment variables, for backward\r
804        compatibility.  Unfortunately it's not feasible to have the env\r
805        vars just set defaults, at least in xboard.  Ugh.\r
806     */\r
807     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
808       ZippyInit();\r
809     }\r
810 #endif\r
811     \r
812     if (appData.noChessProgram) {\r
813         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
814                                         + strlen(PATCHLEVEL));\r
815         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
816     } else {\r
817 #if 0\r
818         char *p, *q;\r
819         q = first.program;\r
820         while (*q != ' ' && *q != NULLCHAR) q++;\r
821         p = q;\r
822         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */\r
823         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
824                                         + strlen(PATCHLEVEL) + (q - p));\r
825         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
826         strncat(programVersion, p, q - p);\r
827 #else\r
828         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */\r
829         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
830                                         + strlen(PATCHLEVEL) + strlen(first.tidy));\r
831         sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);\r
832 #endif\r
833     }\r
834 \r
835     if (!appData.icsActive) {\r
836       char buf[MSG_SIZ];\r
837       /* Check for variants that are supported only in ICS mode,\r
838          or not at all.  Some that are accepted here nevertheless\r
839          have bugs; see comments below.\r
840       */\r
841       VariantClass variant = StringToVariant(appData.variant);\r
842       switch (variant) {\r
843       case VariantBughouse:     /* need four players and two boards */\r
844       case VariantKriegspiel:   /* need to hide pieces and move details */\r
845       /* case VariantFischeRandom: (Fabien: moved below) */\r
846         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);\r
847         DisplayFatalError(buf, 0, 2);\r
848         return;\r
849 \r
850       case VariantUnknown:\r
851       case VariantLoadable:\r
852       case Variant29:\r
853       case Variant30:\r
854       case Variant31:\r
855       case Variant32:\r
856       case Variant33:\r
857       case Variant34:\r
858       case Variant35:\r
859       case Variant36:\r
860       default:\r
861         sprintf(buf, _("Unknown variant name %s"), appData.variant);\r
862         DisplayFatalError(buf, 0, 2);\r
863         return;\r
864 \r
865       case VariantXiangqi:    /* [HGM] repetition rules not implemented */\r
866       case VariantFairy:      /* [HGM] TestLegality definitely off! */\r
867       case VariantGothic:     /* [HGM] should work */\r
868       case VariantCapablanca: /* [HGM] should work */\r
869       case VariantCourier:    /* [HGM] initial forced moves not implemented */\r
870       case VariantShogi:      /* [HGM] drops not tested for legality */\r
871       case VariantKnightmate: /* [HGM] should work */\r
872       case VariantCylinder:   /* [HGM] untested */\r
873       case VariantFalcon:     /* [HGM] untested */\r
874       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)\r
875                                  offboard interposition not understood */\r
876       case VariantNormal:     /* definitely works! */\r
877       case VariantWildCastle: /* pieces not automatically shuffled */\r
878       case VariantNoCastle:   /* pieces not automatically shuffled */\r
879       case VariantFischeRandom: /* [HGM] works and shuffles pieces */\r
880       case VariantLosers:     /* should work except for win condition,\r
881                                  and doesn't know captures are mandatory */\r
882       case VariantSuicide:    /* should work except for win condition,\r
883                                  and doesn't know captures are mandatory */\r
884       case VariantGiveaway:   /* should work except for win condition,\r
885                                  and doesn't know captures are mandatory */\r
886       case VariantTwoKings:   /* should work */\r
887       case VariantAtomic:     /* should work except for win condition */\r
888       case Variant3Check:     /* should work except for win condition */\r
889       case VariantShatranj:   /* should work except for all win conditions */\r
890       case VariantBerolina:   /* might work if TestLegality is off */\r
891       case VariantCapaRandom: /* should work */\r
892       case VariantJanus:      /* should work */\r
893       case VariantSuper:      /* experimental */\r
894       case VariantGreat:      /* experimental, requires legality testing to be off */\r
895         break;\r
896       }\r
897     }\r
898 \r
899     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard\r
900     InitEngineUCI( installDir, &second );\r
901 }\r
902 \r
903 int NextIntegerFromString( char ** str, long * value )\r
904 {\r
905     int result = -1;\r
906     char * s = *str;\r
907 \r
908     while( *s == ' ' || *s == '\t' ) {\r
909         s++;\r
910     }\r
911 \r
912     *value = 0;\r
913 \r
914     if( *s >= '0' && *s <= '9' ) {\r
915         while( *s >= '0' && *s <= '9' ) {\r
916             *value = *value * 10 + (*s - '0');\r
917             s++;\r
918         }\r
919 \r
920         result = 0;\r
921     }\r
922 \r
923     *str = s;\r
924 \r
925     return result;\r
926 }\r
927 \r
928 int NextTimeControlFromString( char ** str, long * value )\r
929 {\r
930     long temp;\r
931     int result = NextIntegerFromString( str, &temp );\r
932 \r
933     if( result == 0 ) {\r
934         *value = temp * 60; /* Minutes */\r
935         if( **str == ':' ) {\r
936             (*str)++;\r
937             result = NextIntegerFromString( str, &temp );\r
938             *value += temp; /* Seconds */\r
939         }\r
940     }\r
941 \r
942     return result;\r
943 }\r
944 \r
945 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)\r
946 {   /* [HGM] routine added to read '+moves/time' for secondary time control */\r
947     int result = -1; long temp, temp2;\r
948 \r
949     if(**str != '+') return -1; // old params remain in force!\r
950     (*str)++;\r
951     if( NextTimeControlFromString( str, &temp ) ) return -1;\r
952 \r
953     if(**str != '/') {\r
954         /* time only: incremental or sudden-death time control */\r
955         if(**str == '+') { /* increment follows; read it */\r
956             (*str)++;\r
957             if(result = NextIntegerFromString( str, &temp2)) return -1;\r
958             *inc = temp2 * 1000;\r
959         } else *inc = 0;\r
960         *moves = 0; *tc = temp * 1000; \r
961         return 0;\r
962     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */\r
963 \r
964     (*str)++; /* classical time control */\r
965     result = NextTimeControlFromString( str, &temp2);\r
966     if(result == 0) {\r
967         *moves = temp/60;\r
968         *tc    = temp2 * 1000;\r
969         *inc   = 0;\r
970     }\r
971     return result;\r
972 }\r
973 \r
974 int GetTimeQuota(int movenr)\r
975 {   /* [HGM] get time to add from the multi-session time-control string */\r
976     int moves=1; /* kludge to force reading of first session */\r
977     long time, increment;\r
978     char *s = fullTimeControlString;\r
979 \r
980     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);\r
981     do {\r
982         if(moves) NextSessionFromString(&s, &moves, &time, &increment);\r
983         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);\r
984         if(movenr == -1) return time;    /* last move before new session     */\r
985         if(!moves) return increment;     /* current session is incremental   */\r
986         if(movenr >= 0) movenr -= moves; /* we already finished this session */\r
987     } while(movenr >= -1);               /* try again for next session       */\r
988 \r
989     return 0; // no new time quota on this move\r
990 }\r
991 \r
992 int\r
993 ParseTimeControl(tc, ti, mps)\r
994      char *tc;\r
995      int ti;\r
996      int mps;\r
997 {\r
998 #if 0\r
999     int matched, min, sec;\r
1000 \r
1001     matched = sscanf(tc, "%d:%d", &min, &sec);\r
1002     if (matched == 1) {\r
1003         timeControl = min * 60 * 1000;\r
1004     } else if (matched == 2) {\r
1005         timeControl = (min * 60 + sec) * 1000;\r
1006     } else {\r
1007         return FALSE;\r
1008     }\r
1009 #else\r
1010     long tc1;\r
1011     long tc2;\r
1012     char buf[MSG_SIZ];\r
1013 \r
1014     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;\r
1015     if(ti > 0) {\r
1016         if(mps)\r
1017              sprintf(buf, "+%d/%s+%d", mps, tc, ti);\r
1018         else sprintf(buf, "+%s+%d", tc, ti);\r
1019     } else {\r
1020         if(mps)\r
1021              sprintf(buf, "+%d/%s", mps, tc);\r
1022         else sprintf(buf, "+%s", tc);\r
1023     }\r
1024     fullTimeControlString = StrSave(buf);\r
1025 \r
1026     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
1027         return FALSE;\r
1028     }\r
1029 \r
1030     if( *tc == '/' ) {\r
1031         /* Parse second time control */\r
1032         tc++;\r
1033 \r
1034         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
1035             return FALSE;\r
1036         }\r
1037 \r
1038         if( tc2 == 0 ) {\r
1039             return FALSE;\r
1040         }\r
1041 \r
1042         timeControl_2 = tc2 * 1000;\r
1043     }\r
1044     else {\r
1045         timeControl_2 = 0;\r
1046     }\r
1047 \r
1048     if( tc1 == 0 ) {\r
1049         return FALSE;\r
1050     }\r
1051 \r
1052     timeControl = tc1 * 1000;\r
1053 #endif\r
1054 \r
1055     if (ti >= 0) {\r
1056         timeIncrement = ti * 1000;  /* convert to ms */\r
1057         movesPerSession = 0;\r
1058     } else {\r
1059         timeIncrement = 0;\r
1060         movesPerSession = mps;\r
1061     }\r
1062     return TRUE;\r
1063 }\r
1064 \r
1065 void\r
1066 InitBackEnd2()\r
1067 {\r
1068     if (appData.debugMode) {\r
1069         fprintf(debugFP, "%s\n", programVersion);\r
1070     }\r
1071 \r
1072     if (appData.matchGames > 0) {\r
1073         appData.matchMode = TRUE;\r
1074     } else if (appData.matchMode) {\r
1075         appData.matchGames = 1;\r
1076     }\r
1077     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */\r
1078         appData.matchGames = appData.sameColorGames;\r
1079     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */\r
1080         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;\r
1081         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;\r
1082     }\r
1083     Reset(TRUE, FALSE);\r
1084     if (appData.noChessProgram || first.protocolVersion == 1) {\r
1085       InitBackEnd3();\r
1086     } else {\r
1087       /* kludge: allow timeout for initial "feature" commands */\r
1088       FreezeUI();\r
1089       DisplayMessage("", _("Starting chess program"));\r
1090       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
1091     }\r
1092 }\r
1093 \r
1094 void\r
1095 InitBackEnd3 P((void))\r
1096 {\r
1097     GameMode initialMode;\r
1098     char buf[MSG_SIZ];\r
1099     int err;\r
1100 \r
1101     InitChessProgram(&first, startedFromSetupPosition);\r
1102 \r
1103 \r
1104     if (appData.icsActive) {\r
1105 #ifdef WIN32\r
1106         /* [DM] Make a console window if needed [HGM] merged ifs */\r
1107         ConsoleCreate(); \r
1108 #endif\r
1109         err = establish();\r
1110         if (err != 0) {\r
1111             if (*appData.icsCommPort != NULLCHAR) {\r
1112                 sprintf(buf, _("Could not open comm port %s"),  \r
1113                         appData.icsCommPort);\r
1114             } else {\r
1115                 sprintf(buf, _("Could not connect to host %s, port %s"),  \r
1116                         appData.icsHost, appData.icsPort);\r
1117             }\r
1118             DisplayFatalError(buf, err, 1);\r
1119             return;\r
1120         }\r
1121         SetICSMode();\r
1122         telnetISR =\r
1123           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
1124         fromUserISR =\r
1125           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
1126     } else if (appData.noChessProgram) {\r
1127         SetNCPMode();\r
1128     } else {\r
1129         SetGNUMode();\r
1130     }\r
1131 \r
1132     if (*appData.cmailGameName != NULLCHAR) {\r
1133         SetCmailMode();\r
1134         OpenLoopback(&cmailPR);\r
1135         cmailISR =\r
1136           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
1137     }\r
1138     \r
1139     ThawUI();\r
1140     DisplayMessage("", "");\r
1141     if (StrCaseCmp(appData.initialMode, "") == 0) {\r
1142       initialMode = BeginningOfGame;\r
1143     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
1144       initialMode = TwoMachinesPlay;\r
1145     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
1146       initialMode = AnalyzeFile; \r
1147     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
1148       initialMode = AnalyzeMode;\r
1149     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
1150       initialMode = MachinePlaysWhite;\r
1151     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
1152       initialMode = MachinePlaysBlack;\r
1153     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
1154       initialMode = EditGame;\r
1155     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
1156       initialMode = EditPosition;\r
1157     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
1158       initialMode = Training;\r
1159     } else {\r
1160       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);\r
1161       DisplayFatalError(buf, 0, 2);\r
1162       return;\r
1163     }\r
1164 \r
1165     if (appData.matchMode) {\r
1166         /* Set up machine vs. machine match */\r
1167         if (appData.noChessProgram) {\r
1168             DisplayFatalError(_("Can't have a match with no chess programs"),\r
1169                               0, 2);\r
1170             return;\r
1171         }\r
1172         matchMode = TRUE;\r
1173         matchGame = 1;\r
1174         if (*appData.loadGameFile != NULLCHAR) {\r
1175             int index = appData.loadGameIndex; // [HGM] autoinc\r
1176             if(index<0) lastIndex = index = 1;\r
1177             if (!LoadGameFromFile(appData.loadGameFile,\r
1178                                   index,\r
1179                                   appData.loadGameFile, FALSE)) {\r
1180                 DisplayFatalError(_("Bad game file"), 0, 1);\r
1181                 return;\r
1182             }\r
1183         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1184             int index = appData.loadPositionIndex; // [HGM] autoinc\r
1185             if(index<0) lastIndex = index = 1;\r
1186             if (!LoadPositionFromFile(appData.loadPositionFile,\r
1187                                       index,\r
1188                                       appData.loadPositionFile)) {\r
1189                 DisplayFatalError(_("Bad position file"), 0, 1);\r
1190                 return;\r
1191             }\r
1192         }\r
1193         TwoMachinesEvent();\r
1194     } else if (*appData.cmailGameName != NULLCHAR) {\r
1195         /* Set up cmail mode */\r
1196         ReloadCmailMsgEvent(TRUE);\r
1197     } else {\r
1198         /* Set up other modes */\r
1199         if (initialMode == AnalyzeFile) {\r
1200           if (*appData.loadGameFile == NULLCHAR) {\r
1201             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);\r
1202             return;\r
1203           }\r
1204         }\r
1205         if (*appData.loadGameFile != NULLCHAR) {\r
1206             (void) LoadGameFromFile(appData.loadGameFile,\r
1207                                     appData.loadGameIndex,\r
1208                                     appData.loadGameFile, TRUE);\r
1209         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1210             (void) LoadPositionFromFile(appData.loadPositionFile,\r
1211                                         appData.loadPositionIndex,\r
1212                                         appData.loadPositionFile);\r
1213             /* [HGM] try to make self-starting even after FEN load */\r
1214             /* to allow automatic setup of fairy variants with wtm */\r
1215             if(initialMode == BeginningOfGame && !blackPlaysFirst) {\r
1216                 gameMode = BeginningOfGame;\r
1217                 setboardSpoiledMachineBlack = 1;\r
1218             }\r
1219             /* [HGM] loadPos: make that every new game uses the setup */\r
1220             /* from file as long as we do not switch variant          */\r
1221             if(!blackPlaysFirst) { int i;\r
1222                 startedFromPositionFile = TRUE;\r
1223                 CopyBoard(filePosition, boards[0]);\r
1224                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];\r
1225             }\r
1226         }\r
1227         if (initialMode == AnalyzeMode) {\r
1228           if (appData.noChessProgram) {\r
1229             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);\r
1230             return;\r
1231           }\r
1232           if (appData.icsActive) {\r
1233             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);\r
1234             return;\r
1235           }\r
1236           AnalyzeModeEvent();\r
1237         } else if (initialMode == AnalyzeFile) {\r
1238           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent\r
1239           ShowThinkingEvent();\r
1240           AnalyzeFileEvent();\r
1241           AnalysisPeriodicEvent(1);\r
1242         } else if (initialMode == MachinePlaysWhite) {\r
1243           if (appData.noChessProgram) {\r
1244             DisplayFatalError(_("MachineWhite mode requires a chess engine"),\r
1245                               0, 2);\r
1246             return;\r
1247           }\r
1248           if (appData.icsActive) {\r
1249             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),\r
1250                               0, 2);\r
1251             return;\r
1252           }\r
1253           MachineWhiteEvent();\r
1254         } else if (initialMode == MachinePlaysBlack) {\r
1255           if (appData.noChessProgram) {\r
1256             DisplayFatalError(_("MachineBlack mode requires a chess engine"),\r
1257                               0, 2);\r
1258             return;\r
1259           }\r
1260           if (appData.icsActive) {\r
1261             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),\r
1262                               0, 2);\r
1263             return;\r
1264           }\r
1265           MachineBlackEvent();\r
1266         } else if (initialMode == TwoMachinesPlay) {\r
1267           if (appData.noChessProgram) {\r
1268             DisplayFatalError(_("TwoMachines mode requires a chess engine"),\r
1269                               0, 2);\r
1270             return;\r
1271           }\r
1272           if (appData.icsActive) {\r
1273             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),\r
1274                               0, 2);\r
1275             return;\r
1276           }\r
1277           TwoMachinesEvent();\r
1278         } else if (initialMode == EditGame) {\r
1279           EditGameEvent();\r
1280         } else if (initialMode == EditPosition) {\r
1281           EditPositionEvent();\r
1282         } else if (initialMode == Training) {\r
1283           if (*appData.loadGameFile == NULLCHAR) {\r
1284             DisplayFatalError(_("Training mode requires a game file"), 0, 2);\r
1285             return;\r
1286           }\r
1287           TrainingEvent();\r
1288         }\r
1289     }\r
1290 }\r
1291 \r
1292 /*\r
1293  * Establish will establish a contact to a remote host.port.\r
1294  * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
1295  *  used to talk to the host.\r
1296  * Returns 0 if okay, error code if not.\r
1297  */\r
1298 int\r
1299 establish()\r
1300 {\r
1301     char buf[MSG_SIZ];\r
1302 \r
1303     if (*appData.icsCommPort != NULLCHAR) {\r
1304         /* Talk to the host through a serial comm port */\r
1305         return OpenCommPort(appData.icsCommPort, &icsPR);\r
1306 \r
1307     } else if (*appData.gateway != NULLCHAR) {\r
1308         if (*appData.remoteShell == NULLCHAR) {\r
1309             /* Use the rcmd protocol to run telnet program on a gateway host */\r
1310             sprintf(buf, "%s %s %s",\r
1311                     appData.telnetProgram, appData.icsHost, appData.icsPort);\r
1312             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
1313 \r
1314         } else {\r
1315             /* Use the rsh program to run telnet program on a gateway host */\r
1316             if (*appData.remoteUser == NULLCHAR) {\r
1317                 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,\r
1318                         appData.gateway, appData.telnetProgram,\r
1319                         appData.icsHost, appData.icsPort);\r
1320             } else {\r
1321                 sprintf(buf, "%s %s -l %s %s %s %s",\r
1322                         appData.remoteShell, appData.gateway, \r
1323                         appData.remoteUser, appData.telnetProgram,\r
1324                         appData.icsHost, appData.icsPort);\r
1325             }\r
1326             return StartChildProcess(buf, "", &icsPR);\r
1327 \r
1328         }\r
1329     } else if (appData.useTelnet) {\r
1330         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
1331 \r
1332     } else {\r
1333         /* TCP socket interface differs somewhat between\r
1334            Unix and NT; handle details in the front end.\r
1335            */\r
1336         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
1337     }\r
1338 }\r
1339 \r
1340 void\r
1341 show_bytes(fp, buf, count)\r
1342      FILE *fp;\r
1343      char *buf;\r
1344      int count;\r
1345 {\r
1346     while (count--) {\r
1347         if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
1348             fprintf(fp, "\\%03o", *buf & 0xff);\r
1349         } else {\r
1350             putc(*buf, fp);\r
1351         }\r
1352         buf++;\r
1353     }\r
1354     fflush(fp);\r
1355 }\r
1356 \r
1357 /* Returns an errno value */\r
1358 int\r
1359 OutputMaybeTelnet(pr, message, count, outError)\r
1360      ProcRef pr;\r
1361      char *message;\r
1362      int count;\r
1363      int *outError;\r
1364 {\r
1365     char buf[8192], *p, *q, *buflim;\r
1366     int left, newcount, outcount;\r
1367 \r
1368     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
1369         *appData.gateway != NULLCHAR) {\r
1370         if (appData.debugMode) {\r
1371             fprintf(debugFP, ">ICS: ");\r
1372             show_bytes(debugFP, message, count);\r
1373             fprintf(debugFP, "\n");\r
1374         }\r
1375         return OutputToProcess(pr, message, count, outError);\r
1376     }\r
1377 \r
1378     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
1379     p = message;\r
1380     q = buf;\r
1381     left = count;\r
1382     newcount = 0;\r
1383     while (left) {\r
1384         if (q >= buflim) {\r
1385             if (appData.debugMode) {\r
1386                 fprintf(debugFP, ">ICS: ");\r
1387                 show_bytes(debugFP, buf, newcount);\r
1388                 fprintf(debugFP, "\n");\r
1389             }\r
1390             outcount = OutputToProcess(pr, buf, newcount, outError);\r
1391             if (outcount < newcount) return -1; /* to be sure */\r
1392             q = buf;\r
1393             newcount = 0;\r
1394         }\r
1395         if (*p == '\n') {\r
1396             *q++ = '\r';\r
1397             newcount++;\r
1398         } else if (((unsigned char) *p) == TN_IAC) {\r
1399             *q++ = (char) TN_IAC;\r
1400             newcount ++;\r
1401         }\r
1402         *q++ = *p++;\r
1403         newcount++;\r
1404         left--;\r
1405     }\r
1406     if (appData.debugMode) {\r
1407         fprintf(debugFP, ">ICS: ");\r
1408         show_bytes(debugFP, buf, newcount);\r
1409         fprintf(debugFP, "\n");\r
1410     }\r
1411     outcount = OutputToProcess(pr, buf, newcount, outError);\r
1412     if (outcount < newcount) return -1; /* to be sure */\r
1413     return count;\r
1414 }\r
1415 \r
1416 void\r
1417 read_from_player(isr, closure, message, count, error)\r
1418      InputSourceRef isr;\r
1419      VOIDSTAR closure;\r
1420      char *message;\r
1421      int count;\r
1422      int error;\r
1423 {\r
1424     int outError, outCount;\r
1425     static int gotEof = 0;\r
1426 \r
1427     /* Pass data read from player on to ICS */\r
1428     if (count > 0) {\r
1429         gotEof = 0;\r
1430         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
1431         if (outCount < count) {\r
1432             DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1433         }\r
1434     } else if (count < 0) {\r
1435         RemoveInputSource(isr);\r
1436         DisplayFatalError(_("Error reading from keyboard"), error, 1);\r
1437     } else if (gotEof++ > 0) {\r
1438         RemoveInputSource(isr);\r
1439         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);\r
1440     }\r
1441 }\r
1442 \r
1443 void\r
1444 SendToICS(s)\r
1445      char *s;\r
1446 {\r
1447     int count, outCount, outError;\r
1448 \r
1449     if (icsPR == NULL) return;\r
1450 \r
1451     count = strlen(s);\r
1452     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
1453     if (outCount < count) {\r
1454         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1455     }\r
1456 }\r
1457 \r
1458 /* This is used for sending logon scripts to the ICS. Sending\r
1459    without a delay causes problems when using timestamp on ICC\r
1460    (at least on my machine). */\r
1461 void\r
1462 SendToICSDelayed(s,msdelay)\r
1463      char *s;\r
1464      long msdelay;\r
1465 {\r
1466     int count, outCount, outError;\r
1467 \r
1468     if (icsPR == NULL) return;\r
1469 \r
1470     count = strlen(s);\r
1471     if (appData.debugMode) {\r
1472         fprintf(debugFP, ">ICS: ");\r
1473         show_bytes(debugFP, s, count);\r
1474         fprintf(debugFP, "\n");\r
1475     }\r
1476     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
1477                                       msdelay);\r
1478     if (outCount < count) {\r
1479         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1480     }\r
1481 }\r
1482 \r
1483 \r
1484 /* Remove all highlighting escape sequences in s\r
1485    Also deletes any suffix starting with '(' \r
1486    */\r
1487 char *\r
1488 StripHighlightAndTitle(s)\r
1489      char *s;\r
1490 {\r
1491     static char retbuf[MSG_SIZ];\r
1492     char *p = retbuf;\r
1493 \r
1494     while (*s != NULLCHAR) {\r
1495         while (*s == '\033') {\r
1496             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1497             if (*s != NULLCHAR) s++;\r
1498         }\r
1499         while (*s != NULLCHAR && *s != '\033') {\r
1500             if (*s == '(' || *s == '[') {\r
1501                 *p = NULLCHAR;\r
1502                 return retbuf;\r
1503             }\r
1504             *p++ = *s++;\r
1505         }\r
1506     }\r
1507     *p = NULLCHAR;\r
1508     return retbuf;\r
1509 }\r
1510 \r
1511 /* Remove all highlighting escape sequences in s */\r
1512 char *\r
1513 StripHighlight(s)\r
1514      char *s;\r
1515 {\r
1516     static char retbuf[MSG_SIZ];\r
1517     char *p = retbuf;\r
1518 \r
1519     while (*s != NULLCHAR) {\r
1520         while (*s == '\033') {\r
1521             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1522             if (*s != NULLCHAR) s++;\r
1523         }\r
1524         while (*s != NULLCHAR && *s != '\033') {\r
1525             *p++ = *s++;\r
1526         }\r
1527     }\r
1528     *p = NULLCHAR;\r
1529     return retbuf;\r
1530 }\r
1531 \r
1532 char *variantNames[] = VARIANT_NAMES;\r
1533 char *\r
1534 VariantName(v)\r
1535      VariantClass v;\r
1536 {\r
1537     return variantNames[v];\r
1538 }\r
1539 \r
1540 \r
1541 /* Identify a variant from the strings the chess servers use or the\r
1542    PGN Variant tag names we use. */\r
1543 VariantClass\r
1544 StringToVariant(e)\r
1545      char *e;\r
1546 {\r
1547     char *p;\r
1548     int wnum = -1;\r
1549     VariantClass v = VariantNormal;\r
1550     int i, found = FALSE;\r
1551     char buf[MSG_SIZ];\r
1552 \r
1553     if (!e) return v;\r
1554 \r
1555     /* [HGM] skip over optional board-size prefixes */\r
1556     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||\r
1557         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
1558         while( *e++ != '_');\r
1559     }\r
1560 \r
1561     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1562       if (StrCaseStr(e, variantNames[i])) {\r
1563         v = (VariantClass) i;\r
1564         found = TRUE;\r
1565         break;\r
1566       }\r
1567     }\r
1568 \r
1569     if (!found) {\r
1570       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1571           || StrCaseStr(e, "wild/fr") \r
1572           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {\r
1573         v = VariantFischeRandom;\r
1574       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1575                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1576         p += i;\r
1577         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1578         if (isdigit(*p)) {\r
1579           wnum = atoi(p);\r
1580         } else {\r
1581           wnum = -1;\r
1582         }\r
1583         switch (wnum) {\r
1584         case 0: /* FICS only, actually */\r
1585         case 1:\r
1586           /* Castling legal even if K starts on d-file */\r
1587           v = VariantWildCastle;\r
1588           break;\r
1589         case 2:\r
1590         case 3:\r
1591         case 4:\r
1592           /* Castling illegal even if K & R happen to start in\r
1593              normal positions. */\r
1594           v = VariantNoCastle;\r
1595           break;\r
1596         case 5:\r
1597         case 7:\r
1598         case 8:\r
1599         case 10:\r
1600         case 11:\r
1601         case 12:\r
1602         case 13:\r
1603         case 14:\r
1604         case 15:\r
1605         case 18:\r
1606         case 19:\r
1607           /* Castling legal iff K & R start in normal positions */\r
1608           v = VariantNormal;\r
1609           break;\r
1610         case 6:\r
1611         case 20:\r
1612         case 21:\r
1613           /* Special wilds for position setup; unclear what to do here */\r
1614           v = VariantLoadable;\r
1615           break;\r
1616         case 9:\r
1617           /* Bizarre ICC game */\r
1618           v = VariantTwoKings;\r
1619           break;\r
1620         case 16:\r
1621           v = VariantKriegspiel;\r
1622           break;\r
1623         case 17:\r
1624           v = VariantLosers;\r
1625           break;\r
1626         case 22:\r
1627           v = VariantFischeRandom;\r
1628           break;\r
1629         case 23:\r
1630           v = VariantCrazyhouse;\r
1631           break;\r
1632         case 24:\r
1633           v = VariantBughouse;\r
1634           break;\r
1635         case 25:\r
1636           v = Variant3Check;\r
1637           break;\r
1638         case 26:\r
1639           /* Not quite the same as FICS suicide! */\r
1640           v = VariantGiveaway;\r
1641           break;\r
1642         case 27:\r
1643           v = VariantAtomic;\r
1644           break;\r
1645         case 28:\r
1646           v = VariantShatranj;\r
1647           break;\r
1648 \r
1649         /* Temporary names for future ICC types.  The name *will* change in \r
1650            the next xboard/WinBoard release after ICC defines it. */\r
1651         case 29:\r
1652           v = Variant29;\r
1653           break;\r
1654         case 30:\r
1655           v = Variant30;\r
1656           break;\r
1657         case 31:\r
1658           v = Variant31;\r
1659           break;\r
1660         case 32:\r
1661           v = Variant32;\r
1662           break;\r
1663         case 33:\r
1664           v = Variant33;\r
1665           break;\r
1666         case 34:\r
1667           v = Variant34;\r
1668           break;\r
1669         case 35:\r
1670           v = Variant35;\r
1671           break;\r
1672         case 36:\r
1673           v = Variant36;\r
1674           break;\r
1675         case 37:\r
1676           v = VariantShogi;\r
1677           break;\r
1678         case 38:\r
1679           v = VariantXiangqi;\r
1680           break;\r
1681         case 39:\r
1682           v = VariantCourier;\r
1683           break;\r
1684         case 40:\r
1685           v = VariantGothic;\r
1686           break;\r
1687         case 41:\r
1688           v = VariantCapablanca;\r
1689           break;\r
1690         case 42:\r
1691           v = VariantKnightmate;\r
1692           break;\r
1693         case 43:\r
1694           v = VariantFairy;\r
1695           break;\r
1696         case 44:\r
1697           v = VariantCylinder;\r
1698           break;\r
1699         case 45:\r
1700           v = VariantFalcon;\r
1701           break;\r
1702         case 46:\r
1703           v = VariantCapaRandom;\r
1704           break;\r
1705         case 47:\r
1706           v = VariantBerolina;\r
1707           break;\r
1708         case 48:\r
1709           v = VariantJanus;\r
1710           break;\r
1711         case 49:\r
1712           v = VariantSuper;\r
1713           break;\r
1714         case 50:\r
1715           v = VariantGreat;\r
1716           break;\r
1717         case -1:\r
1718           /* Found "wild" or "w" in the string but no number;\r
1719              must assume it's normal chess. */\r
1720           v = VariantNormal;\r
1721           break;\r
1722         default:\r
1723           sprintf(buf, _("Unknown wild type %d"), wnum);\r
1724           DisplayError(buf, 0);\r
1725           v = VariantUnknown;\r
1726           break;\r
1727         }\r
1728       }\r
1729     }\r
1730     if (appData.debugMode) {\r
1731       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),\r
1732               e, wnum, VariantName(v));\r
1733     }\r
1734     return v;\r
1735 }\r
1736 \r
1737 static int leftover_start = 0, leftover_len = 0;\r
1738 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1739 \r
1740 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1741    advance *index beyond it, and set leftover_start to the new value of\r
1742    *index; else return FALSE.  If pattern contains the character '*', it\r
1743    matches any sequence of characters not containing '\r', '\n', or the\r
1744    character following the '*' (if any), and the matched sequence(s) are\r
1745    copied into star_match.\r
1746    */\r
1747 int\r
1748 looking_at(buf, index, pattern)\r
1749      char *buf;\r
1750      int *index;\r
1751      char *pattern;\r
1752 {\r
1753     char *bufp = &buf[*index], *patternp = pattern;\r
1754     int star_count = 0;\r
1755     char *matchp = star_match[0];\r
1756     \r
1757     for (;;) {\r
1758         if (*patternp == NULLCHAR) {\r
1759             *index = leftover_start = bufp - buf;\r
1760             *matchp = NULLCHAR;\r
1761             return TRUE;\r
1762         }\r
1763         if (*bufp == NULLCHAR) return FALSE;\r
1764         if (*patternp == '*') {\r
1765             if (*bufp == *(patternp + 1)) {\r
1766                 *matchp = NULLCHAR;\r
1767                 matchp = star_match[++star_count];\r
1768                 patternp += 2;\r
1769                 bufp++;\r
1770                 continue;\r
1771             } else if (*bufp == '\n' || *bufp == '\r') {\r
1772                 patternp++;\r
1773                 if (*patternp == NULLCHAR)\r
1774                   continue;\r
1775                 else\r
1776                   return FALSE;\r
1777             } else {\r
1778                 *matchp++ = *bufp++;\r
1779                 continue;\r
1780             }\r
1781         }\r
1782         if (*patternp != *bufp) return FALSE;\r
1783         patternp++;\r
1784         bufp++;\r
1785     }\r
1786 }\r
1787 \r
1788 void\r
1789 SendToPlayer(data, length)\r
1790      char *data;\r
1791      int length;\r
1792 {\r
1793     int error, outCount;\r
1794     outCount = OutputToProcess(NoProc, data, length, &error);\r
1795     if (outCount < length) {\r
1796         DisplayFatalError(_("Error writing to display"), error, 1);\r
1797     }\r
1798 }\r
1799 \r
1800 void\r
1801 PackHolding(packed, holding)\r
1802      char packed[];\r
1803      char *holding;\r
1804 {\r
1805     char *p = holding;\r
1806     char *q = packed;\r
1807     int runlength = 0;\r
1808     int curr = 9999;\r
1809     do {\r
1810         if (*p == curr) {\r
1811             runlength++;\r
1812         } else {\r
1813             switch (runlength) {\r
1814               case 0:\r
1815                 break;\r
1816               case 1:\r
1817                 *q++ = curr;\r
1818                 break;\r
1819               case 2:\r
1820                 *q++ = curr;\r
1821                 *q++ = curr;\r
1822                 break;\r
1823               default:\r
1824                 sprintf(q, "%d", runlength);\r
1825                 while (*q) q++;\r
1826                 *q++ = curr;\r
1827                 break;\r
1828             }\r
1829             runlength = 1;\r
1830             curr = *p;\r
1831         }\r
1832     } while (*p++);\r
1833     *q = NULLCHAR;\r
1834 }\r
1835 \r
1836 /* Telnet protocol requests from the front end */\r
1837 void\r
1838 TelnetRequest(ddww, option)\r
1839      unsigned char ddww, option;\r
1840 {\r
1841     unsigned char msg[3];\r
1842     int outCount, outError;\r
1843 \r
1844     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1845 \r
1846     if (appData.debugMode) {\r
1847         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1848         switch (ddww) {\r
1849           case TN_DO:\r
1850             ddwwStr = "DO";\r
1851             break;\r
1852           case TN_DONT:\r
1853             ddwwStr = "DONT";\r
1854             break;\r
1855           case TN_WILL:\r
1856             ddwwStr = "WILL";\r
1857             break;\r
1858           case TN_WONT:\r
1859             ddwwStr = "WONT";\r
1860             break;\r
1861           default:\r
1862             ddwwStr = buf1;\r
1863             sprintf(buf1, "%d", ddww);\r
1864             break;\r
1865         }\r
1866         switch (option) {\r
1867           case TN_ECHO:\r
1868             optionStr = "ECHO";\r
1869             break;\r
1870           default:\r
1871             optionStr = buf2;\r
1872             sprintf(buf2, "%d", option);\r
1873             break;\r
1874         }\r
1875         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1876     }\r
1877     msg[0] = TN_IAC;\r
1878     msg[1] = ddww;\r
1879     msg[2] = option;\r
1880     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1881     if (outCount < 3) {\r
1882         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1883     }\r
1884 }\r
1885 \r
1886 void\r
1887 DoEcho()\r
1888 {\r
1889     if (!appData.icsActive) return;\r
1890     TelnetRequest(TN_DO, TN_ECHO);\r
1891 }\r
1892 \r
1893 void\r
1894 DontEcho()\r
1895 {\r
1896     if (!appData.icsActive) return;\r
1897     TelnetRequest(TN_DONT, TN_ECHO);\r
1898 }\r
1899 \r
1900 void\r
1901 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
1902 {\r
1903     /* put the holdings sent to us by the server on the board holdings area */\r
1904     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
1905     char p;\r
1906     ChessSquare piece;\r
1907 \r
1908     if(gameInfo.holdingsWidth < 2)  return;\r
1909 \r
1910     if( (int)lowestPiece >= BlackPawn ) {\r
1911         holdingsColumn = 0;\r
1912         countsColumn = 1;\r
1913         holdingsStartRow = BOARD_HEIGHT-1;\r
1914         direction = -1;\r
1915     } else {\r
1916         holdingsColumn = BOARD_WIDTH-1;\r
1917         countsColumn = BOARD_WIDTH-2;\r
1918         holdingsStartRow = 0;\r
1919         direction = 1;\r
1920     }\r
1921 \r
1922     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
1923         board[i][holdingsColumn] = EmptySquare;\r
1924         board[i][countsColumn]   = (ChessSquare) 0;\r
1925     }\r
1926     while( (p=*holdings++) != NULLCHAR ) {\r
1927         piece = CharToPiece( ToUpper(p) );\r
1928         if(piece == EmptySquare) continue;\r
1929         /*j = (int) piece - (int) WhitePawn;*/\r
1930         j = PieceToNumber(piece);\r
1931         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1932         if(j < 0) continue;               /* should not happen */\r
1933         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );\r
1934         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
1935         board[holdingsStartRow+j*direction][countsColumn]++;\r
1936     }\r
1937 \r
1938 }\r
1939 \r
1940 \r
1941 void\r
1942 VariantSwitch(Board board, VariantClass newVariant)\r
1943 {\r
1944    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;\r
1945    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;\r
1946    Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;\r
1947 \r
1948    startedFromPositionFile = FALSE;\r
1949    if(gameInfo.variant == newVariant) return;\r
1950 \r
1951    /* [HGM] This routine is called each time an assignment is made to\r
1952     * gameInfo.variant during a game, to make sure the board sizes\r
1953     * are set to match the new variant. If that means adding or deleting\r
1954     * holdings, we shift the playing board accordingly\r
1955     * This kludge is needed because in ICS observe mode, we get boards\r
1956     * of an ongoing game without knowing the variant, and learn about the\r
1957     * latter only later. This can be because of the move list we requested,\r
1958     * in which case the game history is refilled from the beginning anyway,\r
1959     * but also when receiving holdings of a crazyhouse game. In the latter\r
1960     * case we want to add those holdings to the already received position.\r
1961     */\r
1962 \r
1963 \r
1964   if (appData.debugMode) {\r
1965     fprintf(debugFP, "Switch board from %s to %s\n",\r
1966                VariantName(gameInfo.variant), VariantName(newVariant));\r
1967     setbuf(debugFP, NULL);\r
1968   }\r
1969     shuffleOpenings = 0;       /* [HGM] shuffle */\r
1970     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
1971     switch(newVariant) {\r
1972             case VariantShogi:\r
1973               newWidth = 9;  newHeight = 9;\r
1974               gameInfo.holdingsSize = 7;\r
1975             case VariantBughouse:\r
1976             case VariantCrazyhouse:\r
1977               newHoldingsWidth = 2; break;\r
1978             default:\r
1979               newHoldingsWidth = gameInfo.holdingsSize = 0;\r
1980     }\r
1981 \r
1982     if(newWidth  != gameInfo.boardWidth  ||\r
1983        newHeight != gameInfo.boardHeight ||\r
1984        newHoldingsWidth != gameInfo.holdingsWidth ) {\r
1985 \r
1986         /* shift position to new playing area, if needed */\r
1987         if(newHoldingsWidth > gameInfo.holdingsWidth) {\r
1988            for(i=0; i<BOARD_HEIGHT; i++) \r
1989                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)\r
1990                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
1991                                                      board[i][j];\r
1992            for(i=0; i<newHeight; i++) {\r
1993                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;\r
1994                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;\r
1995            }\r
1996         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {\r
1997            for(i=0; i<BOARD_HEIGHT; i++)\r
1998                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
1999                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
2000                                                  board[i][j];\r
2001         }\r
2002 \r
2003         gameInfo.boardWidth  = newWidth;\r
2004         gameInfo.boardHeight = newHeight;\r
2005         gameInfo.holdingsWidth = newHoldingsWidth;\r
2006         gameInfo.variant = newVariant;\r
2007         InitDrawingSizes(-2, 0);\r
2008 \r
2009         /* [HGM] The following should definitely be solved in a better way */\r
2010 #if 0\r
2011         CopyBoard(board, tempBoard); /* save position in case it is board[0] */\r
2012         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];\r
2013         saveEP = epStatus[0];\r
2014 #endif\r
2015         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */\r
2016 #if 0\r
2017         epStatus[0] = saveEP;\r
2018         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];\r
2019         CopyBoard(tempBoard, board); /* restore position received from ICS   */\r
2020 #endif\r
2021     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }\r
2022 \r
2023     forwardMostMove = oldForwardMostMove;\r
2024     backwardMostMove = oldBackwardMostMove;\r
2025     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */\r
2026 }\r
2027 \r
2028 static int loggedOn = FALSE;\r
2029 \r
2030 /*-- Game start info cache: --*/\r
2031 int gs_gamenum;\r
2032 char gs_kind[MSG_SIZ];\r
2033 static char player1Name[128] = "";\r
2034 static char player2Name[128] = "";\r
2035 static int player1Rating = -1;\r
2036 static int player2Rating = -1;\r
2037 /*----------------------------*/\r
2038 \r
2039 ColorClass curColor = ColorNormal;\r
2040 int suppressKibitz = 0;\r
2041 \r
2042 void\r
2043 read_from_ics(isr, closure, data, count, error)\r
2044      InputSourceRef isr;\r
2045      VOIDSTAR closure;\r
2046      char *data;\r
2047      int count;\r
2048      int error;\r
2049 {\r
2050 #define BUF_SIZE 8192\r
2051 #define STARTED_NONE 0\r
2052 #define STARTED_MOVES 1\r
2053 #define STARTED_BOARD 2\r
2054 #define STARTED_OBSERVE 3\r
2055 #define STARTED_HOLDINGS 4\r
2056 #define STARTED_CHATTER 5\r
2057 #define STARTED_COMMENT 6\r
2058 #define STARTED_MOVES_NOHIDE 7\r
2059     \r
2060     static int started = STARTED_NONE;\r
2061     static char parse[20000];\r
2062     static int parse_pos = 0;\r
2063     static char buf[BUF_SIZE + 1];\r
2064     static int firstTime = TRUE, intfSet = FALSE;\r
2065     static ColorClass prevColor = ColorNormal;\r
2066     static int savingComment = FALSE;\r
2067     char str[500];\r
2068     int i, oldi;\r
2069     int buf_len;\r
2070     int next_out;\r
2071     int tkind;\r
2072     int backup;    /* [DM] For zippy color lines */\r
2073     char *p;\r
2074 \r
2075     if (appData.debugMode) {\r
2076       if (!error) {\r
2077         fprintf(debugFP, "<ICS: ");\r
2078         show_bytes(debugFP, data, count);\r
2079         fprintf(debugFP, "\n");\r
2080       }\r
2081     }\r
2082 \r
2083     if (appData.debugMode) { int f = forwardMostMove;\r
2084         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,\r
2085                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
2086     }\r
2087     if (count > 0) {\r
2088         /* If last read ended with a partial line that we couldn't parse,\r
2089            prepend it to the new read and try again. */\r
2090         if (leftover_len > 0) {\r
2091             for (i=0; i<leftover_len; i++)\r
2092               buf[i] = buf[leftover_start + i];\r
2093         }\r
2094 \r
2095         /* Copy in new characters, removing nulls and \r's */\r
2096         buf_len = leftover_len;\r
2097         for (i = 0; i < count; i++) {\r
2098             if (data[i] != NULLCHAR && data[i] != '\r')\r
2099               buf[buf_len++] = data[i];\r
2100             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && \r
2101                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') \r
2102                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous\r
2103         }\r
2104 \r
2105         buf[buf_len] = NULLCHAR;\r
2106         next_out = leftover_len;\r
2107         leftover_start = 0;\r
2108         \r
2109         i = 0;\r
2110         while (i < buf_len) {\r
2111             /* Deal with part of the TELNET option negotiation\r
2112                protocol.  We refuse to do anything beyond the\r
2113                defaults, except that we allow the WILL ECHO option,\r
2114                which ICS uses to turn off password echoing when we are\r
2115                directly connected to it.  We reject this option\r
2116                if localLineEditing mode is on (always on in xboard)\r
2117                and we are talking to port 23, which might be a real\r
2118                telnet server that will try to keep WILL ECHO on permanently.\r
2119              */\r
2120             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
2121                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
2122                 unsigned char option;\r
2123                 oldi = i;\r
2124                 switch ((unsigned char) buf[++i]) {\r
2125                   case TN_WILL:\r
2126                     if (appData.debugMode)\r
2127                       fprintf(debugFP, "\n<WILL ");\r
2128                     switch (option = (unsigned char) buf[++i]) {\r
2129                       case TN_ECHO:\r
2130                         if (appData.debugMode)\r
2131                           fprintf(debugFP, "ECHO ");\r
2132                         /* Reply only if this is a change, according\r
2133                            to the protocol rules. */\r
2134                         if (remoteEchoOption) break;\r
2135                         if (appData.localLineEditing &&\r
2136                             atoi(appData.icsPort) == TN_PORT) {\r
2137                             TelnetRequest(TN_DONT, TN_ECHO);\r
2138                         } else {\r
2139                             EchoOff();\r
2140                             TelnetRequest(TN_DO, TN_ECHO);\r
2141                             remoteEchoOption = TRUE;\r
2142                         }\r
2143                         break;\r
2144                       default:\r
2145                         if (appData.debugMode)\r
2146                           fprintf(debugFP, "%d ", option);\r
2147                         /* Whatever this is, we don't want it. */\r
2148                         TelnetRequest(TN_DONT, option);\r
2149                         break;\r
2150                     }\r
2151                     break;\r
2152                   case TN_WONT:\r
2153                     if (appData.debugMode)\r
2154                       fprintf(debugFP, "\n<WONT ");\r
2155                     switch (option = (unsigned char) buf[++i]) {\r
2156                       case TN_ECHO:\r
2157                         if (appData.debugMode)\r
2158                           fprintf(debugFP, "ECHO ");\r
2159                         /* Reply only if this is a change, according\r
2160                            to the protocol rules. */\r
2161                         if (!remoteEchoOption) break;\r
2162                         EchoOn();\r
2163                         TelnetRequest(TN_DONT, TN_ECHO);\r
2164                         remoteEchoOption = FALSE;\r
2165                         break;\r
2166                       default:\r
2167                         if (appData.debugMode)\r
2168                           fprintf(debugFP, "%d ", (unsigned char) option);\r
2169                         /* Whatever this is, it must already be turned\r
2170                            off, because we never agree to turn on\r
2171                            anything non-default, so according to the\r
2172                            protocol rules, we don't reply. */\r
2173                         break;\r
2174                     }\r
2175                     break;\r
2176                   case TN_DO:\r
2177                     if (appData.debugMode)\r
2178                       fprintf(debugFP, "\n<DO ");\r
2179                     switch (option = (unsigned char) buf[++i]) {\r
2180                       default:\r
2181                         /* Whatever this is, we refuse to do it. */\r
2182                         if (appData.debugMode)\r
2183                           fprintf(debugFP, "%d ", option);\r
2184                         TelnetRequest(TN_WONT, option);\r
2185                         break;\r
2186                     }\r
2187                     break;\r
2188                   case TN_DONT:\r
2189                     if (appData.debugMode)\r
2190                       fprintf(debugFP, "\n<DONT ");\r
2191                     switch (option = (unsigned char) buf[++i]) {\r
2192                       default:\r
2193                         if (appData.debugMode)\r
2194                           fprintf(debugFP, "%d ", option);\r
2195                         /* Whatever this is, we are already not doing\r
2196                            it, because we never agree to do anything\r
2197                            non-default, so according to the protocol\r
2198                            rules, we don't reply. */\r
2199                         break;\r
2200                     }\r
2201                     break;\r
2202                   case TN_IAC:\r
2203                     if (appData.debugMode)\r
2204                       fprintf(debugFP, "\n<IAC ");\r
2205                     /* Doubled IAC; pass it through */\r
2206                     i--;\r
2207                     break;\r
2208                   default:\r
2209                     if (appData.debugMode)\r
2210                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
2211                     /* Drop all other telnet commands on the floor */\r
2212                     break;\r
2213                 }\r
2214                 if (oldi > next_out)\r
2215                   SendToPlayer(&buf[next_out], oldi - next_out);\r
2216                 if (++i > next_out)\r
2217                   next_out = i;\r
2218                 continue;\r
2219             }\r
2220                 \r
2221             /* OK, this at least will *usually* work */\r
2222             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
2223                 loggedOn = TRUE;\r
2224             }\r
2225             \r
2226             if (loggedOn && !intfSet) {\r
2227                 if (ics_type == ICS_ICC) {\r
2228                   sprintf(str,\r
2229                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
2230                           programVersion);\r
2231 \r
2232                 } else if (ics_type == ICS_CHESSNET) {\r
2233                   sprintf(str, "/style 12\n");\r
2234                 } else {\r
2235                   strcpy(str, "alias $ @\n$set interface ");\r
2236                   strcat(str, programVersion);\r
2237                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
2238 #ifdef WIN32\r
2239                   strcat(str, "$iset nohighlight 1\n");\r
2240 #endif\r
2241                   strcat(str, "$iset lock 1\n$style 12\n");\r
2242                 }\r
2243                 SendToICS(str);\r
2244                 intfSet = TRUE;\r
2245             }\r
2246 \r
2247             if (started == STARTED_COMMENT) {\r
2248                 /* Accumulate characters in comment */\r
2249                 parse[parse_pos++] = buf[i];\r
2250                 if (buf[i] == '\n') {\r
2251                     parse[parse_pos] = NULLCHAR;\r
2252                     if(!suppressKibitz) // [HGM] kibitz\r
2253                         AppendComment(forwardMostMove, StripHighlight(parse));\r
2254                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window\r
2255                         int nrDigit = 0, nrAlph = 0, i;\r
2256                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input\r
2257                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }\r
2258                         parse[parse_pos] = NULLCHAR;\r
2259                         // try to be smart: if it does not look like search info, it should go to\r
2260                         // ICS interaction window after all, not to engine-output window.\r
2261                         for(i=0; i<parse_pos; i++) { // count letters and digits\r
2262                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');\r
2263                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');\r
2264                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');\r
2265                         }\r
2266                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info\r
2267                             OutputKibitz(suppressKibitz, parse);\r
2268                         } else {\r
2269                             char tmp[MSG_SIZ];\r
2270                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);\r
2271                             SendToPlayer(tmp, strlen(tmp));\r
2272                         }\r
2273                     }\r
2274                     started = STARTED_NONE;\r
2275                 } else {\r
2276                     /* Don't match patterns against characters in chatter */\r
2277                     i++;\r
2278                     continue;\r
2279                 }\r
2280             }\r
2281             if (started == STARTED_CHATTER) {\r
2282                 if (buf[i] != '\n') {\r
2283                     /* Don't match patterns against characters in chatter */\r
2284                     i++;\r
2285                     continue;\r
2286                 }\r
2287                 started = STARTED_NONE;\r
2288             }\r
2289 \r
2290             /* Kludge to deal with rcmd protocol */\r
2291             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2292                 DisplayFatalError(&buf[1], 0, 1);\r
2293                 continue;\r
2294             } else {\r
2295                 firstTime = FALSE;\r
2296             }\r
2297 \r
2298             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2299                 ics_type = ICS_ICC;\r
2300                 ics_prefix = "/";\r
2301                 if (appData.debugMode)\r
2302                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2303                 continue;\r
2304             }\r
2305             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2306                 ics_type = ICS_FICS;\r
2307                 ics_prefix = "$";\r
2308                 if (appData.debugMode)\r
2309                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2310                 continue;\r
2311             }\r
2312             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2313                 ics_type = ICS_CHESSNET;\r
2314                 ics_prefix = "/";\r
2315                 if (appData.debugMode)\r
2316                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2317                 continue;\r
2318             }\r
2319 \r
2320             if (!loggedOn &&\r
2321                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2322                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2323                  looking_at(buf, &i, "will be \"*\""))) {\r
2324               strcpy(ics_handle, star_match[0]);\r
2325               continue;\r
2326             }\r
2327 \r
2328             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2329               char buf[MSG_SIZ];\r
2330               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2331               DisplayIcsInteractionTitle(buf);\r
2332               have_set_title = TRUE;\r
2333             }\r
2334 \r
2335             /* skip finger notes */\r
2336             if (started == STARTED_NONE &&\r
2337                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2338                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2339                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2340               started = STARTED_CHATTER;\r
2341               i += 3;\r
2342               continue;\r
2343             }\r
2344 \r
2345             /* skip formula vars */\r
2346             if (started == STARTED_NONE &&\r
2347                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2348               started = STARTED_CHATTER;\r
2349               i += 3;\r
2350               continue;\r
2351             }\r
2352 \r
2353             oldi = i;\r
2354             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window\r
2355             if (appData.autoKibitz && started == STARTED_NONE && \r
2356                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze\r
2357                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {\r
2358                 if(looking_at(buf, &i, "* kibitzes: ") &&\r
2359                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || \r
2360                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent\r
2361                         suppressKibitz = TRUE;\r
2362                         if((StrStr(star_match[0], gameInfo.white) == star_match[0])\r
2363                                 && (gameMode == IcsPlayingWhite) ||\r
2364                            (StrStr(star_match[0], gameInfo.black) == star_match[0])\r
2365                                 && (gameMode == IcsPlayingBlack)   ) // opponent kibitz\r
2366                             started = STARTED_CHATTER; // own kibitz we simply discard\r
2367                         else {\r
2368                             started = STARTED_COMMENT; // make sure it will be collected in parse[]\r
2369                             parse_pos = 0; parse[0] = NULLCHAR;\r
2370                             savingComment = TRUE;\r
2371                             suppressKibitz = gameMode != IcsObserving ? 2 :\r
2372                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;\r
2373                         } \r
2374                         continue;\r
2375                 } else\r
2376                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz\r
2377                     started = STARTED_CHATTER;\r
2378                     suppressKibitz = TRUE;\r
2379                 }\r
2380             } // [HGM] kibitz: end of patch\r
2381 \r
2382             if (appData.zippyTalk || appData.zippyPlay) {\r
2383                 /* [DM] Backup address for color zippy lines */\r
2384                 backup = i;\r
2385 #if ZIPPY\r
2386        #ifdef WIN32\r
2387                if (loggedOn == TRUE)\r
2388                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||\r
2389                           (appData.zippyPlay && ZippyMatch(buf, &backup)));\r
2390        #else\r
2391                 if (ZippyControl(buf, &i) ||\r
2392                     ZippyConverse(buf, &i) ||\r
2393                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2394                       loggedOn = TRUE;\r
2395                       if (!appData.colorize) continue;\r
2396                 }\r
2397        #endif\r
2398 #endif\r
2399             } // [DM] 'else { ' deleted\r
2400                 if (/* Don't color "message" or "messages" output */\r
2401                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2402                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2403                     looking_at(buf, &i, "--* (*:*): ") ||\r
2404                     /* Regular tells and says */\r
2405                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2406                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2407                     looking_at(buf, &i, "* says: ") ||\r
2408                     /* Message notifications (same color as tells) */\r
2409                     looking_at(buf, &i, "* has left a message ") ||\r
2410                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2411                     /* Whispers and kibitzes */\r
2412                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2413                     looking_at(buf, &i, "* kibitzes: ") ||\r
2414                     /* Channel tells */\r
2415                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2416 \r
2417                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2418                       /* Avoid "tells you:" spoofs in channels */\r
2419                      tkind = 3;\r
2420                   }\r
2421                   if (star_match[0][0] == NULLCHAR ||\r
2422                       strchr(star_match[0], ' ') ||\r
2423                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2424                     /* Reject bogus matches */\r
2425                     i = oldi;\r
2426                   } else {\r
2427                     if (appData.colorize) {\r
2428                       if (oldi > next_out) {\r
2429                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2430                         next_out = oldi;\r
2431                       }\r
2432                       switch (tkind) {\r
2433                       case 1:\r
2434                         Colorize(ColorTell, FALSE);\r
2435                         curColor = ColorTell;\r
2436                         break;\r
2437                       case 2:\r
2438                         Colorize(ColorKibitz, FALSE);\r
2439                         curColor = ColorKibitz;\r
2440                         break;\r
2441                       case 3:\r
2442                         p = strrchr(star_match[1], '(');\r
2443                         if (p == NULL) {\r
2444                           p = star_match[1];\r
2445                         } else {\r
2446                           p++;\r
2447                         }\r
2448                         if (atoi(p) == 1) {\r
2449                           Colorize(ColorChannel1, FALSE);\r
2450                           curColor = ColorChannel1;\r
2451                         } else {\r
2452                           Colorize(ColorChannel, FALSE);\r
2453                           curColor = ColorChannel;\r
2454                         }\r
2455                         break;\r
2456                       case 5:\r
2457                         curColor = ColorNormal;\r
2458                         break;\r
2459                       }\r
2460                     }\r
2461                     if (started == STARTED_NONE && appData.autoComment &&\r
2462                         (gameMode == IcsObserving ||\r
2463                          gameMode == IcsPlayingWhite ||\r
2464                          gameMode == IcsPlayingBlack)) {\r
2465                       parse_pos = i - oldi;\r
2466                       memcpy(parse, &buf[oldi], parse_pos);\r
2467                       parse[parse_pos] = NULLCHAR;\r
2468                       started = STARTED_COMMENT;\r
2469                       savingComment = TRUE;\r
2470                     } else {\r
2471                       started = STARTED_CHATTER;\r
2472                       savingComment = FALSE;\r
2473                     }\r
2474                     loggedOn = TRUE;\r
2475                     continue;\r
2476                   }\r
2477                 }\r
2478 \r
2479                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2480                     looking_at(buf, &i, "* c-shouts: ")) {\r
2481                     if (appData.colorize) {\r
2482                         if (oldi > next_out) {\r
2483                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2484                             next_out = oldi;\r
2485                         }\r
2486                         Colorize(ColorSShout, FALSE);\r
2487                         curColor = ColorSShout;\r
2488                     }\r
2489                     loggedOn = TRUE;\r
2490                     started = STARTED_CHATTER;\r
2491                     continue;\r
2492                 }\r
2493 \r
2494                 if (looking_at(buf, &i, "--->")) {\r
2495                     loggedOn = TRUE;\r
2496                     continue;\r
2497                 }\r
2498 \r
2499                 if (looking_at(buf, &i, "* shouts: ") ||\r
2500                     looking_at(buf, &i, "--> ")) {\r
2501                     if (appData.colorize) {\r
2502                         if (oldi > next_out) {\r
2503                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2504                             next_out = oldi;\r
2505                         }\r
2506                         Colorize(ColorShout, FALSE);\r
2507                         curColor = ColorShout;\r
2508                     }\r
2509                     loggedOn = TRUE;\r
2510                     started = STARTED_CHATTER;\r
2511                     continue;\r
2512                 }\r
2513 \r
2514                 if (looking_at( buf, &i, "Challenge:")) {\r
2515                     if (appData.colorize) {\r
2516                         if (oldi > next_out) {\r
2517                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2518                             next_out = oldi;\r
2519                         }\r
2520                         Colorize(ColorChallenge, FALSE);\r
2521                         curColor = ColorChallenge;\r
2522                     }\r
2523                     loggedOn = TRUE;\r
2524                     continue;\r
2525                 }\r
2526 \r
2527                 if (looking_at(buf, &i, "* offers you") ||\r
2528                     looking_at(buf, &i, "* offers to be") ||\r
2529                     looking_at(buf, &i, "* would like to") ||\r
2530                     looking_at(buf, &i, "* requests to") ||\r
2531                     looking_at(buf, &i, "Your opponent offers") ||\r
2532                     looking_at(buf, &i, "Your opponent requests")) {\r
2533 \r
2534                     if (appData.colorize) {\r
2535                         if (oldi > next_out) {\r
2536                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2537                             next_out = oldi;\r
2538                         }\r
2539                         Colorize(ColorRequest, FALSE);\r
2540                         curColor = ColorRequest;\r
2541                     }\r
2542                     continue;\r
2543                 }\r
2544 \r
2545                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2546                     if (appData.colorize) {\r
2547                         if (oldi > next_out) {\r
2548                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2549                             next_out = oldi;\r
2550                         }\r
2551                         Colorize(ColorSeek, FALSE);\r
2552                         curColor = ColorSeek;\r
2553                     }\r
2554                     continue;\r
2555             }\r
2556 \r
2557             if (looking_at(buf, &i, "\\   ")) {\r
2558                 if (prevColor != ColorNormal) {\r
2559                     if (oldi > next_out) {\r
2560                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2561                         next_out = oldi;\r
2562                     }\r
2563                     Colorize(prevColor, TRUE);\r
2564                     curColor = prevColor;\r
2565                 }\r
2566                 if (savingComment) {\r
2567                     parse_pos = i - oldi;\r
2568                     memcpy(parse, &buf[oldi], parse_pos);\r
2569                     parse[parse_pos] = NULLCHAR;\r
2570                     started = STARTED_COMMENT;\r
2571                 } else {\r
2572                     started = STARTED_CHATTER;\r
2573                 }\r
2574                 continue;\r
2575             }\r
2576 \r
2577             if (looking_at(buf, &i, "Black Strength :") ||\r
2578                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2579                 looking_at(buf, &i, "<10>") ||\r
2580                 looking_at(buf, &i, "#@#")) {\r
2581                 /* Wrong board style */\r
2582                 loggedOn = TRUE;\r
2583                 SendToICS(ics_prefix);\r
2584                 SendToICS("set style 12\n");\r
2585                 SendToICS(ics_prefix);\r
2586                 SendToICS("refresh\n");\r
2587                 continue;\r
2588             }\r
2589             \r
2590             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2591                 ICSInitScript();\r
2592                 have_sent_ICS_logon = 1;\r
2593                 continue;\r
2594             }\r
2595               \r
2596             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2597                 (looking_at(buf, &i, "\n<12> ") ||\r
2598                  looking_at(buf, &i, "<12> "))) {\r
2599                 loggedOn = TRUE;\r
2600                 if (oldi > next_out) {\r
2601                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2602                 }\r
2603                 next_out = i;\r
2604                 started = STARTED_BOARD;\r
2605                 parse_pos = 0;\r
2606                 continue;\r
2607             }\r
2608 \r
2609             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2610                 looking_at(buf, &i, "<b1> ")) {\r
2611                 if (oldi > next_out) {\r
2612                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2613                 }\r
2614                 next_out = i;\r
2615                 started = STARTED_HOLDINGS;\r
2616                 parse_pos = 0;\r
2617                 continue;\r
2618             }\r
2619 \r
2620             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2621                 loggedOn = TRUE;\r
2622                 /* Header for a move list -- first line */\r
2623 \r
2624                 switch (ics_getting_history) {\r
2625                   case H_FALSE:\r
2626                     switch (gameMode) {\r
2627                       case IcsIdle:\r
2628                       case BeginningOfGame:\r
2629                         /* User typed "moves" or "oldmoves" while we\r
2630                            were idle.  Pretend we asked for these\r
2631                            moves and soak them up so user can step\r
2632                            through them and/or save them.\r
2633                            */\r
2634                         Reset(FALSE, TRUE);\r
2635                         gameMode = IcsObserving;\r
2636                         ModeHighlight();\r
2637                         ics_gamenum = -1;\r
2638                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2639                         break;\r
2640                       case EditGame: /*?*/\r
2641                       case EditPosition: /*?*/\r
2642                         /* Should above feature work in these modes too? */\r
2643                         /* For now it doesn't */\r
2644                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2645                         break;\r
2646                       default:\r
2647                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2648                         break;\r
2649                     }\r
2650                     break;\r
2651                   case H_REQUESTED:\r
2652                     /* Is this the right one? */\r
2653                     if (gameInfo.white && gameInfo.black &&\r
2654                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2655                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2656                         /* All is well */\r
2657                         ics_getting_history = H_GOT_REQ_HEADER;\r
2658                     }\r
2659                     break;\r
2660                   case H_GOT_REQ_HEADER:\r
2661                   case H_GOT_UNREQ_HEADER:\r
2662                   case H_GOT_UNWANTED_HEADER:\r
2663                   case H_GETTING_MOVES:\r
2664                     /* Should not happen */\r
2665                     DisplayError(_("Error gathering move list: two headers"), 0);\r
2666                     ics_getting_history = H_FALSE;\r
2667                     break;\r
2668                 }\r
2669 \r
2670                 /* Save player ratings into gameInfo if needed */\r
2671                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2672                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2673                     (gameInfo.whiteRating == -1 ||\r
2674                      gameInfo.blackRating == -1)) {\r
2675 \r
2676                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2677                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2678                     if (appData.debugMode)\r
2679                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), \r
2680                               gameInfo.whiteRating, gameInfo.blackRating);\r
2681                 }\r
2682                 continue;\r
2683             }\r
2684 \r
2685             if (looking_at(buf, &i,\r
2686               "* * match, initial time: * minute*, increment: * second")) {\r
2687                 /* Header for a move list -- second line */\r
2688                 /* Initial board will follow if this is a wild game */\r
2689                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2690                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2691                 gameInfo.event = StrSave(str);\r
2692                 /* [HGM] we switched variant. Translate boards if needed. */\r
2693                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2694                 continue;\r
2695             }\r
2696 \r
2697             if (looking_at(buf, &i, "Move  ")) {\r
2698                 /* Beginning of a move list */\r
2699                 switch (ics_getting_history) {\r
2700                   case H_FALSE:\r
2701                     /* Normally should not happen */\r
2702                     /* Maybe user hit reset while we were parsing */\r
2703                     break;\r
2704                   case H_REQUESTED:\r
2705                     /* Happens if we are ignoring a move list that is not\r
2706                      * the one we just requested.  Common if the user\r
2707                      * tries to observe two games without turning off\r
2708                      * getMoveList */\r
2709                     break;\r
2710                   case H_GETTING_MOVES:\r
2711                     /* Should not happen */\r
2712                     DisplayError(_("Error gathering move list: nested"), 0);\r
2713                     ics_getting_history = H_FALSE;\r
2714                     break;\r
2715                   case H_GOT_REQ_HEADER:\r
2716                     ics_getting_history = H_GETTING_MOVES;\r
2717                     started = STARTED_MOVES;\r
2718                     parse_pos = 0;\r
2719                     if (oldi > next_out) {\r
2720                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2721                     }\r
2722                     break;\r
2723                   case H_GOT_UNREQ_HEADER:\r
2724                     ics_getting_history = H_GETTING_MOVES;\r
2725                     started = STARTED_MOVES_NOHIDE;\r
2726                     parse_pos = 0;\r
2727                     break;\r
2728                   case H_GOT_UNWANTED_HEADER:\r
2729                     ics_getting_history = H_FALSE;\r
2730                     break;\r
2731                 }\r
2732                 continue;\r
2733             }                           \r
2734             \r
2735             if (looking_at(buf, &i, "% ") ||\r
2736                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2737                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book\r
2738                 savingComment = FALSE;\r
2739                 switch (started) {\r
2740                   case STARTED_MOVES:\r
2741                   case STARTED_MOVES_NOHIDE:\r
2742                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2743                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2744                     ParseGameHistory(parse);\r
2745 #if ZIPPY\r
2746                     if (appData.zippyPlay && first.initDone) {\r
2747                         FeedMovesToProgram(&first, forwardMostMove);\r
2748                         if (gameMode == IcsPlayingWhite) {\r
2749                             if (WhiteOnMove(forwardMostMove)) {\r
2750                                 if (first.sendTime) {\r
2751                                   if (first.useColors) {\r
2752                                     SendToProgram("black\n", &first); \r
2753                                   }\r
2754                                   SendTimeRemaining(&first, TRUE);\r
2755                                 }\r
2756 #if 0\r
2757                                 if (first.useColors) {\r
2758                                   SendToProgram("white\ngo\n", &first);\r
2759                                 } else {\r
2760                                   SendToProgram("go\n", &first);\r
2761                                 }\r
2762 #else\r
2763                                 if (first.useColors) {\r
2764                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent\r
2765                                 }\r
2766                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos\r
2767 #endif\r
2768                                 first.maybeThinking = TRUE;\r
2769                             } else {\r
2770                                 if (first.usePlayother) {\r
2771                                   if (first.sendTime) {\r
2772                                     SendTimeRemaining(&first, TRUE);\r
2773                                   }\r
2774                                   SendToProgram("playother\n", &first);\r
2775                                   firstMove = FALSE;\r
2776                                 } else {\r
2777                                   firstMove = TRUE;\r
2778                                 }\r
2779                             }\r
2780                         } else if (gameMode == IcsPlayingBlack) {\r
2781                             if (!WhiteOnMove(forwardMostMove)) {\r
2782                                 if (first.sendTime) {\r
2783                                   if (first.useColors) {\r
2784                                     SendToProgram("white\n", &first);\r
2785                                   }\r
2786                                   SendTimeRemaining(&first, FALSE);\r
2787                                 }\r
2788 #if 0\r
2789                                 if (first.useColors) {\r
2790                                   SendToProgram("black\ngo\n", &first);\r
2791                                 } else {\r
2792                                   SendToProgram("go\n", &first);\r
2793                                 }\r
2794 #else\r
2795                                 if (first.useColors) {\r
2796                                   SendToProgram("black\n", &first);\r
2797                                 }\r
2798                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);\r
2799 #endif\r
2800                                 first.maybeThinking = TRUE;\r
2801                             } else {\r
2802                                 if (first.usePlayother) {\r
2803                                   if (first.sendTime) {\r
2804                                     SendTimeRemaining(&first, FALSE);\r
2805                                   }\r
2806                                   SendToProgram("playother\n", &first);\r
2807                                   firstMove = FALSE;\r
2808                                 } else {\r
2809                                   firstMove = TRUE;\r
2810                                 }\r
2811                             }\r
2812                         }                       \r
2813                     }\r
2814 #endif\r
2815                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2816                         /* Moves came from oldmoves or moves command\r
2817                            while we weren't doing anything else.\r
2818                            */\r
2819                         currentMove = forwardMostMove;\r
2820                         ClearHighlights();/*!!could figure this out*/\r
2821                         flipView = appData.flipView;\r
2822                         DrawPosition(FALSE, boards[currentMove]);\r
2823                         DisplayBothClocks();\r
2824                         sprintf(str, "%s vs. %s",\r
2825                                 gameInfo.white, gameInfo.black);\r
2826                         DisplayTitle(str);\r
2827                         gameMode = IcsIdle;\r
2828                     } else {\r
2829                         /* Moves were history of an active game */\r
2830                         if (gameInfo.resultDetails != NULL) {\r
2831                             free(gameInfo.resultDetails);\r
2832                             gameInfo.resultDetails = NULL;\r
2833                         }\r
2834                     }\r
2835                     HistorySet(parseList, backwardMostMove,\r
2836                                forwardMostMove, currentMove-1);\r
2837                     DisplayMove(currentMove - 1);\r
2838                     if (started == STARTED_MOVES) next_out = i;\r
2839                     started = STARTED_NONE;\r
2840                     ics_getting_history = H_FALSE;\r
2841                     break;\r
2842 \r
2843                   case STARTED_OBSERVE:\r
2844                     started = STARTED_NONE;\r
2845                     SendToICS(ics_prefix);\r
2846                     SendToICS("refresh\n");\r
2847                     break;\r
2848 \r
2849                   default:\r
2850                     break;\r
2851                 }\r
2852                 if(bookHit) { // [HGM] book: simulate book reply\r
2853                     static char bookMove[MSG_SIZ]; // a bit generous?\r
2854 \r
2855                     programStats.depth = programStats.nodes = programStats.time = \r
2856                     programStats.score = programStats.got_only_move = 0;\r
2857                     sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
2858 \r
2859                     strcpy(bookMove, "move ");\r
2860                     strcat(bookMove, bookHit);\r
2861                     HandleMachineMove(bookMove, &first);\r
2862                 }\r
2863                 continue;\r
2864             }\r
2865             \r
2866             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2867                  started == STARTED_HOLDINGS ||\r
2868                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2869                 /* Accumulate characters in move list or board */\r
2870                 parse[parse_pos++] = buf[i];\r
2871             }\r
2872             \r
2873             /* Start of game messages.  Mostly we detect start of game\r
2874                when the first board image arrives.  On some versions\r
2875                of the ICS, though, we need to do a "refresh" after starting\r
2876                to observe in order to get the current board right away. */\r
2877             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2878                 started = STARTED_OBSERVE;\r
2879                 continue;\r
2880             }\r
2881 \r
2882             /* Handle auto-observe */\r
2883             if (appData.autoObserve &&\r
2884                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2885                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2886                 char *player;\r
2887                 /* Choose the player that was highlighted, if any. */\r
2888                 if (star_match[0][0] == '\033' ||\r
2889                     star_match[1][0] != '\033') {\r
2890                     player = star_match[0];\r
2891                 } else {\r
2892                     player = star_match[2];\r
2893                 }\r
2894                 sprintf(str, "%sobserve %s\n",\r
2895                         ics_prefix, StripHighlightAndTitle(player));\r
2896                 SendToICS(str);\r
2897 \r
2898                 /* Save ratings from notify string */\r
2899                 strcpy(player1Name, star_match[0]);\r
2900                 player1Rating = string_to_rating(star_match[1]);\r
2901                 strcpy(player2Name, star_match[2]);\r
2902                 player2Rating = string_to_rating(star_match[3]);\r
2903 \r
2904                 if (appData.debugMode)\r
2905                   fprintf(debugFP, \r
2906                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2907                           player1Name, player1Rating,\r
2908                           player2Name, player2Rating);\r
2909 \r
2910                 continue;\r
2911             }\r
2912 \r
2913             /* Deal with automatic examine mode after a game,\r
2914                and with IcsObserving -> IcsExamining transition */\r
2915             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2916                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2917 \r
2918                 int gamenum = atoi(star_match[0]);\r
2919                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2920                     gamenum == ics_gamenum) {\r
2921                     /* We were already playing or observing this game;\r
2922                        no need to refetch history */\r
2923                     gameMode = IcsExamining;\r
2924                     if (pausing) {\r
2925                         pauseExamForwardMostMove = forwardMostMove;\r
2926                     } else if (currentMove < forwardMostMove) {\r
2927                         ForwardInner(forwardMostMove);\r
2928                     }\r
2929                 } else {\r
2930                     /* I don't think this case really can happen */\r
2931                     SendToICS(ics_prefix);\r
2932                     SendToICS("refresh\n");\r
2933                 }\r
2934                 continue;\r
2935             }    \r
2936             \r
2937             /* Error messages */\r
2938             if (ics_user_moved) {\r
2939                 if (looking_at(buf, &i, "Illegal move") ||\r
2940                     looking_at(buf, &i, "Not a legal move") ||\r
2941                     looking_at(buf, &i, "Your king is in check") ||\r
2942                     looking_at(buf, &i, "It isn't your turn") ||\r
2943                     looking_at(buf, &i, "It is not your move")) {\r
2944                     /* Illegal move */\r
2945                     ics_user_moved = 0;\r
2946                     if (forwardMostMove > backwardMostMove) {\r
2947                         currentMove = --forwardMostMove;\r
2948                         DisplayMove(currentMove - 1); /* before DMError */\r
2949                         DisplayMoveError(_("Illegal move (rejected by ICS)"));\r
2950                         DrawPosition(FALSE, boards[currentMove]);\r
2951                         SwitchClocks();\r
2952                         DisplayBothClocks();\r
2953                     }\r
2954                     continue;\r
2955                 }\r
2956             }\r
2957 \r
2958             if (looking_at(buf, &i, "still have time") ||\r
2959                 looking_at(buf, &i, "not out of time") ||\r
2960                 looking_at(buf, &i, "either player is out of time") ||\r
2961                 looking_at(buf, &i, "has timeseal; checking")) {\r
2962                 /* We must have called his flag a little too soon */\r
2963                 whiteFlag = blackFlag = FALSE;\r
2964                 continue;\r
2965             }\r
2966 \r
2967             if (looking_at(buf, &i, "added * seconds to") ||\r
2968                 looking_at(buf, &i, "seconds were added to")) {\r
2969                 /* Update the clocks */\r
2970                 SendToICS(ics_prefix);\r
2971                 SendToICS("refresh\n");\r
2972                 continue;\r
2973             }\r
2974 \r
2975             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2976                 ics_clock_paused = TRUE;\r
2977                 StopClocks();\r
2978                 continue;\r
2979             }\r
2980 \r
2981             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2982                 ics_clock_paused = FALSE;\r
2983                 StartClocks();\r
2984                 continue;\r
2985             }\r
2986 \r
2987             /* Grab player ratings from the Creating: message.\r
2988                Note we have to check for the special case when\r
2989                the ICS inserts things like [white] or [black]. */\r
2990             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
2991                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
2992                 /* star_matches:\r
2993                    0    player 1 name (not necessarily white)\r
2994                    1    player 1 rating\r
2995                    2    empty, white, or black (IGNORED)\r
2996                    3    player 2 name (not necessarily black)\r
2997                    4    player 2 rating\r
2998                    \r
2999                    The names/ratings are sorted out when the game\r
3000                    actually starts (below).\r
3001                 */\r
3002                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
3003                 player1Rating = string_to_rating(star_match[1]);\r
3004                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
3005                 player2Rating = string_to_rating(star_match[4]);\r
3006 \r
3007                 if (appData.debugMode)\r
3008                   fprintf(debugFP, \r
3009                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
3010                           player1Name, player1Rating,\r
3011                           player2Name, player2Rating);\r
3012 \r
3013                 continue;\r
3014             }\r
3015             \r
3016             /* Improved generic start/end-of-game messages */\r
3017             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
3018                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
3019                 /* If tkind == 0: */\r
3020                 /* star_match[0] is the game number */\r
3021                 /*           [1] is the white player's name */\r
3022                 /*           [2] is the black player's name */\r
3023                 /* For end-of-game: */\r
3024                 /*           [3] is the reason for the game end */\r
3025                 /*           [4] is a PGN end game-token, preceded by " " */\r
3026                 /* For start-of-game: */\r
3027                 /*           [3] begins with "Creating" or "Continuing" */\r
3028                 /*           [4] is " *" or empty (don't care). */\r
3029                 int gamenum = atoi(star_match[0]);\r
3030                 char *whitename, *blackname, *why, *endtoken;\r
3031                 ChessMove endtype = (ChessMove) 0;\r
3032 \r
3033                 if (tkind == 0) {\r
3034                   whitename = star_match[1];\r
3035                   blackname = star_match[2];\r
3036                   why = star_match[3];\r
3037                   endtoken = star_match[4];\r
3038                 } else {\r
3039                   whitename = star_match[1];\r
3040                   blackname = star_match[3];\r
3041                   why = star_match[5];\r
3042                   endtoken = star_match[6];\r
3043                 }\r
3044 \r
3045                 /* Game start messages */\r
3046                 if (strncmp(why, "Creating ", 9) == 0 ||\r
3047                     strncmp(why, "Continuing ", 11) == 0) {\r
3048                     gs_gamenum = gamenum;\r
3049                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
3050 #if ZIPPY\r
3051                     if (appData.zippyPlay) {\r
3052                         ZippyGameStart(whitename, blackname);\r
3053                     }\r
3054 #endif /*ZIPPY*/\r
3055                     continue;\r
3056                 }\r
3057 \r
3058                 /* Game end messages */\r
3059                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
3060                     ics_gamenum != gamenum) {\r
3061                     continue;\r
3062                 }\r
3063                 while (endtoken[0] == ' ') endtoken++;\r
3064                 switch (endtoken[0]) {\r
3065                   case '*':\r
3066                   default:\r
3067                     endtype = GameUnfinished;\r
3068                     break;\r
3069                   case '0':\r
3070                     endtype = BlackWins;\r
3071                     break;\r
3072                   case '1':\r
3073                     if (endtoken[1] == '/')\r
3074                       endtype = GameIsDrawn;\r
3075                     else\r
3076                       endtype = WhiteWins;\r
3077                     break;\r
3078                 }\r
3079                 GameEnds(endtype, why, GE_ICS);\r
3080 #if ZIPPY\r
3081                 if (appData.zippyPlay && first.initDone) {\r
3082                     ZippyGameEnd(endtype, why);\r
3083                     if (first.pr == NULL) {\r
3084                       /* Start the next process early so that we'll\r
3085                          be ready for the next challenge */\r
3086                       StartChessProgram(&first);\r
3087                     }\r
3088                     /* Send "new" early, in case this command takes\r
3089                        a long time to finish, so that we'll be ready\r
3090                        for the next challenge. */\r
3091                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'\r
3092                     Reset(TRUE, TRUE);\r
3093                 }\r
3094 #endif /*ZIPPY*/\r
3095                 continue;\r
3096             }\r
3097 \r
3098             if (looking_at(buf, &i, "Removing game * from observation") ||\r
3099                 looking_at(buf, &i, "no longer observing game *") ||\r
3100                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
3101                 if (gameMode == IcsObserving &&\r
3102                     atoi(star_match[0]) == ics_gamenum)\r
3103                   {\r
3104                       /* icsEngineAnalyze */\r
3105                       if (appData.icsEngineAnalyze) {\r
3106                             ExitAnalyzeMode();\r
3107                             ModeHighlight();\r
3108                       }\r
3109                       StopClocks();\r
3110                       gameMode = IcsIdle;\r
3111                       ics_gamenum = -1;\r
3112                       ics_user_moved = FALSE;\r
3113                   }\r
3114                 continue;\r
3115             }\r
3116 \r
3117             if (looking_at(buf, &i, "no longer examining game *")) {\r
3118                 if (gameMode == IcsExamining &&\r
3119                     atoi(star_match[0]) == ics_gamenum)\r
3120                   {\r
3121                       gameMode = IcsIdle;\r
3122                       ics_gamenum = -1;\r
3123                       ics_user_moved = FALSE;\r
3124                   }\r
3125                 continue;\r
3126             }\r
3127 \r
3128             /* Advance leftover_start past any newlines we find,\r
3129                so only partial lines can get reparsed */\r
3130             if (looking_at(buf, &i, "\n")) {\r
3131                 prevColor = curColor;\r
3132                 if (curColor != ColorNormal) {\r
3133                     if (oldi > next_out) {\r
3134                         SendToPlayer(&buf[next_out], oldi - next_out);\r
3135                         next_out = oldi;\r
3136                     }\r
3137                     Colorize(ColorNormal, FALSE);\r
3138                     curColor = ColorNormal;\r
3139                 }\r
3140                 if (started == STARTED_BOARD) {\r
3141                     started = STARTED_NONE;\r
3142                     parse[parse_pos] = NULLCHAR;\r
3143                     ParseBoard12(parse);\r
3144                     ics_user_moved = 0;\r
3145 \r
3146                     /* Send premove here */\r
3147                     if (appData.premove) {\r
3148                       char str[MSG_SIZ];\r
3149                       if (currentMove == 0 &&\r
3150                           gameMode == IcsPlayingWhite &&\r
3151                           appData.premoveWhite) {\r
3152                         sprintf(str, "%s%s\n", ics_prefix,\r
3153                                 appData.premoveWhiteText);\r
3154                         if (appData.debugMode)\r
3155                           fprintf(debugFP, "Sending premove:\n");\r
3156                         SendToICS(str);\r
3157                       } else if (currentMove == 1 &&\r
3158                                  gameMode == IcsPlayingBlack &&\r
3159                                  appData.premoveBlack) {\r
3160                         sprintf(str, "%s%s\n", ics_prefix,\r
3161                                 appData.premoveBlackText);\r
3162                         if (appData.debugMode)\r
3163                           fprintf(debugFP, "Sending premove:\n");\r
3164                         SendToICS(str);\r
3165                       } else if (gotPremove) {\r
3166                         gotPremove = 0;\r
3167                         ClearPremoveHighlights();\r
3168                         if (appData.debugMode)\r
3169                           fprintf(debugFP, "Sending premove:\n");\r
3170                           UserMoveEvent(premoveFromX, premoveFromY, \r
3171                                         premoveToX, premoveToY, \r
3172                                         premovePromoChar);\r
3173                       }\r
3174                     }\r
3175 \r
3176                     /* Usually suppress following prompt */\r
3177                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
3178                         if (looking_at(buf, &i, "*% ")) {\r
3179                             savingComment = FALSE;\r
3180                         }\r
3181                     }\r
3182                     next_out = i;\r
3183                 } else if (started == STARTED_HOLDINGS) {\r
3184                     int gamenum;\r
3185                     char new_piece[MSG_SIZ];\r
3186                     started = STARTED_NONE;\r
3187                     parse[parse_pos] = NULLCHAR;\r
3188                     if (appData.debugMode)\r
3189                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
3190                                                         parse, currentMove);\r
3191                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
3192                         gamenum == ics_gamenum) {\r
3193                         if (gameInfo.variant == VariantNormal) {\r
3194                           /* [HGM] We seem to switch variant during a game!\r
3195                            * Presumably no holdings were displayed, so we have\r
3196                            * to move the position two files to the right to\r
3197                            * create room for them!\r
3198                            */\r
3199                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
3200                           /* Get a move list just to see the header, which\r
3201                              will tell us whether this is really bug or zh */\r
3202                           if (ics_getting_history == H_FALSE) {\r
3203                             ics_getting_history = H_REQUESTED;\r
3204                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3205                             SendToICS(str);\r
3206                           }\r
3207                         }\r
3208                         new_piece[0] = NULLCHAR;\r
3209                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
3210                                &gamenum, white_holding, black_holding,\r
3211                                new_piece);\r
3212                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
3213                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
3214                         /* [HGM] copy holdings to board holdings area */\r
3215                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
3216                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
3217 #if ZIPPY\r
3218                         if (appData.zippyPlay && first.initDone) {\r
3219                             ZippyHoldings(white_holding, black_holding,\r
3220                                           new_piece);\r
3221                         }\r
3222 #endif /*ZIPPY*/\r
3223                         if (tinyLayout || smallLayout) {\r
3224                             char wh[16], bh[16];\r
3225                             PackHolding(wh, white_holding);\r
3226                             PackHolding(bh, black_holding);\r
3227                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
3228                                     gameInfo.white, gameInfo.black);\r
3229                         } else {\r
3230                             sprintf(str, "%s [%s] vs. %s [%s]",\r
3231                                     gameInfo.white, white_holding,\r
3232                                     gameInfo.black, black_holding);\r
3233                         }\r
3234 \r
3235                         DrawPosition(FALSE, boards[currentMove]);\r
3236                         DisplayTitle(str);\r
3237                     }\r
3238                     /* Suppress following prompt */\r
3239                     if (looking_at(buf, &i, "*% ")) {\r
3240                         savingComment = FALSE;\r
3241                     }\r
3242                     next_out = i;\r
3243                 }\r
3244                 continue;\r
3245             }\r
3246 \r
3247             i++;                /* skip unparsed character and loop back */\r
3248         }\r
3249         \r
3250         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window\r
3251             started != STARTED_HOLDINGS && i > next_out) {\r
3252             SendToPlayer(&buf[next_out], i - next_out);\r
3253             next_out = i;\r
3254         }\r
3255         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above\r
3256         \r
3257         leftover_len = buf_len - leftover_start;\r
3258         /* if buffer ends with something we couldn't parse,\r
3259            reparse it after appending the next read */\r
3260         \r
3261     } else if (count == 0) {\r
3262         RemoveInputSource(isr);\r
3263         DisplayFatalError(_("Connection closed by ICS"), 0, 0);\r
3264     } else {\r
3265         DisplayFatalError(_("Error reading from ICS"), error, 1);\r
3266     }\r
3267 }\r
3268 \r
3269 \r
3270 /* Board style 12 looks like this:\r
3271    \r
3272    <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
3273    \r
3274  * The "<12> " is stripped before it gets to this routine.  The two\r
3275  * trailing 0's (flip state and clock ticking) are later addition, and\r
3276  * some chess servers may not have them, or may have only the first.\r
3277  * Additional trailing fields may be added in the future.  \r
3278  */\r
3279 \r
3280 #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
3281 \r
3282 #define RELATION_OBSERVING_PLAYED    0\r
3283 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
3284 #define RELATION_PLAYING_MYMOVE      1\r
3285 #define RELATION_PLAYING_NOTMYMOVE  -1\r
3286 #define RELATION_EXAMINING           2\r
3287 #define RELATION_ISOLATED_BOARD     -3\r
3288 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
3289 \r
3290 void\r
3291 ParseBoard12(string)\r
3292      char *string;\r
3293\r
3294     GameMode newGameMode;\r
3295     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;\r
3296     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;\r
3297     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
3298     char to_play, board_chars[200];\r
3299     char move_str[500], str[500], elapsed_time[500];\r
3300     char black[32], white[32];\r
3301     Board board;\r
3302     int prevMove = currentMove;\r
3303     int ticking = 2;\r
3304     ChessMove moveType;\r
3305     int fromX, fromY, toX, toY;\r
3306     char promoChar;\r
3307     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */\r
3308     char *bookHit = NULL; // [HGM] book\r
3309 \r
3310     fromX = fromY = toX = toY = -1;\r
3311     \r
3312     newGame = FALSE;\r
3313 \r
3314     if (appData.debugMode)\r
3315       fprintf(debugFP, _("Parsing board: %s\n"), string);\r
3316 \r
3317     move_str[0] = NULLCHAR;\r
3318     elapsed_time[0] = NULLCHAR;\r
3319     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */\r
3320         int  i = 0, j;\r
3321         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {\r
3322             if(string[i] == ' ') { ranks++; files = 0; }\r
3323             else files++;\r
3324             i++;\r
3325         }\r
3326         for(j = 0; j <i; j++) board_chars[j] = string[j];\r
3327         board_chars[i] = '\0';\r
3328         string += i + 1;\r
3329     }\r
3330     n = sscanf(string, PATTERN, &to_play, &double_push,\r
3331                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
3332                &gamenum, white, black, &relation, &basetime, &increment,\r
3333                &white_stren, &black_stren, &white_time, &black_time,\r
3334                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
3335                &ticking);\r
3336 \r
3337     if (n < 21) {\r
3338         sprintf(str, _("Failed to parse board string:\n\"%s\""), string);\r
3339         DisplayError(str, 0);\r
3340         return;\r
3341     }\r
3342 \r
3343     /* Convert the move number to internal form */\r
3344     moveNum = (moveNum - 1) * 2;\r
3345     if (to_play == 'B') moveNum++;\r
3346     if (moveNum >= MAX_MOVES) {\r
3347       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
3348                         0, 1);\r
3349       return;\r
3350     }\r
3351     \r
3352     switch (relation) {\r
3353       case RELATION_OBSERVING_PLAYED:\r
3354       case RELATION_OBSERVING_STATIC:\r
3355         if (gamenum == -1) {\r
3356             /* Old ICC buglet */\r
3357             relation = RELATION_OBSERVING_STATIC;\r
3358         }\r
3359         newGameMode = IcsObserving;\r
3360         break;\r
3361       case RELATION_PLAYING_MYMOVE:\r
3362       case RELATION_PLAYING_NOTMYMOVE:\r
3363         newGameMode =\r
3364           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3365             IcsPlayingWhite : IcsPlayingBlack;\r
3366         break;\r
3367       case RELATION_EXAMINING:\r
3368         newGameMode = IcsExamining;\r
3369         break;\r
3370       case RELATION_ISOLATED_BOARD:\r
3371       default:\r
3372         /* Just display this board.  If user was doing something else,\r
3373            we will forget about it until the next board comes. */ \r
3374         newGameMode = IcsIdle;\r
3375         break;\r
3376       case RELATION_STARTING_POSITION:\r
3377         newGameMode = gameMode;\r
3378         break;\r
3379     }\r
3380     \r
3381     /* Modify behavior for initial board display on move listing\r
3382        of wild games.\r
3383        */\r
3384     switch (ics_getting_history) {\r
3385       case H_FALSE:\r
3386       case H_REQUESTED:\r
3387         break;\r
3388       case H_GOT_REQ_HEADER:\r
3389       case H_GOT_UNREQ_HEADER:\r
3390         /* This is the initial position of the current game */\r
3391         gamenum = ics_gamenum;\r
3392         moveNum = 0;            /* old ICS bug workaround */\r
3393         if (to_play == 'B') {\r
3394           startedFromSetupPosition = TRUE;\r
3395           blackPlaysFirst = TRUE;\r
3396           moveNum = 1;\r
3397           if (forwardMostMove == 0) forwardMostMove = 1;\r
3398           if (backwardMostMove == 0) backwardMostMove = 1;\r
3399           if (currentMove == 0) currentMove = 1;\r
3400         }\r
3401         newGameMode = gameMode;\r
3402         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3403         break;\r
3404       case H_GOT_UNWANTED_HEADER:\r
3405         /* This is an initial board that we don't want */\r
3406         return;\r
3407       case H_GETTING_MOVES:\r
3408         /* Should not happen */\r
3409         DisplayError(_("Error gathering move list: extra board"), 0);\r
3410         ics_getting_history = H_FALSE;\r
3411         return;\r
3412     }\r
3413     \r
3414     /* Take action if this is the first board of a new game, or of a\r
3415        different game than is currently being displayed.  */\r
3416     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3417         relation == RELATION_ISOLATED_BOARD) {\r
3418         \r
3419         /* Forget the old game and get the history (if any) of the new one */\r
3420         if (gameMode != BeginningOfGame) {\r
3421           Reset(FALSE, TRUE);\r
3422         }\r
3423         newGame = TRUE;\r
3424         if (appData.autoRaiseBoard) BoardToTop();\r
3425         prevMove = -3;\r
3426         if (gamenum == -1) {\r
3427             newGameMode = IcsIdle;\r
3428         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3429                    appData.getMoveList) {\r
3430             /* Need to get game history */\r
3431             ics_getting_history = H_REQUESTED;\r
3432             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3433             SendToICS(str);\r
3434         }\r
3435         \r
3436         /* Initially flip the board to have black on the bottom if playing\r
3437            black or if the ICS flip flag is set, but let the user change\r
3438            it with the Flip View button. */\r
3439         flipView = appData.autoFlipView ? \r
3440           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3441           appData.flipView;\r
3442         \r
3443         /* Done with values from previous mode; copy in new ones */\r
3444         gameMode = newGameMode;\r
3445         ModeHighlight();\r
3446         ics_gamenum = gamenum;\r
3447         if (gamenum == gs_gamenum) {\r
3448             int klen = strlen(gs_kind);\r
3449             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3450             sprintf(str, "ICS %s", gs_kind);\r
3451             gameInfo.event = StrSave(str);\r
3452         } else {\r
3453             gameInfo.event = StrSave("ICS game");\r
3454         }\r
3455         gameInfo.site = StrSave(appData.icsHost);\r
3456         gameInfo.date = PGNDate();\r
3457         gameInfo.round = StrSave("-");\r
3458         gameInfo.white = StrSave(white);\r
3459         gameInfo.black = StrSave(black);\r
3460         timeControl = basetime * 60 * 1000;\r
3461         timeControl_2 = 0;\r
3462         timeIncrement = increment * 1000;\r
3463         movesPerSession = 0;\r
3464         gameInfo.timeControl = TimeControlTagValue();\r
3465         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3466   if (appData.debugMode) {\r
3467     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3468     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3469     setbuf(debugFP, NULL);\r
3470   }\r
3471 \r
3472         gameInfo.outOfBook = NULL;\r
3473         \r
3474         /* Do we have the ratings? */\r
3475         if (strcmp(player1Name, white) == 0 &&\r
3476             strcmp(player2Name, black) == 0) {\r
3477             if (appData.debugMode)\r
3478               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3479                       player1Rating, player2Rating);\r
3480             gameInfo.whiteRating = player1Rating;\r
3481             gameInfo.blackRating = player2Rating;\r
3482         } else if (strcmp(player2Name, white) == 0 &&\r
3483                    strcmp(player1Name, black) == 0) {\r
3484             if (appData.debugMode)\r
3485               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3486                       player2Rating, player1Rating);\r
3487             gameInfo.whiteRating = player2Rating;\r
3488             gameInfo.blackRating = player1Rating;\r
3489         }\r
3490         player1Name[0] = player2Name[0] = NULLCHAR;\r
3491 \r
3492         /* Silence shouts if requested */\r
3493         if (appData.quietPlay &&\r
3494             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3495             SendToICS(ics_prefix);\r
3496             SendToICS("set shout 0\n");\r
3497         }\r
3498     }\r
3499     \r
3500     /* Deal with midgame name changes */\r
3501     if (!newGame) {\r
3502         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3503             if (gameInfo.white) free(gameInfo.white);\r
3504             gameInfo.white = StrSave(white);\r
3505         }\r
3506         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3507             if (gameInfo.black) free(gameInfo.black);\r
3508             gameInfo.black = StrSave(black);\r
3509         }\r
3510     }\r
3511     \r
3512     /* Throw away game result if anything actually changes in examine mode */\r
3513     if (gameMode == IcsExamining && !newGame) {\r
3514         gameInfo.result = GameUnfinished;\r
3515         if (gameInfo.resultDetails != NULL) {\r
3516             free(gameInfo.resultDetails);\r
3517             gameInfo.resultDetails = NULL;\r
3518         }\r
3519     }\r
3520     \r
3521     /* In pausing && IcsExamining mode, we ignore boards coming\r
3522        in if they are in a different variation than we are. */\r
3523     if (pauseExamInvalid) return;\r
3524     if (pausing && gameMode == IcsExamining) {\r
3525         if (moveNum <= pauseExamForwardMostMove) {\r
3526             pauseExamInvalid = TRUE;\r
3527             forwardMostMove = pauseExamForwardMostMove;\r
3528             return;\r
3529         }\r
3530     }\r
3531     \r
3532   if (appData.debugMode) {\r
3533     fprintf(debugFP, "load %dx%d board\n", files, ranks);\r
3534   }\r
3535     /* Parse the board */\r
3536     for (k = 0; k < ranks; k++) {\r
3537       for (j = 0; j < files; j++)\r
3538         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);\r
3539       if(gameInfo.holdingsWidth > 1) {\r
3540            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3541            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3542       }\r
3543     }\r
3544     CopyBoard(boards[moveNum], board);\r
3545     if (moveNum == 0) {\r
3546         startedFromSetupPosition =\r
3547           !CompareBoards(board, initialPosition);\r
3548         if(startedFromSetupPosition)\r
3549             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */\r
3550     }\r
3551 \r
3552     /* [HGM] Set castling rights. Take the outermost Rooks,\r
3553        to make it also work for FRC opening positions. Note that board12\r
3554        is really defective for later FRC positions, as it has no way to\r
3555        indicate which Rook can castle if they are on the same side of King.\r
3556        For the initial position we grant rights to the outermost Rooks,\r
3557        and remember thos rights, and we then copy them on positions\r
3558        later in an FRC game. This means WB might not recognize castlings with\r
3559        Rooks that have moved back to their original position as illegal,\r
3560        but in ICS mode that is not its job anyway.\r
3561     */\r
3562     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)\r
3563     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;\r
3564 \r
3565         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3566             if(board[0][i] == WhiteRook) j = i;\r
3567         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3568         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3569             if(board[0][i] == WhiteRook) j = i;\r
3570         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3571         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3572             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3573         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3574         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3575             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3576         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3577 \r
3578         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }\r
3579         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3580             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;\r
3581         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3582             if(board[BOARD_HEIGHT-1][k] == bKing)\r
3583                 initialRights[5] = castlingRights[moveNum][5] = k;\r
3584     } else { int r;\r
3585         r = castlingRights[moveNum][0] = initialRights[0];\r
3586         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;\r
3587         r = castlingRights[moveNum][1] = initialRights[1];\r
3588         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;\r
3589         r = castlingRights[moveNum][3] = initialRights[3];\r
3590         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;\r
3591         r = castlingRights[moveNum][4] = initialRights[4];\r
3592         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;\r
3593         /* wildcastle kludge: always assume King has rights */\r
3594         r = castlingRights[moveNum][2] = initialRights[2];\r
3595         r = castlingRights[moveNum][5] = initialRights[5];\r
3596     }\r
3597     /* [HGM] e.p. rights. Assume that ICS sends file number here? */\r
3598     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;\r
3599 \r
3600     \r
3601     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3602         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3603         /* This was an initial position from a move list, not\r
3604            the current position */\r
3605         return;\r
3606     }\r
3607     \r
3608     /* Update currentMove and known move number limits */\r
3609     newMove = newGame || moveNum > forwardMostMove;\r
3610 \r
3611     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */\r
3612     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {\r
3613         takeback = forwardMostMove - moveNum;\r
3614         for (i = 0; i < takeback; i++) {\r
3615              if (appData.debugMode) fprintf(debugFP, "take back move\n");\r
3616              SendToProgram("undo\n", &first);\r
3617         }\r
3618     }\r
3619 \r
3620     if (newGame) {\r
3621         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3622         if (gameMode == IcsExamining && moveNum == 0) {\r
3623           /* Workaround for ICS limitation: we are not told the wild\r
3624              type when starting to examine a game.  But if we ask for\r
3625              the move list, the move list header will tell us */\r
3626             ics_getting_history = H_REQUESTED;\r
3627             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3628             SendToICS(str);\r
3629         }\r
3630     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3631                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3632         forwardMostMove = moveNum;\r
3633         if (!pausing || currentMove > forwardMostMove)\r
3634           currentMove = forwardMostMove;\r
3635     } else {\r
3636         /* New part of history that is not contiguous with old part */ \r
3637         if (pausing && gameMode == IcsExamining) {\r
3638             pauseExamInvalid = TRUE;\r
3639             forwardMostMove = pauseExamForwardMostMove;\r
3640             return;\r
3641         }\r
3642         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3643         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\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     }\r
3649     \r
3650     /* Update the clocks */\r
3651     if (strchr(elapsed_time, '.')) {\r
3652       /* Time is in ms */\r
3653       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3654       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3655     } else {\r
3656       /* Time is in seconds */\r
3657       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3658       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3659     }\r
3660       \r
3661 \r
3662 #if ZIPPY\r
3663     if (appData.zippyPlay && newGame &&\r
3664         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3665         gameMode != IcsExamining)\r
3666       ZippyFirstBoard(moveNum, basetime, increment);\r
3667 #endif\r
3668     \r
3669     /* Put the move on the move list, first converting\r
3670        to canonical algebraic form. */\r
3671     if (moveNum > 0) {\r
3672   if (appData.debugMode) {\r
3673     if (appData.debugMode) { int f = forwardMostMove;\r
3674         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,\r
3675                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
3676     }\r
3677     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3678     fprintf(debugFP, "moveNum = %d\n", moveNum);\r
3679     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3680     setbuf(debugFP, NULL);\r
3681   }\r
3682         if (moveNum <= backwardMostMove) {\r
3683             /* We don't know what the board looked like before\r
3684                this move.  Punt. */\r
3685             strcpy(parseList[moveNum - 1], move_str);\r
3686             strcat(parseList[moveNum - 1], " ");\r
3687             strcat(parseList[moveNum - 1], elapsed_time);\r
3688             moveList[moveNum - 1][0] = NULLCHAR;\r
3689         } else if (strcmp(move_str, "none") == 0) {\r
3690             // [HGM] long SAN: swapped order; test for 'none' before parsing move\r
3691             /* Again, we don't know what the board looked like;\r
3692                this is really the start of the game. */\r
3693             parseList[moveNum - 1][0] = NULLCHAR;\r
3694             moveList[moveNum - 1][0] = NULLCHAR;\r
3695             backwardMostMove = moveNum;\r
3696             startedFromSetupPosition = TRUE;\r
3697             fromX = fromY = toX = toY = -1;\r
3698         } else {\r
3699           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. \r
3700           //                 So we parse the long-algebraic move string in stead of the SAN move\r
3701           int valid; char buf[MSG_SIZ], *prom;\r
3702 \r
3703           // str looks something like "Q/a1-a2"; kill the slash\r
3704           if(str[1] == '/') \r
3705                 sprintf(buf, "%c%s", str[0], str+2);\r
3706           else  strcpy(buf, str); // might be castling\r
3707           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) \r
3708                 strcat(buf, prom); // long move lacks promo specification!\r
3709           if(!appData.testLegality) {\r
3710                 if(appData.debugMode) \r
3711                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);\r
3712                 strcpy(move_str, buf);\r
3713           }\r
3714           valid = ParseOneMove(move_str, moveNum - 1, &moveType,\r
3715                                 &fromX, &fromY, &toX, &toY, &promoChar)\r
3716                || ParseOneMove(buf, moveNum - 1, &moveType,\r
3717                                 &fromX, &fromY, &toX, &toY, &promoChar);\r
3718           // end of long SAN patch\r
3719           if (valid) {\r
3720             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3721                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3722                                      fromY, fromX, toY, toX, promoChar,\r
3723                                      parseList[moveNum-1]);\r
3724             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3725                              castlingRights[moveNum]) ) {\r
3726               case MT_NONE:\r
3727               case MT_STALEMATE:\r
3728               default:\r
3729                 break;\r
3730               case MT_CHECK:\r
3731                 if(gameInfo.variant != VariantShogi)\r
3732                     strcat(parseList[moveNum - 1], "+");\r
3733                 break;\r
3734               case MT_CHECKMATE:\r
3735                 strcat(parseList[moveNum - 1], "#");\r
3736                 break;\r
3737             }\r
3738             strcat(parseList[moveNum - 1], " ");\r
3739             strcat(parseList[moveNum - 1], elapsed_time);\r
3740             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3741             strcpy(moveList[moveNum - 1], currentMoveString);\r
3742             strcat(moveList[moveNum - 1], "\n");\r
3743           } else {\r
3744             /* Move from ICS was illegal!?  Punt. */\r
3745   if (appData.debugMode) {\r
3746     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3747     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3748   }\r
3749 #if 0\r
3750             if (appData.testLegality && appData.debugMode) {\r
3751                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3752                 DisplayError(str, 0);\r
3753             }\r
3754 #endif\r
3755             strcpy(parseList[moveNum - 1], move_str);\r
3756             strcat(parseList[moveNum - 1], " ");\r
3757             strcat(parseList[moveNum - 1], elapsed_time);\r
3758             moveList[moveNum - 1][0] = NULLCHAR;\r
3759             fromX = fromY = toX = toY = -1;\r
3760           }\r
3761         }\r
3762   if (appData.debugMode) {\r
3763     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3764     setbuf(debugFP, NULL);\r
3765   }\r
3766 \r
3767 #if ZIPPY\r
3768         /* Send move to chess program (BEFORE animating it). */\r
3769         if (appData.zippyPlay && !newGame && newMove && \r
3770            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3771 \r
3772             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3773                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3774                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3775                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),\r
3776                             move_str);\r
3777                     DisplayError(str, 0);\r
3778                 } else {\r
3779                     if (first.sendTime) {\r
3780                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3781                     }\r
3782                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book\r
3783                     if (firstMove && !bookHit) {\r
3784                         firstMove = FALSE;\r
3785                         if (first.useColors) {\r
3786                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3787                                         "white\ngo\n" :\r
3788                                         "black\ngo\n", &first);\r
3789                         } else {\r
3790                           SendToProgram("go\n", &first);\r
3791                         }\r
3792                         first.maybeThinking = TRUE;\r
3793                     }\r
3794                 }\r
3795             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3796               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3797                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);\r
3798                 DisplayError(str, 0);\r
3799               } else {\r
3800                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!\r
3801                 SendMoveToProgram(moveNum - 1, &first);\r
3802               }\r
3803             }\r
3804         }\r
3805 #endif\r
3806     }\r
3807 \r
3808     if (moveNum > 0 && !gotPremove) {\r
3809         /* If move comes from a remote source, animate it.  If it\r
3810            isn't remote, it will have already been animated. */\r
3811         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3812             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3813         }\r
3814         if (!pausing && appData.highlightLastMove) {\r
3815             SetHighlights(fromX, fromY, toX, toY);\r
3816         }\r
3817     }\r
3818     \r
3819     /* Start the clocks */\r
3820     whiteFlag = blackFlag = FALSE;\r
3821     appData.clockMode = !(basetime == 0 && increment == 0);\r
3822     if (ticking == 0) {\r
3823       ics_clock_paused = TRUE;\r
3824       StopClocks();\r
3825     } else if (ticking == 1) {\r
3826       ics_clock_paused = FALSE;\r
3827     }\r
3828     if (gameMode == IcsIdle ||\r
3829         relation == RELATION_OBSERVING_STATIC ||\r
3830         relation == RELATION_EXAMINING ||\r
3831         ics_clock_paused)\r
3832       DisplayBothClocks();\r
3833     else\r
3834       StartClocks();\r
3835     \r
3836     /* Display opponents and material strengths */\r
3837     if (gameInfo.variant != VariantBughouse &&\r
3838         gameInfo.variant != VariantCrazyhouse) {\r
3839         if (tinyLayout || smallLayout) {\r
3840             if(gameInfo.variant == VariantNormal)\r
3841                 sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3842                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3843                     basetime, increment);\r
3844             else\r
3845                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", \r
3846                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3847                     basetime, increment, (int) gameInfo.variant);\r
3848         } else {\r
3849             if(gameInfo.variant == VariantNormal)\r
3850                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3851                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3852                     basetime, increment);\r
3853             else\r
3854                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", \r
3855                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3856                     basetime, increment, VariantName(gameInfo.variant));\r
3857         }\r
3858         DisplayTitle(str);\r
3859   if (appData.debugMode) {\r
3860     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);\r
3861   }\r
3862     }\r
3863 \r
3864    \r
3865     /* Display the board */\r
3866     if (!pausing) {\r
3867       \r
3868       if (appData.premove)\r
3869           if (!gotPremove || \r
3870              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3871              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3872               ClearPremoveHighlights();\r
3873 \r
3874       DrawPosition(FALSE, boards[currentMove]);\r
3875       DisplayMove(moveNum - 1);\r
3876       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3877         RingBell();\r
3878     }\r
3879 \r
3880     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3881 #if ZIPPY\r
3882     if(bookHit) { // [HGM] book: simulate book reply\r
3883         static char bookMove[MSG_SIZ]; // a bit generous?\r
3884 \r
3885         programStats.depth = programStats.nodes = programStats.time = \r
3886         programStats.score = programStats.got_only_move = 0;\r
3887         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
3888 \r
3889         strcpy(bookMove, "move ");\r
3890         strcat(bookMove, bookHit);\r
3891         HandleMachineMove(bookMove, &first);\r
3892     }\r
3893 #endif\r
3894 }\r
3895 \r
3896 void\r
3897 GetMoveListEvent()\r
3898 {\r
3899     char buf[MSG_SIZ];\r
3900     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3901         ics_getting_history = H_REQUESTED;\r
3902         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3903         SendToICS(buf);\r
3904     }\r
3905 }\r
3906 \r
3907 void\r
3908 AnalysisPeriodicEvent(force)\r
3909      int force;\r
3910 {\r
3911     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3912          && !force) || !appData.periodicUpdates)\r
3913       return;\r
3914 \r
3915     /* Send . command to Crafty to collect stats */\r
3916     SendToProgram(".\n", &first);\r
3917 \r
3918     /* Don't send another until we get a response (this makes\r
3919        us stop sending to old Crafty's which don't understand\r
3920        the "." command (sending illegal cmds resets node count & time,\r
3921        which looks bad)) */\r
3922     programStats.ok_to_send = 0;\r
3923 }\r
3924 \r
3925 void\r
3926 SendMoveToProgram(moveNum, cps)\r
3927      int moveNum;\r
3928      ChessProgramState *cps;\r
3929 {\r
3930     char buf[MSG_SIZ];\r
3931 \r
3932     if (cps->useUsermove) {\r
3933       SendToProgram("usermove ", cps);\r
3934     }\r
3935     if (cps->useSAN) {\r
3936       char *space;\r
3937       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3938         int len = space - parseList[moveNum];\r
3939         memcpy(buf, parseList[moveNum], len);\r
3940         buf[len++] = '\n';\r
3941         buf[len] = NULLCHAR;\r
3942       } else {\r
3943         sprintf(buf, "%s\n", parseList[moveNum]);\r
3944       }\r
3945       SendToProgram(buf, cps);\r
3946     } else {\r
3947       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */\r
3948         AlphaRank(moveList[moveNum], 4);\r
3949         SendToProgram(moveList[moveNum], cps);\r
3950         AlphaRank(moveList[moveNum], 4); // and back\r
3951       } else\r
3952       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3953        * the engine. It would be nice to have a better way to identify castle \r
3954        * moves here. */\r
3955       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)\r
3956                                                                          && cps->useOOCastle) {\r
3957         int fromX = moveList[moveNum][0] - AAA; \r
3958         int fromY = moveList[moveNum][1] - ONE;\r
3959         int toX = moveList[moveNum][2] - AAA; \r
3960         int toY = moveList[moveNum][3] - ONE;\r
3961         if((boards[moveNum][fromY][fromX] == WhiteKing \r
3962             && boards[moveNum][toY][toX] == WhiteRook)\r
3963            || (boards[moveNum][fromY][fromX] == BlackKing \r
3964                && boards[moveNum][toY][toX] == BlackRook)) {\r
3965           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3966           else SendToProgram("O-O-O\n", cps);\r
3967         }\r
3968         else SendToProgram(moveList[moveNum], cps);\r
3969       }\r
3970       else SendToProgram(moveList[moveNum], cps);\r
3971       /* End of additions by Tord */\r
3972     }\r
3973 \r
3974     /* [HGM] setting up the opening has brought engine in force mode! */\r
3975     /*       Send 'go' if we are in a mode where machine should play. */\r
3976     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
3977         (gameMode == TwoMachinesPlay   ||\r
3978 #ifdef ZIPPY\r
3979          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||\r
3980 #endif\r
3981          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
3982         SendToProgram("go\n", cps);\r
3983   if (appData.debugMode) {\r
3984     fprintf(debugFP, "(extra)\n");\r
3985   }\r
3986     }\r
3987     setboardSpoiledMachineBlack = 0;\r
3988 }\r
3989 \r
3990 void\r
3991 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
3992      ChessMove moveType;\r
3993      int fromX, fromY, toX, toY;\r
3994 {\r
3995     char user_move[MSG_SIZ];\r
3996 \r
3997     switch (moveType) {\r
3998       default:\r
3999         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),\r
4000                 (int)moveType, fromX, fromY, toX, toY);\r
4001         DisplayError(user_move + strlen("say "), 0);\r
4002         break;\r
4003       case WhiteKingSideCastle:\r
4004       case BlackKingSideCastle:\r
4005       case WhiteQueenSideCastleWild:\r
4006       case BlackQueenSideCastleWild:\r
4007       /* PUSH Fabien */\r
4008       case WhiteHSideCastleFR:\r
4009       case BlackHSideCastleFR:\r
4010       /* POP Fabien */\r
4011         sprintf(user_move, "o-o\n");\r
4012         break;\r
4013       case WhiteQueenSideCastle:\r
4014       case BlackQueenSideCastle:\r
4015       case WhiteKingSideCastleWild:\r
4016       case BlackKingSideCastleWild:\r
4017       /* PUSH Fabien */\r
4018       case WhiteASideCastleFR:\r
4019       case BlackASideCastleFR:\r
4020       /* POP Fabien */\r
4021         sprintf(user_move, "o-o-o\n");\r
4022         break;\r
4023       case WhitePromotionQueen:\r
4024       case BlackPromotionQueen:\r
4025       case WhitePromotionRook:\r
4026       case BlackPromotionRook:\r
4027       case WhitePromotionBishop:\r
4028       case BlackPromotionBishop:\r
4029       case WhitePromotionKnight:\r
4030       case BlackPromotionKnight:\r
4031       case WhitePromotionKing:\r
4032       case BlackPromotionKing:\r
4033       case WhitePromotionChancellor:\r
4034       case BlackPromotionChancellor:\r
4035       case WhitePromotionArchbishop:\r
4036       case BlackPromotionArchbishop:\r
4037         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)\r
4038             sprintf(user_move, "%c%c%c%c=%c\n",\r
4039                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4040                 PieceToChar(WhiteFerz));\r
4041         else if(gameInfo.variant == VariantGreat)\r
4042             sprintf(user_move, "%c%c%c%c=%c\n",\r
4043                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4044                 PieceToChar(WhiteMan));\r
4045         else\r
4046             sprintf(user_move, "%c%c%c%c=%c\n",\r
4047                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4048                 PieceToChar(PromoPiece(moveType)));\r
4049         break;\r
4050       case WhiteDrop:\r
4051       case BlackDrop:\r
4052         sprintf(user_move, "%c@%c%c\n",\r
4053                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
4054                 AAA + toX, ONE + toY);\r
4055         break;\r
4056       case NormalMove:\r
4057       case WhiteCapturesEnPassant:\r
4058       case BlackCapturesEnPassant:\r
4059       case IllegalMove:  /* could be a variant we don't quite understand */\r
4060         sprintf(user_move, "%c%c%c%c\n",\r
4061                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
4062         break;\r
4063     }\r
4064     SendToICS(user_move);\r
4065 }\r
4066 \r
4067 void\r
4068 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
4069      int rf, ff, rt, ft;\r
4070      char promoChar;\r
4071      char move[7];\r
4072 {\r
4073     if (rf == DROP_RANK) {\r
4074         sprintf(move, "%c@%c%c\n",\r
4075                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
4076     } else {\r
4077         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
4078             sprintf(move, "%c%c%c%c\n",\r
4079                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
4080         } else {\r
4081             sprintf(move, "%c%c%c%c%c\n",\r
4082                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
4083         }\r
4084     }\r
4085 }\r
4086 \r
4087 void\r
4088 ProcessICSInitScript(f)\r
4089      FILE *f;\r
4090 {\r
4091     char buf[MSG_SIZ];\r
4092 \r
4093     while (fgets(buf, MSG_SIZ, f)) {\r
4094         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
4095     }\r
4096 \r
4097     fclose(f);\r
4098 }\r
4099 \r
4100 \r
4101 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
4102 void\r
4103 AlphaRank(char *move, int n)\r
4104 {\r
4105     char *p = move, c; int x, y;\r
4106 \r
4107     if (appData.debugMode) {\r
4108         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
4109     }\r
4110 \r
4111     if(move[1]=='*' && \r
4112        move[2]>='0' && move[2]<='9' &&\r
4113        move[3]>='a' && move[3]<='x'    ) {\r
4114         move[1] = '@';\r
4115         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4116         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4117     } else\r
4118     if(move[0]>='0' && move[0]<='9' &&\r
4119        move[1]>='a' && move[1]<='x' &&\r
4120        move[2]>='0' && move[2]<='9' &&\r
4121        move[3]>='a' && move[3]<='x'    ) {\r
4122         /* input move, Shogi -> normal */\r
4123         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;\r
4124         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\r
4125         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4126         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4127     } else\r
4128     if(move[1]=='@' &&\r
4129        move[3]>='0' && move[3]<='9' &&\r
4130        move[2]>='a' && move[2]<='x'    ) {\r
4131         move[1] = '*';\r
4132         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4133         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4134     } else\r
4135     if(\r
4136        move[0]>='a' && move[0]<='x' &&\r
4137        move[3]>='0' && move[3]<='9' &&\r
4138        move[2]>='a' && move[2]<='x'    ) {\r
4139          /* output move, normal -> Shogi */\r
4140         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
4141         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
4142         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4143         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4144         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
4145     }\r
4146     if (appData.debugMode) {\r
4147         fprintf(debugFP, "   out = '%s'\n", move);\r
4148     }\r
4149 }\r
4150 \r
4151 /* Parser for moves from gnuchess, ICS, or user typein box */\r
4152 Boolean\r
4153 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
4154      char *move;\r
4155      int moveNum;\r
4156      ChessMove *moveType;\r
4157      int *fromX, *fromY, *toX, *toY;\r
4158      char *promoChar;\r
4159 {       \r
4160     if (appData.debugMode) {\r
4161         fprintf(debugFP, "move to parse: %s\n", move);\r
4162     }\r
4163     *moveType = yylexstr(moveNum, move);\r
4164 \r
4165     switch (*moveType) {\r
4166       case WhitePromotionChancellor:\r
4167       case BlackPromotionChancellor:\r
4168       case WhitePromotionArchbishop:\r
4169       case BlackPromotionArchbishop:\r
4170       case WhitePromotionQueen:\r
4171       case BlackPromotionQueen:\r
4172       case WhitePromotionRook:\r
4173       case BlackPromotionRook:\r
4174       case WhitePromotionBishop:\r
4175       case BlackPromotionBishop:\r
4176       case WhitePromotionKnight:\r
4177       case BlackPromotionKnight:\r
4178       case WhitePromotionKing:\r
4179       case BlackPromotionKing:\r
4180       case NormalMove:\r
4181       case WhiteCapturesEnPassant:\r
4182       case BlackCapturesEnPassant:\r
4183       case WhiteKingSideCastle:\r
4184       case WhiteQueenSideCastle:\r
4185       case BlackKingSideCastle:\r
4186       case BlackQueenSideCastle:\r
4187       case WhiteKingSideCastleWild:\r
4188       case WhiteQueenSideCastleWild:\r
4189       case BlackKingSideCastleWild:\r
4190       case BlackQueenSideCastleWild:\r
4191       /* Code added by Tord: */\r
4192       case WhiteHSideCastleFR:\r
4193       case WhiteASideCastleFR:\r
4194       case BlackHSideCastleFR:\r
4195       case BlackASideCastleFR:\r
4196       /* End of code added by Tord */\r
4197       case IllegalMove:         /* bug or odd chess variant */\r
4198         *fromX = currentMoveString[0] - AAA;\r
4199         *fromY = currentMoveString[1] - ONE;\r
4200         *toX = currentMoveString[2] - AAA;\r
4201         *toY = currentMoveString[3] - ONE;\r
4202         *promoChar = currentMoveString[4];\r
4203         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
4204             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
4205     if (appData.debugMode) {\r
4206         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
4207     }\r
4208             *fromX = *fromY = *toX = *toY = 0;\r
4209             return FALSE;\r
4210         }\r
4211         if (appData.testLegality) {\r
4212           return (*moveType != IllegalMove);\r
4213         } else {\r
4214           return !(fromX == fromY && toX == toY);\r
4215         }\r
4216 \r
4217       case WhiteDrop:\r
4218       case BlackDrop:\r
4219         *fromX = *moveType == WhiteDrop ?\r
4220           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
4221           (int) CharToPiece(ToLower(currentMoveString[0]));\r
4222         *fromY = DROP_RANK;\r
4223         *toX = currentMoveString[2] - AAA;\r
4224         *toY = currentMoveString[3] - ONE;\r
4225         *promoChar = NULLCHAR;\r
4226         return TRUE;\r
4227 \r
4228       case AmbiguousMove:\r
4229       case ImpossibleMove:\r
4230       case (ChessMove) 0:       /* end of file */\r
4231       case ElapsedTime:\r
4232       case Comment:\r
4233       case PGNTag:\r
4234       case NAG:\r
4235       case WhiteWins:\r
4236       case BlackWins:\r
4237       case GameIsDrawn:\r
4238       default:\r
4239     if (appData.debugMode) {\r
4240         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
4241     }\r
4242         /* bug? */\r
4243         *fromX = *fromY = *toX = *toY = 0;\r
4244         *promoChar = NULLCHAR;\r
4245         return FALSE;\r
4246     }\r
4247 }\r
4248 \r
4249 /* [AS] FRC game initialization */\r
4250 static int FindEmptySquare( Board board, int n )\r
4251 {\r
4252     int i = 0;\r
4253 \r
4254     while( 1 ) {\r
4255         while( board[0][i] != EmptySquare ) i++;\r
4256         if( n == 0 )\r
4257             break;\r
4258         n--;\r
4259         i++;\r
4260     }\r
4261 \r
4262     return i;\r
4263 }\r
4264 \r
4265 #if 0\r
4266 static void ShuffleFRC( Board board )\r
4267 {\r
4268     int i;\r
4269 \r
4270     srand( time(0) );\r
4271     \r
4272     for( i=0; i<8; i++ ) {\r
4273         board[0][i] = EmptySquare;\r
4274     }\r
4275 \r
4276     board[0][(rand() % 4)*2  ] = WhiteBishop; /* On dark square */\r
4277     board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */\r
4278     board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;\r
4279     board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;\r
4280     board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;\r
4281     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4282     initialRights[1]  = initialRights[4]  =\r
4283     castlingRights[0][1] = castlingRights[0][4] = i;\r
4284     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4285     initialRights[2]  = initialRights[5]  =\r
4286     castlingRights[0][2] = castlingRights[0][5] = i;\r
4287     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4288     initialRights[0]  = initialRights[3]  =\r
4289     castlingRights[0][0] = castlingRights[0][3] = i;\r
4290 \r
4291     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4292         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4293     }\r
4294 }\r
4295 \r
4296 static unsigned char FRC_KnightTable[10] = {\r
4297     0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33\r
4298 };\r
4299 \r
4300 static void SetupFRC( Board board, int pos_index )\r
4301 {\r
4302     int i;\r
4303     unsigned char knights;\r
4304 \r
4305     /* Bring the position index into a safe range (just in case...) */\r
4306     if( pos_index < 0 ) pos_index = 0;\r
4307 \r
4308     pos_index %= 960;\r
4309 \r
4310     /* Clear the board */\r
4311     for( i=0; i<8; i++ ) {\r
4312         board[0][i] = EmptySquare;\r
4313     }\r
4314 \r
4315     /* Place bishops and queen */\r
4316     board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */\r
4317     pos_index /= 4;\r
4318     \r
4319     board[0][ (pos_index % 4)*2     ] = WhiteBishop; /* On dark square */\r
4320     pos_index /= 4;\r
4321 \r
4322     board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;\r
4323     pos_index /= 6;\r
4324 \r
4325     /* Place knigths */\r
4326     knights = FRC_KnightTable[ pos_index ];\r
4327 \r
4328     board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;\r
4329     board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;\r
4330 \r
4331     /* Place rooks and king */\r
4332     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4333     initialRights[1]  = initialRights[4]  =\r
4334     castlingRights[0][1] = castlingRights[0][4] = i;\r
4335     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4336     initialRights[2]  = initialRights[5]  =\r
4337     castlingRights[0][2] = castlingRights[0][5] = i;\r
4338     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4339     initialRights[0]  = initialRights[3]  =\r
4340     castlingRights[0][0] = castlingRights[0][3] = i;\r
4341 \r
4342     /* Mirror piece placement for black */\r
4343     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4344         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4345     }\r
4346 }\r
4347 #else\r
4348 // [HGM] shuffle: a more general way to suffle opening setups, applicable to arbitrry variants.\r
4349 // All positions will have equal probability, but the current method will not provide a unique\r
4350 // numbering scheme for arrays that contain 3 or more pieces of the same kind.\r
4351 #define DARK 1\r
4352 #define LITE 2\r
4353 #define ANY 3\r
4354 \r
4355 int squaresLeft[4];\r
4356 int piecesLeft[(int)BlackPawn];\r
4357 long long int seed, nrOfShuffles;\r
4358 \r
4359 void GetPositionNumber()\r
4360 {       // sets global variable seed\r
4361         int i;\r
4362 \r
4363         seed = appData.defaultFrcPosition;\r
4364         if(seed < 0) { // randomize based on time for negative FRC position numbers\r
4365                 srandom(time(0)); \r
4366                 for(i=0; i<50; i++) seed += random();\r
4367                 seed = random() ^ random() >> 8 ^ random() << 8;\r
4368                 if(seed<0) seed = -seed;\r
4369         }\r
4370 }\r
4371 \r
4372 int put(Board board, int pieceType, int rank, int n, int shade)\r
4373 // put the piece on the (n-1)-th empty squares of the given shade\r
4374 {\r
4375         int i;\r
4376 \r
4377         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
4378                 if( ((i-BOARD_LEFT)&1)+1 & shade && board[rank][i] == EmptySquare && n-- == 0) {\r
4379                         board[rank][i] = (ChessSquare) pieceType;\r
4380                         squaresLeft[(i-BOARD_LEFT&1) + 1]--;\r
4381                         squaresLeft[ANY]--;\r
4382                         piecesLeft[pieceType]--; \r
4383                         return i;\r
4384                 }\r
4385         }\r
4386         return -1;\r
4387 }\r
4388 \r
4389 \r
4390 void AddOnePiece(Board board, int pieceType, int rank, int shade)\r
4391 // calculate where the next piece goes, (any empty square), and put it there\r
4392 {\r
4393         int i;\r
4394 \r
4395         i = seed % squaresLeft[shade];\r
4396         nrOfShuffles *= squaresLeft[shade];\r
4397         seed /= squaresLeft[shade];\r
4398         put(board, pieceType, rank, i, shade);\r
4399 }\r
4400 \r
4401 void AddTwoPieces(Board board, int pieceType, int rank)\r
4402 // calculate where the next 2 identical pieces go, (any empty square), and put it there\r
4403 {\r
4404         int i, n=squaresLeft[ANY], j=n-1, k;\r
4405 \r
4406         k = n*(n-1)/2; // nr of possibilities, not counting permutations\r
4407         i = seed % k;  // pick one\r
4408         nrOfShuffles *= k;\r
4409         seed /= k;\r
4410         while(i >= j) i -= j--;\r
4411         j = n - 1 - j; i += j;\r
4412         put(board, pieceType, rank, j, ANY);\r
4413         put(board, pieceType, rank, i, ANY);\r
4414 }\r
4415 \r
4416 void SetUpShuffle(Board board, int number)\r
4417 {\r
4418         int i, p, first=1;\r
4419 \r
4420         GetPositionNumber(); nrOfShuffles = 1;\r
4421 \r
4422         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;\r
4423         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;\r
4424         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];\r
4425 \r
4426         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;\r
4427 \r
4428         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board\r
4429             p = (int) board[0][i];\r
4430             if(p < (int) BlackPawn) piecesLeft[p] ++;\r
4431             board[0][i] = EmptySquare;\r
4432         }\r
4433 \r
4434         if(PosFlags(0) & F_ALL_CASTLE_OK) {\r
4435             // shuffles restricted to allow normal castling put KRR first\r
4436             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle\r
4437                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4438             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles\r
4439                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4440             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling\r
4441                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);\r
4442             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling\r
4443                 put(board, WhiteRook, 0, 0, ANY);\r
4444             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle\r
4445         }\r
4446 \r
4447         if((BOARD_RGHT-BOARD_LEFT & 1) == 0)\r
4448             // only for even boards make effort to put pairs of colorbound pieces on opposite colors\r
4449             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {\r
4450                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;\r
4451                 while(piecesLeft[p] >= 2) {\r
4452                     AddOnePiece(board, p, 0, LITE);\r
4453                     AddOnePiece(board, p, 0, DARK);\r
4454                 }\r
4455                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)\r
4456             }\r
4457 \r
4458         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {\r
4459             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere\r
4460             // but we leave King and Rooks for last, to possibly obey FRC restriction\r
4461             if(p == (int)WhiteRook) continue;\r
4462             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations\r
4463             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece\r
4464         }\r
4465 \r
4466         // now everything is placed, except perhaps King (Unicorn) and Rooks\r
4467 \r
4468         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {\r
4469             // Last King gets castling rights\r
4470             while(piecesLeft[(int)WhiteUnicorn]) {\r
4471                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4472                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4473             }\r
4474 \r
4475             while(piecesLeft[(int)WhiteKing]) {\r
4476                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4477                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4478             }\r
4479 \r
4480 \r
4481         } else {\r
4482             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);\r
4483             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);\r
4484         }\r
4485 \r
4486         // Only Rooks can be left; simply place them all\r
4487         while(piecesLeft[(int)WhiteRook]) {\r
4488                 i = put(board, WhiteRook, 0, 0, ANY);\r
4489                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights\r
4490                         if(first) {\r
4491                                 first=0;\r
4492                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;\r
4493                         }\r
4494                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;\r
4495                 }\r
4496         }\r
4497         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white\r
4498             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;\r
4499         }\r
4500 \r
4501         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize\r
4502 }\r
4503 \r
4504 #endif\r
4505 \r
4506 int SetCharTable( char *table, const char * map )\r
4507 /* [HGM] moved here from winboard.c because of its general usefulness */\r
4508 /*       Basically a safe strcpy that uses the last character as King */\r
4509 {\r
4510     int result = FALSE; int NrPieces;\r
4511 \r
4512     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
4513                     && NrPieces >= 12 && !(NrPieces&1)) {\r
4514         int i; /* [HGM] Accept even length from 12 to 34 */\r
4515 \r
4516         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
4517         for( i=0; i<NrPieces/2-1; i++ ) {\r
4518             table[i] = map[i];\r
4519             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
4520         }\r
4521         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
4522         table[(int) BlackKing]  = map[NrPieces-1];\r
4523 \r
4524         result = TRUE;\r
4525     }\r
4526 \r
4527     return result;\r
4528 }\r
4529 \r
4530 void Prelude(Board board)\r
4531 {       // [HGM] superchess: random selection of exo-pieces\r
4532         int i, j, k; ChessSquare p; \r
4533         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };\r
4534 \r
4535         GetPositionNumber(); // use FRC position number\r
4536 \r
4537         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table\r
4538             SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4539             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) \r
4540                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;\r
4541         }\r
4542 \r
4543         j = seed%4;                 seed /= 4; \r
4544         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4545         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4546         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4547         j = seed%3 + (seed%3 >= j); seed /= 3; \r
4548         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4549         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4550         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4551         j = seed%3;                 seed /= 3; \r
4552         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4553         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4554         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4555         j = seed%2 + (seed%2 >= j); seed /= 2; \r
4556         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4557         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4558         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4559         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);\r
4560         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);\r
4561         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);\r
4562         put(board, exoPieces[0],    0, 0, ANY);\r
4563         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];\r
4564 }\r
4565 \r
4566 void\r
4567 InitPosition(redraw)\r
4568      int redraw;\r
4569 {\r
4570     ChessSquare (* pieces)[BOARD_SIZE];\r
4571     int i, j, pawnRow, overrule,\r
4572     oldx = gameInfo.boardWidth,\r
4573     oldy = gameInfo.boardHeight,\r
4574     oldh = gameInfo.holdingsWidth,\r
4575     oldv = gameInfo.variant;\r
4576 \r
4577     currentMove = forwardMostMove = backwardMostMove = 0;\r
4578     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request\r
4579 \r
4580     /* [AS] Initialize pv info list [HGM] and game status */\r
4581     {\r
4582         for( i=0; i<MAX_MOVES; i++ ) {\r
4583             pvInfoList[i].depth = 0;\r
4584             epStatus[i]=EP_NONE;\r
4585             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
4586         }\r
4587 \r
4588         initialRulePlies = 0; /* 50-move counter start */\r
4589 \r
4590         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4591         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4592     }\r
4593 \r
4594     \r
4595     /* [HGM] logic here is completely changed. In stead of full positions */\r
4596     /* the initialized data only consist of the two backranks. The switch */\r
4597     /* selects which one we will use, which is than copied to the Board   */\r
4598     /* initialPosition, which for the rest is initialized by Pawns and    */\r
4599     /* empty squares. This initial position is then copied to boards[0],  */\r
4600     /* possibly after shuffling, so that it remains available.            */\r
4601 \r
4602     gameInfo.holdingsWidth = 0; /* default board sizes */\r
4603     gameInfo.boardWidth    = 8;\r
4604     gameInfo.boardHeight   = 8;\r
4605     gameInfo.holdingsSize  = 0;\r
4606     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
4607     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
4608     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
4609 \r
4610     switch (gameInfo.variant) {\r
4611     case VariantFischeRandom:\r
4612       shuffleOpenings = TRUE;\r
4613     default:\r
4614       pieces = FIDEArray;\r
4615       break;\r
4616     case VariantShatranj:\r
4617       pieces = ShatranjArray;\r
4618       nrCastlingRights = 0;\r
4619       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
4620       break;\r
4621     case VariantTwoKings:\r
4622       pieces = twoKingsArray;\r
4623       nrCastlingRights = 8;                 /* add rights for second King */\r
4624       castlingRights[0][6] = initialRights[2] = 5;\r
4625       castlingRights[0][7] = initialRights[5] = 5;\r
4626       castlingRank[6] = 0;\r
4627       castlingRank[7] = BOARD_HEIGHT-1;\r
4628       break;\r
4629     case VariantCapaRandom:\r
4630       shuffleOpenings = TRUE;\r
4631     case VariantCapablanca:\r
4632       pieces = CapablancaArray;\r
4633       gameInfo.boardWidth = 10;\r
4634       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4635       break;\r
4636     case VariantGothic:\r
4637       pieces = GothicArray;\r
4638       gameInfo.boardWidth = 10;\r
4639       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4640       break;\r
4641     case VariantJanus:\r
4642       pieces = JanusArray;\r
4643       gameInfo.boardWidth = 10;\r
4644       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
4645       nrCastlingRights = 6;\r
4646         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4647         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4648         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH-1>>1;\r
4649         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4650         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4651         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH-1>>1;\r
4652       break;\r
4653     case VariantFalcon:\r
4654       pieces = FalconArray;\r
4655       gameInfo.boardWidth = 10;\r
4656       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4657       break;\r
4658     case VariantXiangqi:\r
4659       pieces = XiangqiArray;\r
4660       gameInfo.boardWidth  = 9;\r
4661       gameInfo.boardHeight = 10;\r
4662       nrCastlingRights = 0;\r
4663       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4664       break;\r
4665     case VariantShogi:\r
4666       pieces = ShogiArray;\r
4667       gameInfo.boardWidth  = 9;\r
4668       gameInfo.boardHeight = 9;\r
4669       gameInfo.holdingsSize = 7;\r
4670       nrCastlingRights = 0;\r
4671       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4672       break;\r
4673     case VariantCourier:\r
4674       pieces = CourierArray;\r
4675       gameInfo.boardWidth  = 12;\r
4676       nrCastlingRights = 0;\r
4677       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4678       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4679       break;\r
4680     case VariantKnightmate:\r
4681       pieces = KnightmateArray;\r
4682       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4683       break;\r
4684     case VariantFairy:\r
4685       pieces = fairyArray;\r
4686       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4687       break;\r
4688     case VariantGreat:\r
4689       pieces = GreatArray;\r
4690       gameInfo.boardWidth = 10;\r
4691       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");\r
4692       gameInfo.holdingsSize = 8;\r
4693       break;\r
4694     case VariantSuper:\r
4695       pieces = FIDEArray;\r
4696       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
4697       gameInfo.holdingsSize = 8;\r
4698       startedFromSetupPosition = TRUE;\r
4699       break;\r
4700     case VariantCrazyhouse:\r
4701     case VariantBughouse:\r
4702       pieces = FIDEArray;\r
4703       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4704       gameInfo.holdingsSize = 5;\r
4705       break;\r
4706     case VariantWildCastle:\r
4707       pieces = FIDEArray;\r
4708       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4709       shuffleOpenings = 1;\r
4710       break;\r
4711     case VariantNoCastle:\r
4712       pieces = FIDEArray;\r
4713       nrCastlingRights = 0;\r
4714       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4715       /* !!?unconstrained back-rank shuffle */\r
4716       shuffleOpenings = 1;\r
4717       break;\r
4718     }\r
4719 \r
4720     overrule = 0;\r
4721     if(appData.NrFiles >= 0) {\r
4722         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4723         gameInfo.boardWidth = appData.NrFiles;\r
4724     }\r
4725     if(appData.NrRanks >= 0) {\r
4726         gameInfo.boardHeight = appData.NrRanks;\r
4727     }\r
4728     if(appData.holdingsSize >= 0) {\r
4729         i = appData.holdingsSize;\r
4730         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4731         gameInfo.holdingsSize = i;\r
4732     }\r
4733     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4734     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4735         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);\r
4736 \r
4737     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4738     if(pawnRow < 1) pawnRow = 1;\r
4739 \r
4740     /* User pieceToChar list overrules defaults */\r
4741     if(appData.pieceToCharTable != NULL)\r
4742         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4743 \r
4744     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4745 \r
4746         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4747             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4748         for( i=0; i<BOARD_HEIGHT; i++ )\r
4749             initialPosition[i][j] = s;\r
4750 \r
4751         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4752         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4753         initialPosition[pawnRow][j] = WhitePawn;\r
4754         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4755         if(gameInfo.variant == VariantXiangqi) {\r
4756             if(j&1) {\r
4757                 initialPosition[pawnRow][j] = \r
4758                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4759                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4760                    initialPosition[2][j] = WhiteCannon;\r
4761                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4762                 }\r
4763             }\r
4764         }\r
4765         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4766     }\r
4767     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4768 \r
4769             j=BOARD_LEFT+1;\r
4770             initialPosition[1][j] = WhiteBishop;\r
4771             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4772             j=BOARD_RGHT-2;\r
4773             initialPosition[1][j] = WhiteRook;\r
4774             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4775     }\r
4776 \r
4777     if( nrCastlingRights == -1) {\r
4778         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4779         /*       This sets default castling rights from none to normal corners   */\r
4780         /* Variants with other castling rights must set them themselves above    */\r
4781         nrCastlingRights = 6;\r
4782        \r
4783         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4784         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4785         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4786         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4787         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4788         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4789      }\r
4790 \r
4791      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
4792      if(gameInfo.variant == VariantGreat) { // promotion commoners\r
4793         initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-1] = WhiteMan;\r
4794         initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-2] = 9;\r
4795         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;\r
4796         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;\r
4797      }\r
4798 #if 0\r
4799     if(gameInfo.variant == VariantFischeRandom) {\r
4800       if( appData.defaultFrcPosition < 0 ) {\r
4801         ShuffleFRC( initialPosition );\r
4802       }\r
4803       else {\r
4804         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4805       }\r
4806       startedFromSetupPosition = TRUE;\r
4807     } else \r
4808 #else\r
4809   if (appData.debugMode) {\r
4810     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
4811   }\r
4812     if(shuffleOpenings) {\r
4813         SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
4814         startedFromSetupPosition = TRUE;\r
4815     }\r
4816 #endif\r
4817     if(startedFromPositionFile) {\r
4818       /* [HGM] loadPos: use PositionFile for every new game */\r
4819       CopyBoard(initialPosition, filePosition);\r
4820       for(i=0; i<nrCastlingRights; i++)\r
4821           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4822       startedFromSetupPosition = TRUE;\r
4823     }\r
4824 \r
4825     CopyBoard(boards[0], initialPosition);\r
4826 \r
4827     if(oldx != gameInfo.boardWidth ||\r
4828        oldy != gameInfo.boardHeight ||\r
4829        oldh != gameInfo.holdingsWidth\r
4830 #ifdef GOTHIC\r
4831        || oldv == VariantGothic ||        // For licensing popups\r
4832        gameInfo.variant == VariantGothic\r
4833 #endif\r
4834 #ifdef FALCON\r
4835        || oldv == VariantFalcon ||\r
4836        gameInfo.variant == VariantFalcon\r
4837 #endif\r
4838                                          )\r
4839             InitDrawingSizes(-2 ,0);\r
4840 \r
4841     if (redraw)\r
4842       DrawPosition(TRUE, boards[currentMove]);\r
4843 }\r
4844 \r
4845 void\r
4846 SendBoard(cps, moveNum)\r
4847      ChessProgramState *cps;\r
4848      int moveNum;\r
4849 {\r
4850     char message[MSG_SIZ];\r
4851     \r
4852     if (cps->useSetboard) {\r
4853       char* fen = PositionToFEN(moveNum, cps->useFEN960);\r
4854       sprintf(message, "setboard %s\n", fen);\r
4855       SendToProgram(message, cps);\r
4856       free(fen);\r
4857 \r
4858     } else {\r
4859       ChessSquare *bp;\r
4860       int i, j;\r
4861       /* Kludge to set black to move, avoiding the troublesome and now\r
4862        * deprecated "black" command.\r
4863        */\r
4864       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4865 \r
4866       SendToProgram("edit\n", cps);\r
4867       SendToProgram("#\n", cps);\r
4868       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4869         bp = &boards[moveNum][i][BOARD_LEFT];\r
4870         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4871           if ((int) *bp < (int) BlackPawn) {\r
4872             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4873                     AAA + j, ONE + i);\r
4874             if(message[0] == '+' || message[0] == '~') {\r
4875                 sprintf(message, "%c%c%c+\n",\r
4876                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4877                         AAA + j, ONE + i);\r
4878             }\r
4879             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4880                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4881                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4882             }\r
4883             SendToProgram(message, cps);\r
4884           }\r
4885         }\r
4886       }\r
4887     \r
4888       SendToProgram("c\n", cps);\r
4889       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4890         bp = &boards[moveNum][i][BOARD_LEFT];\r
4891         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4892           if (((int) *bp != (int) EmptySquare)\r
4893               && ((int) *bp >= (int) BlackPawn)) {\r
4894             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4895                     AAA + j, ONE + i);\r
4896             if(message[0] == '+' || message[0] == '~') {\r
4897                 sprintf(message, "%c%c%c+\n",\r
4898                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4899                         AAA + j, ONE + i);\r
4900             }\r
4901             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4902                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4903                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4904             }\r
4905             SendToProgram(message, cps);\r
4906           }\r
4907         }\r
4908       }\r
4909     \r
4910       SendToProgram(".\n", cps);\r
4911     }\r
4912     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4913 }\r
4914 \r
4915 int\r
4916 IsPromotion(fromX, fromY, toX, toY)\r
4917      int fromX, fromY, toX, toY;\r
4918 {\r
4919     /* [HGM] add Shogi promotions */\r
4920     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4921     ChessSquare piece;\r
4922 \r
4923     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4924       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4925    /* [HGM] Note to self: line above also weeds out drops */\r
4926     piece = boards[currentMove][fromY][fromX];\r
4927     if(gameInfo.variant == VariantShogi) {\r
4928         promotionZoneSize = 3;\r
4929         highestPromotingPiece = (int)WhiteKing;\r
4930         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4931            and if in normal chess we then allow promotion to King, why not\r
4932            allow promotion of other piece in Shogi?                         */\r
4933     }\r
4934     if((int)piece >= BlackPawn) {\r
4935         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4936              return FALSE;\r
4937         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4938     } else {\r
4939         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4940            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4941     }\r
4942     return ( (int)piece <= highestPromotingPiece );\r
4943 }\r
4944 \r
4945 int\r
4946 InPalace(row, column)\r
4947      int row, column;\r
4948 {   /* [HGM] for Xiangqi */\r
4949     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4950          column < (BOARD_WIDTH + 4)/2 &&\r
4951          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4952     return FALSE;\r
4953 }\r
4954 \r
4955 int\r
4956 PieceForSquare (x, y)\r
4957      int x;\r
4958      int y;\r
4959 {\r
4960   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
4961      return -1;\r
4962   else\r
4963      return boards[currentMove][y][x];\r
4964 }\r
4965 \r
4966 int\r
4967 OKToStartUserMove(x, y)\r
4968      int x, y;\r
4969 {\r
4970     ChessSquare from_piece;\r
4971     int white_piece;\r
4972 \r
4973     if (matchMode) return FALSE;\r
4974     if (gameMode == EditPosition) return TRUE;\r
4975 \r
4976     if (x >= 0 && y >= 0)\r
4977       from_piece = boards[currentMove][y][x];\r
4978     else\r
4979       from_piece = EmptySquare;\r
4980 \r
4981     if (from_piece == EmptySquare) return FALSE;\r
4982 \r
4983     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4984       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4985 \r
4986     switch (gameMode) {\r
4987       case PlayFromGameFile:\r
4988       case AnalyzeFile:\r
4989       case TwoMachinesPlay:\r
4990       case EndOfGame:\r
4991         return FALSE;\r
4992 \r
4993       case IcsObserving:\r
4994       case IcsIdle:\r
4995         return FALSE;\r
4996 \r
4997       case MachinePlaysWhite:\r
4998       case IcsPlayingBlack:\r
4999         if (appData.zippyPlay) return FALSE;\r
5000         if (white_piece) {\r
5001             DisplayMoveError(_("You are playing Black"));\r
5002             return FALSE;\r
5003         }\r
5004         break;\r
5005 \r
5006       case MachinePlaysBlack:\r
5007       case IcsPlayingWhite:\r
5008         if (appData.zippyPlay) return FALSE;\r
5009         if (!white_piece) {\r
5010             DisplayMoveError(_("You are playing White"));\r
5011             return FALSE;\r
5012         }\r
5013         break;\r
5014 \r
5015       case EditGame:\r
5016         if (!white_piece && WhiteOnMove(currentMove)) {\r
5017             DisplayMoveError(_("It is White's turn"));\r
5018             return FALSE;\r
5019         }           \r
5020         if (white_piece && !WhiteOnMove(currentMove)) {\r
5021             DisplayMoveError(_("It is Black's turn"));\r
5022             return FALSE;\r
5023         }           \r
5024         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
5025             /* Editing correspondence game history */\r
5026             /* Could disallow this or prompt for confirmation */\r
5027             cmailOldMove = -1;\r
5028         }\r
5029         if (currentMove < forwardMostMove) {\r
5030             /* Discarding moves */\r
5031             /* Could prompt for confirmation here,\r
5032                but I don't think that's such a good idea */\r
5033             forwardMostMove = currentMove;\r
5034         }\r
5035         break;\r
5036 \r
5037       case BeginningOfGame:\r
5038         if (appData.icsActive) return FALSE;\r
5039         if (!appData.noChessProgram) {\r
5040             if (!white_piece) {\r
5041                 DisplayMoveError(_("You are playing White"));\r
5042                 return FALSE;\r
5043             }\r
5044         }\r
5045         break;\r
5046         \r
5047       case Training:\r
5048         if (!white_piece && WhiteOnMove(currentMove)) {\r
5049             DisplayMoveError(_("It is White's turn"));\r
5050             return FALSE;\r
5051         }           \r
5052         if (white_piece && !WhiteOnMove(currentMove)) {\r
5053             DisplayMoveError(_("It is Black's turn"));\r
5054             return FALSE;\r
5055         }           \r
5056         break;\r
5057 \r
5058       default:\r
5059       case IcsExamining:\r
5060         break;\r
5061     }\r
5062     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
5063         && gameMode != AnalyzeFile && gameMode != Training) {\r
5064         DisplayMoveError(_("Displayed position is not current"));\r
5065         return FALSE;\r
5066     }\r
5067     return TRUE;\r
5068 }\r
5069 \r
5070 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
5071 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
5072 int lastLoadGameUseList = FALSE;\r
5073 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
5074 ChessMove lastLoadGameStart = (ChessMove) 0;\r
5075 \r
5076 \r
5077 ChessMove\r
5078 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
5079      int fromX, fromY, toX, toY;\r
5080      int promoChar;\r
5081 {\r
5082     ChessMove moveType;\r
5083     ChessSquare pdown, pup;\r
5084 \r
5085     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
5086     if ((fromX == toX) && (fromY == toY)) {\r
5087         return ImpossibleMove;\r
5088     }\r
5089 \r
5090     /* [HGM] suppress all moves into holdings area and guard band */\r
5091     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
5092             return ImpossibleMove;\r
5093 \r
5094     /* [HGM] <sameColor> moved to here from winboard.c */\r
5095     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
5096     pdown = boards[currentMove][fromY][fromX];\r
5097     pup = boards[currentMove][toY][toX];\r
5098     if (    gameMode != EditPosition &&\r
5099             (WhitePawn <= pdown && pdown < BlackPawn &&\r
5100              WhitePawn <= pup && pup < BlackPawn  ||\r
5101              BlackPawn <= pdown && pdown < EmptySquare &&\r
5102              BlackPawn <= pup && pup < EmptySquare \r
5103             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
5104                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
5105                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
5106         )           )\r
5107          return ImpossibleMove;\r
5108 \r
5109     /* Check if the user is playing in turn.  This is complicated because we\r
5110        let the user "pick up" a piece before it is his turn.  So the piece he\r
5111        tried to pick up may have been captured by the time he puts it down!\r
5112        Therefore we use the color the user is supposed to be playing in this\r
5113        test, not the color of the piece that is currently on the starting\r
5114        square---except in EditGame mode, where the user is playing both\r
5115        sides; fortunately there the capture race can't happen.  (It can\r
5116        now happen in IcsExamining mode, but that's just too bad.  The user\r
5117        will get a somewhat confusing message in that case.)\r
5118        */\r
5119 \r
5120     switch (gameMode) {\r
5121       case PlayFromGameFile:\r
5122       case AnalyzeFile:\r
5123       case TwoMachinesPlay:\r
5124       case EndOfGame:\r
5125       case IcsObserving:\r
5126       case IcsIdle:\r
5127         /* We switched into a game mode where moves are not accepted,\r
5128            perhaps while the mouse button was down. */\r
5129         return ImpossibleMove;\r
5130 \r
5131       case MachinePlaysWhite:\r
5132         /* User is moving for Black */\r
5133         if (WhiteOnMove(currentMove)) {\r
5134             DisplayMoveError(_("It is White's turn"));\r
5135             return ImpossibleMove;\r
5136         }\r
5137         break;\r
5138 \r
5139       case MachinePlaysBlack:\r
5140         /* User is moving for White */\r
5141         if (!WhiteOnMove(currentMove)) {\r
5142             DisplayMoveError(_("It is Black's turn"));\r
5143             return ImpossibleMove;\r
5144         }\r
5145         break;\r
5146 \r
5147       case EditGame:\r
5148       case IcsExamining:\r
5149       case BeginningOfGame:\r
5150       case AnalyzeMode:\r
5151       case Training:\r
5152         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
5153             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
5154             /* User is moving for Black */\r
5155             if (WhiteOnMove(currentMove)) {\r
5156                 DisplayMoveError(_("It is White's turn"));\r
5157                 return ImpossibleMove;\r
5158             }\r
5159         } else {\r
5160             /* User is moving for White */\r
5161             if (!WhiteOnMove(currentMove)) {\r
5162                 DisplayMoveError(_("It is Black's turn"));\r
5163                 return ImpossibleMove;\r
5164             }\r
5165         }\r
5166         break;\r
5167 \r
5168       case IcsPlayingBlack:\r
5169         /* User is moving for Black */\r
5170         if (WhiteOnMove(currentMove)) {\r
5171             if (!appData.premove) {\r
5172                 DisplayMoveError(_("It is White's turn"));\r
5173             } else if (toX >= 0 && toY >= 0) {\r
5174                 premoveToX = toX;\r
5175                 premoveToY = toY;\r
5176                 premoveFromX = fromX;\r
5177                 premoveFromY = fromY;\r
5178                 premovePromoChar = promoChar;\r
5179                 gotPremove = 1;\r
5180                 if (appData.debugMode) \r
5181                     fprintf(debugFP, "Got premove: fromX %d,"\r
5182                             "fromY %d, toX %d, toY %d\n",\r
5183                             fromX, fromY, toX, toY);\r
5184             }\r
5185             return ImpossibleMove;\r
5186         }\r
5187         break;\r
5188 \r
5189       case IcsPlayingWhite:\r
5190         /* User is moving for White */\r
5191         if (!WhiteOnMove(currentMove)) {\r
5192             if (!appData.premove) {\r
5193                 DisplayMoveError(_("It is Black's turn"));\r
5194             } else if (toX >= 0 && toY >= 0) {\r
5195                 premoveToX = toX;\r
5196                 premoveToY = toY;\r
5197                 premoveFromX = fromX;\r
5198                 premoveFromY = fromY;\r
5199                 premovePromoChar = promoChar;\r
5200                 gotPremove = 1;\r
5201                 if (appData.debugMode) \r
5202                     fprintf(debugFP, "Got premove: fromX %d,"\r
5203                             "fromY %d, toX %d, toY %d\n",\r
5204                             fromX, fromY, toX, toY);\r
5205             }\r
5206             return ImpossibleMove;\r
5207         }\r
5208         break;\r
5209 \r
5210       default:\r
5211         break;\r
5212 \r
5213       case EditPosition:\r
5214         /* EditPosition, empty square, or different color piece;\r
5215            click-click move is possible */\r
5216         if (toX == -2 || toY == -2) {\r
5217             boards[0][fromY][fromX] = EmptySquare;\r
5218             return AmbiguousMove;\r
5219         } else if (toX >= 0 && toY >= 0) {\r
5220             boards[0][toY][toX] = boards[0][fromY][fromX];\r
5221             boards[0][fromY][fromX] = EmptySquare;\r
5222             return AmbiguousMove;\r
5223         }\r
5224         return ImpossibleMove;\r
5225     }\r
5226 \r
5227     /* [HGM] If move started in holdings, it means a drop */\r
5228     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
5229          if( pup != EmptySquare ) return ImpossibleMove;\r
5230          if(appData.testLegality) {\r
5231              /* it would be more logical if LegalityTest() also figured out\r
5232               * which drops are legal. For now we forbid pawns on back rank.\r
5233               * Shogi is on its own here...\r
5234               */\r
5235              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
5236                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
5237                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
5238          }\r
5239          return WhiteDrop; /* Not needed to specify white or black yet */\r
5240     }\r
5241 \r
5242     userOfferedDraw = FALSE;\r
5243         \r
5244     /* [HGM] always test for legality, to get promotion info */\r
5245     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
5246                           epStatus[currentMove], castlingRights[currentMove],\r
5247                                          fromY, fromX, toY, toX, promoChar);\r
5248 \r
5249     /* [HGM] but possibly ignore an IllegalMove result */\r
5250     if (appData.testLegality) {\r
5251         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
5252             DisplayMoveError(_("Illegal move"));\r
5253             return ImpossibleMove;\r
5254         }\r
5255     }\r
5256 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);\r
5257     return moveType;\r
5258     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
5259        function is made into one that returns an OK move type if FinishMove\r
5260        should be called. This to give the calling driver routine the\r
5261        opportunity to finish the userMove input with a promotion popup,\r
5262        without bothering the user with this for invalid or illegal moves */\r
5263 \r
5264 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
5265 }\r
5266 \r
5267 /* Common tail of UserMoveEvent and DropMenuEvent */\r
5268 int\r
5269 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
5270      ChessMove moveType;\r
5271      int fromX, fromY, toX, toY;\r
5272      /*char*/int promoChar;\r
5273 {\r
5274     char *bookHit = 0;\r
5275 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);\r
5276     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { \r
5277         // [HGM] superchess: suppress promotions to non-available piece\r
5278         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5279         if(WhiteOnMove(currentMove)) {\r
5280             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;\r
5281         } else {\r
5282             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;\r
5283         }\r
5284     }\r
5285 \r
5286     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion\r
5287        move type in caller when we know the move is a legal promotion */\r
5288     if(moveType == NormalMove && promoChar)\r
5289         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
5290 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);\r
5291     /* [HGM] convert drag-and-drop piece drops to standard form */\r
5292     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
5293          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
5294          fromX = boards[currentMove][fromY][fromX];\r
5295          fromY = DROP_RANK;\r
5296     }\r
5297 \r
5298     /* [HGM] <popupFix> The following if has been moved here from\r
5299        UserMoveEvent(). Because it seemed to belon here (why not allow\r
5300        piece drops in training games?), and because it can only be\r
5301        performed after it is known to what we promote. */\r
5302     if (gameMode == Training) {\r
5303       /* compare the move played on the board to the next move in the\r
5304        * game. If they match, display the move and the opponent's response. \r
5305        * If they don't match, display an error message.\r
5306        */\r
5307       int saveAnimate;\r
5308       Board testBoard;\r
5309       CopyBoard(testBoard, boards[currentMove]);\r
5310       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
5311 \r
5312       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
5313         ForwardInner(currentMove+1);\r
5314 \r
5315         /* Autoplay the opponent's response.\r
5316          * if appData.animate was TRUE when Training mode was entered,\r
5317          * the response will be animated.\r
5318          */\r
5319         saveAnimate = appData.animate;\r
5320         appData.animate = animateTraining;\r
5321         ForwardInner(currentMove+1);\r
5322         appData.animate = saveAnimate;\r
5323 \r
5324         /* check for the end of the game */\r
5325         if (currentMove >= forwardMostMove) {\r
5326           gameMode = PlayFromGameFile;\r
5327           ModeHighlight();\r
5328           SetTrainingModeOff();\r
5329           DisplayInformation(_("End of game"));\r
5330         }\r
5331       } else {\r
5332         DisplayError(_("Incorrect move"), 0);\r
5333       }\r
5334       return 1;\r
5335     }\r
5336 \r
5337   /* Ok, now we know that the move is good, so we can kill\r
5338      the previous line in Analysis Mode */\r
5339   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
5340     forwardMostMove = currentMove;\r
5341   }\r
5342 \r
5343   /* If we need the chess program but it's dead, restart it */\r
5344   ResurrectChessProgram();\r
5345 \r
5346   /* A user move restarts a paused game*/\r
5347   if (pausing)\r
5348     PauseEvent();\r
5349 \r
5350   thinkOutput[0] = NULLCHAR;\r
5351 \r
5352   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
5353 \r
5354     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) \r
5355                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
5356         // [HGM] superchess: take promotion piece out of holdings\r
5357         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5358         if(WhiteOnMove(forwardMostMove-1)) {\r
5359             if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])\r
5360                 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;\r
5361         } else {\r
5362             if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])\r
5363                 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;\r
5364         }\r
5365     }\r
5366 \r
5367   if (gameMode == BeginningOfGame) {\r
5368     if (appData.noChessProgram) {\r
5369       gameMode = EditGame;\r
5370       SetGameInfo();\r
5371     } else {\r
5372       char buf[MSG_SIZ];\r
5373       gameMode = MachinePlaysBlack;\r
5374       StartClocks();\r
5375       SetGameInfo();\r
5376       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
5377       DisplayTitle(buf);\r
5378       if (first.sendName) {\r
5379         sprintf(buf, "name %s\n", gameInfo.white);\r
5380         SendToProgram(buf, &first);\r
5381       }\r
5382       StartClocks();\r
5383     }\r
5384     ModeHighlight();\r
5385   }\r
5386 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);\r
5387   /* Relay move to ICS or chess engine */\r
5388   if (appData.icsActive) {\r
5389     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
5390         gameMode == IcsExamining) {\r
5391       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5392       ics_user_moved = 1;\r
5393     }\r
5394   } else {\r
5395     if (first.sendTime && (gameMode == BeginningOfGame ||\r
5396                            gameMode == MachinePlaysWhite ||\r
5397                            gameMode == MachinePlaysBlack)) {\r
5398       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
5399     }\r
5400     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
5401          // [HGM] book: if program might be playing, let it use book\r
5402         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);\r
5403         first.maybeThinking = TRUE;\r
5404     } else SendMoveToProgram(forwardMostMove-1, &first);\r
5405     if (currentMove == cmailOldMove + 1) {\r
5406       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
5407     }\r
5408   }\r
5409 \r
5410   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5411 \r
5412   switch (gameMode) {\r
5413   case EditGame:\r
5414     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
5415                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
5416     case MT_NONE:\r
5417     case MT_CHECK:\r
5418       break;\r
5419     case MT_CHECKMATE:\r
5420       if (WhiteOnMove(currentMove)) {\r
5421         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
5422       } else {\r
5423         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
5424       }\r
5425       break;\r
5426     case MT_STALEMATE:\r
5427       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
5428       break;\r
5429     }\r
5430     break;\r
5431     \r
5432   case MachinePlaysBlack:\r
5433   case MachinePlaysWhite:\r
5434     /* disable certain menu options while machine is thinking */\r
5435     SetMachineThinkingEnables();\r
5436     break;\r
5437 \r
5438   default:\r
5439     break;\r
5440   }\r
5441 \r
5442   if(bookHit) { // [HGM] book: simulate book reply\r
5443         static char bookMove[MSG_SIZ]; // a bit generous?\r
5444 \r
5445         programStats.depth = programStats.nodes = programStats.time = \r
5446         programStats.score = programStats.got_only_move = 0;\r
5447         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
5448 \r
5449         strcpy(bookMove, "move ");\r
5450         strcat(bookMove, bookHit);\r
5451         HandleMachineMove(bookMove, &first);\r
5452   }\r
5453   return 1;\r
5454 }\r
5455 \r
5456 void\r
5457 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
5458      int fromX, fromY, toX, toY;\r
5459      int promoChar;\r
5460 {\r
5461     /* [HGM] This routine was added to allow calling of its two logical\r
5462        parts from other modules in the old way. Before, UserMoveEvent()\r
5463        automatically called FinishMove() if the move was OK, and returned\r
5464        otherwise. I separated the two, in order to make it possible to\r
5465        slip a promotion popup in between. But that it always needs two\r
5466        calls, to the first part, (now called UserMoveTest() ), and to\r
5467        FinishMove if the first part succeeded. Calls that do not need\r
5468        to do anything in between, can call this routine the old way. \r
5469     */\r
5470     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
5471 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);\r
5472     if(moveType != ImpossibleMove)\r
5473         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
5474 }\r
5475 \r
5476 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
5477 {\r
5478     char * hint = lastHint;\r
5479     FrontEndProgramStats stats;\r
5480 \r
5481     stats.which = cps == &first ? 0 : 1;\r
5482     stats.depth = cpstats->depth;\r
5483     stats.nodes = cpstats->nodes;\r
5484     stats.score = cpstats->score;\r
5485     stats.time = cpstats->time;\r
5486     stats.pv = cpstats->movelist;\r
5487     stats.hint = lastHint;\r
5488     stats.an_move_index = 0;\r
5489     stats.an_move_count = 0;\r
5490 \r
5491     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
5492         stats.hint = cpstats->move_name;\r
5493         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
5494         stats.an_move_count = cpstats->nr_moves;\r
5495     }\r
5496 \r
5497     SetProgramStats( &stats );\r
5498 }\r
5499 \r
5500 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)\r
5501 {   // [HGM] book: this routine intercepts moves to simulate book replies\r
5502     char *bookHit = NULL;\r
5503 \r
5504     //first determine if the incoming move brings opponent into his book\r
5505     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
5506         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
5507     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");\r
5508     if(bookHit != NULL && !cps->bookSuspend) {\r
5509         // make sure opponent is not going to reply after receiving move to book position\r
5510         SendToProgram("force\n", cps);\r
5511         cps->bookSuspend = TRUE; // flag indicating it has to be restarted\r
5512     }\r
5513     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move\r
5514     // now arrange restart after book miss\r
5515     if(bookHit) {\r
5516         // after a book hit we never send 'go', and the code after the call to this routine\r
5517         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').\r
5518         char buf[MSG_SIZ];\r
5519         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(\r
5520         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it\r
5521         SendToProgram(buf, cps);\r
5522         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'\r
5523     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine\r
5524         SendToProgram("go\n", cps);\r
5525         cps->bookSuspend = FALSE; // after a 'go' we are never suspended\r
5526     } else { // 'go' might be sent based on 'firstMove' after this routine returns\r
5527         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return\r
5528             SendToProgram("go\n", cps); \r
5529         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss\r
5530     }\r
5531     return bookHit; // notify caller of hit, so it can take action to send move to opponent\r
5532 }\r
5533 \r
5534 char *savedMessage;\r
5535 ChessProgramState *savedState;\r
5536 void DeferredBookMove(void)\r
5537 {\r
5538         if(savedState->lastPing != savedState->lastPong)\r
5539                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
5540         else\r
5541         HandleMachineMove(savedMessage, savedState);\r
5542 }\r
5543 \r
5544 void\r
5545 HandleMachineMove(message, cps)\r
5546      char *message;\r
5547      ChessProgramState *cps;\r
5548 {\r
5549     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
5550     char realname[MSG_SIZ];\r
5551     int fromX, fromY, toX, toY;\r
5552     ChessMove moveType;\r
5553     char promoChar;\r
5554     char *p;\r
5555     int machineWhite;\r
5556     char *bookHit;\r
5557 \r
5558 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit\r
5559     /*\r
5560      * Kludge to ignore BEL characters\r
5561      */\r
5562     while (*message == '\007') message++;\r
5563 \r
5564     /*\r
5565      * [HGM] engine debug message: ignore lines starting with '#' character\r
5566      */\r
5567     if(cps->debug && *message == '#') return;\r
5568 \r
5569     /*\r
5570      * Look for book output\r
5571      */\r
5572     if (cps == &first && bookRequested) {\r
5573         if (message[0] == '\t' || message[0] == ' ') {\r
5574             /* Part of the book output is here; append it */\r
5575             strcat(bookOutput, message);\r
5576             strcat(bookOutput, "  \n");\r
5577             return;\r
5578         } else if (bookOutput[0] != NULLCHAR) {\r
5579             /* All of book output has arrived; display it */\r
5580             char *p = bookOutput;\r
5581             while (*p != NULLCHAR) {\r
5582                 if (*p == '\t') *p = ' ';\r
5583                 p++;\r
5584             }\r
5585             DisplayInformation(bookOutput);\r
5586             bookRequested = FALSE;\r
5587             /* Fall through to parse the current output */\r
5588         }\r
5589     }\r
5590 \r
5591     /*\r
5592      * Look for machine move.\r
5593      */\r
5594     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
5595         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
5596     {\r
5597         /* This method is only useful on engines that support ping */\r
5598         if (cps->lastPing != cps->lastPong) {\r
5599           if (gameMode == BeginningOfGame) {\r
5600             /* Extra move from before last new; ignore */\r
5601             if (appData.debugMode) {\r
5602                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5603             }\r
5604           } else {\r
5605             if (appData.debugMode) {\r
5606                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5607                         cps->which, gameMode);\r
5608             }\r
5609 \r
5610             SendToProgram("undo\n", cps);\r
5611           }\r
5612           return;\r
5613         }\r
5614 \r
5615         switch (gameMode) {\r
5616           case BeginningOfGame:\r
5617             /* Extra move from before last reset; ignore */\r
5618             if (appData.debugMode) {\r
5619                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5620             }\r
5621             return;\r
5622 \r
5623           case EndOfGame:\r
5624           case IcsIdle:\r
5625           default:\r
5626             /* Extra move after we tried to stop.  The mode test is\r
5627                not a reliable way of detecting this problem, but it's\r
5628                the best we can do on engines that don't support ping.\r
5629             */\r
5630             if (appData.debugMode) {\r
5631                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5632                         cps->which, gameMode);\r
5633             }\r
5634             SendToProgram("undo\n", cps);\r
5635             return;\r
5636 \r
5637           case MachinePlaysWhite:\r
5638           case IcsPlayingWhite:\r
5639             machineWhite = TRUE;\r
5640             break;\r
5641 \r
5642           case MachinePlaysBlack:\r
5643           case IcsPlayingBlack:\r
5644             machineWhite = FALSE;\r
5645             break;\r
5646 \r
5647           case TwoMachinesPlay:\r
5648             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5649             break;\r
5650         }\r
5651         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5652             if (appData.debugMode) {\r
5653                 fprintf(debugFP,\r
5654                         "Ignoring move out of turn by %s, gameMode %d"\r
5655                         ", forwardMost %d\n",\r
5656                         cps->which, gameMode, forwardMostMove);\r
5657             }\r
5658             return;\r
5659         }\r
5660 \r
5661     if (appData.debugMode) { int f = forwardMostMove;\r
5662         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5663                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5664     }\r
5665         if(cps->alphaRank) AlphaRank(machineMove, 4);\r
5666         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5667                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5668             /* Machine move could not be parsed; ignore it. */\r
5669             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),\r
5670                     machineMove, cps->which);\r
5671             DisplayError(buf1, 0);\r
5672             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",\r
5673                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5674             if (gameMode == TwoMachinesPlay) {\r
5675               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5676                        buf1, GE_XBOARD);\r
5677             }\r
5678             return;\r
5679         }\r
5680 \r
5681         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5682         /* So we have to redo legality test with true e.p. status here,  */\r
5683         /* to make sure an illegal e.p. capture does not slip through,   */\r
5684         /* to cause a forfeit on a justified illegal-move complaint      */\r
5685         /* of the opponent.                                              */\r
5686         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5687             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5688                                                               ) {\r
5689            ChessMove moveType;\r
5690            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5691                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5692                              fromY, fromX, toY, toX, promoChar);\r
5693             if (appData.debugMode) {\r
5694                 int i;\r
5695                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5696                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5697                 fprintf(debugFP, "castling rights\n");\r
5698             }\r
5699             if(moveType == IllegalMove) {\r
5700                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5701                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5702                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5703                            buf1, GE_XBOARD);\r
5704            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
5705            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5706               when they shouldn't (like TSCP-Gothic) */\r
5707            switch(moveType) {\r
5708              case WhiteASideCastleFR:\r
5709              case BlackASideCastleFR:\r
5710                toX+=2;\r
5711                currentMoveString[2]++;\r
5712                break;\r
5713              case WhiteHSideCastleFR:\r
5714              case BlackHSideCastleFR:\r
5715                toX--;\r
5716                currentMoveString[2]--;\r
5717                break;\r
5718            }\r
5719         }\r
5720         hintRequested = FALSE;\r
5721         lastHint[0] = NULLCHAR;\r
5722         bookRequested = FALSE;\r
5723         /* Program may be pondering now */\r
5724         cps->maybeThinking = TRUE;\r
5725         if (cps->sendTime == 2) cps->sendTime = 1;\r
5726         if (cps->offeredDraw) cps->offeredDraw--;\r
5727 \r
5728 #if ZIPPY\r
5729         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5730             first.initDone) {\r
5731           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5732           ics_user_moved = 1;\r
5733           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
5734                 char buf[3*MSG_SIZ];\r
5735 \r
5736                 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",\r
5737                         programStats.depth,\r
5738                         programStats.score / 100.,\r
5739                         programStats.time / 100.,\r
5740                         (double) programStats.nodes,\r
5741                         programStats.nodes / (10*abs(programStats.time) + 1.),\r
5742                         programStats.movelist);\r
5743                 SendToICS(buf);\r
5744           }\r
5745         }\r
5746 #endif\r
5747         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5748         strcpy(machineMove, currentMoveString);\r
5749         strcat(machineMove, "\n");\r
5750         strcpy(moveList[forwardMostMove], machineMove);\r
5751 \r
5752         /* [AS] Save move info and clear stats for next move */\r
5753         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5754         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5755         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5756         ClearProgramStats();\r
5757         thinkOutput[0] = NULLCHAR;\r
5758         hiddenThinkOutputState = 0;\r
5759 \r
5760         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5761 \r
5762         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5763         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5764             int count = 0;\r
5765 \r
5766             while( count < adjudicateLossPlies ) {\r
5767                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5768 \r
5769                 if( count & 1 ) {\r
5770                     score = -score; /* Flip score for winning side */\r
5771                 }\r
5772 \r
5773                 if( score > adjudicateLossThreshold ) {\r
5774                     break;\r
5775                 }\r
5776 \r
5777                 count++;\r
5778             }\r
5779 \r
5780             if( count >= adjudicateLossPlies ) {\r
5781                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5782 \r
5783                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5784                     "Xboard adjudication", \r
5785                     GE_XBOARD );\r
5786 \r
5787                 return;\r
5788             }\r
5789         }\r
5790 \r
5791         if( gameMode == TwoMachinesPlay ) {\r
5792           // [HGM] some adjudications useful with buggy engines\r
5793             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
5794           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {\r
5795 \r
5796             if(appData.testLegality)\r
5797             // don't wait for engine to announce game end if we can judge ourselves\r
5798             switch (MateTest(boards[forwardMostMove],\r
5799                                  PosFlags(forwardMostMove), epFile,\r
5800                                        castlingRights[forwardMostMove]) ) {\r
5801               case MT_NONE:\r
5802               case MT_CHECK:\r
5803               default:\r
5804                 break;\r
5805               case MT_STALEMATE:\r
5806                 epStatus[forwardMostMove] = EP_STALEMATE;\r
5807                 if(appData.checkMates) {\r
5808                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5809                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5810                     GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",\r
5811                         GE_XBOARD );\r
5812                 }\r
5813                 break;\r
5814               case MT_CHECKMATE:\r
5815                 epStatus[forwardMostMove] = EP_CHECKMATE;\r
5816                 if(appData.checkMates) {\r
5817                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5818                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5819                     GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5820                     "Xboard adjudication: Checkmate", \r
5821                     GE_XBOARD );\r
5822                 }\r
5823                 break;\r
5824             }\r
5825 \r
5826             if( appData.testLegality )\r
5827             {   /* [HGM] Some more adjudications for obstinate engines */\r
5828                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5829                     NrWQ=0, NrBQ=0, NrW=0, bishopsColor = 0,\r
5830                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;\r
5831                 static int moveCount = 6;\r
5832 \r
5833                 /* First absolutely insufficient mating material. Count what is on board. */\r
5834                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5835                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5836                     int m=i;\r
5837 \r
5838                     switch((int) p)\r
5839                     {   /* count B,N,R and other of each side */\r
5840                         case WhiteKnight:\r
5841                              NrWN++; break;\r
5842                         case WhiteBishop:\r
5843                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5844                              bishopsColor |= 1 << ((i^j)&1);\r
5845                              NrWB++; break;\r
5846                         case BlackKnight:\r
5847                              NrBN++; break;\r
5848                         case BlackBishop:\r
5849                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5850                              bishopsColor |= 1 << ((i^j)&1);\r
5851                              NrBB++; break;\r
5852                         case WhiteRook:\r
5853                              NrWR++; break;\r
5854                         case BlackRook:\r
5855                              NrBR++; break;\r
5856                         case WhiteQueen:\r
5857                              NrWQ++; break;\r
5858                         case BlackQueen:\r
5859                              NrBQ++; break;\r
5860                         case EmptySquare: \r
5861                              break;\r
5862                         case BlackPawn:\r
5863                              m = 7-i;\r
5864                         case WhitePawn:\r
5865                              PawnAdvance += m; NrPawns++;\r
5866                     }\r
5867                     NrPieces += (p != EmptySquare);\r
5868                     NrW += ((int)p < (int)BlackPawn);\r
5869                     if(gameInfo.variant == VariantXiangqi && \r
5870                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {\r
5871                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces\r
5872                         NrW -= ((int)p < (int)BlackPawn);\r
5873                     }\r
5874                 }\r
5875 \r
5876                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&\r
5877                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||\r
5878                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color\r
5879                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5880 \r
5881                      /* always flag draws, for judging claims */\r
5882                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5883 \r
5884                      if(appData.materialDraws) {\r
5885                          /* but only adjudicate them if adjudication enabled */\r
5886                          SendToProgram("force\n", cps->other); // suppress reply\r
5887                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */\r
5888                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5889                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5890                          return;\r
5891                      }\r
5892                 }\r
5893 \r
5894                 /* Shatranj baring rule */\r
5895                 if( gameInfo.variant == VariantShatranj && (NrW == 1 || NrPieces - NrW == 1) )\r
5896                 {    /* bare King */\r
5897 \r
5898                      if(--bare < 0 && appData.checkMates) {\r
5899                          /* but only adjudicate them if adjudication enabled */\r
5900                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5901                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5902                          GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
5903                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5904                          return;\r
5905                      }\r
5906                 } else bare = 1;\r
5907 \r
5908                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5909                 if(NrPieces == 4 && \r
5910                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5911                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5912                    || NrWN==2 || NrBN==2     /* KNNK */\r
5913                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5914                   ) ) {\r
5915                      if(--moveCount < 0 && appData.trivialDraws)\r
5916                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5917                           SendToProgram("force\n", cps->other); // suppress reply\r
5918                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5919                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5920                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5921                           return;\r
5922                      }\r
5923                 } else moveCount = 6;\r
5924             }\r
5925           }\r
5926 #if 1\r
5927     if (appData.debugMode) { int i;\r
5928       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5929               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5930               appData.drawRepeats);\r
5931       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5932            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5933 \r
5934     }\r
5935 #endif\r
5936                 /* Check for rep-draws */\r
5937                 count = 0;\r
5938                 for(k = forwardMostMove-2;\r
5939                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5940                         epStatus[k] < EP_UNKNOWN &&\r
5941                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5942                     k-=2)\r
5943                 {   int rights=0;\r
5944 #if 0\r
5945     if (appData.debugMode) {\r
5946       fprintf(debugFP, " loop\n");\r
5947     }\r
5948 #endif\r
5949                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5950 #if 0\r
5951     if (appData.debugMode) {\r
5952       fprintf(debugFP, "match\n");\r
5953     }\r
5954 #endif\r
5955                         /* compare castling rights */\r
5956                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5957                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5958                                 rights++; /* King lost rights, while rook still had them */\r
5959                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5960                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5961                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5962                                    rights++; /* but at least one rook lost them */\r
5963                         }\r
5964                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5965                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5966                                 rights++; \r
5967                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5968                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5969                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5970                                    rights++;\r
5971                         }\r
5972 #if 0\r
5973     if (appData.debugMode) {\r
5974       for(i=0; i<nrCastlingRights; i++)\r
5975       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5976     }\r
5977 \r
5978     if (appData.debugMode) {\r
5979       fprintf(debugFP, " %d %d\n", rights, k);\r
5980     }\r
5981 #endif\r
5982                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5983                             && appData.drawRepeats > 1) {\r
5984                              /* adjudicate after user-specified nr of repeats */\r
5985                              SendToProgram("force\n", cps->other); // suppress reply\r
5986                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5987                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5988                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { \r
5989                                 // [HGM] xiangqi: check for forbidden perpetuals\r
5990                                 int m, ourPerpetual = 1, hisPerpetual = 1;\r
5991                                 for(m=forwardMostMove; m>k; m-=2) {\r
5992                                     if(MateTest(boards[m], PosFlags(m), \r
5993                                                         EP_NONE, castlingRights[m]) != MT_CHECK)\r
5994                                         ourPerpetual = 0; // the current mover did not always check\r
5995                                     if(MateTest(boards[m-1], PosFlags(m-1), \r
5996                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)\r
5997                                         hisPerpetual = 0; // the opponent did not always check\r
5998                                 }\r
5999                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",\r
6000                                                                         ourPerpetual, hisPerpetual);\r
6001                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
6002                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
6003                                            "Xboard adjudication: perpetual checking", GE_XBOARD );\r
6004                                     return;\r
6005                                 }\r
6006                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet\r
6007                                     break; // (or we would have caught him before). Abort repetition-checking loop.\r
6008                                 // Now check for perpetual chases\r
6009                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase\r
6010                                     hisPerpetual = PerpetualChase(k, forwardMostMove);\r
6011                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);\r
6012                                     if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
6013                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
6014                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );\r
6015                                         return;\r
6016                                     }\r
6017                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet\r
6018                                         break; // Abort repetition-checking loop.\r
6019                                 }\r
6020                                 // if neither of us is checking or chasing all the time, or both are, it is draw\r
6021                              }\r
6022                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
6023                              return;\r
6024                         }\r
6025                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
6026                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
6027                     }\r
6028                 }\r
6029 \r
6030                 /* Now we test for 50-move draws. Determine ply count */\r
6031                 count = forwardMostMove;\r
6032                 /* look for last irreversble move */\r
6033                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
6034                     count--;\r
6035                 /* if we hit starting position, add initial plies */\r
6036                 if( count == backwardMostMove )\r
6037                     count -= initialRulePlies;\r
6038                 count = forwardMostMove - count; \r
6039                 if( count >= 100)\r
6040                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
6041                          /* this is used to judge if draw claims are legal */\r
6042                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
6043                          SendToProgram("force\n", cps->other); // suppress reply\r
6044                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6045                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6046                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
6047                          return;\r
6048                 }\r
6049 \r
6050                 /* if draw offer is pending, treat it as a draw claim\r
6051                  * when draw condition present, to allow engines a way to\r
6052                  * claim draws before making their move to avoid a race\r
6053                  * condition occurring after their move\r
6054                  */\r
6055                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
6056                          char *p = NULL;\r
6057                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
6058                              p = "Draw claim: 50-move rule";\r
6059                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
6060                              p = "Draw claim: 3-fold repetition";\r
6061                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
6062                              p = "Draw claim: insufficient mating material";\r
6063                          if( p != NULL ) {\r
6064                              SendToProgram("force\n", cps->other); // suppress reply\r
6065                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6066                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
6067                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6068                              return;\r
6069                          }\r
6070                 }\r
6071 \r
6072 \r
6073                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
6074                     SendToProgram("force\n", cps->other); // suppress reply\r
6075                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6076                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6077 \r
6078                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
6079 \r
6080                     return;\r
6081                 }\r
6082         }\r
6083 \r
6084         bookHit = NULL;\r
6085         if (gameMode == TwoMachinesPlay) {\r
6086             /* [HGM] relaying draw offers moved to after reception of move */\r
6087             /* and interpreting offer as claim if it brings draw condition */\r
6088             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
6089                 SendToProgram("draw\n", cps->other);\r
6090             }\r
6091             if (cps->other->sendTime) {\r
6092                 SendTimeRemaining(cps->other,\r
6093                                   cps->other->twoMachinesColor[0] == 'w');\r
6094             }\r
6095             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);\r
6096             if (firstMove && !bookHit) {\r
6097                 firstMove = FALSE;\r
6098                 if (cps->other->useColors) {\r
6099                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
6100                 }\r
6101                 SendToProgram("go\n", cps->other);\r
6102             }\r
6103             cps->other->maybeThinking = TRUE;\r
6104         }\r
6105 \r
6106         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6107         \r
6108         if (!pausing && appData.ringBellAfterMoves) {\r
6109             RingBell();\r
6110         }\r
6111 \r
6112         /* \r
6113          * Reenable menu items that were disabled while\r
6114          * machine was thinking\r
6115          */\r
6116         if (gameMode != TwoMachinesPlay)\r
6117             SetUserThinkingEnables();\r
6118 \r
6119         // [HGM] book: after book hit opponent has received move and is now in force mode\r
6120         // force the book reply into it, and then fake that it outputted this move by jumping\r
6121         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move\r
6122         if(bookHit) {\r
6123                 static char bookMove[MSG_SIZ]; // a bit generous?\r
6124 \r
6125                 strcpy(bookMove, "move ");\r
6126                 strcat(bookMove, bookHit);\r
6127                 message = bookMove;\r
6128                 cps = cps->other;\r
6129                 programStats.depth = programStats.nodes = programStats.time = \r
6130                 programStats.score = programStats.got_only_move = 0;\r
6131                 sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
6132 \r
6133                 if(cps->lastPing != cps->lastPong) {\r
6134                     savedMessage = message; // args for deferred call\r
6135                     savedState = cps;\r
6136                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
6137                     return;\r
6138                 }\r
6139                 goto FakeBookMove;\r
6140         }\r
6141 \r
6142         return;\r
6143     }\r
6144 \r
6145     /* Set special modes for chess engines.  Later something general\r
6146      *  could be added here; for now there is just one kludge feature,\r
6147      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
6148      *  when "xboard" is given as an interactive command.\r
6149      */\r
6150     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
6151         cps->useSigint = FALSE;\r
6152         cps->useSigterm = FALSE;\r
6153     }\r
6154 \r
6155     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
6156      * want this, I was asked to put it in, and obliged.\r
6157      */\r
6158     if (!strncmp(message, "setboard ", 9)) {\r
6159         Board initial_position; int i;\r
6160 \r
6161         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
6162 \r
6163         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
6164             DisplayError(_("Bad FEN received from engine"), 0);\r
6165             return ;\r
6166         } else {\r
6167            Reset(FALSE, FALSE);\r
6168            CopyBoard(boards[0], initial_position);\r
6169            initialRulePlies = FENrulePlies;\r
6170            epStatus[0] = FENepStatus;\r
6171            for( i=0; i<nrCastlingRights; i++ )\r
6172                 castlingRights[0][i] = FENcastlingRights[i];\r
6173            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
6174            else gameMode = MachinePlaysBlack;                 \r
6175            DrawPosition(FALSE, boards[currentMove]);\r
6176         }\r
6177         return;\r
6178     }\r
6179 \r
6180     /*\r
6181      * Look for communication commands\r
6182      */\r
6183     if (!strncmp(message, "telluser ", 9)) {\r
6184         DisplayNote(message + 9);\r
6185         return;\r
6186     }\r
6187     if (!strncmp(message, "tellusererror ", 14)) {\r
6188         DisplayError(message + 14, 0);\r
6189         return;\r
6190     }\r
6191     if (!strncmp(message, "tellopponent ", 13)) {\r
6192       if (appData.icsActive) {\r
6193         if (loggedOn) {\r
6194           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
6195           SendToICS(buf1);\r
6196         }\r
6197       } else {\r
6198         DisplayNote(message + 13);\r
6199       }\r
6200       return;\r
6201     }\r
6202     if (!strncmp(message, "tellothers ", 11)) {\r
6203       if (appData.icsActive) {\r
6204         if (loggedOn) {\r
6205           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
6206           SendToICS(buf1);\r
6207         }\r
6208       }\r
6209       return;\r
6210     }\r
6211     if (!strncmp(message, "tellall ", 8)) {\r
6212       if (appData.icsActive) {\r
6213         if (loggedOn) {\r
6214           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
6215           SendToICS(buf1);\r
6216         }\r
6217       } else {\r
6218         DisplayNote(message + 8);\r
6219       }\r
6220       return;\r
6221     }\r
6222     if (strncmp(message, "warning", 7) == 0) {\r
6223         /* Undocumented feature, use tellusererror in new code */\r
6224         DisplayError(message, 0);\r
6225         return;\r
6226     }\r
6227     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
6228         strcpy(realname, cps->tidy);\r
6229         strcat(realname, " query");\r
6230         AskQuestion(realname, buf2, buf1, cps->pr);\r
6231         return;\r
6232     }\r
6233     /* Commands from the engine directly to ICS.  We don't allow these to be \r
6234      *  sent until we are logged on. Crafty kibitzes have been known to \r
6235      *  interfere with the login process.\r
6236      */\r
6237     if (loggedOn) {\r
6238         if (!strncmp(message, "tellics ", 8)) {\r
6239             SendToICS(message + 8);\r
6240             SendToICS("\n");\r
6241             return;\r
6242         }\r
6243         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
6244             SendToICS(ics_prefix);\r
6245             SendToICS(message + 15);\r
6246             SendToICS("\n");\r
6247             return;\r
6248         }\r
6249         /* The following are for backward compatibility only */\r
6250         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
6251             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
6252             SendToICS(ics_prefix);\r
6253             SendToICS(message);\r
6254             SendToICS("\n");\r
6255             return;\r
6256         }\r
6257     }\r
6258     if (strncmp(message, "feature ", 8) == 0) {\r
6259       ParseFeatures(message+8, cps);\r
6260     }\r
6261     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
6262         return;\r
6263     }\r
6264     /*\r
6265      * If the move is illegal, cancel it and redraw the board.\r
6266      * Also deal with other error cases.  Matching is rather loose\r
6267      * here to accommodate engines written before the spec.\r
6268      */\r
6269     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
6270         strncmp(message, "Error", 5) == 0) {\r
6271         if (StrStr(message, "name") || \r
6272             StrStr(message, "rating") || StrStr(message, "?") ||\r
6273             StrStr(message, "result") || StrStr(message, "board") ||\r
6274             StrStr(message, "bk") || StrStr(message, "computer") ||\r
6275             StrStr(message, "variant") || StrStr(message, "hint") ||\r
6276             StrStr(message, "random") || StrStr(message, "depth") ||\r
6277             StrStr(message, "accepted")) {\r
6278             return;\r
6279         }\r
6280         if (StrStr(message, "protover")) {\r
6281           /* Program is responding to input, so it's apparently done\r
6282              initializing, and this error message indicates it is\r
6283              protocol version 1.  So we don't need to wait any longer\r
6284              for it to initialize and send feature commands. */\r
6285           FeatureDone(cps, 1);\r
6286           cps->protocolVersion = 1;\r
6287           return;\r
6288         }\r
6289         cps->maybeThinking = FALSE;\r
6290 \r
6291         if (StrStr(message, "draw")) {\r
6292             /* Program doesn't have "draw" command */\r
6293             cps->sendDrawOffers = 0;\r
6294             return;\r
6295         }\r
6296         if (cps->sendTime != 1 &&\r
6297             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
6298           /* Program apparently doesn't have "time" or "otim" command */\r
6299           cps->sendTime = 0;\r
6300           return;\r
6301         }\r
6302         if (StrStr(message, "analyze")) {\r
6303             cps->analysisSupport = FALSE;\r
6304             cps->analyzing = FALSE;\r
6305             Reset(FALSE, TRUE);\r
6306             sprintf(buf2, _("%s does not support analysis"), cps->tidy);\r
6307             DisplayError(buf2, 0);\r
6308             return;\r
6309         }\r
6310         if (StrStr(message, "(no matching move)st")) {\r
6311           /* Special kludge for GNU Chess 4 only */\r
6312           cps->stKludge = TRUE;\r
6313           SendTimeControl(cps, movesPerSession, timeControl,\r
6314                           timeIncrement, appData.searchDepth,\r
6315                           searchTime);\r
6316           return;\r
6317         }\r
6318         if (StrStr(message, "(no matching move)sd")) {\r
6319           /* Special kludge for GNU Chess 4 only */\r
6320           cps->sdKludge = TRUE;\r
6321           SendTimeControl(cps, movesPerSession, timeControl,\r
6322                           timeIncrement, appData.searchDepth,\r
6323                           searchTime);\r
6324           return;\r
6325         }\r
6326         if (!StrStr(message, "llegal")) {\r
6327             return;\r
6328         }\r
6329         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6330             gameMode == IcsIdle) return;\r
6331         if (forwardMostMove <= backwardMostMove) return;\r
6332 #if 0\r
6333         /* Following removed: it caused a bug where a real illegal move\r
6334            message in analyze mored would be ignored. */\r
6335         if (cps == &first && programStats.ok_to_send == 0) {\r
6336             /* Bogus message from Crafty responding to "."  This filtering\r
6337                can miss some of the bad messages, but fortunately the bug \r
6338                is fixed in current Crafty versions, so it doesn't matter. */\r
6339             return;\r
6340         }\r
6341 #endif\r
6342         if (pausing) PauseEvent();\r
6343         if (gameMode == PlayFromGameFile) {\r
6344             /* Stop reading this game file */\r
6345             gameMode = EditGame;\r
6346             ModeHighlight();\r
6347         }\r
6348         currentMove = --forwardMostMove;\r
6349         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
6350         SwitchClocks();\r
6351         DisplayBothClocks();\r
6352         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),\r
6353                 parseList[currentMove], cps->which);\r
6354         DisplayMoveError(buf1);\r
6355         DrawPosition(FALSE, boards[currentMove]);\r
6356 \r
6357         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
6358         /* only passes fully legal moves                            */\r
6359         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
6360             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
6361                                 "False illegal-move claim", GE_XBOARD );\r
6362         }\r
6363         return;\r
6364     }\r
6365     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
6366         /* Program has a broken "time" command that\r
6367            outputs a string not ending in newline.\r
6368            Don't use it. */\r
6369         cps->sendTime = 0;\r
6370     }\r
6371     \r
6372     /*\r
6373      * If chess program startup fails, exit with an error message.\r
6374      * Attempts to recover here are futile.\r
6375      */\r
6376     if ((StrStr(message, "unknown host") != NULL)\r
6377         || (StrStr(message, "No remote directory") != NULL)\r
6378         || (StrStr(message, "not found") != NULL)\r
6379         || (StrStr(message, "No such file") != NULL)\r
6380         || (StrStr(message, "can't alloc") != NULL)\r
6381         || (StrStr(message, "Permission denied") != NULL)) {\r
6382 \r
6383         cps->maybeThinking = FALSE;\r
6384         sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),\r
6385                 cps->which, cps->program, cps->host, message);\r
6386         RemoveInputSource(cps->isr);\r
6387         DisplayFatalError(buf1, 0, 1);\r
6388         return;\r
6389     }\r
6390     \r
6391     /* \r
6392      * Look for hint output\r
6393      */\r
6394     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
6395         if (cps == &first && hintRequested) {\r
6396             hintRequested = FALSE;\r
6397             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
6398                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
6399                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
6400                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
6401                                     fromY, fromX, toY, toX, promoChar, buf1);\r
6402                 sprintf(buf2, _("Hint: %s"), buf1);\r
6403                 DisplayInformation(buf2);\r
6404             } else {\r
6405                 /* Hint move could not be parsed!? */\r
6406                 sprintf(buf2,\r
6407                         _("Illegal hint move \"%s\"\nfrom %s chess program"),\r
6408                         buf1, cps->which);\r
6409                 DisplayError(buf2, 0);\r
6410             }\r
6411         } else {\r
6412             strcpy(lastHint, buf1);\r
6413         }\r
6414         return;\r
6415     }\r
6416 \r
6417     /*\r
6418      * Ignore other messages if game is not in progress\r
6419      */\r
6420     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6421         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
6422 \r
6423     /*\r
6424      * look for win, lose, draw, or draw offer\r
6425      */\r
6426     if (strncmp(message, "1-0", 3) == 0) {\r
6427         char *p, *q, *r = "";\r
6428         p = strchr(message, '{');\r
6429         if (p) {\r
6430             q = strchr(p, '}');\r
6431             if (q) {\r
6432                 *q = NULLCHAR;\r
6433                 r = p + 1;\r
6434             }\r
6435         }\r
6436         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
6437         return;\r
6438     } else if (strncmp(message, "0-1", 3) == 0) {\r
6439         char *p, *q, *r = "";\r
6440         p = strchr(message, '{');\r
6441         if (p) {\r
6442             q = strchr(p, '}');\r
6443             if (q) {\r
6444                 *q = NULLCHAR;\r
6445                 r = p + 1;\r
6446             }\r
6447         }\r
6448         /* Kludge for Arasan 4.1 bug */\r
6449         if (strcmp(r, "Black resigns") == 0) {\r
6450             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
6451             return;\r
6452         }\r
6453         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
6454         return;\r
6455     } else if (strncmp(message, "1/2", 3) == 0) {\r
6456         char *p, *q, *r = "";\r
6457         p = strchr(message, '{');\r
6458         if (p) {\r
6459             q = strchr(p, '}');\r
6460             if (q) {\r
6461                 *q = NULLCHAR;\r
6462                 r = p + 1;\r
6463             }\r
6464         }\r
6465             \r
6466         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
6467         return;\r
6468 \r
6469     } else if (strncmp(message, "White resign", 12) == 0) {\r
6470         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6471         return;\r
6472     } else if (strncmp(message, "Black resign", 12) == 0) {\r
6473         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6474         return;\r
6475     } else if (strncmp(message, "White matches", 13) == 0 ||\r
6476                strncmp(message, "Black matches", 13) == 0   ) {\r
6477         /* [HGM] ignore GNUShogi noises */\r
6478         return;\r
6479     } else if (strncmp(message, "White", 5) == 0 &&\r
6480                message[5] != '(' &&\r
6481                StrStr(message, "Black") == NULL) {\r
6482         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6483         return;\r
6484     } else if (strncmp(message, "Black", 5) == 0 &&\r
6485                message[5] != '(') {\r
6486         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6487         return;\r
6488     } else if (strcmp(message, "resign") == 0 ||\r
6489                strcmp(message, "computer resigns") == 0) {\r
6490         switch (gameMode) {\r
6491           case MachinePlaysBlack:\r
6492           case IcsPlayingBlack:\r
6493             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
6494             break;\r
6495           case MachinePlaysWhite:\r
6496           case IcsPlayingWhite:\r
6497             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
6498             break;\r
6499           case TwoMachinesPlay:\r
6500             if (cps->twoMachinesColor[0] == 'w')\r
6501               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6502             else\r
6503               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6504             break;\r
6505           default:\r
6506             /* can't happen */\r
6507             break;\r
6508         }\r
6509         return;\r
6510     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
6511         switch (gameMode) {\r
6512           case MachinePlaysBlack:\r
6513           case IcsPlayingBlack:\r
6514             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6515             break;\r
6516           case MachinePlaysWhite:\r
6517           case IcsPlayingWhite:\r
6518             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
6519             break;\r
6520           case TwoMachinesPlay:\r
6521             if (cps->twoMachinesColor[0] == 'w')\r
6522               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6523             else\r
6524               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6525             break;\r
6526           default:\r
6527             /* can't happen */\r
6528             break;\r
6529         }\r
6530         return;\r
6531     } else if (strncmp(message, "computer mates", 14) == 0) {\r
6532         switch (gameMode) {\r
6533           case MachinePlaysBlack:\r
6534           case IcsPlayingBlack:\r
6535             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
6536             break;\r
6537           case MachinePlaysWhite:\r
6538           case IcsPlayingWhite:\r
6539             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6540             break;\r
6541           case TwoMachinesPlay:\r
6542             if (cps->twoMachinesColor[0] == 'w')\r
6543               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6544             else\r
6545               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6546             break;\r
6547           default:\r
6548             /* can't happen */\r
6549             break;\r
6550         }\r
6551         return;\r
6552     } else if (strncmp(message, "checkmate", 9) == 0) {\r
6553         if (WhiteOnMove(forwardMostMove)) {\r
6554             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6555         } else {\r
6556             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6557         }\r
6558         return;\r
6559     } else if (strstr(message, "Draw") != NULL ||\r
6560                strstr(message, "game is a draw") != NULL) {\r
6561         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
6562         return;\r
6563     } else if (strstr(message, "offer") != NULL &&\r
6564                strstr(message, "draw") != NULL) {\r
6565 #if ZIPPY\r
6566         if (appData.zippyPlay && first.initDone) {\r
6567             /* Relay offer to ICS */\r
6568             SendToICS(ics_prefix);\r
6569             SendToICS("draw\n");\r
6570         }\r
6571 #endif\r
6572         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
6573         if (gameMode == TwoMachinesPlay) {\r
6574             if (cps->other->offeredDraw) {\r
6575                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6576             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
6577             /* until after we also have move, to see if it is really claim */\r
6578             }\r
6579 #if 0\r
6580               else {\r
6581                 if (cps->other->sendDrawOffers) {\r
6582                     SendToProgram("draw\n", cps->other);\r
6583                 }\r
6584             }\r
6585 #endif\r
6586         } else if (gameMode == MachinePlaysWhite ||\r
6587                    gameMode == MachinePlaysBlack) {\r
6588           if (userOfferedDraw) {\r
6589             DisplayInformation(_("Machine accepts your draw offer"));\r
6590             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6591           } else {\r
6592             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));\r
6593           }\r
6594         }\r
6595     }\r
6596 \r
6597     \r
6598     /*\r
6599      * Look for thinking output\r
6600      */\r
6601     if ( appData.showThinking // [HGM] thinking: test all options that cause this output\r
6602           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
6603                                 ) {\r
6604         int plylev, mvleft, mvtot, curscore, time;\r
6605         char mvname[MOVE_LEN];\r
6606         u64 nodes; // [DM]\r
6607         char plyext;\r
6608         int ignore = FALSE;\r
6609         int prefixHint = FALSE;\r
6610         mvname[0] = NULLCHAR;\r
6611 \r
6612         switch (gameMode) {\r
6613           case MachinePlaysBlack:\r
6614           case IcsPlayingBlack:\r
6615             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6616             break;\r
6617           case MachinePlaysWhite:\r
6618           case IcsPlayingWhite:\r
6619             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6620             break;\r
6621           case AnalyzeMode:\r
6622           case AnalyzeFile:\r
6623             break;\r
6624           case IcsObserving: /* [DM] icsEngineAnalyze */\r
6625             if (!appData.icsEngineAnalyze) ignore = TRUE;\r
6626             break;\r
6627           case TwoMachinesPlay:\r
6628             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
6629                 ignore = TRUE;\r
6630             }\r
6631             break;\r
6632           default:\r
6633             ignore = TRUE;\r
6634             break;\r
6635         }\r
6636 \r
6637         if (!ignore) {\r
6638             buf1[0] = NULLCHAR;\r
6639             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6640                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
6641 \r
6642                 if (plyext != ' ' && plyext != '\t') {\r
6643                     time *= 100;\r
6644                 }\r
6645 \r
6646                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6647                 if( cps->scoreIsAbsolute && \r
6648                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
6649                 {\r
6650                     curscore = -curscore;\r
6651                 }\r
6652 \r
6653 \r
6654                 programStats.depth = plylev;\r
6655                 programStats.nodes = nodes;\r
6656                 programStats.time = time;\r
6657                 programStats.score = curscore;\r
6658                 programStats.got_only_move = 0;\r
6659 \r
6660                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
6661                         int ticklen;\r
6662 \r
6663                         if(cps->nps == 0) ticklen = 10*time;       // use engine reported time\r
6664                         else ticklen = (1000. * nodes) / cps->nps; // convert node count to time\r
6665                         if(WhiteOnMove(forwardMostMove)) \r
6666                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
6667                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
6668                 }\r
6669 \r
6670                 /* Buffer overflow protection */\r
6671                 if (buf1[0] != NULLCHAR) {\r
6672                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
6673                         && appData.debugMode) {\r
6674                         fprintf(debugFP,\r
6675                                 "PV is too long; using the first %d bytes.\n",\r
6676                                 sizeof(programStats.movelist) - 1);\r
6677                     }\r
6678 \r
6679                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
6680                 } else {\r
6681                     sprintf(programStats.movelist, " no PV\n");\r
6682                 }\r
6683 \r
6684                 if (programStats.seen_stat) {\r
6685                     programStats.ok_to_send = 1;\r
6686                 }\r
6687 \r
6688                 if (strchr(programStats.movelist, '(') != NULL) {\r
6689                     programStats.line_is_book = 1;\r
6690                     programStats.nr_moves = 0;\r
6691                     programStats.moves_left = 0;\r
6692                 } else {\r
6693                     programStats.line_is_book = 0;\r
6694                 }\r
6695 \r
6696                 SendProgramStatsToFrontend( cps, &programStats );\r
6697 \r
6698                 /* \r
6699                     [AS] Protect the thinkOutput buffer from overflow... this\r
6700                     is only useful if buf1 hasn't overflowed first!\r
6701                 */\r
6702                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
6703                         plylev, \r
6704                         (gameMode == TwoMachinesPlay ?\r
6705                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
6706                         ((double) curscore) / 100.0,\r
6707                         prefixHint ? lastHint : "",\r
6708                         prefixHint ? " " : "" );\r
6709 \r
6710                 if( buf1[0] != NULLCHAR ) {\r
6711                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
6712 \r
6713                     if( strlen(buf1) > max_len ) {\r
6714                         if( appData.debugMode) {\r
6715                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
6716                         }\r
6717                         buf1[max_len+1] = '\0';\r
6718                     }\r
6719 \r
6720                     strcat( thinkOutput, buf1 );\r
6721                 }\r
6722 \r
6723                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode\r
6724                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6725                     DisplayMove(currentMove - 1);\r
6726                     DisplayAnalysis();\r
6727                 }\r
6728                 return;\r
6729 \r
6730             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6731                 /* crafty (9.25+) says "(only move) <move>"\r
6732                  * if there is only 1 legal move\r
6733                  */\r
6734                 sscanf(p, "(only move) %s", buf1);\r
6735                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6736                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6737                 programStats.depth = 1;\r
6738                 programStats.nr_moves = 1;\r
6739                 programStats.moves_left = 1;\r
6740                 programStats.nodes = 1;\r
6741                 programStats.time = 1;\r
6742                 programStats.got_only_move = 1;\r
6743 \r
6744                 /* Not really, but we also use this member to\r
6745                    mean "line isn't going to change" (Crafty\r
6746                    isn't searching, so stats won't change) */\r
6747                 programStats.line_is_book = 1;\r
6748 \r
6749                 SendProgramStatsToFrontend( cps, &programStats );\r
6750                 \r
6751                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || \r
6752                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6753                     DisplayMove(currentMove - 1);\r
6754                     DisplayAnalysis();\r
6755                 }\r
6756                 return;\r
6757             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",\r
6758                               &time, &nodes, &plylev, &mvleft,\r
6759                               &mvtot, mvname) >= 5) {\r
6760                 /* The stat01: line is from Crafty (9.29+) in response\r
6761                    to the "." command */\r
6762                 programStats.seen_stat = 1;\r
6763                 cps->maybeThinking = TRUE;\r
6764 \r
6765                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6766                   return;\r
6767 \r
6768                 programStats.depth = plylev;\r
6769                 programStats.time = time;\r
6770                 programStats.nodes = nodes;\r
6771                 programStats.moves_left = mvleft;\r
6772                 programStats.nr_moves = mvtot;\r
6773                 strcpy(programStats.move_name, mvname);\r
6774                 programStats.ok_to_send = 1;\r
6775                 programStats.movelist[0] = '\0';\r
6776 \r
6777                 SendProgramStatsToFrontend( cps, &programStats );\r
6778 \r
6779                 DisplayAnalysis();\r
6780                 return;\r
6781 \r
6782             } else if (strncmp(message,"++",2) == 0) {\r
6783                 /* Crafty 9.29+ outputs this */\r
6784                 programStats.got_fail = 2;\r
6785                 return;\r
6786 \r
6787             } else if (strncmp(message,"--",2) == 0) {\r
6788                 /* Crafty 9.29+ outputs this */\r
6789                 programStats.got_fail = 1;\r
6790                 return;\r
6791 \r
6792             } else if (thinkOutput[0] != NULLCHAR &&\r
6793                        strncmp(message, "    ", 4) == 0) {\r
6794                 unsigned message_len;\r
6795 \r
6796                 p = message;\r
6797                 while (*p && *p == ' ') p++;\r
6798 \r
6799                 message_len = strlen( p );\r
6800 \r
6801                 /* [AS] Avoid buffer overflow */\r
6802                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6803                     strcat(thinkOutput, " ");\r
6804                     strcat(thinkOutput, p);\r
6805                 }\r
6806 \r
6807                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6808                     strcat(programStats.movelist, " ");\r
6809                     strcat(programStats.movelist, p);\r
6810                 }\r
6811 \r
6812                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||\r
6813                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6814                     DisplayMove(currentMove - 1);\r
6815                     DisplayAnalysis();\r
6816                 }\r
6817                 return;\r
6818             }\r
6819         }\r
6820         else {\r
6821             buf1[0] = NULLCHAR;\r
6822 \r
6823             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
6824                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6825             {\r
6826                 ChessProgramStats cpstats;\r
6827 \r
6828                 if (plyext != ' ' && plyext != '\t') {\r
6829                     time *= 100;\r
6830                 }\r
6831 \r
6832                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6833                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6834                     curscore = -curscore;\r
6835                 }\r
6836 \r
6837                 cpstats.depth = plylev;\r
6838                 cpstats.nodes = nodes;\r
6839                 cpstats.time = time;\r
6840                 cpstats.score = curscore;\r
6841                 cpstats.got_only_move = 0;\r
6842                 cpstats.movelist[0] = '\0';\r
6843 \r
6844                 if (buf1[0] != NULLCHAR) {\r
6845                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6846                 }\r
6847 \r
6848                 cpstats.ok_to_send = 0;\r
6849                 cpstats.line_is_book = 0;\r
6850                 cpstats.nr_moves = 0;\r
6851                 cpstats.moves_left = 0;\r
6852 \r
6853                 SendProgramStatsToFrontend( cps, &cpstats );\r
6854             }\r
6855         }\r
6856     }\r
6857 }\r
6858 \r
6859 \r
6860 /* Parse a game score from the character string "game", and\r
6861    record it as the history of the current game.  The game\r
6862    score is NOT assumed to start from the standard position. \r
6863    The display is not updated in any way.\r
6864    */\r
6865 void\r
6866 ParseGameHistory(game)\r
6867      char *game;\r
6868 {\r
6869     ChessMove moveType;\r
6870     int fromX, fromY, toX, toY, boardIndex;\r
6871     char promoChar;\r
6872     char *p, *q;\r
6873     char buf[MSG_SIZ];\r
6874 \r
6875     if (appData.debugMode)\r
6876       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6877 \r
6878     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6879     gameInfo.site = StrSave(appData.icsHost);\r
6880     gameInfo.date = PGNDate();\r
6881     gameInfo.round = StrSave("-");\r
6882 \r
6883     /* Parse out names of players */\r
6884     while (*game == ' ') game++;\r
6885     p = buf;\r
6886     while (*game != ' ') *p++ = *game++;\r
6887     *p = NULLCHAR;\r
6888     gameInfo.white = StrSave(buf);\r
6889     while (*game == ' ') game++;\r
6890     p = buf;\r
6891     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6892     *p = NULLCHAR;\r
6893     gameInfo.black = StrSave(buf);\r
6894 \r
6895     /* Parse moves */\r
6896     boardIndex = blackPlaysFirst ? 1 : 0;\r
6897     yynewstr(game);\r
6898     for (;;) {\r
6899         yyboardindex = boardIndex;\r
6900         moveType = (ChessMove) yylex();\r
6901         switch (moveType) {\r
6902           case IllegalMove:             /* maybe suicide chess, etc. */\r
6903   if (appData.debugMode) {\r
6904     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6905     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6906     setbuf(debugFP, NULL);\r
6907   }\r
6908           case WhitePromotionChancellor:\r
6909           case BlackPromotionChancellor:\r
6910           case WhitePromotionArchbishop:\r
6911           case BlackPromotionArchbishop:\r
6912           case WhitePromotionQueen:\r
6913           case BlackPromotionQueen:\r
6914           case WhitePromotionRook:\r
6915           case BlackPromotionRook:\r
6916           case WhitePromotionBishop:\r
6917           case BlackPromotionBishop:\r
6918           case WhitePromotionKnight:\r
6919           case BlackPromotionKnight:\r
6920           case WhitePromotionKing:\r
6921           case BlackPromotionKing:\r
6922           case NormalMove:\r
6923           case WhiteCapturesEnPassant:\r
6924           case BlackCapturesEnPassant:\r
6925           case WhiteKingSideCastle:\r
6926           case WhiteQueenSideCastle:\r
6927           case BlackKingSideCastle:\r
6928           case BlackQueenSideCastle:\r
6929           case WhiteKingSideCastleWild:\r
6930           case WhiteQueenSideCastleWild:\r
6931           case BlackKingSideCastleWild:\r
6932           case BlackQueenSideCastleWild:\r
6933           /* PUSH Fabien */\r
6934           case WhiteHSideCastleFR:\r
6935           case WhiteASideCastleFR:\r
6936           case BlackHSideCastleFR:\r
6937           case BlackASideCastleFR:\r
6938           /* POP Fabien */\r
6939             fromX = currentMoveString[0] - AAA;\r
6940             fromY = currentMoveString[1] - ONE;\r
6941             toX = currentMoveString[2] - AAA;\r
6942             toY = currentMoveString[3] - ONE;\r
6943             promoChar = currentMoveString[4];\r
6944             break;\r
6945           case WhiteDrop:\r
6946           case BlackDrop:\r
6947             fromX = moveType == WhiteDrop ?\r
6948               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6949             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6950             fromY = DROP_RANK;\r
6951             toX = currentMoveString[2] - AAA;\r
6952             toY = currentMoveString[3] - ONE;\r
6953             promoChar = NULLCHAR;\r
6954             break;\r
6955           case AmbiguousMove:\r
6956             /* bug? */\r
6957             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);\r
6958   if (appData.debugMode) {\r
6959     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
6960     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6961     setbuf(debugFP, NULL);\r
6962   }\r
6963             DisplayError(buf, 0);\r
6964             return;\r
6965           case ImpossibleMove:\r
6966             /* bug? */\r
6967             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);\r
6968   if (appData.debugMode) {\r
6969     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
6970     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6971     setbuf(debugFP, NULL);\r
6972   }\r
6973             DisplayError(buf, 0);\r
6974             return;\r
6975           case (ChessMove) 0:   /* end of file */\r
6976             if (boardIndex < backwardMostMove) {\r
6977                 /* Oops, gap.  How did that happen? */\r
6978                 DisplayError(_("Gap in move list"), 0);\r
6979                 return;\r
6980             }\r
6981             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
6982             if (boardIndex > forwardMostMove) {\r
6983                 forwardMostMove = boardIndex;\r
6984             }\r
6985             return;\r
6986           case ElapsedTime:\r
6987             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
6988                 strcat(parseList[boardIndex-1], " ");\r
6989                 strcat(parseList[boardIndex-1], yy_text);\r
6990             }\r
6991             continue;\r
6992           case Comment:\r
6993           case PGNTag:\r
6994           case NAG:\r
6995           default:\r
6996             /* ignore */\r
6997             continue;\r
6998           case WhiteWins:\r
6999           case BlackWins:\r
7000           case GameIsDrawn:\r
7001           case GameUnfinished:\r
7002             if (gameMode == IcsExamining) {\r
7003                 if (boardIndex < backwardMostMove) {\r
7004                     /* Oops, gap.  How did that happen? */\r
7005                     return;\r
7006                 }\r
7007                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
7008                 return;\r
7009             }\r
7010             gameInfo.result = moveType;\r
7011             p = strchr(yy_text, '{');\r
7012             if (p == NULL) p = strchr(yy_text, '(');\r
7013             if (p == NULL) {\r
7014                 p = yy_text;\r
7015                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
7016             } else {\r
7017                 q = strchr(p, *p == '{' ? '}' : ')');\r
7018                 if (q != NULL) *q = NULLCHAR;\r
7019                 p++;\r
7020             }\r
7021             gameInfo.resultDetails = StrSave(p);\r
7022             continue;\r
7023         }\r
7024         if (boardIndex >= forwardMostMove &&\r
7025             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
7026             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
7027             return;\r
7028         }\r
7029         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
7030                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
7031                                  parseList[boardIndex]);\r
7032         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
7033         /* currentMoveString is set as a side-effect of yylex */\r
7034         strcpy(moveList[boardIndex], currentMoveString);\r
7035         strcat(moveList[boardIndex], "\n");\r
7036         boardIndex++;\r
7037         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
7038         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
7039                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
7040           case MT_NONE:\r
7041           case MT_STALEMATE:\r
7042           default:\r
7043             break;\r
7044           case MT_CHECK:\r
7045             if(gameInfo.variant != VariantShogi)\r
7046                 strcat(parseList[boardIndex - 1], "+");\r
7047             break;\r
7048           case MT_CHECKMATE:\r
7049             strcat(parseList[boardIndex - 1], "#");\r
7050             break;\r
7051         }\r
7052     }\r
7053 }\r
7054 \r
7055 \r
7056 /* Apply a move to the given board  */\r
7057 void\r
7058 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
7059      int fromX, fromY, toX, toY;\r
7060      int promoChar;\r
7061      Board board;\r
7062 {\r
7063   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
7064 \r
7065     /* [HGM] compute & store e.p. status and castling rights for new position */\r
7066     /* if we are updating a board for which those exist (i.e. in boards[])    */\r
7067     if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)\r
7068     { int i, j;\r
7069 \r
7070       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
7071       oldEP = epStatus[p-1];\r
7072       epStatus[p] = EP_NONE;\r
7073 \r
7074       if( board[toY][toX] != EmptySquare ) \r
7075            epStatus[p] = EP_CAPTURE;  \r
7076 \r
7077       if( board[fromY][fromX] == WhitePawn ) {\r
7078            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7079                epStatus[p] = EP_PAWN_MOVE;\r
7080            if( toY-fromY==2) {\r
7081                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
7082                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7083                       epStatus[p] = toX | berolina;\r
7084                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
7085                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7086                       epStatus[p] = toX;\r
7087            }\r
7088       } else \r
7089       if( board[fromY][fromX] == BlackPawn ) {\r
7090            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7091                epStatus[p] = EP_PAWN_MOVE; \r
7092            if( toY-fromY== -2) {\r
7093                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
7094                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7095                       epStatus[p] = toX | berolina;\r
7096                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
7097                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7098                       epStatus[p] = toX;\r
7099            }\r
7100        }\r
7101 \r
7102        for(i=0; i<nrCastlingRights; i++) {\r
7103            castlingRights[p][i] = castlingRights[p-1][i];\r
7104            if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||\r
7105               castlingRights[p][i] == toX   && castlingRank[i] == toY   \r
7106              ) castlingRights[p][i] = -1; // revoke for moved or captured piece\r
7107        }\r
7108 \r
7109     }\r
7110 \r
7111   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
7112   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
7113        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
7114          \r
7115   if (fromX == toX && fromY == toY) return;\r
7116 \r
7117   if (fromY == DROP_RANK) {\r
7118         /* must be first */\r
7119         piece = board[toY][toX] = (ChessSquare) fromX;\r
7120   } else {\r
7121      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
7122      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
7123      if(gameInfo.variant == VariantKnightmate)\r
7124          king += (int) WhiteUnicorn - (int) WhiteKing;\r
7125 \r
7126     /* Code added by Tord: */\r
7127     /* FRC castling assumed when king captures friendly rook. */\r
7128     if (board[fromY][fromX] == WhiteKing &&\r
7129              board[toY][toX] == WhiteRook) {\r
7130       board[fromY][fromX] = EmptySquare;\r
7131       board[toY][toX] = EmptySquare;\r
7132       if(toX > fromX) {\r
7133         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
7134       } else {\r
7135         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
7136       }\r
7137     } else if (board[fromY][fromX] == BlackKing &&\r
7138                board[toY][toX] == BlackRook) {\r
7139       board[fromY][fromX] = EmptySquare;\r
7140       board[toY][toX] = EmptySquare;\r
7141       if(toX > fromX) {\r
7142         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
7143       } else {\r
7144         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
7145       }\r
7146     /* End of code added by Tord */\r
7147 \r
7148     } else if (board[fromY][fromX] == king\r
7149         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7150         && toY == fromY && toX > fromX+1) {\r
7151         board[fromY][fromX] = EmptySquare;\r
7152         board[toY][toX] = king;\r
7153         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7154         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7155     } else if (board[fromY][fromX] == king\r
7156         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7157                && toY == fromY && toX < fromX-1) {\r
7158         board[fromY][fromX] = EmptySquare;\r
7159         board[toY][toX] = king;\r
7160         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7161         board[fromY][BOARD_LEFT] = EmptySquare;\r
7162     } else if (board[fromY][fromX] == WhitePawn\r
7163                && toY == BOARD_HEIGHT-1\r
7164                && gameInfo.variant != VariantXiangqi\r
7165                ) {\r
7166         /* white pawn promotion */\r
7167         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
7168         if (board[toY][toX] == EmptySquare) {\r
7169             board[toY][toX] = WhiteQueen;\r
7170         }\r
7171         if(gameInfo.variant==VariantBughouse ||\r
7172            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7173             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7174         board[fromY][fromX] = EmptySquare;\r
7175     } else if ((fromY == BOARD_HEIGHT-4)\r
7176                && (toX != fromX)\r
7177                && gameInfo.variant != VariantXiangqi\r
7178                && gameInfo.variant != VariantBerolina\r
7179                && (board[fromY][fromX] == WhitePawn)\r
7180                && (board[toY][toX] == EmptySquare)) {\r
7181         board[fromY][fromX] = EmptySquare;\r
7182         board[toY][toX] = WhitePawn;\r
7183         captured = board[toY - 1][toX];\r
7184         board[toY - 1][toX] = EmptySquare;\r
7185     } else if ((fromY == BOARD_HEIGHT-4)\r
7186                && (toX == fromX)\r
7187                && gameInfo.variant == VariantBerolina\r
7188                && (board[fromY][fromX] == WhitePawn)\r
7189                && (board[toY][toX] == EmptySquare)) {\r
7190         board[fromY][fromX] = EmptySquare;\r
7191         board[toY][toX] = WhitePawn;\r
7192         if(oldEP & EP_BEROLIN_A) {\r
7193                 captured = board[fromY][fromX-1];\r
7194                 board[fromY][fromX-1] = EmptySquare;\r
7195         }else{  captured = board[fromY][fromX+1];\r
7196                 board[fromY][fromX+1] = EmptySquare;\r
7197         }\r
7198     } else if (board[fromY][fromX] == king\r
7199         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7200                && toY == fromY && toX > fromX+1) {\r
7201         board[fromY][fromX] = EmptySquare;\r
7202         board[toY][toX] = king;\r
7203         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7204         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7205     } else if (board[fromY][fromX] == king\r
7206         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7207                && toY == fromY && toX < fromX-1) {\r
7208         board[fromY][fromX] = EmptySquare;\r
7209         board[toY][toX] = king;\r
7210         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7211         board[fromY][BOARD_LEFT] = EmptySquare;\r
7212     } else if (fromY == 7 && fromX == 3\r
7213                && board[fromY][fromX] == BlackKing\r
7214                && toY == 7 && toX == 5) {\r
7215         board[fromY][fromX] = EmptySquare;\r
7216         board[toY][toX] = BlackKing;\r
7217         board[fromY][7] = EmptySquare;\r
7218         board[toY][4] = BlackRook;\r
7219     } else if (fromY == 7 && fromX == 3\r
7220                && board[fromY][fromX] == BlackKing\r
7221                && toY == 7 && toX == 1) {\r
7222         board[fromY][fromX] = EmptySquare;\r
7223         board[toY][toX] = BlackKing;\r
7224         board[fromY][0] = EmptySquare;\r
7225         board[toY][2] = BlackRook;\r
7226     } else if (board[fromY][fromX] == BlackPawn\r
7227                && toY == 0\r
7228                && gameInfo.variant != VariantXiangqi\r
7229                ) {\r
7230         /* black pawn promotion */\r
7231         board[0][toX] = CharToPiece(ToLower(promoChar));\r
7232         if (board[0][toX] == EmptySquare) {\r
7233             board[0][toX] = BlackQueen;\r
7234         }\r
7235         if(gameInfo.variant==VariantBughouse ||\r
7236            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7237             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7238         board[fromY][fromX] = EmptySquare;\r
7239     } else if ((fromY == 3)\r
7240                && (toX != fromX)\r
7241                && gameInfo.variant != VariantXiangqi\r
7242                && gameInfo.variant != VariantBerolina\r
7243                && (board[fromY][fromX] == BlackPawn)\r
7244                && (board[toY][toX] == EmptySquare)) {\r
7245         board[fromY][fromX] = EmptySquare;\r
7246         board[toY][toX] = BlackPawn;\r
7247         captured = board[toY + 1][toX];\r
7248         board[toY + 1][toX] = EmptySquare;\r
7249     } else if ((fromY == 3)\r
7250                && (toX == fromX)\r
7251                && gameInfo.variant == VariantBerolina\r
7252                && (board[fromY][fromX] == BlackPawn)\r
7253                && (board[toY][toX] == EmptySquare)) {\r
7254         board[fromY][fromX] = EmptySquare;\r
7255         board[toY][toX] = BlackPawn;\r
7256         if(oldEP & EP_BEROLIN_A) {\r
7257                 captured = board[fromY][fromX-1];\r
7258                 board[fromY][fromX-1] = EmptySquare;\r
7259         }else{  captured = board[fromY][fromX+1];\r
7260                 board[fromY][fromX+1] = EmptySquare;\r
7261         }\r
7262     } else {\r
7263         board[toY][toX] = board[fromY][fromX];\r
7264         board[fromY][fromX] = EmptySquare;\r
7265     }\r
7266 \r
7267     /* [HGM] now we promote for Shogi, if needed */\r
7268     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
7269         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7270   }\r
7271 \r
7272     if (gameInfo.holdingsWidth != 0) {\r
7273 \r
7274       /* !!A lot more code needs to be written to support holdings  */\r
7275       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
7276       /* penultimate board files, so they are automaticlly stored   */\r
7277       /* in the game history.                                       */\r
7278       if (fromY == DROP_RANK) {\r
7279         /* Delete from holdings, by decreasing count */\r
7280         /* and erasing image if necessary            */\r
7281         p = (int) fromX;\r
7282         if(p < (int) BlackPawn) { /* white drop */\r
7283              p -= (int)WhitePawn;\r
7284              if(p >= gameInfo.holdingsSize) p = 0;\r
7285              if(--board[p][BOARD_WIDTH-2] == 0)\r
7286                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
7287         } else {                  /* black drop */\r
7288              p -= (int)BlackPawn;\r
7289              if(p >= gameInfo.holdingsSize) p = 0;\r
7290              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
7291                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
7292         }\r
7293       }\r
7294       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
7295           && gameInfo.variant != VariantBughouse        ) {\r
7296         /* [HGM] holdings: Add to holdings, if holdings exist */\r
7297         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { \r
7298                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
7299                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
7300         }\r
7301         p = (int) captured;\r
7302         if (p >= (int) BlackPawn) {\r
7303           p -= (int)BlackPawn;\r
7304           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7305                   /* in Shogi restore piece to its original  first */\r
7306                   captured = (ChessSquare) (DEMOTED captured);\r
7307                   p = DEMOTED p;\r
7308           }\r
7309           p = PieceToNumber((ChessSquare)p);\r
7310           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
7311           board[p][BOARD_WIDTH-2]++;\r
7312           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;\r
7313         } else {\r
7314           p -= (int)WhitePawn;\r
7315           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7316                   captured = (ChessSquare) (DEMOTED captured);\r
7317                   p = DEMOTED p;\r
7318           }\r
7319           p = PieceToNumber((ChessSquare)p);\r
7320           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
7321           board[BOARD_HEIGHT-1-p][1]++;\r
7322           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;\r
7323         }\r
7324       }\r
7325 \r
7326     } else if (gameInfo.variant == VariantAtomic) {\r
7327       if (captured != EmptySquare) {\r
7328         int y, x;\r
7329         for (y = toY-1; y <= toY+1; y++) {\r
7330           for (x = toX-1; x <= toX+1; x++) {\r
7331             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
7332                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
7333               board[y][x] = EmptySquare;\r
7334             }\r
7335           }\r
7336         }\r
7337         board[toY][toX] = EmptySquare;\r
7338       }\r
7339     }\r
7340     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
7341         /* [HGM] Shogi promotions */\r
7342         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7343     }\r
7344 \r
7345 }\r
7346 \r
7347 /* Updates forwardMostMove */\r
7348 void\r
7349 MakeMove(fromX, fromY, toX, toY, promoChar)\r
7350      int fromX, fromY, toX, toY;\r
7351      int promoChar;\r
7352 {\r
7353 //    forwardMostMove++; // [HGM] bare: moved downstream\r
7354 \r
7355     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */\r
7356         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
7357         piece = boards[forwardMostMove][fromY][fromX];\r
7358         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
7359         if(gameInfo.variant == VariantKnightmate)\r
7360             king += (int) WhiteUnicorn - (int) WhiteKing;\r
7361         if(forwardMostMove == 0) {\r
7362             if(blackPlaysFirst) \r
7363                 fprintf(serverMoves, "%s;", second.tidy);\r
7364             fprintf(serverMoves, "%s;", first.tidy);\r
7365             if(!blackPlaysFirst) \r
7366                 fprintf(serverMoves, "%s;", second.tidy);\r
7367         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
7368         lastLoadFlag = loadFlag;\r
7369         // print base move\r
7370         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
7371         // print castling suffix\r
7372         if( toY == fromY && piece == king ) {\r
7373             if(toX-fromX > 1)\r
7374                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
7375             if(fromX-toX >1)\r
7376                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
7377         }\r
7378         // e.p. suffix\r
7379         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||\r
7380              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&\r
7381              boards[forwardMostMove][toY][toX] == EmptySquare\r
7382              && fromX != toX )\r
7383                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
7384         // promotion suffix\r
7385         if(promoChar != NULLCHAR)\r
7386                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
7387         if(!loadFlag) {\r
7388             fprintf(serverMoves, "/%d/%d",\r
7389                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);\r
7390             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;\r
7391             else                      timeLeft = blackTimeRemaining/1000;\r
7392             fprintf(serverMoves, "/%d", timeLeft);\r
7393         }\r
7394         fflush(serverMoves);\r
7395     }\r
7396 \r
7397     if (forwardMostMove+1 >= MAX_MOVES) {\r
7398       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
7399                         0, 1);\r
7400       return;\r
7401     }\r
7402     SwitchClocks();\r
7403     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;\r
7404     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;\r
7405     if (commentList[forwardMostMove+1] != NULL) {\r
7406         free(commentList[forwardMostMove+1]);\r
7407         commentList[forwardMostMove+1] = NULL;\r
7408     }\r
7409     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);\r
7410     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);\r
7411     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board\r
7412     gameInfo.result = GameUnfinished;\r
7413     if (gameInfo.resultDetails != NULL) {\r
7414         free(gameInfo.resultDetails);\r
7415         gameInfo.resultDetails = NULL;\r
7416     }\r
7417     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
7418                               moveList[forwardMostMove - 1]);\r
7419     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
7420                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
7421                              fromY, fromX, toY, toX, promoChar,\r
7422                              parseList[forwardMostMove - 1]);\r
7423     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
7424                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
7425                             castlingRights[forwardMostMove]) ) {\r
7426       case MT_NONE:\r
7427       case MT_STALEMATE:\r
7428       default:\r
7429         break;\r
7430       case MT_CHECK:\r
7431         if(gameInfo.variant != VariantShogi)\r
7432             strcat(parseList[forwardMostMove - 1], "+");\r
7433         break;\r
7434       case MT_CHECKMATE:\r
7435         strcat(parseList[forwardMostMove - 1], "#");\r
7436         break;\r
7437     }\r
7438     if (appData.debugMode) {\r
7439         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
7440     }\r
7441 \r
7442 }\r
7443 \r
7444 /* Updates currentMove if not pausing */\r
7445 void\r
7446 ShowMove(fromX, fromY, toX, toY)\r
7447 {\r
7448     int instant = (gameMode == PlayFromGameFile) ?\r
7449         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
7450     if(appData.noGUI) return;\r
7451     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
7452         if (!instant) {\r
7453             if (forwardMostMove == currentMove + 1) {\r
7454                 AnimateMove(boards[forwardMostMove - 1],\r
7455                             fromX, fromY, toX, toY);\r
7456             }\r
7457             if (appData.highlightLastMove) {\r
7458                 SetHighlights(fromX, fromY, toX, toY);\r
7459             }\r
7460         }\r
7461         currentMove = forwardMostMove;\r
7462     }\r
7463 \r
7464     if (instant) return;\r
7465 \r
7466     DisplayMove(currentMove - 1);\r
7467     DrawPosition(FALSE, boards[currentMove]);\r
7468     DisplayBothClocks();\r
7469     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
7470 }\r
7471 \r
7472 void SendEgtPath(ChessProgramState *cps)\r
7473 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */\r
7474         char buf[MSG_SIZ], name[MSG_SIZ], *p;\r
7475 \r
7476         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;\r
7477 \r
7478         while(*p) {\r
7479             char c, *q = name+1, *r, *s;\r
7480 \r
7481             name[0] = ','; // extract next format name from feature and copy with prefixed ','\r
7482             while(*p && *p != ',') *q++ = *p++;\r
7483             *q++ = ':'; *q = 0;\r
7484             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && \r
7485                 strcmp(name, ",nalimov:") == 0 ) {\r
7486                 // take nalimov path from the menu-changeable option first, if it is defined\r
7487                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);\r
7488                 SendToProgram(buf,cps);     // send egtbpath command for nalimov\r
7489             } else\r
7490             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||\r
7491                 (s = StrStr(appData.egtFormats, name)) != NULL) {\r
7492                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma\r
7493                 s = r = StrStr(s, ":") + 1; // beginning of path info\r
7494                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string\r
7495                 c = *r; *r = 0;             // temporarily null-terminate path info\r
7496                     *--q = 0;               // strip of trailig ':' from name\r
7497                     sprintf(buf, "egtbpath %s %s\n", name+1, s);\r
7498                 *r = c;\r
7499                 SendToProgram(buf,cps);     // send egtbpath command for this format\r
7500             }\r
7501             if(*p == ',') p++; // read away comma to position for next format name\r
7502         }\r
7503 }\r
7504 \r
7505 void\r
7506 InitChessProgram(cps, setup)\r
7507      ChessProgramState *cps;\r
7508      int setup; /* [HGM] needed to setup FRC opening position */\r
7509 {\r
7510     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
7511     if (appData.noChessProgram) return;\r
7512     hintRequested = FALSE;\r
7513     bookRequested = FALSE;\r
7514 \r
7515     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */\r
7516     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */\r
7517     if(cps->memSize) { /* [HGM] memory */\r
7518         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);\r
7519         SendToProgram(buf, cps);\r
7520     }\r
7521     SendEgtPath(cps); /* [HGM] EGT */\r
7522     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */\r
7523         sprintf(buf, "cores %d\n", appData.smpCores);\r
7524         SendToProgram(buf, cps);\r
7525     }\r
7526 \r
7527     SendToProgram(cps->initString, cps);\r
7528     if (gameInfo.variant != VariantNormal &&\r
7529         gameInfo.variant != VariantLoadable\r
7530         /* [HGM] also send variant if board size non-standard */\r
7531         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
7532                                             ) {\r
7533       char *v = VariantName(gameInfo.variant);\r
7534       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
7535         /* [HGM] in protocol 1 we have to assume all variants valid */\r
7536         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);\r
7537         DisplayFatalError(buf, 0, 1);\r
7538         return;\r
7539       }\r
7540 \r
7541       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
7542       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7543       if( gameInfo.variant == VariantXiangqi )\r
7544            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
7545       if( gameInfo.variant == VariantShogi )\r
7546            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
7547       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
7548            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
7549       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
7550                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
7551            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7552       if( gameInfo.variant == VariantCourier )\r
7553            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7554       if( gameInfo.variant == VariantSuper )\r
7555            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7556       if( gameInfo.variant == VariantGreat )\r
7557            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7558 \r
7559       if(overruled) {\r
7560            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
7561                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
7562            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
7563            if(StrStr(cps->variants, b) == NULL) { \r
7564                // specific sized variant not known, check if general sizing allowed\r
7565                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
7566                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
7567                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
7568                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
7569                        DisplayFatalError(buf, 0, 1);\r
7570                        return;\r
7571                    }\r
7572                    /* [HGM] here we really should compare with the maximum supported board size */\r
7573                }\r
7574            }\r
7575       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
7576       sprintf(buf, "variant %s\n", b);\r
7577       SendToProgram(buf, cps);\r
7578     }\r
7579     currentlyInitializedVariant = gameInfo.variant;\r
7580 \r
7581     /* [HGM] send opening position in FRC to first engine */\r
7582     if(setup) {\r
7583           SendToProgram("force\n", cps);\r
7584           SendBoard(cps, 0);\r
7585           /* engine is now in force mode! Set flag to wake it up after first move. */\r
7586           setboardSpoiledMachineBlack = 1;\r
7587     }\r
7588 \r
7589     if (cps->sendICS) {\r
7590       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
7591       SendToProgram(buf, cps);\r
7592     }\r
7593     cps->maybeThinking = FALSE;\r
7594     cps->offeredDraw = 0;\r
7595     if (!appData.icsActive) {\r
7596         SendTimeControl(cps, movesPerSession, timeControl,\r
7597                         timeIncrement, appData.searchDepth,\r
7598                         searchTime);\r
7599     }\r
7600     if (appData.showThinking \r
7601         // [HGM] thinking: four options require thinking output to be sent\r
7602         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
7603                                 ) {\r
7604         SendToProgram("post\n", cps);\r
7605     }\r
7606     SendToProgram("hard\n", cps);\r
7607     if (!appData.ponderNextMove) {\r
7608         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
7609            it without being sure what state we are in first.  "hard"\r
7610            is not a toggle, so that one is OK.\r
7611          */\r
7612         SendToProgram("easy\n", cps);\r
7613     }\r
7614     if (cps->usePing) {\r
7615       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
7616       SendToProgram(buf, cps);\r
7617     }\r
7618     cps->initDone = TRUE;\r
7619 }   \r
7620 \r
7621 \r
7622 void\r
7623 StartChessProgram(cps)\r
7624      ChessProgramState *cps;\r
7625 {\r
7626     char buf[MSG_SIZ];\r
7627     int err;\r
7628 \r
7629     if (appData.noChessProgram) return;\r
7630     cps->initDone = FALSE;\r
7631 \r
7632     if (strcmp(cps->host, "localhost") == 0) {\r
7633         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
7634     } else if (*appData.remoteShell == NULLCHAR) {\r
7635         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
7636     } else {\r
7637         if (*appData.remoteUser == NULLCHAR) {\r
7638             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
7639                     cps->program);\r
7640         } else {\r
7641             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
7642                     cps->host, appData.remoteUser, cps->program);\r
7643         }\r
7644         err = StartChildProcess(buf, "", &cps->pr);\r
7645     }\r
7646     \r
7647     if (err != 0) {\r
7648         sprintf(buf, _("Startup failure on '%s'"), cps->program);\r
7649         DisplayFatalError(buf, err, 1);\r
7650         cps->pr = NoProc;\r
7651         cps->isr = NULL;\r
7652         return;\r
7653     }\r
7654     \r
7655     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
7656     if (cps->protocolVersion > 1) {\r
7657       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
7658       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options\r
7659       cps->comboCnt = 0;  //                and values of combo boxes\r
7660       SendToProgram(buf, cps);\r
7661     } else {\r
7662       SendToProgram("xboard\n", cps);\r
7663     }\r
7664 }\r
7665 \r
7666 \r
7667 void\r
7668 TwoMachinesEventIfReady P((void))\r
7669 {\r
7670   if (first.lastPing != first.lastPong) {\r
7671     DisplayMessage("", _("Waiting for first chess program"));\r
7672     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7673     return;\r
7674   }\r
7675   if (second.lastPing != second.lastPong) {\r
7676     DisplayMessage("", _("Waiting for second chess program"));\r
7677     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7678     return;\r
7679   }\r
7680   ThawUI();\r
7681   TwoMachinesEvent();\r
7682 }\r
7683 \r
7684 void\r
7685 NextMatchGame P((void))\r
7686 {\r
7687     int index; /* [HGM] autoinc: step lod index during match */\r
7688     Reset(FALSE, TRUE);\r
7689     if (*appData.loadGameFile != NULLCHAR) {\r
7690         index = appData.loadGameIndex;\r
7691         if(index < 0) { // [HGM] autoinc\r
7692             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7693             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7694         } \r
7695         LoadGameFromFile(appData.loadGameFile,\r
7696                          index,\r
7697                          appData.loadGameFile, FALSE);\r
7698     } else if (*appData.loadPositionFile != NULLCHAR) {\r
7699         index = appData.loadPositionIndex;\r
7700         if(index < 0) { // [HGM] autoinc\r
7701             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7702             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7703         } \r
7704         LoadPositionFromFile(appData.loadPositionFile,\r
7705                              index,\r
7706                              appData.loadPositionFile);\r
7707     }\r
7708     TwoMachinesEventIfReady();\r
7709 }\r
7710 \r
7711 void UserAdjudicationEvent( int result )\r
7712 {\r
7713     ChessMove gameResult = GameIsDrawn;\r
7714 \r
7715     if( result > 0 ) {\r
7716         gameResult = WhiteWins;\r
7717     }\r
7718     else if( result < 0 ) {\r
7719         gameResult = BlackWins;\r
7720     }\r
7721 \r
7722     if( gameMode == TwoMachinesPlay ) {\r
7723         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
7724     }\r
7725 }\r
7726 \r
7727 \r
7728 void\r
7729 GameEnds(result, resultDetails, whosays)\r
7730      ChessMove result;\r
7731      char *resultDetails;\r
7732      int whosays;\r
7733 {\r
7734     GameMode nextGameMode;\r
7735     int isIcsGame;\r
7736     char buf[MSG_SIZ];\r
7737 \r
7738     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
7739     endingGame = 1;\r
7740 \r
7741     if (appData.debugMode) {\r
7742       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
7743               result, resultDetails ? resultDetails : "(null)", whosays);\r
7744     }\r
7745 \r
7746     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
7747         /* If we are playing on ICS, the server decides when the\r
7748            game is over, but the engine can offer to draw, claim \r
7749            a draw, or resign. \r
7750          */\r
7751 #if ZIPPY\r
7752         if (appData.zippyPlay && first.initDone) {\r
7753             if (result == GameIsDrawn) {\r
7754                 /* In case draw still needs to be claimed */\r
7755                 SendToICS(ics_prefix);\r
7756                 SendToICS("draw\n");\r
7757             } else if (StrCaseStr(resultDetails, "resign")) {\r
7758                 SendToICS(ics_prefix);\r
7759                 SendToICS("resign\n");\r
7760             }\r
7761         }\r
7762 #endif\r
7763         endingGame = 0; /* [HGM] crash */\r
7764         return;\r
7765     }\r
7766 \r
7767     /* If we're loading the game from a file, stop */\r
7768     if (whosays == GE_FILE) {\r
7769       (void) StopLoadGameTimer();\r
7770       gameFileFP = NULL;\r
7771     }\r
7772 \r
7773     /* Cancel draw offers */\r
7774     first.offeredDraw = second.offeredDraw = 0;\r
7775 \r
7776     /* If this is an ICS game, only ICS can really say it's done;\r
7777        if not, anyone can. */\r
7778     isIcsGame = (gameMode == IcsPlayingWhite || \r
7779                  gameMode == IcsPlayingBlack || \r
7780                  gameMode == IcsObserving    || \r
7781                  gameMode == IcsExamining);\r
7782 \r
7783     if (!isIcsGame || whosays == GE_ICS) {\r
7784         /* OK -- not an ICS game, or ICS said it was done */\r
7785         StopClocks();\r
7786         if (!isIcsGame && !appData.noChessProgram) \r
7787           SetUserThinkingEnables();\r
7788     \r
7789         /* [HGM] if a machine claims the game end we verify this claim */\r
7790         if(gameMode == TwoMachinesPlay && appData.testClaims) {\r
7791             if(appData.testLegality && whosays >= GE_ENGINE1 ) {\r
7792                 char claimer;\r
7793 \r
7794                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
7795                                             first.twoMachinesColor[0] :\r
7796                                             second.twoMachinesColor[0] ;\r
7797                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) &&\r
7798                     (result == WhiteWins && claimer == 'w' ||\r
7799                      result == BlackWins && claimer == 'b'   ) ) {\r
7800                 if (appData.debugMode) {\r
7801                      fprintf(debugFP, "result=%d sp=%d move=%d\n",\r
7802                         result, epStatus[forwardMostMove], forwardMostMove);\r
7803                 }\r
7804                       /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7805                       if(epStatus[forwardMostMove] != EP_CHECKMATE &&\r
7806                          result != (WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins)) {\r
7807                               sprintf(buf, "False win claim: '%s'", resultDetails);\r
7808                               result = claimer == 'w' ? BlackWins : WhiteWins;\r
7809                               resultDetails = buf;\r
7810                       }\r
7811                 } else\r
7812                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
7813                     && (forwardMostMove <= backwardMostMove ||\r
7814                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
7815                         (claimer=='b')==(forwardMostMove&1))\r
7816                                                                                   ) {\r
7817                       /* [HGM] verify: draws that were not flagged are false claims */\r
7818                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
7819                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7820                       resultDetails = buf;\r
7821                 }\r
7822                 /* (Claiming a loss is accepted no questions asked!) */\r
7823             }\r
7824             /* [HGM] bare: don't allow bare King to win */\r
7825             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)\r
7826                          && result != GameIsDrawn)\r
7827             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
7828                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
7829                         int p = (int)boards[forwardMostMove][i][j] - color;\r
7830                         if(p >= 0 && p <= (int)WhiteKing) k++;\r
7831                 }\r
7832                 if (appData.debugMode) {\r
7833                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",\r
7834                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);\r
7835                 }\r
7836                 if(k <= 1) {\r
7837                         result = GameIsDrawn;\r
7838                         sprintf(buf, "%s but bare king", resultDetails);\r
7839                         resultDetails = buf;\r
7840                 }\r
7841             }\r
7842         }\r
7843 \r
7844 \r
7845         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
7846             if(result==WhiteWins) c = '+';\r
7847             if(result==BlackWins) c = '-';\r
7848             if(resultDetails != NULL)\r
7849                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
7850         }\r
7851         if (resultDetails != NULL) {\r
7852             gameInfo.result = result;\r
7853             gameInfo.resultDetails = StrSave(resultDetails);\r
7854 \r
7855             /* display last move only if game was not loaded from file */\r
7856             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7857                 DisplayMove(currentMove - 1);\r
7858     \r
7859             if (forwardMostMove != 0) {\r
7860                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7861                     if (*appData.saveGameFile != NULLCHAR) {\r
7862                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7863                     } else if (appData.autoSaveGames) {\r
7864                         AutoSaveGame();\r
7865                     }\r
7866                     if (*appData.savePositionFile != NULLCHAR) {\r
7867                         SavePositionToFile(appData.savePositionFile);\r
7868                     }\r
7869                 }\r
7870             }\r
7871 \r
7872             /* Tell program how game ended in case it is learning */\r
7873             /* [HGM] Moved this to after saving the PGN, just in case */\r
7874             /* engine died and we got here through time loss. In that */\r
7875             /* case we will get a fatal error writing the pipe, which */\r
7876             /* would otherwise lose us the PGN.                       */\r
7877             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7878             /* output during GameEnds should never be fatal anymore   */\r
7879             if (gameMode == MachinePlaysWhite ||\r
7880                 gameMode == MachinePlaysBlack ||\r
7881                 gameMode == TwoMachinesPlay ||\r
7882                 gameMode == IcsPlayingWhite ||\r
7883                 gameMode == IcsPlayingBlack ||\r
7884                 gameMode == BeginningOfGame) {\r
7885                 char buf[MSG_SIZ];\r
7886                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7887                         resultDetails);\r
7888                 if (first.pr != NoProc) {\r
7889                     SendToProgram(buf, &first);\r
7890                 }\r
7891                 if (second.pr != NoProc &&\r
7892                     gameMode == TwoMachinesPlay) {\r
7893                     SendToProgram(buf, &second);\r
7894                 }\r
7895             }\r
7896         }\r
7897 \r
7898         if (appData.icsActive) {\r
7899             if (appData.quietPlay &&\r
7900                 (gameMode == IcsPlayingWhite ||\r
7901                  gameMode == IcsPlayingBlack)) {\r
7902                 SendToICS(ics_prefix);\r
7903                 SendToICS("set shout 1\n");\r
7904             }\r
7905             nextGameMode = IcsIdle;\r
7906             ics_user_moved = FALSE;\r
7907             /* clean up premove.  It's ugly when the game has ended and the\r
7908              * premove highlights are still on the board.\r
7909              */\r
7910             if (gotPremove) {\r
7911               gotPremove = FALSE;\r
7912               ClearPremoveHighlights();\r
7913               DrawPosition(FALSE, boards[currentMove]);\r
7914             }\r
7915             if (whosays == GE_ICS) {\r
7916                 switch (result) {\r
7917                 case WhiteWins:\r
7918                     if (gameMode == IcsPlayingWhite)\r
7919                         PlayIcsWinSound();\r
7920                     else if(gameMode == IcsPlayingBlack)\r
7921                         PlayIcsLossSound();\r
7922                     break;\r
7923                 case BlackWins:\r
7924                     if (gameMode == IcsPlayingBlack)\r
7925                         PlayIcsWinSound();\r
7926                     else if(gameMode == IcsPlayingWhite)\r
7927                         PlayIcsLossSound();\r
7928                     break;\r
7929                 case GameIsDrawn:\r
7930                     PlayIcsDrawSound();\r
7931                     break;\r
7932                 default:\r
7933                     PlayIcsUnfinishedSound();\r
7934                 }\r
7935             }\r
7936         } else if (gameMode == EditGame ||\r
7937                    gameMode == PlayFromGameFile || \r
7938                    gameMode == AnalyzeMode || \r
7939                    gameMode == AnalyzeFile) {\r
7940             nextGameMode = gameMode;\r
7941         } else {\r
7942             nextGameMode = EndOfGame;\r
7943         }\r
7944         pausing = FALSE;\r
7945         ModeHighlight();\r
7946     } else {\r
7947         nextGameMode = gameMode;\r
7948     }\r
7949 \r
7950     if (appData.noChessProgram) {\r
7951         gameMode = nextGameMode;\r
7952         ModeHighlight();\r
7953         endingGame = 0; /* [HGM] crash */\r
7954         return;\r
7955     }\r
7956 \r
7957     if (first.reuse) {\r
7958         /* Put first chess program into idle state */\r
7959         if (first.pr != NoProc &&\r
7960             (gameMode == MachinePlaysWhite ||\r
7961              gameMode == MachinePlaysBlack ||\r
7962              gameMode == TwoMachinesPlay ||\r
7963              gameMode == IcsPlayingWhite ||\r
7964              gameMode == IcsPlayingBlack ||\r
7965              gameMode == BeginningOfGame)) {\r
7966             SendToProgram("force\n", &first);\r
7967             if (first.usePing) {\r
7968               char buf[MSG_SIZ];\r
7969               sprintf(buf, "ping %d\n", ++first.lastPing);\r
7970               SendToProgram(buf, &first);\r
7971             }\r
7972         }\r
7973     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7974         /* Kill off first chess program */\r
7975         if (first.isr != NULL)\r
7976           RemoveInputSource(first.isr);\r
7977         first.isr = NULL;\r
7978     \r
7979         if (first.pr != NoProc) {\r
7980             ExitAnalyzeMode();\r
7981             DoSleep( appData.delayBeforeQuit );\r
7982             SendToProgram("quit\n", &first);\r
7983             DoSleep( appData.delayAfterQuit );\r
7984             DestroyChildProcess(first.pr, first.useSigterm);\r
7985         }\r
7986         first.pr = NoProc;\r
7987     }\r
7988     if (second.reuse) {\r
7989         /* Put second chess program into idle state */\r
7990         if (second.pr != NoProc &&\r
7991             gameMode == TwoMachinesPlay) {\r
7992             SendToProgram("force\n", &second);\r
7993             if (second.usePing) {\r
7994               char buf[MSG_SIZ];\r
7995               sprintf(buf, "ping %d\n", ++second.lastPing);\r
7996               SendToProgram(buf, &second);\r
7997             }\r
7998         }\r
7999     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
8000         /* Kill off second chess program */\r
8001         if (second.isr != NULL)\r
8002           RemoveInputSource(second.isr);\r
8003         second.isr = NULL;\r
8004     \r
8005         if (second.pr != NoProc) {\r
8006             DoSleep( appData.delayBeforeQuit );\r
8007             SendToProgram("quit\n", &second);\r
8008             DoSleep( appData.delayAfterQuit );\r
8009             DestroyChildProcess(second.pr, second.useSigterm);\r
8010         }\r
8011         second.pr = NoProc;\r
8012     }\r
8013 \r
8014     if (matchMode && gameMode == TwoMachinesPlay) {\r
8015         switch (result) {\r
8016         case WhiteWins:\r
8017           if (first.twoMachinesColor[0] == 'w') {\r
8018             first.matchWins++;\r
8019           } else {\r
8020             second.matchWins++;\r
8021           }\r
8022           break;\r
8023         case BlackWins:\r
8024           if (first.twoMachinesColor[0] == 'b') {\r
8025             first.matchWins++;\r
8026           } else {\r
8027             second.matchWins++;\r
8028           }\r
8029           break;\r
8030         default:\r
8031           break;\r
8032         }\r
8033         if (matchGame < appData.matchGames) {\r
8034             char *tmp;\r
8035             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */\r
8036                 tmp = first.twoMachinesColor;\r
8037                 first.twoMachinesColor = second.twoMachinesColor;\r
8038                 second.twoMachinesColor = tmp;\r
8039             }\r
8040             gameMode = nextGameMode;\r
8041             matchGame++;\r
8042             if(appData.matchPause>10000 || appData.matchPause<10)\r
8043                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
8044             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
8045             endingGame = 0; /* [HGM] crash */\r
8046             return;\r
8047         } else {\r
8048             char buf[MSG_SIZ];\r
8049             gameMode = nextGameMode;\r
8050             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),\r
8051                     first.tidy, second.tidy,\r
8052                     first.matchWins, second.matchWins,\r
8053                     appData.matchGames - (first.matchWins + second.matchWins));\r
8054             DisplayFatalError(buf, 0, 0);\r
8055         }\r
8056     }\r
8057     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
8058         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
8059       ExitAnalyzeMode();\r
8060     gameMode = nextGameMode;\r
8061     ModeHighlight();\r
8062     endingGame = 0;  /* [HGM] crash */\r
8063 }\r
8064 \r
8065 /* Assumes program was just initialized (initString sent).\r
8066    Leaves program in force mode. */\r
8067 void\r
8068 FeedMovesToProgram(cps, upto) \r
8069      ChessProgramState *cps;\r
8070      int upto;\r
8071 {\r
8072     int i;\r
8073     \r
8074     if (appData.debugMode)\r
8075       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
8076               startedFromSetupPosition ? "position and " : "",\r
8077               backwardMostMove, upto, cps->which);\r
8078     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
8079         // [HGM] variantswitch: make engine aware of new variant\r
8080         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
8081                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
8082         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
8083         SendToProgram(buf, cps);\r
8084         currentlyInitializedVariant = gameInfo.variant;\r
8085     }\r
8086     SendToProgram("force\n", cps);\r
8087     if (startedFromSetupPosition) {\r
8088         SendBoard(cps, backwardMostMove);\r
8089     if (appData.debugMode) {\r
8090         fprintf(debugFP, "feedMoves\n");\r
8091     }\r
8092     }\r
8093     for (i = backwardMostMove; i < upto; i++) {\r
8094         SendMoveToProgram(i, cps);\r
8095     }\r
8096 }\r
8097 \r
8098 \r
8099 void\r
8100 ResurrectChessProgram()\r
8101 {\r
8102      /* The chess program may have exited.\r
8103         If so, restart it and feed it all the moves made so far. */\r
8104 \r
8105     if (appData.noChessProgram || first.pr != NoProc) return;\r
8106     \r
8107     StartChessProgram(&first);\r
8108     InitChessProgram(&first, FALSE);\r
8109     FeedMovesToProgram(&first, currentMove);\r
8110 \r
8111     if (!first.sendTime) {\r
8112         /* can't tell gnuchess what its clock should read,\r
8113            so we bow to its notion. */\r
8114         ResetClocks();\r
8115         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
8116         timeRemaining[1][currentMove] = blackTimeRemaining;\r
8117     }\r
8118 \r
8119     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||\r
8120                 appData.icsEngineAnalyze) && first.analysisSupport) {\r
8121       SendToProgram("analyze\n", &first);\r
8122       first.analyzing = TRUE;\r
8123     }\r
8124 }\r
8125 \r
8126 /*\r
8127  * Button procedures\r
8128  */\r
8129 void\r
8130 Reset(redraw, init)\r
8131      int redraw, init;\r
8132 {\r
8133     int i;\r
8134 \r
8135     if (appData.debugMode) {\r
8136         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
8137                 redraw, init, gameMode);\r
8138     }\r
8139     pausing = pauseExamInvalid = FALSE;\r
8140     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
8141     firstMove = TRUE;\r
8142     whiteFlag = blackFlag = FALSE;\r
8143     userOfferedDraw = FALSE;\r
8144     hintRequested = bookRequested = FALSE;\r
8145     first.maybeThinking = FALSE;\r
8146     second.maybeThinking = FALSE;\r
8147     first.bookSuspend = FALSE; // [HGM] book\r
8148     second.bookSuspend = FALSE;\r
8149     thinkOutput[0] = NULLCHAR;\r
8150     lastHint[0] = NULLCHAR;\r
8151     ClearGameInfo(&gameInfo);\r
8152     gameInfo.variant = StringToVariant(appData.variant);\r
8153     ics_user_moved = ics_clock_paused = FALSE;\r
8154     ics_getting_history = H_FALSE;\r
8155     ics_gamenum = -1;\r
8156     white_holding[0] = black_holding[0] = NULLCHAR;\r
8157     ClearProgramStats();\r
8158     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode\r
8159     \r
8160     ResetFrontEnd();\r
8161     ClearHighlights();\r
8162     flipView = appData.flipView;\r
8163     ClearPremoveHighlights();\r
8164     gotPremove = FALSE;\r
8165     alarmSounded = FALSE;\r
8166 \r
8167     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8168     if(appData.serverMovesName != NULL) {\r
8169         /* [HGM] prepare to make moves file for broadcasting */\r
8170         clock_t t = clock();\r
8171         if(serverMoves != NULL) fclose(serverMoves);\r
8172         serverMoves = fopen(appData.serverMovesName, "r");\r
8173         if(serverMoves != NULL) {\r
8174             fclose(serverMoves);\r
8175             /* delay 15 sec before overwriting, so all clients can see end */\r
8176             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
8177         }\r
8178         serverMoves = fopen(appData.serverMovesName, "w");\r
8179     }\r
8180 \r
8181     ExitAnalyzeMode();\r
8182     gameMode = BeginningOfGame;\r
8183     ModeHighlight();\r
8184     if(appData.icsActive) gameInfo.variant = VariantNormal;\r
8185     InitPosition(redraw);\r
8186     for (i = 0; i < MAX_MOVES; i++) {\r
8187         if (commentList[i] != NULL) {\r
8188             free(commentList[i]);\r
8189             commentList[i] = NULL;\r
8190         }\r
8191     }\r
8192     ResetClocks();\r
8193     timeRemaining[0][0] = whiteTimeRemaining;\r
8194     timeRemaining[1][0] = blackTimeRemaining;\r
8195     if (first.pr == NULL) {\r
8196         StartChessProgram(&first);\r
8197     }\r
8198     if (init) {\r
8199             InitChessProgram(&first, startedFromSetupPosition);\r
8200     }\r
8201     DisplayTitle("");\r
8202     DisplayMessage("", "");\r
8203     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8204 }\r
8205 \r
8206 void\r
8207 AutoPlayGameLoop()\r
8208 {\r
8209     for (;;) {\r
8210         if (!AutoPlayOneMove())\r
8211           return;\r
8212         if (matchMode || appData.timeDelay == 0)\r
8213           continue;\r
8214         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
8215           return;\r
8216         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
8217         break;\r
8218     }\r
8219 }\r
8220 \r
8221 \r
8222 int\r
8223 AutoPlayOneMove()\r
8224 {\r
8225     int fromX, fromY, toX, toY;\r
8226 \r
8227     if (appData.debugMode) {\r
8228       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
8229     }\r
8230 \r
8231     if (gameMode != PlayFromGameFile)\r
8232       return FALSE;\r
8233 \r
8234     if (currentMove >= forwardMostMove) {\r
8235       gameMode = EditGame;\r
8236       ModeHighlight();\r
8237 \r
8238       /* [AS] Clear current move marker at the end of a game */\r
8239       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
8240 \r
8241       return FALSE;\r
8242     }\r
8243     \r
8244     toX = moveList[currentMove][2] - AAA;\r
8245     toY = moveList[currentMove][3] - ONE;\r
8246 \r
8247     if (moveList[currentMove][1] == '@') {\r
8248         if (appData.highlightLastMove) {\r
8249             SetHighlights(-1, -1, toX, toY);\r
8250         }\r
8251     } else {\r
8252         fromX = moveList[currentMove][0] - AAA;\r
8253         fromY = moveList[currentMove][1] - ONE;\r
8254 \r
8255         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
8256 \r
8257         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
8258 \r
8259         if (appData.highlightLastMove) {\r
8260             SetHighlights(fromX, fromY, toX, toY);\r
8261         }\r
8262     }\r
8263     DisplayMove(currentMove);\r
8264     SendMoveToProgram(currentMove++, &first);\r
8265     DisplayBothClocks();\r
8266     DrawPosition(FALSE, boards[currentMove]);\r
8267     // [HGM] PV info: always display, routine tests if empty\r
8268     DisplayComment(currentMove - 1, commentList[currentMove]);\r
8269     return TRUE;\r
8270 }\r
8271 \r
8272 \r
8273 int\r
8274 LoadGameOneMove(readAhead)\r
8275      ChessMove readAhead;\r
8276 {\r
8277     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
8278     char promoChar = NULLCHAR;\r
8279     ChessMove moveType;\r
8280     char move[MSG_SIZ];\r
8281     char *p, *q;\r
8282     \r
8283     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
8284         gameMode != AnalyzeMode && gameMode != Training) {\r
8285         gameFileFP = NULL;\r
8286         return FALSE;\r
8287     }\r
8288     \r
8289     yyboardindex = forwardMostMove;\r
8290     if (readAhead != (ChessMove)0) {\r
8291       moveType = readAhead;\r
8292     } else {\r
8293       if (gameFileFP == NULL)\r
8294           return FALSE;\r
8295       moveType = (ChessMove) yylex();\r
8296     }\r
8297     \r
8298     done = FALSE;\r
8299     switch (moveType) {\r
8300       case Comment:\r
8301         if (appData.debugMode) \r
8302           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8303         p = yy_text;\r
8304         if (*p == '{' || *p == '[' || *p == '(') {\r
8305             p[strlen(p) - 1] = NULLCHAR;\r
8306             p++;\r
8307         }\r
8308 \r
8309         /* append the comment but don't display it */\r
8310         while (*p == '\n') p++;\r
8311         AppendComment(currentMove, p);\r
8312         return TRUE;\r
8313 \r
8314       case WhiteCapturesEnPassant:\r
8315       case BlackCapturesEnPassant:\r
8316       case WhitePromotionChancellor:\r
8317       case BlackPromotionChancellor:\r
8318       case WhitePromotionArchbishop:\r
8319       case BlackPromotionArchbishop:\r
8320       case WhitePromotionCentaur:\r
8321       case BlackPromotionCentaur:\r
8322       case WhitePromotionQueen:\r
8323       case BlackPromotionQueen:\r
8324       case WhitePromotionRook:\r
8325       case BlackPromotionRook:\r
8326       case WhitePromotionBishop:\r
8327       case BlackPromotionBishop:\r
8328       case WhitePromotionKnight:\r
8329       case BlackPromotionKnight:\r
8330       case WhitePromotionKing:\r
8331       case BlackPromotionKing:\r
8332       case NormalMove:\r
8333       case WhiteKingSideCastle:\r
8334       case WhiteQueenSideCastle:\r
8335       case BlackKingSideCastle:\r
8336       case BlackQueenSideCastle:\r
8337       case WhiteKingSideCastleWild:\r
8338       case WhiteQueenSideCastleWild:\r
8339       case BlackKingSideCastleWild:\r
8340       case BlackQueenSideCastleWild:\r
8341       /* PUSH Fabien */\r
8342       case WhiteHSideCastleFR:\r
8343       case WhiteASideCastleFR:\r
8344       case BlackHSideCastleFR:\r
8345       case BlackASideCastleFR:\r
8346       /* POP Fabien */\r
8347         if (appData.debugMode)\r
8348           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8349         fromX = currentMoveString[0] - AAA;\r
8350         fromY = currentMoveString[1] - ONE;\r
8351         toX = currentMoveString[2] - AAA;\r
8352         toY = currentMoveString[3] - ONE;\r
8353         promoChar = currentMoveString[4];\r
8354         break;\r
8355 \r
8356       case WhiteDrop:\r
8357       case BlackDrop:\r
8358         if (appData.debugMode)\r
8359           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8360         fromX = moveType == WhiteDrop ?\r
8361           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
8362         (int) CharToPiece(ToLower(currentMoveString[0]));\r
8363         fromY = DROP_RANK;\r
8364         toX = currentMoveString[2] - AAA;\r
8365         toY = currentMoveString[3] - ONE;\r
8366         break;\r
8367 \r
8368       case WhiteWins:\r
8369       case BlackWins:\r
8370       case GameIsDrawn:\r
8371       case GameUnfinished:\r
8372         if (appData.debugMode)\r
8373           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
8374         p = strchr(yy_text, '{');\r
8375         if (p == NULL) p = strchr(yy_text, '(');\r
8376         if (p == NULL) {\r
8377             p = yy_text;\r
8378             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
8379         } else {\r
8380             q = strchr(p, *p == '{' ? '}' : ')');\r
8381             if (q != NULL) *q = NULLCHAR;\r
8382             p++;\r
8383         }\r
8384         GameEnds(moveType, p, GE_FILE);\r
8385         done = TRUE;\r
8386         if (cmailMsgLoaded) {\r
8387             ClearHighlights();\r
8388             flipView = WhiteOnMove(currentMove);\r
8389             if (moveType == GameUnfinished) flipView = !flipView;\r
8390             if (appData.debugMode)\r
8391               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
8392         }\r
8393         break;\r
8394 \r
8395       case (ChessMove) 0:       /* end of file */\r
8396         if (appData.debugMode)\r
8397           fprintf(debugFP, "Parser hit end of file\n");\r
8398         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8399                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8400           case MT_NONE:\r
8401           case MT_CHECK:\r
8402             break;\r
8403           case MT_CHECKMATE:\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             if (WhiteOnMove(currentMove)) {\r
8440                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8441             } else {\r
8442                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8443             }\r
8444             break;\r
8445           case MT_STALEMATE:\r
8446             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8447             break;\r
8448         }\r
8449         done = TRUE;\r
8450         break;\r
8451 \r
8452       case PositionDiagram:     /* should not happen; ignore */\r
8453       case ElapsedTime:         /* ignore */\r
8454       case NAG:                 /* ignore */\r
8455         if (appData.debugMode)\r
8456           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8457                   yy_text, (int) moveType);\r
8458         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8459 \r
8460       case IllegalMove:\r
8461         if (appData.testLegality) {\r
8462             if (appData.debugMode)\r
8463               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
8464             sprintf(move, _("Illegal move: %d.%s%s"),\r
8465                     (forwardMostMove / 2) + 1,\r
8466                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8467             DisplayError(move, 0);\r
8468             done = TRUE;\r
8469         } else {\r
8470             if (appData.debugMode)\r
8471               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
8472                       yy_text, currentMoveString);\r
8473             fromX = currentMoveString[0] - AAA;\r
8474             fromY = currentMoveString[1] - ONE;\r
8475             toX = currentMoveString[2] - AAA;\r
8476             toY = currentMoveString[3] - ONE;\r
8477             promoChar = currentMoveString[4];\r
8478         }\r
8479         break;\r
8480 \r
8481       case AmbiguousMove:\r
8482         if (appData.debugMode)\r
8483           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
8484         sprintf(move, _("Ambiguous move: %d.%s%s"),\r
8485                 (forwardMostMove / 2) + 1,\r
8486                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8487         DisplayError(move, 0);\r
8488         done = TRUE;\r
8489         break;\r
8490 \r
8491       default:\r
8492       case ImpossibleMove:\r
8493         if (appData.debugMode)\r
8494           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);\r
8495         sprintf(move, _("Illegal move: %d.%s%s"),\r
8496                 (forwardMostMove / 2) + 1,\r
8497                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8498         DisplayError(move, 0);\r
8499         done = TRUE;\r
8500         break;\r
8501     }\r
8502 \r
8503     if (done) {\r
8504         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
8505             DrawPosition(FALSE, boards[currentMove]);\r
8506             DisplayBothClocks();\r
8507             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
8508               DisplayComment(currentMove - 1, commentList[currentMove]);\r
8509         }\r
8510         (void) StopLoadGameTimer();\r
8511         gameFileFP = NULL;\r
8512         cmailOldMove = forwardMostMove;\r
8513         return FALSE;\r
8514     } else {\r
8515         /* currentMoveString is set as a side-effect of yylex */\r
8516         strcat(currentMoveString, "\n");\r
8517         strcpy(moveList[forwardMostMove], currentMoveString);\r
8518         \r
8519         thinkOutput[0] = NULLCHAR;\r
8520         MakeMove(fromX, fromY, toX, toY, promoChar);\r
8521         currentMove = forwardMostMove;\r
8522         return TRUE;\r
8523     }\r
8524 }\r
8525 \r
8526 /* Load the nth game from the given file */\r
8527 int\r
8528 LoadGameFromFile(filename, n, title, useList)\r
8529      char *filename;\r
8530      int n;\r
8531      char *title;\r
8532      /*Boolean*/ int useList;\r
8533 {\r
8534     FILE *f;\r
8535     char buf[MSG_SIZ];\r
8536 \r
8537     if (strcmp(filename, "-") == 0) {\r
8538         f = stdin;\r
8539         title = "stdin";\r
8540     } else {\r
8541         f = fopen(filename, "rb");\r
8542         if (f == NULL) {\r
8543             sprintf(buf, _("Can't open \"%s\""), filename);\r
8544             DisplayError(buf, errno);\r
8545             return FALSE;\r
8546         }\r
8547     }\r
8548     if (fseek(f, 0, 0) == -1) {\r
8549         /* f is not seekable; probably a pipe */\r
8550         useList = FALSE;\r
8551     }\r
8552     if (useList && n == 0) {\r
8553         int error = GameListBuild(f);\r
8554         if (error) {\r
8555             DisplayError(_("Cannot build game list"), error);\r
8556         } else if (!ListEmpty(&gameList) &&\r
8557                    ((ListGame *) gameList.tailPred)->number > 1) {\r
8558             GameListPopUp(f, title);\r
8559             return TRUE;\r
8560         }\r
8561         GameListDestroy();\r
8562         n = 1;\r
8563     }\r
8564     if (n == 0) n = 1;\r
8565     return LoadGame(f, n, title, FALSE);\r
8566 }\r
8567 \r
8568 \r
8569 void\r
8570 MakeRegisteredMove()\r
8571 {\r
8572     int fromX, fromY, toX, toY;\r
8573     char promoChar;\r
8574     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8575         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
8576           case CMAIL_MOVE:\r
8577           case CMAIL_DRAW:\r
8578             if (appData.debugMode)\r
8579               fprintf(debugFP, "Restoring %s for game %d\n",\r
8580                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8581     \r
8582             thinkOutput[0] = NULLCHAR;\r
8583             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
8584             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
8585             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
8586             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
8587             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
8588             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
8589             MakeMove(fromX, fromY, toX, toY, promoChar);\r
8590             ShowMove(fromX, fromY, toX, toY);\r
8591               \r
8592             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8593                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8594               case MT_NONE:\r
8595               case MT_CHECK:\r
8596                 break;\r
8597                 \r
8598               case MT_CHECKMATE:\r
8599                 if (WhiteOnMove(currentMove)) {\r
8600                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
8601                 } else {\r
8602                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
8603                 }\r
8604                 break;\r
8605                 \r
8606               case MT_STALEMATE:\r
8607                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
8608                 break;\r
8609             }\r
8610 \r
8611             break;\r
8612             \r
8613           case CMAIL_RESIGN:\r
8614             if (WhiteOnMove(currentMove)) {\r
8615                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
8616             } else {\r
8617                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
8618             }\r
8619             break;\r
8620             \r
8621           case CMAIL_ACCEPT:\r
8622             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
8623             break;\r
8624               \r
8625           default:\r
8626             break;\r
8627         }\r
8628     }\r
8629 \r
8630     return;\r
8631 }\r
8632 \r
8633 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
8634 int\r
8635 CmailLoadGame(f, gameNumber, title, useList)\r
8636      FILE *f;\r
8637      int gameNumber;\r
8638      char *title;\r
8639      int useList;\r
8640 {\r
8641     int retVal;\r
8642 \r
8643     if (gameNumber > nCmailGames) {\r
8644         DisplayError(_("No more games in this message"), 0);\r
8645         return FALSE;\r
8646     }\r
8647     if (f == lastLoadGameFP) {\r
8648         int offset = gameNumber - lastLoadGameNumber;\r
8649         if (offset == 0) {\r
8650             cmailMsg[0] = NULLCHAR;\r
8651             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8652                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8653                 nCmailMovesRegistered--;\r
8654             }\r
8655             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
8656             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
8657                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
8658             }\r
8659         } else {\r
8660             if (! RegisterMove()) return FALSE;\r
8661         }\r
8662     }\r
8663 \r
8664     retVal = LoadGame(f, gameNumber, title, useList);\r
8665 \r
8666     /* Make move registered during previous look at this game, if any */\r
8667     MakeRegisteredMove();\r
8668 \r
8669     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
8670         commentList[currentMove]\r
8671           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
8672         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8673     }\r
8674 \r
8675     return retVal;\r
8676 }\r
8677 \r
8678 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
8679 int\r
8680 ReloadGame(offset)\r
8681      int offset;\r
8682 {\r
8683     int gameNumber = lastLoadGameNumber + offset;\r
8684     if (lastLoadGameFP == NULL) {\r
8685         DisplayError(_("No game has been loaded yet"), 0);\r
8686         return FALSE;\r
8687     }\r
8688     if (gameNumber <= 0) {\r
8689         DisplayError(_("Can't back up any further"), 0);\r
8690         return FALSE;\r
8691     }\r
8692     if (cmailMsgLoaded) {\r
8693         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
8694                              lastLoadGameTitle, lastLoadGameUseList);\r
8695     } else {\r
8696         return LoadGame(lastLoadGameFP, gameNumber,\r
8697                         lastLoadGameTitle, lastLoadGameUseList);\r
8698     }\r
8699 }\r
8700 \r
8701 \r
8702 \r
8703 /* Load the nth game from open file f */\r
8704 int\r
8705 LoadGame(f, gameNumber, title, useList)\r
8706      FILE *f;\r
8707      int gameNumber;\r
8708      char *title;\r
8709      int useList;\r
8710 {\r
8711     ChessMove cm;\r
8712     char buf[MSG_SIZ];\r
8713     int gn = gameNumber;\r
8714     ListGame *lg = NULL;\r
8715     int numPGNTags = 0;\r
8716     int err;\r
8717     GameMode oldGameMode;\r
8718     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
8719 \r
8720     if (appData.debugMode) \r
8721         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
8722 \r
8723     if (gameMode == Training )\r
8724         SetTrainingModeOff();\r
8725 \r
8726     oldGameMode = gameMode;\r
8727     if (gameMode != BeginningOfGame) {\r
8728       Reset(FALSE, TRUE);\r
8729     }\r
8730 \r
8731     gameFileFP = f;\r
8732     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
8733         fclose(lastLoadGameFP);\r
8734     }\r
8735 \r
8736     if (useList) {\r
8737         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
8738         \r
8739         if (lg) {\r
8740             fseek(f, lg->offset, 0);\r
8741             GameListHighlight(gameNumber);\r
8742             gn = 1;\r
8743         }\r
8744         else {\r
8745             DisplayError(_("Game number out of range"), 0);\r
8746             return FALSE;\r
8747         }\r
8748     } else {\r
8749         GameListDestroy();\r
8750         if (fseek(f, 0, 0) == -1) {\r
8751             if (f == lastLoadGameFP ?\r
8752                 gameNumber == lastLoadGameNumber + 1 :\r
8753                 gameNumber == 1) {\r
8754                 gn = 1;\r
8755             } else {\r
8756                 DisplayError(_("Can't seek on game file"), 0);\r
8757                 return FALSE;\r
8758             }\r
8759         }\r
8760     }\r
8761     lastLoadGameFP = f;\r
8762     lastLoadGameNumber = gameNumber;\r
8763     strcpy(lastLoadGameTitle, title);\r
8764     lastLoadGameUseList = useList;\r
8765 \r
8766     yynewfile(f);\r
8767 \r
8768     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
8769         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
8770                 lg->gameInfo.black);\r
8771             DisplayTitle(buf);\r
8772     } else if (*title != NULLCHAR) {\r
8773         if (gameNumber > 1) {\r
8774             sprintf(buf, "%s %d", title, gameNumber);\r
8775             DisplayTitle(buf);\r
8776         } else {\r
8777             DisplayTitle(title);\r
8778         }\r
8779     }\r
8780 \r
8781     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
8782         gameMode = PlayFromGameFile;\r
8783         ModeHighlight();\r
8784     }\r
8785 \r
8786     currentMove = forwardMostMove = backwardMostMove = 0;\r
8787     CopyBoard(boards[0], initialPosition);\r
8788     StopClocks();\r
8789 \r
8790     /*\r
8791      * Skip the first gn-1 games in the file.\r
8792      * Also skip over anything that precedes an identifiable \r
8793      * start of game marker, to avoid being confused by \r
8794      * garbage at the start of the file.  Currently \r
8795      * recognized start of game markers are the move number "1",\r
8796      * the pattern "gnuchess .* game", the pattern\r
8797      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
8798      * A game that starts with one of the latter two patterns\r
8799      * will also have a move number 1, possibly\r
8800      * following a position diagram.\r
8801      * 5-4-02: Let's try being more lenient and allowing a game to\r
8802      * start with an unnumbered move.  Does that break anything?\r
8803      */\r
8804     cm = lastLoadGameStart = (ChessMove) 0;\r
8805     while (gn > 0) {\r
8806         yyboardindex = forwardMostMove;\r
8807         cm = (ChessMove) yylex();\r
8808         switch (cm) {\r
8809           case (ChessMove) 0:\r
8810             if (cmailMsgLoaded) {\r
8811                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
8812             } else {\r
8813                 Reset(TRUE, TRUE);\r
8814                 DisplayError(_("Game not found in file"), 0);\r
8815             }\r
8816             return FALSE;\r
8817 \r
8818           case GNUChessGame:\r
8819           case XBoardGame:\r
8820             gn--;\r
8821             lastLoadGameStart = cm;\r
8822             break;\r
8823             \r
8824           case MoveNumberOne:\r
8825             switch (lastLoadGameStart) {\r
8826               case GNUChessGame:\r
8827               case XBoardGame:\r
8828               case PGNTag:\r
8829                 break;\r
8830               case MoveNumberOne:\r
8831               case (ChessMove) 0:\r
8832                 gn--;           /* count this game */\r
8833                 lastLoadGameStart = cm;\r
8834                 break;\r
8835               default:\r
8836                 /* impossible */\r
8837                 break;\r
8838             }\r
8839             break;\r
8840 \r
8841           case PGNTag:\r
8842             switch (lastLoadGameStart) {\r
8843               case GNUChessGame:\r
8844               case PGNTag:\r
8845               case MoveNumberOne:\r
8846               case (ChessMove) 0:\r
8847                 gn--;           /* count this game */\r
8848                 lastLoadGameStart = cm;\r
8849                 break;\r
8850               case XBoardGame:\r
8851                 lastLoadGameStart = cm; /* game counted already */\r
8852                 break;\r
8853               default:\r
8854                 /* impossible */\r
8855                 break;\r
8856             }\r
8857             if (gn > 0) {\r
8858                 do {\r
8859                     yyboardindex = forwardMostMove;\r
8860                     cm = (ChessMove) yylex();\r
8861                 } while (cm == PGNTag || cm == Comment);\r
8862             }\r
8863             break;\r
8864 \r
8865           case WhiteWins:\r
8866           case BlackWins:\r
8867           case GameIsDrawn:\r
8868             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8869                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8870                     != CMAIL_OLD_RESULT) {\r
8871                     nCmailResults ++ ;\r
8872                     cmailResult[  CMAIL_MAX_GAMES\r
8873                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8874                 }\r
8875             }\r
8876             break;\r
8877 \r
8878           case NormalMove:\r
8879             /* Only a NormalMove can be at the start of a game\r
8880              * without a position diagram. */\r
8881             if (lastLoadGameStart == (ChessMove) 0) {\r
8882               gn--;\r
8883               lastLoadGameStart = MoveNumberOne;\r
8884             }\r
8885             break;\r
8886 \r
8887           default:\r
8888             break;\r
8889         }\r
8890     }\r
8891     \r
8892     if (appData.debugMode)\r
8893       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8894 \r
8895     if (cm == XBoardGame) {\r
8896         /* Skip any header junk before position diagram and/or move 1 */\r
8897         for (;;) {\r
8898             yyboardindex = forwardMostMove;\r
8899             cm = (ChessMove) yylex();\r
8900 \r
8901             if (cm == (ChessMove) 0 ||\r
8902                 cm == GNUChessGame || cm == XBoardGame) {\r
8903                 /* Empty game; pretend end-of-file and handle later */\r
8904                 cm = (ChessMove) 0;\r
8905                 break;\r
8906             }\r
8907 \r
8908             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8909                 cm == PGNTag || cm == Comment)\r
8910               break;\r
8911         }\r
8912     } else if (cm == GNUChessGame) {\r
8913         if (gameInfo.event != NULL) {\r
8914             free(gameInfo.event);\r
8915         }\r
8916         gameInfo.event = StrSave(yy_text);\r
8917     }   \r
8918 \r
8919     startedFromSetupPosition = FALSE;\r
8920     while (cm == PGNTag) {\r
8921         if (appData.debugMode) \r
8922           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8923         err = ParsePGNTag(yy_text, &gameInfo);\r
8924         if (!err) numPGNTags++;\r
8925 \r
8926         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8927         if(gameInfo.variant != oldVariant) {\r
8928             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8929             InitPosition(TRUE);\r
8930             oldVariant = gameInfo.variant;\r
8931             if (appData.debugMode) \r
8932               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8933         }\r
8934 \r
8935 \r
8936         if (gameInfo.fen != NULL) {\r
8937           Board initial_position;\r
8938           startedFromSetupPosition = TRUE;\r
8939           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8940             Reset(TRUE, TRUE);\r
8941             DisplayError(_("Bad FEN position in file"), 0);\r
8942             return FALSE;\r
8943           }\r
8944           CopyBoard(boards[0], initial_position);\r
8945           if (blackPlaysFirst) {\r
8946             currentMove = forwardMostMove = backwardMostMove = 1;\r
8947             CopyBoard(boards[1], initial_position);\r
8948             strcpy(moveList[0], "");\r
8949             strcpy(parseList[0], "");\r
8950             timeRemaining[0][1] = whiteTimeRemaining;\r
8951             timeRemaining[1][1] = blackTimeRemaining;\r
8952             if (commentList[0] != NULL) {\r
8953               commentList[1] = commentList[0];\r
8954               commentList[0] = NULL;\r
8955             }\r
8956           } else {\r
8957             currentMove = forwardMostMove = backwardMostMove = 0;\r
8958           }\r
8959           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */\r
8960           {   int i;\r
8961               initialRulePlies = FENrulePlies;\r
8962               epStatus[forwardMostMove] = FENepStatus;\r
8963               for( i=0; i< nrCastlingRights; i++ )\r
8964                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
8965           }\r
8966           yyboardindex = forwardMostMove;\r
8967           free(gameInfo.fen);\r
8968           gameInfo.fen = NULL;\r
8969         }\r
8970 \r
8971         yyboardindex = forwardMostMove;\r
8972         cm = (ChessMove) yylex();\r
8973 \r
8974         /* Handle comments interspersed among the tags */\r
8975         while (cm == Comment) {\r
8976             char *p;\r
8977             if (appData.debugMode) \r
8978               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8979             p = yy_text;\r
8980             if (*p == '{' || *p == '[' || *p == '(') {\r
8981                 p[strlen(p) - 1] = NULLCHAR;\r
8982                 p++;\r
8983             }\r
8984             while (*p == '\n') p++;\r
8985             AppendComment(currentMove, p);\r
8986             yyboardindex = forwardMostMove;\r
8987             cm = (ChessMove) yylex();\r
8988         }\r
8989     }\r
8990 \r
8991     /* don't rely on existence of Event tag since if game was\r
8992      * pasted from clipboard the Event tag may not exist\r
8993      */\r
8994     if (numPGNTags > 0){\r
8995         char *tags;\r
8996         if (gameInfo.variant == VariantNormal) {\r
8997           gameInfo.variant = StringToVariant(gameInfo.event);\r
8998         }\r
8999         if (!matchMode) {\r
9000           if( appData.autoDisplayTags ) {\r
9001             tags = PGNTags(&gameInfo);\r
9002             TagsPopUp(tags, CmailMsg());\r
9003             free(tags);\r
9004           }\r
9005         }\r
9006     } else {\r
9007         /* Make something up, but don't display it now */\r
9008         SetGameInfo();\r
9009         TagsPopDown();\r
9010     }\r
9011 \r
9012     if (cm == PositionDiagram) {\r
9013         int i, j;\r
9014         char *p;\r
9015         Board initial_position;\r
9016 \r
9017         if (appData.debugMode)\r
9018           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
9019 \r
9020         if (!startedFromSetupPosition) {\r
9021             p = yy_text;\r
9022             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
9023               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
9024                 switch (*p) {\r
9025                   case '[':\r
9026                   case '-':\r
9027                   case ' ':\r
9028                   case '\t':\r
9029                   case '\n':\r
9030                   case '\r':\r
9031                     break;\r
9032                   default:\r
9033                     initial_position[i][j++] = CharToPiece(*p);\r
9034                     break;\r
9035                 }\r
9036             while (*p == ' ' || *p == '\t' ||\r
9037                    *p == '\n' || *p == '\r') p++;\r
9038         \r
9039             if (strncmp(p, "black", strlen("black"))==0)\r
9040               blackPlaysFirst = TRUE;\r
9041             else\r
9042               blackPlaysFirst = FALSE;\r
9043             startedFromSetupPosition = TRUE;\r
9044         \r
9045             CopyBoard(boards[0], initial_position);\r
9046             if (blackPlaysFirst) {\r
9047                 currentMove = forwardMostMove = backwardMostMove = 1;\r
9048                 CopyBoard(boards[1], initial_position);\r
9049                 strcpy(moveList[0], "");\r
9050                 strcpy(parseList[0], "");\r
9051                 timeRemaining[0][1] = whiteTimeRemaining;\r
9052                 timeRemaining[1][1] = blackTimeRemaining;\r
9053                 if (commentList[0] != NULL) {\r
9054                     commentList[1] = commentList[0];\r
9055                     commentList[0] = NULL;\r
9056                 }\r
9057             } else {\r
9058                 currentMove = forwardMostMove = backwardMostMove = 0;\r
9059             }\r
9060         }\r
9061         yyboardindex = forwardMostMove;\r
9062         cm = (ChessMove) yylex();\r
9063     }\r
9064 \r
9065     if (first.pr == NoProc) {\r
9066         StartChessProgram(&first);\r
9067     }\r
9068     InitChessProgram(&first, FALSE);\r
9069     SendToProgram("force\n", &first);\r
9070     if (startedFromSetupPosition) {\r
9071         SendBoard(&first, forwardMostMove);\r
9072     if (appData.debugMode) {\r
9073         fprintf(debugFP, "Load Game\n");\r
9074     }\r
9075         DisplayBothClocks();\r
9076     }      \r
9077 \r
9078     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
9079     loadFlag = appData.suppressLoadMoves;\r
9080 \r
9081     while (cm == Comment) {\r
9082         char *p;\r
9083         if (appData.debugMode) \r
9084           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
9085         p = yy_text;\r
9086         if (*p == '{' || *p == '[' || *p == '(') {\r
9087             p[strlen(p) - 1] = NULLCHAR;\r
9088             p++;\r
9089         }\r
9090         while (*p == '\n') p++;\r
9091         AppendComment(currentMove, p);\r
9092         yyboardindex = forwardMostMove;\r
9093         cm = (ChessMove) yylex();\r
9094     }\r
9095 \r
9096     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
9097         cm == WhiteWins || cm == BlackWins ||\r
9098         cm == GameIsDrawn || cm == GameUnfinished) {\r
9099         DisplayMessage("", _("No moves in game"));\r
9100         if (cmailMsgLoaded) {\r
9101             if (appData.debugMode)\r
9102               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
9103             ClearHighlights();\r
9104             flipView = FALSE;\r
9105         }\r
9106         DrawPosition(FALSE, boards[currentMove]);\r
9107         DisplayBothClocks();\r
9108         gameMode = EditGame;\r
9109         ModeHighlight();\r
9110         gameFileFP = NULL;\r
9111         cmailOldMove = 0;\r
9112         return TRUE;\r
9113     }\r
9114 \r
9115     // [HGM] PV info: routine tests if comment empty\r
9116     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
9117         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9118     }\r
9119     if (!matchMode && appData.timeDelay != 0) \r
9120       DrawPosition(FALSE, boards[currentMove]);\r
9121 \r
9122     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
9123       programStats.ok_to_send = 1;\r
9124     }\r
9125 \r
9126     /* if the first token after the PGN tags is a move\r
9127      * and not move number 1, retrieve it from the parser \r
9128      */\r
9129     if (cm != MoveNumberOne)\r
9130         LoadGameOneMove(cm);\r
9131 \r
9132     /* load the remaining moves from the file */\r
9133     while (LoadGameOneMove((ChessMove)0)) {\r
9134       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9135       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9136     }\r
9137 \r
9138     /* rewind to the start of the game */\r
9139     currentMove = backwardMostMove;\r
9140 \r
9141     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9142 \r
9143     if (oldGameMode == AnalyzeFile ||\r
9144         oldGameMode == AnalyzeMode) {\r
9145       AnalyzeFileEvent();\r
9146     }\r
9147 \r
9148     if (matchMode || appData.timeDelay == 0) {\r
9149       ToEndEvent();\r
9150       gameMode = EditGame;\r
9151       ModeHighlight();\r
9152     } else if (appData.timeDelay > 0) {\r
9153       AutoPlayGameLoop();\r
9154     }\r
9155 \r
9156     if (appData.debugMode) \r
9157         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
9158 \r
9159     loadFlag = 0; /* [HGM] true game starts */\r
9160     return TRUE;\r
9161 }\r
9162 \r
9163 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
9164 int\r
9165 ReloadPosition(offset)\r
9166      int offset;\r
9167 {\r
9168     int positionNumber = lastLoadPositionNumber + offset;\r
9169     if (lastLoadPositionFP == NULL) {\r
9170         DisplayError(_("No position has been loaded yet"), 0);\r
9171         return FALSE;\r
9172     }\r
9173     if (positionNumber <= 0) {\r
9174         DisplayError(_("Can't back up any further"), 0);\r
9175         return FALSE;\r
9176     }\r
9177     return LoadPosition(lastLoadPositionFP, positionNumber,\r
9178                         lastLoadPositionTitle);\r
9179 }\r
9180 \r
9181 /* Load the nth position from the given file */\r
9182 int\r
9183 LoadPositionFromFile(filename, n, title)\r
9184      char *filename;\r
9185      int n;\r
9186      char *title;\r
9187 {\r
9188     FILE *f;\r
9189     char buf[MSG_SIZ];\r
9190 \r
9191     if (strcmp(filename, "-") == 0) {\r
9192         return LoadPosition(stdin, n, "stdin");\r
9193     } else {\r
9194         f = fopen(filename, "rb");\r
9195         if (f == NULL) {\r
9196             sprintf(buf, _("Can't open \"%s\""), filename);\r
9197             DisplayError(buf, errno);\r
9198             return FALSE;\r
9199         } else {\r
9200             return LoadPosition(f, n, title);\r
9201         }\r
9202     }\r
9203 }\r
9204 \r
9205 /* Load the nth position from the given open file, and close it */\r
9206 int\r
9207 LoadPosition(f, positionNumber, title)\r
9208      FILE *f;\r
9209      int positionNumber;\r
9210      char *title;\r
9211 {\r
9212     char *p, line[MSG_SIZ];\r
9213     Board initial_position;\r
9214     int i, j, fenMode, pn;\r
9215     \r
9216     if (gameMode == Training )\r
9217         SetTrainingModeOff();\r
9218 \r
9219     if (gameMode != BeginningOfGame) {\r
9220         Reset(FALSE, TRUE);\r
9221     }\r
9222     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
9223         fclose(lastLoadPositionFP);\r
9224     }\r
9225     if (positionNumber == 0) positionNumber = 1;\r
9226     lastLoadPositionFP = f;\r
9227     lastLoadPositionNumber = positionNumber;\r
9228     strcpy(lastLoadPositionTitle, title);\r
9229     if (first.pr == NoProc) {\r
9230       StartChessProgram(&first);\r
9231       InitChessProgram(&first, FALSE);\r
9232     }    \r
9233     pn = positionNumber;\r
9234     if (positionNumber < 0) {\r
9235         /* Negative position number means to seek to that byte offset */\r
9236         if (fseek(f, -positionNumber, 0) == -1) {\r
9237             DisplayError(_("Can't seek on position file"), 0);\r
9238             return FALSE;\r
9239         };\r
9240         pn = 1;\r
9241     } else {\r
9242         if (fseek(f, 0, 0) == -1) {\r
9243             if (f == lastLoadPositionFP ?\r
9244                 positionNumber == lastLoadPositionNumber + 1 :\r
9245                 positionNumber == 1) {\r
9246                 pn = 1;\r
9247             } else {\r
9248                 DisplayError(_("Can't seek on position file"), 0);\r
9249                 return FALSE;\r
9250             }\r
9251         }\r
9252     }\r
9253     /* See if this file is FEN or old-style xboard */\r
9254     if (fgets(line, MSG_SIZ, f) == NULL) {\r
9255         DisplayError(_("Position not found in file"), 0);\r
9256         return FALSE;\r
9257     }\r
9258 #if 0\r
9259     switch (line[0]) {\r
9260       case '#':  case 'x':\r
9261       default:\r
9262         fenMode = FALSE;\r
9263         break;\r
9264       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
9265       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
9266       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
9267       case '7':  case '8':  case '9':\r
9268       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
9269       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
9270       case 'C':  case 'W':             case 'c':  case 'w': \r
9271         fenMode = TRUE;\r
9272         break;\r
9273     }\r
9274 #else\r
9275     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
9276     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
9277 #endif\r
9278 \r
9279     if (pn >= 2) {\r
9280         if (fenMode || line[0] == '#') pn--;\r
9281         while (pn > 0) {\r
9282             /* skip positions before number pn */\r
9283             if (fgets(line, MSG_SIZ, f) == NULL) {\r
9284                 Reset(TRUE, TRUE);\r
9285                 DisplayError(_("Position not found in file"), 0);\r
9286                 return FALSE;\r
9287             }\r
9288             if (fenMode || line[0] == '#') pn--;\r
9289         }\r
9290     }\r
9291 \r
9292     if (fenMode) {\r
9293         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
9294             DisplayError(_("Bad FEN position in file"), 0);\r
9295             return FALSE;\r
9296         }\r
9297     } else {\r
9298         (void) fgets(line, MSG_SIZ, f);\r
9299         (void) fgets(line, MSG_SIZ, f);\r
9300     \r
9301         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
9302             (void) fgets(line, MSG_SIZ, f);\r
9303             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
9304                 if (*p == ' ')\r
9305                   continue;\r
9306                 initial_position[i][j++] = CharToPiece(*p);\r
9307             }\r
9308         }\r
9309     \r
9310         blackPlaysFirst = FALSE;\r
9311         if (!feof(f)) {\r
9312             (void) fgets(line, MSG_SIZ, f);\r
9313             if (strncmp(line, "black", strlen("black"))==0)\r
9314               blackPlaysFirst = TRUE;\r
9315         }\r
9316     }\r
9317     startedFromSetupPosition = TRUE;\r
9318     \r
9319     SendToProgram("force\n", &first);\r
9320     CopyBoard(boards[0], initial_position);\r
9321     if (blackPlaysFirst) {\r
9322         currentMove = forwardMostMove = backwardMostMove = 1;\r
9323         strcpy(moveList[0], "");\r
9324         strcpy(parseList[0], "");\r
9325         CopyBoard(boards[1], initial_position);\r
9326         DisplayMessage("", _("Black to play"));\r
9327     } else {\r
9328         currentMove = forwardMostMove = backwardMostMove = 0;\r
9329         DisplayMessage("", _("White to play"));\r
9330     }\r
9331           /* [HGM] copy FEN attributes as well */\r
9332           {   int i;\r
9333               initialRulePlies = FENrulePlies;\r
9334               epStatus[forwardMostMove] = FENepStatus;\r
9335               for( i=0; i< nrCastlingRights; i++ )\r
9336                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
9337           }\r
9338     SendBoard(&first, forwardMostMove);\r
9339     if (appData.debugMode) {\r
9340 int i, j;\r
9341   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}\r
9342   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");\r
9343         fprintf(debugFP, "Load Position\n");\r
9344     }\r
9345 \r
9346     if (positionNumber > 1) {\r
9347         sprintf(line, "%s %d", title, positionNumber);\r
9348         DisplayTitle(line);\r
9349     } else {\r
9350         DisplayTitle(title);\r
9351     }\r
9352     gameMode = EditGame;\r
9353     ModeHighlight();\r
9354     ResetClocks();\r
9355     timeRemaining[0][1] = whiteTimeRemaining;\r
9356     timeRemaining[1][1] = blackTimeRemaining;\r
9357     DrawPosition(FALSE, boards[currentMove]);\r
9358    \r
9359     return TRUE;\r
9360 }\r
9361 \r
9362 \r
9363 void\r
9364 CopyPlayerNameIntoFileName(dest, src)\r
9365      char **dest, *src;\r
9366 {\r
9367     while (*src != NULLCHAR && *src != ',') {\r
9368         if (*src == ' ') {\r
9369             *(*dest)++ = '_';\r
9370             src++;\r
9371         } else {\r
9372             *(*dest)++ = *src++;\r
9373         }\r
9374     }\r
9375 }\r
9376 \r
9377 char *DefaultFileName(ext)\r
9378      char *ext;\r
9379 {\r
9380     static char def[MSG_SIZ];\r
9381     char *p;\r
9382 \r
9383     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
9384         p = def;\r
9385         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
9386         *p++ = '-';\r
9387         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
9388         *p++ = '.';\r
9389         strcpy(p, ext);\r
9390     } else {\r
9391         def[0] = NULLCHAR;\r
9392     }\r
9393     return def;\r
9394 }\r
9395 \r
9396 /* Save the current game to the given file */\r
9397 int\r
9398 SaveGameToFile(filename, append)\r
9399      char *filename;\r
9400      int append;\r
9401 {\r
9402     FILE *f;\r
9403     char buf[MSG_SIZ];\r
9404 \r
9405     if (strcmp(filename, "-") == 0) {\r
9406         return SaveGame(stdout, 0, NULL);\r
9407     } else {\r
9408         f = fopen(filename, append ? "a" : "w");\r
9409         if (f == NULL) {\r
9410             sprintf(buf, _("Can't open \"%s\""), filename);\r
9411             DisplayError(buf, errno);\r
9412             return FALSE;\r
9413         } else {\r
9414             return SaveGame(f, 0, NULL);\r
9415         }\r
9416     }\r
9417 }\r
9418 \r
9419 char *\r
9420 SavePart(str)\r
9421      char *str;\r
9422 {\r
9423     static char buf[MSG_SIZ];\r
9424     char *p;\r
9425     \r
9426     p = strchr(str, ' ');\r
9427     if (p == NULL) return str;\r
9428     strncpy(buf, str, p - str);\r
9429     buf[p - str] = NULLCHAR;\r
9430     return buf;\r
9431 }\r
9432 \r
9433 #define PGN_MAX_LINE 75\r
9434 \r
9435 #define PGN_SIDE_WHITE  0\r
9436 #define PGN_SIDE_BLACK  1\r
9437 \r
9438 /* [AS] */\r
9439 static int FindFirstMoveOutOfBook( int side )\r
9440 {\r
9441     int result = -1;\r
9442 \r
9443     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
9444         int index = backwardMostMove;\r
9445         int has_book_hit = 0;\r
9446 \r
9447         if( (index % 2) != side ) {\r
9448             index++;\r
9449         }\r
9450 \r
9451         while( index < forwardMostMove ) {\r
9452             /* Check to see if engine is in book */\r
9453             int depth = pvInfoList[index].depth;\r
9454             int score = pvInfoList[index].score;\r
9455             int in_book = 0;\r
9456 \r
9457             if( depth <= 2 ) {\r
9458                 in_book = 1;\r
9459             }\r
9460             else if( score == 0 && depth == 63 ) {\r
9461                 in_book = 1; /* Zappa */\r
9462             }\r
9463             else if( score == 2 && depth == 99 ) {\r
9464                 in_book = 1; /* Abrok */\r
9465             }\r
9466 \r
9467             has_book_hit += in_book;\r
9468 \r
9469             if( ! in_book ) {\r
9470                 result = index;\r
9471 \r
9472                 break;\r
9473             }\r
9474 \r
9475             index += 2;\r
9476         }\r
9477     }\r
9478 \r
9479     return result;\r
9480 }\r
9481 \r
9482 /* [AS] */\r
9483 void GetOutOfBookInfo( char * buf )\r
9484 {\r
9485     int oob[2];\r
9486     int i;\r
9487     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9488 \r
9489     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
9490     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
9491 \r
9492     *buf = '\0';\r
9493 \r
9494     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
9495         for( i=0; i<2; i++ ) {\r
9496             int idx = oob[i];\r
9497 \r
9498             if( idx >= 0 ) {\r
9499                 if( i > 0 && oob[0] >= 0 ) {\r
9500                     strcat( buf, "   " );\r
9501                 }\r
9502 \r
9503                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
9504                 sprintf( buf+strlen(buf), "%s%.2f", \r
9505                     pvInfoList[idx].score >= 0 ? "+" : "",\r
9506                     pvInfoList[idx].score / 100.0 );\r
9507             }\r
9508         }\r
9509     }\r
9510 }\r
9511 \r
9512 /* Save game in PGN style and close the file */\r
9513 int\r
9514 SaveGamePGN(f)\r
9515      FILE *f;\r
9516 {\r
9517     int i, offset, linelen, newblock;\r
9518     time_t tm;\r
9519     char *movetext;\r
9520     char numtext[32];\r
9521     int movelen, numlen, blank;\r
9522     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
9523 \r
9524     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9525     \r
9526     tm = time((time_t *) NULL);\r
9527     \r
9528     PrintPGNTags(f, &gameInfo);\r
9529     \r
9530     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9531         char *fen = PositionToFEN(backwardMostMove, 1);\r
9532         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
9533         fprintf(f, "\n{--------------\n");\r
9534         PrintPosition(f, backwardMostMove);\r
9535         fprintf(f, "--------------}\n");\r
9536         free(fen);\r
9537     }\r
9538     else {\r
9539         /* [AS] Out of book annotation */\r
9540         if( appData.saveOutOfBookInfo ) {\r
9541             char buf[64];\r
9542 \r
9543             GetOutOfBookInfo( buf );\r
9544 \r
9545             if( buf[0] != '\0' ) {\r
9546                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
9547             }\r
9548         }\r
9549 \r
9550         fprintf(f, "\n");\r
9551     }\r
9552 \r
9553     i = backwardMostMove;\r
9554     linelen = 0;\r
9555     newblock = TRUE;\r
9556 \r
9557     while (i < forwardMostMove) {\r
9558         /* Print comments preceding this move */\r
9559         if (commentList[i] != NULL) {\r
9560             if (linelen > 0) fprintf(f, "\n");\r
9561             fprintf(f, "{\n%s}\n", commentList[i]);\r
9562             linelen = 0;\r
9563             newblock = TRUE;\r
9564         }\r
9565 \r
9566         /* Format move number */\r
9567         if ((i % 2) == 0) {\r
9568             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
9569         } else {\r
9570             if (newblock) {\r
9571                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
9572             } else {\r
9573                 numtext[0] = NULLCHAR;\r
9574             }\r
9575         }\r
9576         numlen = strlen(numtext);\r
9577         newblock = FALSE;\r
9578 \r
9579         /* Print move number */\r
9580         blank = linelen > 0 && numlen > 0;\r
9581         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
9582             fprintf(f, "\n");\r
9583             linelen = 0;\r
9584             blank = 0;\r
9585         }\r
9586         if (blank) {\r
9587             fprintf(f, " ");\r
9588             linelen++;\r
9589         }\r
9590         fprintf(f, numtext);\r
9591         linelen += numlen;\r
9592 \r
9593         /* Get move */\r
9594         movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */\r
9595 \r
9596         /* Print move */\r
9597         blank = linelen > 0 && movelen > 0;\r
9598         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9599             fprintf(f, "\n");\r
9600             linelen = 0;\r
9601             blank = 0;\r
9602         }\r
9603         if (blank) {\r
9604             fprintf(f, " ");\r
9605             linelen++;\r
9606         }\r
9607         fprintf(f, parseList[i]);\r
9608         linelen += movelen;\r
9609 \r
9610         /* [AS] Add PV info if present */\r
9611         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9612             /* [HGM] add time */\r
9613             char buf[MSG_SIZ]; int seconds = 0;\r
9614 \r
9615 #if 0\r
9616             if(i >= backwardMostMove) {\r
9617                 if(WhiteOnMove(i))\r
9618                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]\r
9619                                   + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;\r
9620                 else\r
9621                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]\r
9622                                   + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;\r
9623             }\r
9624             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest\r
9625 #else\r
9626             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time\r
9627 #endif\r
9628     if (appData.debugMode,0) {\r
9629         fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",\r
9630                 timeRemaining[0][i+1], timeRemaining[0][i],\r
9631                      timeRemaining[1][i+1], timeRemaining[1][i], seconds\r
9632         );\r
9633     }\r
9634 \r
9635             if( seconds <= 0) buf[0] = 0; else\r
9636             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {\r
9637                 seconds = (seconds + 4)/10; // round to full seconds\r
9638                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else\r
9639                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
9640             }\r
9641 \r
9642             sprintf( move_buffer, "{%s%.2f/%d%s}", \r
9643                 pvInfoList[i].score >= 0 ? "+" : "",\r
9644                 pvInfoList[i].score / 100.0,\r
9645                 pvInfoList[i].depth,\r
9646                 buf );\r
9647 \r
9648             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */\r
9649 \r
9650             /* Print score/depth */\r
9651             blank = linelen > 0 && movelen > 0;\r
9652             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9653                 fprintf(f, "\n");\r
9654                 linelen = 0;\r
9655                 blank = 0;\r
9656             }\r
9657             if (blank) {\r
9658                 fprintf(f, " ");\r
9659                 linelen++;\r
9660             }\r
9661             fprintf(f, move_buffer);\r
9662             linelen += movelen;\r
9663         }\r
9664 \r
9665         i++;\r
9666     }\r
9667     \r
9668     /* Start a new line */\r
9669     if (linelen > 0) fprintf(f, "\n");\r
9670 \r
9671     /* Print comments after last move */\r
9672     if (commentList[i] != NULL) {\r
9673         fprintf(f, "{\n%s}\n", commentList[i]);\r
9674     }\r
9675 \r
9676     /* Print result */\r
9677     if (gameInfo.resultDetails != NULL &&\r
9678         gameInfo.resultDetails[0] != NULLCHAR) {\r
9679         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
9680                 PGNResult(gameInfo.result));\r
9681     } else {\r
9682         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9683     }\r
9684 \r
9685     fclose(f);\r
9686     return TRUE;\r
9687 }\r
9688 \r
9689 /* Save game in old style and close the file */\r
9690 int\r
9691 SaveGameOldStyle(f)\r
9692      FILE *f;\r
9693 {\r
9694     int i, offset;\r
9695     time_t tm;\r
9696     \r
9697     tm = time((time_t *) NULL);\r
9698     \r
9699     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
9700     PrintOpponents(f);\r
9701     \r
9702     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9703         fprintf(f, "\n[--------------\n");\r
9704         PrintPosition(f, backwardMostMove);\r
9705         fprintf(f, "--------------]\n");\r
9706     } else {\r
9707         fprintf(f, "\n");\r
9708     }\r
9709 \r
9710     i = backwardMostMove;\r
9711     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9712 \r
9713     while (i < forwardMostMove) {\r
9714         if (commentList[i] != NULL) {\r
9715             fprintf(f, "[%s]\n", commentList[i]);\r
9716         }\r
9717 \r
9718         if ((i % 2) == 1) {\r
9719             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
9720             i++;\r
9721         } else {\r
9722             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
9723             i++;\r
9724             if (commentList[i] != NULL) {\r
9725                 fprintf(f, "\n");\r
9726                 continue;\r
9727             }\r
9728             if (i >= forwardMostMove) {\r
9729                 fprintf(f, "\n");\r
9730                 break;\r
9731             }\r
9732             fprintf(f, "%s\n", parseList[i]);\r
9733             i++;\r
9734         }\r
9735     }\r
9736     \r
9737     if (commentList[i] != NULL) {\r
9738         fprintf(f, "[%s]\n", commentList[i]);\r
9739     }\r
9740 \r
9741     /* This isn't really the old style, but it's close enough */\r
9742     if (gameInfo.resultDetails != NULL &&\r
9743         gameInfo.resultDetails[0] != NULLCHAR) {\r
9744         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
9745                 gameInfo.resultDetails);\r
9746     } else {\r
9747         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9748     }\r
9749 \r
9750     fclose(f);\r
9751     return TRUE;\r
9752 }\r
9753 \r
9754 /* Save the current game to open file f and close the file */\r
9755 int\r
9756 SaveGame(f, dummy, dummy2)\r
9757      FILE *f;\r
9758      int dummy;\r
9759      char *dummy2;\r
9760 {\r
9761     if (gameMode == EditPosition) EditPositionDone();\r
9762     if (appData.oldSaveStyle)\r
9763       return SaveGameOldStyle(f);\r
9764     else\r
9765       return SaveGamePGN(f);\r
9766 }\r
9767 \r
9768 /* Save the current position to the given file */\r
9769 int\r
9770 SavePositionToFile(filename)\r
9771      char *filename;\r
9772 {\r
9773     FILE *f;\r
9774     char buf[MSG_SIZ];\r
9775 \r
9776     if (strcmp(filename, "-") == 0) {\r
9777         return SavePosition(stdout, 0, NULL);\r
9778     } else {\r
9779         f = fopen(filename, "a");\r
9780         if (f == NULL) {\r
9781             sprintf(buf, _("Can't open \"%s\""), filename);\r
9782             DisplayError(buf, errno);\r
9783             return FALSE;\r
9784         } else {\r
9785             SavePosition(f, 0, NULL);\r
9786             return TRUE;\r
9787         }\r
9788     }\r
9789 }\r
9790 \r
9791 /* Save the current position to the given open file and close the file */\r
9792 int\r
9793 SavePosition(f, dummy, dummy2)\r
9794      FILE *f;\r
9795      int dummy;\r
9796      char *dummy2;\r
9797 {\r
9798     time_t tm;\r
9799     char *fen;\r
9800     \r
9801     if (appData.oldSaveStyle) {\r
9802         tm = time((time_t *) NULL);\r
9803     \r
9804         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
9805         PrintOpponents(f);\r
9806         fprintf(f, "[--------------\n");\r
9807         PrintPosition(f, currentMove);\r
9808         fprintf(f, "--------------]\n");\r
9809     } else {\r
9810         fen = PositionToFEN(currentMove, 1);\r
9811         fprintf(f, "%s\n", fen);\r
9812         free(fen);\r
9813     }\r
9814     fclose(f);\r
9815     return TRUE;\r
9816 }\r
9817 \r
9818 void\r
9819 ReloadCmailMsgEvent(unregister)\r
9820      int unregister;\r
9821 {\r
9822 #if !WIN32\r
9823     static char *inFilename = NULL;\r
9824     static char *outFilename;\r
9825     int i;\r
9826     struct stat inbuf, outbuf;\r
9827     int status;\r
9828     \r
9829     /* Any registered moves are unregistered if unregister is set, */\r
9830     /* i.e. invoked by the signal handler */\r
9831     if (unregister) {\r
9832         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9833             cmailMoveRegistered[i] = FALSE;\r
9834             if (cmailCommentList[i] != NULL) {\r
9835                 free(cmailCommentList[i]);\r
9836                 cmailCommentList[i] = NULL;\r
9837             }\r
9838         }\r
9839         nCmailMovesRegistered = 0;\r
9840     }\r
9841 \r
9842     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9843         cmailResult[i] = CMAIL_NOT_RESULT;\r
9844     }\r
9845     nCmailResults = 0;\r
9846 \r
9847     if (inFilename == NULL) {\r
9848         /* Because the filenames are static they only get malloced once  */\r
9849         /* and they never get freed                                      */\r
9850         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
9851         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
9852 \r
9853         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
9854         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
9855     }\r
9856     \r
9857     status = stat(outFilename, &outbuf);\r
9858     if (status < 0) {\r
9859         cmailMailedMove = FALSE;\r
9860     } else {\r
9861         status = stat(inFilename, &inbuf);\r
9862         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
9863     }\r
9864     \r
9865     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
9866        counts the games, notes how each one terminated, etc.\r
9867        \r
9868        It would be nice to remove this kludge and instead gather all\r
9869        the information while building the game list.  (And to keep it\r
9870        in the game list nodes instead of having a bunch of fixed-size\r
9871        parallel arrays.)  Note this will require getting each game's\r
9872        termination from the PGN tags, as the game list builder does\r
9873        not process the game moves.  --mann\r
9874        */\r
9875     cmailMsgLoaded = TRUE;\r
9876     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
9877     \r
9878     /* Load first game in the file or popup game menu */\r
9879     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
9880 \r
9881 #endif /* !WIN32 */\r
9882     return;\r
9883 }\r
9884 \r
9885 int\r
9886 RegisterMove()\r
9887 {\r
9888     FILE *f;\r
9889     char string[MSG_SIZ];\r
9890 \r
9891     if (   cmailMailedMove\r
9892         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9893         return TRUE;            /* Allow free viewing  */\r
9894     }\r
9895 \r
9896     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9897     /* with the move registered when the conditions for registering no   */\r
9898     /* longer hold                                                       */\r
9899     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9900         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9901         nCmailMovesRegistered --;\r
9902 \r
9903         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9904           {\r
9905               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9906               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9907           }\r
9908     }\r
9909 \r
9910     if (cmailOldMove == -1) {\r
9911         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);\r
9912         return FALSE;\r
9913     }\r
9914 \r
9915     if (currentMove > cmailOldMove + 1) {\r
9916         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);\r
9917         return FALSE;\r
9918     }\r
9919 \r
9920     if (currentMove < cmailOldMove) {\r
9921         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);\r
9922         return FALSE;\r
9923     }\r
9924 \r
9925     if (forwardMostMove > currentMove) {\r
9926         /* Silently truncate extra moves */\r
9927         TruncateGame();\r
9928     }\r
9929 \r
9930     if (   (currentMove == cmailOldMove + 1)\r
9931         || (   (currentMove == cmailOldMove)\r
9932             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9933                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9934         if (gameInfo.result != GameUnfinished) {\r
9935             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9936         }\r
9937 \r
9938         if (commentList[currentMove] != NULL) {\r
9939             cmailCommentList[lastLoadGameNumber - 1]\r
9940               = StrSave(commentList[currentMove]);\r
9941         }\r
9942         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9943 \r
9944         if (appData.debugMode)\r
9945           fprintf(debugFP, "Saving %s for game %d\n",\r
9946                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9947 \r
9948         sprintf(string,\r
9949                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
9950         \r
9951         f = fopen(string, "w");\r
9952         if (appData.oldSaveStyle) {\r
9953             SaveGameOldStyle(f); /* also closes the file */\r
9954             \r
9955             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
9956             f = fopen(string, "w");\r
9957             SavePosition(f, 0, NULL); /* also closes the file */\r
9958         } else {\r
9959             fprintf(f, "{--------------\n");\r
9960             PrintPosition(f, currentMove);\r
9961             fprintf(f, "--------------}\n\n");\r
9962             \r
9963             SaveGame(f, 0, NULL); /* also closes the file*/\r
9964         }\r
9965         \r
9966         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
9967         nCmailMovesRegistered ++;\r
9968     } else if (nCmailGames == 1) {\r
9969         DisplayError(_("You have not made a move yet"), 0);\r
9970         return FALSE;\r
9971     }\r
9972 \r
9973     return TRUE;\r
9974 }\r
9975 \r
9976 void\r
9977 MailMoveEvent()\r
9978 {\r
9979 #if !WIN32\r
9980     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
9981     FILE *commandOutput;\r
9982     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
9983     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
9984     int nBuffers;\r
9985     int i;\r
9986     int archived;\r
9987     char *arcDir;\r
9988 \r
9989     if (! cmailMsgLoaded) {\r
9990         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);\r
9991         return;\r
9992     }\r
9993 \r
9994     if (nCmailGames == nCmailResults) {\r
9995         DisplayError(_("No unfinished games"), 0);\r
9996         return;\r
9997     }\r
9998 \r
9999 #if CMAIL_PROHIBIT_REMAIL\r
10000     if (cmailMailedMove) {\r
10001         sprintf(msg, _("You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line."), appData.cmailGameName);\r
10002         DisplayError(msg, 0);\r
10003         return;\r
10004     }\r
10005 #endif\r
10006 \r
10007     if (! (cmailMailedMove || RegisterMove())) return;\r
10008     \r
10009     if (   cmailMailedMove\r
10010         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
10011         sprintf(string, partCommandString,\r
10012                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
10013         commandOutput = popen(string, "r");\r
10014 \r
10015         if (commandOutput == NULL) {\r
10016             DisplayError(_("Failed to invoke cmail"), 0);\r
10017         } else {\r
10018             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
10019                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
10020             }\r
10021             if (nBuffers > 1) {\r
10022                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
10023                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
10024                 nBytes = MSG_SIZ - 1;\r
10025             } else {\r
10026                 (void) memcpy(msg, buffer, nBytes);\r
10027             }\r
10028             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
10029 \r
10030             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
10031                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
10032 \r
10033                 archived = TRUE;\r
10034                 for (i = 0; i < nCmailGames; i ++) {\r
10035                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
10036                         archived = FALSE;\r
10037                     }\r
10038                 }\r
10039                 if (   archived\r
10040                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
10041                         != NULL)) {\r
10042                     sprintf(buffer, "%s/%s.%s.archive",\r
10043                             arcDir,\r
10044                             appData.cmailGameName,\r
10045                             gameInfo.date);\r
10046                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
10047                     cmailMsgLoaded = FALSE;\r
10048                 }\r
10049             }\r
10050 \r
10051             DisplayInformation(msg);\r
10052             pclose(commandOutput);\r
10053         }\r
10054     } else {\r
10055         if ((*cmailMsg) != '\0') {\r
10056             DisplayInformation(cmailMsg);\r
10057         }\r
10058     }\r
10059 \r
10060     return;\r
10061 #endif /* !WIN32 */\r
10062 }\r
10063 \r
10064 char *\r
10065 CmailMsg()\r
10066 {\r
10067 #if WIN32\r
10068     return NULL;\r
10069 #else\r
10070     int  prependComma = 0;\r
10071     char number[5];\r
10072     char string[MSG_SIZ];       /* Space for game-list */\r
10073     int  i;\r
10074     \r
10075     if (!cmailMsgLoaded) return "";\r
10076 \r
10077     if (cmailMailedMove) {\r
10078         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));\r
10079     } else {\r
10080         /* Create a list of games left */\r
10081         sprintf(string, "[");\r
10082         for (i = 0; i < nCmailGames; i ++) {\r
10083             if (! (   cmailMoveRegistered[i]\r
10084                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
10085                 if (prependComma) {\r
10086                     sprintf(number, ",%d", i + 1);\r
10087                 } else {\r
10088                     sprintf(number, "%d", i + 1);\r
10089                     prependComma = 1;\r
10090                 }\r
10091                 \r
10092                 strcat(string, number);\r
10093             }\r
10094         }\r
10095         strcat(string, "]");\r
10096 \r
10097         if (nCmailMovesRegistered + nCmailResults == 0) {\r
10098             switch (nCmailGames) {\r
10099               case 1:\r
10100                 sprintf(cmailMsg,\r
10101                         _("Still need to make move for game\n"));\r
10102                 break;\r
10103                 \r
10104               case 2:\r
10105                 sprintf(cmailMsg,\r
10106                         _("Still need to make moves for both games\n"));\r
10107                 break;\r
10108                 \r
10109               default:\r
10110                 sprintf(cmailMsg,\r
10111                         _("Still need to make moves for all %d games\n"),\r
10112                         nCmailGames);\r
10113                 break;\r
10114             }\r
10115         } else {\r
10116             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
10117               case 1:\r
10118                 sprintf(cmailMsg,\r
10119                         _("Still need to make a move for game %s\n"),\r
10120                         string);\r
10121                 break;\r
10122                 \r
10123               case 0:\r
10124                 if (nCmailResults == nCmailGames) {\r
10125                     sprintf(cmailMsg, _("No unfinished games\n"));\r
10126                 } else {\r
10127                     sprintf(cmailMsg, _("Ready to send mail\n"));\r
10128                 }\r
10129                 break;\r
10130                 \r
10131               default:\r
10132                 sprintf(cmailMsg,\r
10133                         _("Still need to make moves for games %s\n"),\r
10134                         string);\r
10135             }\r
10136         }\r
10137     }\r
10138     return cmailMsg;\r
10139 #endif /* WIN32 */\r
10140 }\r
10141 \r
10142 void\r
10143 ResetGameEvent()\r
10144 {\r
10145     if (gameMode == Training)\r
10146       SetTrainingModeOff();\r
10147 \r
10148     Reset(TRUE, TRUE);\r
10149     cmailMsgLoaded = FALSE;\r
10150     if (appData.icsActive) {\r
10151       SendToICS(ics_prefix);\r
10152       SendToICS("refresh\n");\r
10153     }\r
10154 }\r
10155 \r
10156 void\r
10157 ExitEvent(status)\r
10158      int status;\r
10159 {\r
10160     exiting++;\r
10161     if (exiting > 2) {\r
10162       /* Give up on clean exit */\r
10163       exit(status);\r
10164     }\r
10165     if (exiting > 1) {\r
10166       /* Keep trying for clean exit */\r
10167       return;\r
10168     }\r
10169 \r
10170     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
10171 \r
10172     if (telnetISR != NULL) {\r
10173       RemoveInputSource(telnetISR);\r
10174     }\r
10175     if (icsPR != NoProc) {\r
10176       DestroyChildProcess(icsPR, TRUE);\r
10177     }\r
10178 #if 0\r
10179     /* Save game if resource set and not already saved by GameEnds() */\r
10180     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
10181                              && forwardMostMove > 0) {\r
10182       if (*appData.saveGameFile != NULLCHAR) {\r
10183         SaveGameToFile(appData.saveGameFile, TRUE);\r
10184       } else if (appData.autoSaveGames) {\r
10185         AutoSaveGame();\r
10186       }\r
10187       if (*appData.savePositionFile != NULLCHAR) {\r
10188         SavePositionToFile(appData.savePositionFile);\r
10189       }\r
10190     }\r
10191     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10192 #else\r
10193     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
10194     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);\r
10195 #endif\r
10196     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
10197     /* make sure this other one finishes before killing it!                  */\r
10198     if(endingGame) { int count = 0;\r
10199         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
10200         while(endingGame && count++ < 10) DoSleep(1);\r
10201         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
10202     }\r
10203 \r
10204     /* Kill off chess programs */\r
10205     if (first.pr != NoProc) {\r
10206         ExitAnalyzeMode();\r
10207         \r
10208         DoSleep( appData.delayBeforeQuit );\r
10209         SendToProgram("quit\n", &first);\r
10210         DoSleep( appData.delayAfterQuit );\r
10211         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
10212     }\r
10213     if (second.pr != NoProc) {\r
10214         DoSleep( appData.delayBeforeQuit );\r
10215         SendToProgram("quit\n", &second);\r
10216         DoSleep( appData.delayAfterQuit );\r
10217         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
10218     }\r
10219     if (first.isr != NULL) {\r
10220         RemoveInputSource(first.isr);\r
10221     }\r
10222     if (second.isr != NULL) {\r
10223         RemoveInputSource(second.isr);\r
10224     }\r
10225 \r
10226     ShutDownFrontEnd();\r
10227     exit(status);\r
10228 }\r
10229 \r
10230 void\r
10231 PauseEvent()\r
10232 {\r
10233     if (appData.debugMode)\r
10234         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
10235     if (pausing) {\r
10236         pausing = FALSE;\r
10237         ModeHighlight();\r
10238         if (gameMode == MachinePlaysWhite ||\r
10239             gameMode == MachinePlaysBlack) {\r
10240             StartClocks();\r
10241         } else {\r
10242             DisplayBothClocks();\r
10243         }\r
10244         if (gameMode == PlayFromGameFile) {\r
10245             if (appData.timeDelay >= 0) \r
10246                 AutoPlayGameLoop();\r
10247         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
10248             Reset(FALSE, TRUE);\r
10249             SendToICS(ics_prefix);\r
10250             SendToICS("refresh\n");\r
10251         } else if (currentMove < forwardMostMove) {\r
10252             ForwardInner(forwardMostMove);\r
10253         }\r
10254         pauseExamInvalid = FALSE;\r
10255     } else {\r
10256         switch (gameMode) {\r
10257           default:\r
10258             return;\r
10259           case IcsExamining:\r
10260             pauseExamForwardMostMove = forwardMostMove;\r
10261             pauseExamInvalid = FALSE;\r
10262             /* fall through */\r
10263           case IcsObserving:\r
10264           case IcsPlayingWhite:\r
10265           case IcsPlayingBlack:\r
10266             pausing = TRUE;\r
10267             ModeHighlight();\r
10268             return;\r
10269           case PlayFromGameFile:\r
10270             (void) StopLoadGameTimer();\r
10271             pausing = TRUE;\r
10272             ModeHighlight();\r
10273             break;\r
10274           case BeginningOfGame:\r
10275             if (appData.icsActive) return;\r
10276             /* else fall through */\r
10277           case MachinePlaysWhite:\r
10278           case MachinePlaysBlack:\r
10279           case TwoMachinesPlay:\r
10280             if (forwardMostMove == 0)\r
10281               return;           /* don't pause if no one has moved */\r
10282             if ((gameMode == MachinePlaysWhite &&\r
10283                  !WhiteOnMove(forwardMostMove)) ||\r
10284                 (gameMode == MachinePlaysBlack &&\r
10285                  WhiteOnMove(forwardMostMove))) {\r
10286                 StopClocks();\r
10287             }\r
10288             pausing = TRUE;\r
10289             ModeHighlight();\r
10290             break;\r
10291         }\r
10292     }\r
10293 }\r
10294 \r
10295 void\r
10296 EditCommentEvent()\r
10297 {\r
10298     char title[MSG_SIZ];\r
10299 \r
10300     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
10301         strcpy(title, _("Edit comment"));\r
10302     } else {\r
10303         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,\r
10304                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
10305                 parseList[currentMove - 1]);\r
10306     }\r
10307 \r
10308     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
10309 }\r
10310 \r
10311 \r
10312 void\r
10313 EditTagsEvent()\r
10314 {\r
10315     char *tags = PGNTags(&gameInfo);\r
10316     EditTagsPopUp(tags);\r
10317     free(tags);\r
10318 }\r
10319 \r
10320 void\r
10321 AnalyzeModeEvent()\r
10322 {\r
10323     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
10324       return;\r
10325 \r
10326     if (gameMode != AnalyzeFile) {\r
10327         if (!appData.icsEngineAnalyze) {\r
10328                EditGameEvent();\r
10329                if (gameMode != EditGame) return;\r
10330         }\r
10331         ResurrectChessProgram();\r
10332         SendToProgram("analyze\n", &first);\r
10333         first.analyzing = TRUE;\r
10334         /*first.maybeThinking = TRUE;*/\r
10335         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10336         AnalysisPopUp(_("Analysis"),\r
10337                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10338     }\r
10339     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;\r
10340     pausing = FALSE;\r
10341     ModeHighlight();\r
10342     SetGameInfo();\r
10343 \r
10344     StartAnalysisClock();\r
10345     GetTimeMark(&lastNodeCountTime);\r
10346     lastNodeCount = 0;\r
10347 }\r
10348 \r
10349 void\r
10350 AnalyzeFileEvent()\r
10351 {\r
10352     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
10353       return;\r
10354 \r
10355     if (gameMode != AnalyzeMode) {\r
10356         EditGameEvent();\r
10357         if (gameMode != EditGame) return;\r
10358         ResurrectChessProgram();\r
10359         SendToProgram("analyze\n", &first);\r
10360         first.analyzing = TRUE;\r
10361         /*first.maybeThinking = TRUE;*/\r
10362         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10363         AnalysisPopUp(_("Analysis"),\r
10364                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10365     }\r
10366     gameMode = AnalyzeFile;\r
10367     pausing = FALSE;\r
10368     ModeHighlight();\r
10369     SetGameInfo();\r
10370 \r
10371     StartAnalysisClock();\r
10372     GetTimeMark(&lastNodeCountTime);\r
10373     lastNodeCount = 0;\r
10374 }\r
10375 \r
10376 void\r
10377 MachineWhiteEvent()\r
10378 {\r
10379     char buf[MSG_SIZ];\r
10380     char *bookHit = NULL;\r
10381 \r
10382     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
10383       return;\r
10384 \r
10385 \r
10386     if (gameMode == PlayFromGameFile || \r
10387         gameMode == TwoMachinesPlay  || \r
10388         gameMode == Training         || \r
10389         gameMode == AnalyzeMode      || \r
10390         gameMode == EndOfGame)\r
10391         EditGameEvent();\r
10392 \r
10393     if (gameMode == EditPosition) \r
10394         EditPositionDone();\r
10395 \r
10396     if (!WhiteOnMove(currentMove)) {\r
10397         DisplayError(_("It is not White's turn"), 0);\r
10398         return;\r
10399     }\r
10400   \r
10401     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10402       ExitAnalyzeMode();\r
10403 \r
10404     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10405         gameMode == AnalyzeFile)\r
10406         TruncateGame();\r
10407 \r
10408     ResurrectChessProgram();    /* in case it isn't running */\r
10409     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */\r
10410         gameMode = MachinePlaysWhite;\r
10411         ResetClocks();\r
10412     } else\r
10413     gameMode = MachinePlaysWhite;\r
10414     pausing = FALSE;\r
10415     ModeHighlight();\r
10416     SetGameInfo();\r
10417     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10418     DisplayTitle(buf);\r
10419     if (first.sendName) {\r
10420       sprintf(buf, "name %s\n", gameInfo.black);\r
10421       SendToProgram(buf, &first);\r
10422     }\r
10423     if (first.sendTime) {\r
10424       if (first.useColors) {\r
10425         SendToProgram("black\n", &first); /*gnu kludge*/\r
10426       }\r
10427       SendTimeRemaining(&first, TRUE);\r
10428     }\r
10429     if (first.useColors) {\r
10430       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately\r
10431     }\r
10432     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10433     SetMachineThinkingEnables();\r
10434     first.maybeThinking = TRUE;\r
10435     StartClocks();\r
10436 \r
10437     if (appData.autoFlipView && !flipView) {\r
10438       flipView = !flipView;\r
10439       DrawPosition(FALSE, NULL);\r
10440       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10441     }\r
10442 \r
10443     if(bookHit) { // [HGM] book: simulate book reply\r
10444         static char bookMove[MSG_SIZ]; // a bit generous?\r
10445 \r
10446         programStats.depth = programStats.nodes = programStats.time = \r
10447         programStats.score = programStats.got_only_move = 0;\r
10448         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10449 \r
10450         strcpy(bookMove, "move ");\r
10451         strcat(bookMove, bookHit);\r
10452         HandleMachineMove(bookMove, &first);\r
10453     }\r
10454 }\r
10455 \r
10456 void\r
10457 MachineBlackEvent()\r
10458 {\r
10459     char buf[MSG_SIZ];\r
10460    char *bookHit = NULL;\r
10461 \r
10462     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
10463         return;\r
10464 \r
10465 \r
10466     if (gameMode == PlayFromGameFile || \r
10467         gameMode == TwoMachinesPlay  || \r
10468         gameMode == Training         || \r
10469         gameMode == AnalyzeMode      || \r
10470         gameMode == EndOfGame)\r
10471         EditGameEvent();\r
10472 \r
10473     if (gameMode == EditPosition) \r
10474         EditPositionDone();\r
10475 \r
10476     if (WhiteOnMove(currentMove)) {\r
10477         DisplayError(_("It is not Black's turn"), 0);\r
10478         return;\r
10479     }\r
10480     \r
10481     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10482       ExitAnalyzeMode();\r
10483 \r
10484     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10485         gameMode == AnalyzeFile)\r
10486         TruncateGame();\r
10487 \r
10488     ResurrectChessProgram();    /* in case it isn't running */\r
10489     gameMode = MachinePlaysBlack;\r
10490     pausing = FALSE;\r
10491     ModeHighlight();\r
10492     SetGameInfo();\r
10493     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10494     DisplayTitle(buf);\r
10495     if (first.sendName) {\r
10496       sprintf(buf, "name %s\n", gameInfo.white);\r
10497       SendToProgram(buf, &first);\r
10498     }\r
10499     if (first.sendTime) {\r
10500       if (first.useColors) {\r
10501         SendToProgram("white\n", &first); /*gnu kludge*/\r
10502       }\r
10503       SendTimeRemaining(&first, FALSE);\r
10504     }\r
10505     if (first.useColors) {\r
10506       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately\r
10507     }\r
10508     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10509     SetMachineThinkingEnables();\r
10510     first.maybeThinking = TRUE;\r
10511     StartClocks();\r
10512 \r
10513     if (appData.autoFlipView && flipView) {\r
10514       flipView = !flipView;\r
10515       DrawPosition(FALSE, NULL);\r
10516       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10517     }\r
10518     if(bookHit) { // [HGM] book: simulate book reply\r
10519         static char bookMove[MSG_SIZ]; // a bit generous?\r
10520 \r
10521         programStats.depth = programStats.nodes = programStats.time = \r
10522         programStats.score = programStats.got_only_move = 0;\r
10523         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10524 \r
10525         strcpy(bookMove, "move ");\r
10526         strcat(bookMove, bookHit);\r
10527         HandleMachineMove(bookMove, &first);\r
10528     }\r
10529 }\r
10530 \r
10531 \r
10532 void\r
10533 DisplayTwoMachinesTitle()\r
10534 {\r
10535     char buf[MSG_SIZ];\r
10536     if (appData.matchGames > 0) {\r
10537         if (first.twoMachinesColor[0] == 'w') {\r
10538             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10539                     gameInfo.white, gameInfo.black,\r
10540                     first.matchWins, second.matchWins,\r
10541                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10542         } else {\r
10543             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10544                     gameInfo.white, gameInfo.black,\r
10545                     second.matchWins, first.matchWins,\r
10546                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10547         }\r
10548     } else {\r
10549         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10550     }\r
10551     DisplayTitle(buf);\r
10552 }\r
10553 \r
10554 void\r
10555 TwoMachinesEvent P((void))\r
10556 {\r
10557     int i;\r
10558     char buf[MSG_SIZ];\r
10559     ChessProgramState *onmove;\r
10560     char *bookHit = NULL;\r
10561     \r
10562     if (appData.noChessProgram) return;\r
10563 \r
10564     switch (gameMode) {\r
10565       case TwoMachinesPlay:\r
10566         return;\r
10567       case MachinePlaysWhite:\r
10568       case MachinePlaysBlack:\r
10569         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10570             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
10571             return;\r
10572         }\r
10573         /* fall through */\r
10574       case BeginningOfGame:\r
10575       case PlayFromGameFile:\r
10576       case EndOfGame:\r
10577         EditGameEvent();\r
10578         if (gameMode != EditGame) return;\r
10579         break;\r
10580       case EditPosition:\r
10581         EditPositionDone();\r
10582         break;\r
10583       case AnalyzeMode:\r
10584       case AnalyzeFile:\r
10585         ExitAnalyzeMode();\r
10586         break;\r
10587       case EditGame:\r
10588       default:\r
10589         break;\r
10590     }\r
10591 \r
10592     forwardMostMove = currentMove;\r
10593     ResurrectChessProgram();    /* in case first program isn't running */\r
10594 \r
10595     if (second.pr == NULL) {\r
10596         StartChessProgram(&second);\r
10597         if (second.protocolVersion == 1) {\r
10598           TwoMachinesEventIfReady();\r
10599         } else {\r
10600           /* kludge: allow timeout for initial "feature" command */\r
10601           FreezeUI();\r
10602           DisplayMessage("", _("Starting second chess program"));\r
10603           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
10604         }\r
10605         return;\r
10606     }\r
10607     DisplayMessage("", "");\r
10608     InitChessProgram(&second, FALSE);\r
10609     SendToProgram("force\n", &second);\r
10610     if (startedFromSetupPosition) {\r
10611         SendBoard(&second, backwardMostMove);\r
10612     if (appData.debugMode) {\r
10613         fprintf(debugFP, "Two Machines\n");\r
10614     }\r
10615     }\r
10616     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
10617         SendMoveToProgram(i, &second);\r
10618     }\r
10619 \r
10620     gameMode = TwoMachinesPlay;\r
10621     pausing = FALSE;\r
10622     ModeHighlight();\r
10623     SetGameInfo();\r
10624     DisplayTwoMachinesTitle();\r
10625     firstMove = TRUE;\r
10626     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
10627         onmove = &first;\r
10628     } else {\r
10629         onmove = &second;\r
10630     }\r
10631 \r
10632     SendToProgram(first.computerString, &first);\r
10633     if (first.sendName) {\r
10634       sprintf(buf, "name %s\n", second.tidy);\r
10635       SendToProgram(buf, &first);\r
10636     }\r
10637     SendToProgram(second.computerString, &second);\r
10638     if (second.sendName) {\r
10639       sprintf(buf, "name %s\n", first.tidy);\r
10640       SendToProgram(buf, &second);\r
10641     }\r
10642 \r
10643     ResetClocks();\r
10644     if (!first.sendTime || !second.sendTime) {\r
10645         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10646         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10647     }\r
10648     if (onmove->sendTime) {\r
10649       if (onmove->useColors) {\r
10650         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
10651       }\r
10652       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
10653     }\r
10654     if (onmove->useColors) {\r
10655       SendToProgram(onmove->twoMachinesColor, onmove);\r
10656     }\r
10657     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move\r
10658 //    SendToProgram("go\n", onmove);\r
10659     onmove->maybeThinking = TRUE;\r
10660     SetMachineThinkingEnables();\r
10661 \r
10662     StartClocks();\r
10663 \r
10664     if(bookHit) { // [HGM] book: simulate book reply\r
10665         static char bookMove[MSG_SIZ]; // a bit generous?\r
10666 \r
10667         programStats.depth = programStats.nodes = programStats.time = \r
10668         programStats.score = programStats.got_only_move = 0;\r
10669         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10670 \r
10671         strcpy(bookMove, "move ");\r
10672         strcat(bookMove, bookHit);\r
10673         HandleMachineMove(bookMove, &first);\r
10674     }\r
10675 }\r
10676 \r
10677 void\r
10678 TrainingEvent()\r
10679 {\r
10680     if (gameMode == Training) {\r
10681       SetTrainingModeOff();\r
10682       gameMode = PlayFromGameFile;\r
10683       DisplayMessage("", _("Training mode off"));\r
10684     } else {\r
10685       gameMode = Training;\r
10686       animateTraining = appData.animate;\r
10687 \r
10688       /* make sure we are not already at the end of the game */\r
10689       if (currentMove < forwardMostMove) {\r
10690         SetTrainingModeOn();\r
10691         DisplayMessage("", _("Training mode on"));\r
10692       } else {\r
10693         gameMode = PlayFromGameFile;\r
10694         DisplayError(_("Already at end of game"), 0);\r
10695       }\r
10696     }\r
10697     ModeHighlight();\r
10698 }\r
10699 \r
10700 void\r
10701 IcsClientEvent()\r
10702 {\r
10703     if (!appData.icsActive) return;\r
10704     switch (gameMode) {\r
10705       case IcsPlayingWhite:\r
10706       case IcsPlayingBlack:\r
10707       case IcsObserving:\r
10708       case IcsIdle:\r
10709       case BeginningOfGame:\r
10710       case IcsExamining:\r
10711         return;\r
10712 \r
10713       case EditGame:\r
10714         break;\r
10715 \r
10716       case EditPosition:\r
10717         EditPositionDone();\r
10718         break;\r
10719 \r
10720       case AnalyzeMode:\r
10721       case AnalyzeFile:\r
10722         ExitAnalyzeMode();\r
10723         break;\r
10724         \r
10725       default:\r
10726         EditGameEvent();\r
10727         break;\r
10728     }\r
10729 \r
10730     gameMode = IcsIdle;\r
10731     ModeHighlight();\r
10732     return;\r
10733 }\r
10734 \r
10735 \r
10736 void\r
10737 EditGameEvent()\r
10738 {\r
10739     int i;\r
10740 \r
10741     switch (gameMode) {\r
10742       case Training:\r
10743         SetTrainingModeOff();\r
10744         break;\r
10745       case MachinePlaysWhite:\r
10746       case MachinePlaysBlack:\r
10747       case BeginningOfGame:\r
10748         SendToProgram("force\n", &first);\r
10749         SetUserThinkingEnables();\r
10750         break;\r
10751       case PlayFromGameFile:\r
10752         (void) StopLoadGameTimer();\r
10753         if (gameFileFP != NULL) {\r
10754             gameFileFP = NULL;\r
10755         }\r
10756         break;\r
10757       case EditPosition:\r
10758         EditPositionDone();\r
10759         break;\r
10760       case AnalyzeMode:\r
10761       case AnalyzeFile:\r
10762         ExitAnalyzeMode();\r
10763         SendToProgram("force\n", &first);\r
10764         break;\r
10765       case TwoMachinesPlay:\r
10766         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10767         ResurrectChessProgram();\r
10768         SetUserThinkingEnables();\r
10769         break;\r
10770       case EndOfGame:\r
10771         ResurrectChessProgram();\r
10772         break;\r
10773       case IcsPlayingBlack:\r
10774       case IcsPlayingWhite:\r
10775         DisplayError(_("Warning: You are still playing a game"), 0);\r
10776         break;\r
10777       case IcsObserving:\r
10778         DisplayError(_("Warning: You are still observing a game"), 0);\r
10779         break;\r
10780       case IcsExamining:\r
10781         DisplayError(_("Warning: You are still examining a game"), 0);\r
10782         break;\r
10783       case IcsIdle:\r
10784         break;\r
10785       case EditGame:\r
10786       default:\r
10787         return;\r
10788     }\r
10789     \r
10790     pausing = FALSE;\r
10791     StopClocks();\r
10792     first.offeredDraw = second.offeredDraw = 0;\r
10793 \r
10794     if (gameMode == PlayFromGameFile) {\r
10795         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10796         blackTimeRemaining = timeRemaining[1][currentMove];\r
10797         DisplayTitle("");\r
10798     }\r
10799 \r
10800     if (gameMode == MachinePlaysWhite ||\r
10801         gameMode == MachinePlaysBlack ||\r
10802         gameMode == TwoMachinesPlay ||\r
10803         gameMode == EndOfGame) {\r
10804         i = forwardMostMove;\r
10805         while (i > currentMove) {\r
10806             SendToProgram("undo\n", &first);\r
10807             i--;\r
10808         }\r
10809         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10810         blackTimeRemaining = timeRemaining[1][currentMove];\r
10811         DisplayBothClocks();\r
10812         if (whiteFlag || blackFlag) {\r
10813             whiteFlag = blackFlag = 0;\r
10814         }\r
10815         DisplayTitle("");\r
10816     }           \r
10817     \r
10818     gameMode = EditGame;\r
10819     ModeHighlight();\r
10820     SetGameInfo();\r
10821 }\r
10822 \r
10823 \r
10824 void\r
10825 EditPositionEvent()\r
10826 {\r
10827     if (gameMode == EditPosition) {\r
10828         EditGameEvent();\r
10829         return;\r
10830     }\r
10831     \r
10832     EditGameEvent();\r
10833     if (gameMode != EditGame) return;\r
10834     \r
10835     gameMode = EditPosition;\r
10836     ModeHighlight();\r
10837     SetGameInfo();\r
10838     if (currentMove > 0)\r
10839       CopyBoard(boards[0], boards[currentMove]);\r
10840     \r
10841     blackPlaysFirst = !WhiteOnMove(currentMove);\r
10842     ResetClocks();\r
10843     currentMove = forwardMostMove = backwardMostMove = 0;\r
10844     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10845     DisplayMove(-1);\r
10846 }\r
10847 \r
10848 void\r
10849 ExitAnalyzeMode()\r
10850 {\r
10851     /* [DM] icsEngineAnalyze - possible call from other functions */\r
10852     if (appData.icsEngineAnalyze) {\r
10853         appData.icsEngineAnalyze = FALSE;\r
10854 \r
10855         DisplayMessage("",_("Close ICS engine analyze..."));\r
10856     }\r
10857     if (first.analysisSupport && first.analyzing) {\r
10858       SendToProgram("exit\n", &first);\r
10859       first.analyzing = FALSE;\r
10860     }\r
10861     AnalysisPopDown();\r
10862     thinkOutput[0] = NULLCHAR;\r
10863 }\r
10864 \r
10865 void\r
10866 EditPositionDone()\r
10867 {\r
10868     startedFromSetupPosition = TRUE;\r
10869     InitChessProgram(&first, FALSE);\r
10870     SendToProgram("force\n", &first);\r
10871     if (blackPlaysFirst) {\r
10872         strcpy(moveList[0], "");\r
10873         strcpy(parseList[0], "");\r
10874         currentMove = forwardMostMove = backwardMostMove = 1;\r
10875         CopyBoard(boards[1], boards[0]);\r
10876         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */\r
10877         { int i;\r
10878           epStatus[1] = epStatus[0];\r
10879           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];\r
10880         }\r
10881     } else {\r
10882         currentMove = forwardMostMove = backwardMostMove = 0;\r
10883     }\r
10884     SendBoard(&first, forwardMostMove);\r
10885     if (appData.debugMode) {\r
10886         fprintf(debugFP, "EditPosDone\n");\r
10887     }\r
10888     DisplayTitle("");\r
10889     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10890     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10891     gameMode = EditGame;\r
10892     ModeHighlight();\r
10893     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10894     ClearHighlights(); /* [AS] */\r
10895 }\r
10896 \r
10897 /* Pause for `ms' milliseconds */\r
10898 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10899 void\r
10900 TimeDelay(ms)\r
10901      long ms;\r
10902 {\r
10903     TimeMark m1, m2;\r
10904 \r
10905     GetTimeMark(&m1);\r
10906     do {\r
10907         GetTimeMark(&m2);\r
10908     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
10909 }\r
10910 \r
10911 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10912 void\r
10913 SendMultiLineToICS(buf)\r
10914      char *buf;\r
10915 {\r
10916     char temp[MSG_SIZ+1], *p;\r
10917     int len;\r
10918 \r
10919     len = strlen(buf);\r
10920     if (len > MSG_SIZ)\r
10921       len = MSG_SIZ;\r
10922   \r
10923     strncpy(temp, buf, len);\r
10924     temp[len] = 0;\r
10925 \r
10926     p = temp;\r
10927     while (*p) {\r
10928         if (*p == '\n' || *p == '\r')\r
10929           *p = ' ';\r
10930         ++p;\r
10931     }\r
10932 \r
10933     strcat(temp, "\n");\r
10934     SendToICS(temp);\r
10935     SendToPlayer(temp, strlen(temp));\r
10936 }\r
10937 \r
10938 void\r
10939 SetWhiteToPlayEvent()\r
10940 {\r
10941     if (gameMode == EditPosition) {\r
10942         blackPlaysFirst = FALSE;\r
10943         DisplayBothClocks();    /* works because currentMove is 0 */\r
10944     } else if (gameMode == IcsExamining) {\r
10945         SendToICS(ics_prefix);\r
10946         SendToICS("tomove white\n");\r
10947     }\r
10948 }\r
10949 \r
10950 void\r
10951 SetBlackToPlayEvent()\r
10952 {\r
10953     if (gameMode == EditPosition) {\r
10954         blackPlaysFirst = TRUE;\r
10955         currentMove = 1;        /* kludge */\r
10956         DisplayBothClocks();\r
10957         currentMove = 0;\r
10958     } else if (gameMode == IcsExamining) {\r
10959         SendToICS(ics_prefix);\r
10960         SendToICS("tomove black\n");\r
10961     }\r
10962 }\r
10963 \r
10964 void\r
10965 EditPositionMenuEvent(selection, x, y)\r
10966      ChessSquare selection;\r
10967      int x, y;\r
10968 {\r
10969     char buf[MSG_SIZ];\r
10970     ChessSquare piece = boards[0][y][x];\r
10971 \r
10972     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
10973 \r
10974     switch (selection) {\r
10975       case ClearBoard:\r
10976         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
10977             SendToICS(ics_prefix);\r
10978             SendToICS("bsetup clear\n");\r
10979         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
10980             SendToICS(ics_prefix);\r
10981             SendToICS("clearboard\n");\r
10982         } else {\r
10983             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;\r
10984                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */\r
10985                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
10986                     if (gameMode == IcsExamining) {\r
10987                         if (boards[currentMove][y][x] != EmptySquare) {\r
10988                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
10989                                     AAA + x, ONE + y);\r
10990                             SendToICS(buf);\r
10991                         }\r
10992                     } else {\r
10993                         boards[0][y][x] = p;\r
10994                     }\r
10995                 }\r
10996             }\r
10997         }\r
10998         if (gameMode == EditPosition) {\r
10999             DrawPosition(FALSE, boards[0]);\r
11000         }\r
11001         break;\r
11002 \r
11003       case WhitePlay:\r
11004         SetWhiteToPlayEvent();\r
11005         break;\r
11006 \r
11007       case BlackPlay:\r
11008         SetBlackToPlayEvent();\r
11009         break;\r
11010 \r
11011       case EmptySquare:\r
11012         if (gameMode == IcsExamining) {\r
11013             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
11014             SendToICS(buf);\r
11015         } else {\r
11016             boards[0][y][x] = EmptySquare;\r
11017             DrawPosition(FALSE, boards[0]);\r
11018         }\r
11019         break;\r
11020 \r
11021       case PromotePiece:\r
11022         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
11023            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
11024             selection = (ChessSquare) (PROMOTED piece);\r
11025         } else if(piece == EmptySquare) selection = WhiteSilver;\r
11026         else selection = (ChessSquare)((int)piece - 1);\r
11027         goto defaultlabel;\r
11028 \r
11029       case DemotePiece:\r
11030         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
11031            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
11032             selection = (ChessSquare) (DEMOTED piece);\r
11033         } else if(piece == EmptySquare) selection = BlackSilver;\r
11034         else selection = (ChessSquare)((int)piece + 1);       \r
11035         goto defaultlabel;\r
11036 \r
11037       case WhiteQueen:\r
11038       case BlackQueen:\r
11039         if(gameInfo.variant == VariantShatranj ||\r
11040            gameInfo.variant == VariantXiangqi  ||\r
11041            gameInfo.variant == VariantCourier    )\r
11042             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
11043         goto defaultlabel;\r
11044 \r
11045       case WhiteKing:\r
11046       case BlackKing:\r
11047         if(gameInfo.variant == VariantXiangqi)\r
11048             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
11049         if(gameInfo.variant == VariantKnightmate)\r
11050             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
11051       default:\r
11052         defaultlabel:\r
11053         if (gameMode == IcsExamining) {\r
11054             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
11055                     PieceToChar(selection), AAA + x, ONE + y);\r
11056             SendToICS(buf);\r
11057         } else {\r
11058             boards[0][y][x] = selection;\r
11059             DrawPosition(FALSE, boards[0]);\r
11060         }\r
11061         break;\r
11062     }\r
11063 }\r
11064 \r
11065 \r
11066 void\r
11067 DropMenuEvent(selection, x, y)\r
11068      ChessSquare selection;\r
11069      int x, y;\r
11070 {\r
11071     ChessMove moveType;\r
11072 \r
11073     switch (gameMode) {\r
11074       case IcsPlayingWhite:\r
11075       case MachinePlaysBlack:\r
11076         if (!WhiteOnMove(currentMove)) {\r
11077             DisplayMoveError(_("It is Black's turn"));\r
11078             return;\r
11079         }\r
11080         moveType = WhiteDrop;\r
11081         break;\r
11082       case IcsPlayingBlack:\r
11083       case MachinePlaysWhite:\r
11084         if (WhiteOnMove(currentMove)) {\r
11085             DisplayMoveError(_("It is White's turn"));\r
11086             return;\r
11087         }\r
11088         moveType = BlackDrop;\r
11089         break;\r
11090       case EditGame:\r
11091         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
11092         break;\r
11093       default:\r
11094         return;\r
11095     }\r
11096 \r
11097     if (moveType == BlackDrop && selection < BlackPawn) {\r
11098       selection = (ChessSquare) ((int) selection\r
11099                                  + (int) BlackPawn - (int) WhitePawn);\r
11100     }\r
11101     if (boards[currentMove][y][x] != EmptySquare) {\r
11102         DisplayMoveError(_("That square is occupied"));\r
11103         return;\r
11104     }\r
11105 \r
11106     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
11107 }\r
11108 \r
11109 void\r
11110 AcceptEvent()\r
11111 {\r
11112     /* Accept a pending offer of any kind from opponent */\r
11113     \r
11114     if (appData.icsActive) {\r
11115         SendToICS(ics_prefix);\r
11116         SendToICS("accept\n");\r
11117     } else if (cmailMsgLoaded) {\r
11118         if (currentMove == cmailOldMove &&\r
11119             commentList[cmailOldMove] != NULL &&\r
11120             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11121                    "Black offers a draw" : "White offers a draw")) {\r
11122             TruncateGame();\r
11123             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11124             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11125         } else {\r
11126             DisplayError(_("There is no pending offer on this move"), 0);\r
11127             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11128         }\r
11129     } else {\r
11130         /* Not used for offers from chess program */\r
11131     }\r
11132 }\r
11133 \r
11134 void\r
11135 DeclineEvent()\r
11136 {\r
11137     /* Decline a pending offer of any kind from opponent */\r
11138     \r
11139     if (appData.icsActive) {\r
11140         SendToICS(ics_prefix);\r
11141         SendToICS("decline\n");\r
11142     } else if (cmailMsgLoaded) {\r
11143         if (currentMove == cmailOldMove &&\r
11144             commentList[cmailOldMove] != NULL &&\r
11145             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11146                    "Black offers a draw" : "White offers a draw")) {\r
11147 #ifdef NOTDEF\r
11148             AppendComment(cmailOldMove, "Draw declined");\r
11149             DisplayComment(cmailOldMove - 1, "Draw declined");\r
11150 #endif /*NOTDEF*/\r
11151         } else {\r
11152             DisplayError(_("There is no pending offer on this move"), 0);\r
11153         }\r
11154     } else {\r
11155         /* Not used for offers from chess program */\r
11156     }\r
11157 }\r
11158 \r
11159 void\r
11160 RematchEvent()\r
11161 {\r
11162     /* Issue ICS rematch command */\r
11163     if (appData.icsActive) {\r
11164         SendToICS(ics_prefix);\r
11165         SendToICS("rematch\n");\r
11166     }\r
11167 }\r
11168 \r
11169 void\r
11170 CallFlagEvent()\r
11171 {\r
11172     /* Call your opponent's flag (claim a win on time) */\r
11173     if (appData.icsActive) {\r
11174         SendToICS(ics_prefix);\r
11175         SendToICS("flag\n");\r
11176     } else {\r
11177         switch (gameMode) {\r
11178           default:\r
11179             return;\r
11180           case MachinePlaysWhite:\r
11181             if (whiteFlag) {\r
11182                 if (blackFlag)\r
11183                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11184                            GE_PLAYER);\r
11185                 else\r
11186                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
11187             } else {\r
11188                 DisplayError(_("Your opponent is not out of time"), 0);\r
11189             }\r
11190             break;\r
11191           case MachinePlaysBlack:\r
11192             if (blackFlag) {\r
11193                 if (whiteFlag)\r
11194                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11195                            GE_PLAYER);\r
11196                 else\r
11197                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
11198             } else {\r
11199                 DisplayError(_("Your opponent is not out of time"), 0);\r
11200             }\r
11201             break;\r
11202         }\r
11203     }\r
11204 }\r
11205 \r
11206 void\r
11207 DrawEvent()\r
11208 {\r
11209     /* Offer draw or accept pending draw offer from opponent */\r
11210     \r
11211     if (appData.icsActive) {\r
11212         /* Note: tournament rules require draw offers to be\r
11213            made after you make your move but before you punch\r
11214            your clock.  Currently ICS doesn't let you do that;\r
11215            instead, you immediately punch your clock after making\r
11216            a move, but you can offer a draw at any time. */\r
11217         \r
11218         SendToICS(ics_prefix);\r
11219         SendToICS("draw\n");\r
11220     } else if (cmailMsgLoaded) {\r
11221         if (currentMove == cmailOldMove &&\r
11222             commentList[cmailOldMove] != NULL &&\r
11223             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11224                    "Black offers a draw" : "White offers a draw")) {\r
11225             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11226             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11227         } else if (currentMove == cmailOldMove + 1) {\r
11228             char *offer = WhiteOnMove(cmailOldMove) ?\r
11229               "White offers a draw" : "Black offers a draw";\r
11230             AppendComment(currentMove, offer);\r
11231             DisplayComment(currentMove - 1, offer);\r
11232             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
11233         } else {\r
11234             DisplayError(_("You must make your move before offering a draw"), 0);\r
11235             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11236         }\r
11237     } else if (first.offeredDraw) {\r
11238         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
11239     } else {\r
11240         if (first.sendDrawOffers) {\r
11241             SendToProgram("draw\n", &first);\r
11242             userOfferedDraw = TRUE;\r
11243         }\r
11244     }\r
11245 }\r
11246 \r
11247 void\r
11248 AdjournEvent()\r
11249 {\r
11250     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
11251     \r
11252     if (appData.icsActive) {\r
11253         SendToICS(ics_prefix);\r
11254         SendToICS("adjourn\n");\r
11255     } else {\r
11256         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
11257     }\r
11258 }\r
11259 \r
11260 \r
11261 void\r
11262 AbortEvent()\r
11263 {\r
11264     /* Offer Abort or accept pending Abort offer from opponent */\r
11265     \r
11266     if (appData.icsActive) {\r
11267         SendToICS(ics_prefix);\r
11268         SendToICS("abort\n");\r
11269     } else {\r
11270         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
11271     }\r
11272 }\r
11273 \r
11274 void\r
11275 ResignEvent()\r
11276 {\r
11277     /* Resign.  You can do this even if it's not your turn. */\r
11278     \r
11279     if (appData.icsActive) {\r
11280         SendToICS(ics_prefix);\r
11281         SendToICS("resign\n");\r
11282     } else {\r
11283         switch (gameMode) {\r
11284           case MachinePlaysWhite:\r
11285             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11286             break;\r
11287           case MachinePlaysBlack:\r
11288             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11289             break;\r
11290           case EditGame:\r
11291             if (cmailMsgLoaded) {\r
11292                 TruncateGame();\r
11293                 if (WhiteOnMove(cmailOldMove)) {\r
11294                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11295                 } else {\r
11296                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11297                 }\r
11298                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
11299             }\r
11300             break;\r
11301           default:\r
11302             break;\r
11303         }\r
11304     }\r
11305 }\r
11306 \r
11307 \r
11308 void\r
11309 StopObservingEvent()\r
11310 {\r
11311     /* Stop observing current games */\r
11312     SendToICS(ics_prefix);\r
11313     SendToICS("unobserve\n");\r
11314 }\r
11315 \r
11316 void\r
11317 StopExaminingEvent()\r
11318 {\r
11319     /* Stop observing current game */\r
11320     SendToICS(ics_prefix);\r
11321     SendToICS("unexamine\n");\r
11322 }\r
11323 \r
11324 void\r
11325 ForwardInner(target)\r
11326      int target;\r
11327 {\r
11328     int limit;\r
11329 \r
11330     if (appData.debugMode)\r
11331         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
11332                 target, currentMove, forwardMostMove);\r
11333 \r
11334     if (gameMode == EditPosition)\r
11335       return;\r
11336 \r
11337     if (gameMode == PlayFromGameFile && !pausing)\r
11338       PauseEvent();\r
11339     \r
11340     if (gameMode == IcsExamining && pausing)\r
11341       limit = pauseExamForwardMostMove;\r
11342     else\r
11343       limit = forwardMostMove;\r
11344     \r
11345     if (target > limit) target = limit;\r
11346 \r
11347     if (target > 0 && moveList[target - 1][0]) {\r
11348         int fromX, fromY, toX, toY;\r
11349         toX = moveList[target - 1][2] - AAA;\r
11350         toY = moveList[target - 1][3] - ONE;\r
11351         if (moveList[target - 1][1] == '@') {\r
11352             if (appData.highlightLastMove) {\r
11353                 SetHighlights(-1, -1, toX, toY);\r
11354             }\r
11355         } else {\r
11356             fromX = moveList[target - 1][0] - AAA;\r
11357             fromY = moveList[target - 1][1] - ONE;\r
11358             if (target == currentMove + 1) {\r
11359                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
11360             }\r
11361             if (appData.highlightLastMove) {\r
11362                 SetHighlights(fromX, fromY, toX, toY);\r
11363             }\r
11364         }\r
11365     }\r
11366     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
11367         gameMode == Training || gameMode == PlayFromGameFile || \r
11368         gameMode == AnalyzeFile) {\r
11369         while (currentMove < target) {\r
11370             SendMoveToProgram(currentMove++, &first);\r
11371         }\r
11372     } else {\r
11373         currentMove = target;\r
11374     }\r
11375     \r
11376     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11377         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11378         blackTimeRemaining = timeRemaining[1][currentMove];\r
11379     }\r
11380     DisplayBothClocks();\r
11381     DisplayMove(currentMove - 1);\r
11382     DrawPosition(FALSE, boards[currentMove]);\r
11383     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11384     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
11385         DisplayComment(currentMove - 1, commentList[currentMove]);\r
11386     }\r
11387 }\r
11388 \r
11389 \r
11390 void\r
11391 ForwardEvent()\r
11392 {\r
11393     if (gameMode == IcsExamining && !pausing) {\r
11394         SendToICS(ics_prefix);\r
11395         SendToICS("forward\n");\r
11396     } else {\r
11397         ForwardInner(currentMove + 1);\r
11398     }\r
11399 }\r
11400 \r
11401 void\r
11402 ToEndEvent()\r
11403 {\r
11404     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11405         /* to optimze, we temporarily turn off analysis mode while we feed\r
11406          * the remaining moves to the engine. Otherwise we get analysis output\r
11407          * after each move.\r
11408          */ \r
11409         if (first.analysisSupport) {\r
11410           SendToProgram("exit\nforce\n", &first);\r
11411           first.analyzing = FALSE;\r
11412         }\r
11413     }\r
11414         \r
11415     if (gameMode == IcsExamining && !pausing) {\r
11416         SendToICS(ics_prefix);\r
11417         SendToICS("forward 999999\n");\r
11418     } else {\r
11419         ForwardInner(forwardMostMove);\r
11420     }\r
11421 \r
11422     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11423         /* we have fed all the moves, so reactivate analysis mode */\r
11424         SendToProgram("analyze\n", &first);\r
11425         first.analyzing = TRUE;\r
11426         /*first.maybeThinking = TRUE;*/\r
11427         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11428     }\r
11429 }\r
11430 \r
11431 void\r
11432 BackwardInner(target)\r
11433      int target;\r
11434 {\r
11435     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
11436 \r
11437     if (appData.debugMode)\r
11438         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
11439                 target, currentMove, forwardMostMove);\r
11440 \r
11441     if (gameMode == EditPosition) return;\r
11442     if (currentMove <= backwardMostMove) {\r
11443         ClearHighlights();\r
11444         DrawPosition(full_redraw, boards[currentMove]);\r
11445         return;\r
11446     }\r
11447     if (gameMode == PlayFromGameFile && !pausing)\r
11448       PauseEvent();\r
11449     \r
11450     if (moveList[target][0]) {\r
11451         int fromX, fromY, toX, toY;\r
11452         toX = moveList[target][2] - AAA;\r
11453         toY = moveList[target][3] - ONE;\r
11454         if (moveList[target][1] == '@') {\r
11455             if (appData.highlightLastMove) {\r
11456                 SetHighlights(-1, -1, toX, toY);\r
11457             }\r
11458         } else {\r
11459             fromX = moveList[target][0] - AAA;\r
11460             fromY = moveList[target][1] - ONE;\r
11461             if (target == currentMove - 1) {\r
11462                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
11463             }\r
11464             if (appData.highlightLastMove) {\r
11465                 SetHighlights(fromX, fromY, toX, toY);\r
11466             }\r
11467         }\r
11468     }\r
11469     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
11470         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
11471         while (currentMove > target) {\r
11472             SendToProgram("undo\n", &first);\r
11473             currentMove--;\r
11474         }\r
11475     } else {\r
11476         currentMove = target;\r
11477     }\r
11478     \r
11479     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11480         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11481         blackTimeRemaining = timeRemaining[1][currentMove];\r
11482     }\r
11483     DisplayBothClocks();\r
11484     DisplayMove(currentMove - 1);\r
11485     DrawPosition(full_redraw, boards[currentMove]);\r
11486     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11487     // [HGM] PV info: routine tests if comment empty\r
11488     DisplayComment(currentMove - 1, commentList[currentMove]);\r
11489 }\r
11490 \r
11491 void\r
11492 BackwardEvent()\r
11493 {\r
11494     if (gameMode == IcsExamining && !pausing) {\r
11495         SendToICS(ics_prefix);\r
11496         SendToICS("backward\n");\r
11497     } else {\r
11498         BackwardInner(currentMove - 1);\r
11499     }\r
11500 }\r
11501 \r
11502 void\r
11503 ToStartEvent()\r
11504 {\r
11505     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11506         /* to optimze, we temporarily turn off analysis mode while we undo\r
11507          * all the moves. Otherwise we get analysis output after each undo.\r
11508          */ \r
11509         if (first.analysisSupport) {\r
11510           SendToProgram("exit\nforce\n", &first);\r
11511           first.analyzing = FALSE;\r
11512         }\r
11513     }\r
11514 \r
11515     if (gameMode == IcsExamining && !pausing) {\r
11516         SendToICS(ics_prefix);\r
11517         SendToICS("backward 999999\n");\r
11518     } else {\r
11519         BackwardInner(backwardMostMove);\r
11520     }\r
11521 \r
11522     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11523         /* we have fed all the moves, so reactivate analysis mode */\r
11524         SendToProgram("analyze\n", &first);\r
11525         first.analyzing = TRUE;\r
11526         /*first.maybeThinking = TRUE;*/\r
11527         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11528     }\r
11529 }\r
11530 \r
11531 void\r
11532 ToNrEvent(int to)\r
11533 {\r
11534   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
11535   if (to >= forwardMostMove) to = forwardMostMove;\r
11536   if (to <= backwardMostMove) to = backwardMostMove;\r
11537   if (to < currentMove) {\r
11538     BackwardInner(to);\r
11539   } else {\r
11540     ForwardInner(to);\r
11541   }\r
11542 }\r
11543 \r
11544 void\r
11545 RevertEvent()\r
11546 {\r
11547     if (gameMode != IcsExamining) {\r
11548         DisplayError(_("You are not examining a game"), 0);\r
11549         return;\r
11550     }\r
11551     if (pausing) {\r
11552         DisplayError(_("You can't revert while pausing"), 0);\r
11553         return;\r
11554     }\r
11555     SendToICS(ics_prefix);\r
11556     SendToICS("revert\n");\r
11557 }\r
11558 \r
11559 void\r
11560 RetractMoveEvent()\r
11561 {\r
11562     switch (gameMode) {\r
11563       case MachinePlaysWhite:\r
11564       case MachinePlaysBlack:\r
11565         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
11566             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
11567             return;\r
11568         }\r
11569         if (forwardMostMove < 2) return;\r
11570         currentMove = forwardMostMove = forwardMostMove - 2;\r
11571         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11572         blackTimeRemaining = timeRemaining[1][currentMove];\r
11573         DisplayBothClocks();\r
11574         DisplayMove(currentMove - 1);\r
11575         ClearHighlights();/*!! could figure this out*/\r
11576         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
11577         SendToProgram("remove\n", &first);\r
11578         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
11579         break;\r
11580 \r
11581       case BeginningOfGame:\r
11582       default:\r
11583         break;\r
11584 \r
11585       case IcsPlayingWhite:\r
11586       case IcsPlayingBlack:\r
11587         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
11588             SendToICS(ics_prefix);\r
11589             SendToICS("takeback 2\n");\r
11590         } else {\r
11591             SendToICS(ics_prefix);\r
11592             SendToICS("takeback 1\n");\r
11593         }\r
11594         break;\r
11595     }\r
11596 }\r
11597 \r
11598 void\r
11599 MoveNowEvent()\r
11600 {\r
11601     ChessProgramState *cps;\r
11602 \r
11603     switch (gameMode) {\r
11604       case MachinePlaysWhite:\r
11605         if (!WhiteOnMove(forwardMostMove)) {\r
11606             DisplayError(_("It is your turn"), 0);\r
11607             return;\r
11608         }\r
11609         cps = &first;\r
11610         break;\r
11611       case MachinePlaysBlack:\r
11612         if (WhiteOnMove(forwardMostMove)) {\r
11613             DisplayError(_("It is your turn"), 0);\r
11614             return;\r
11615         }\r
11616         cps = &first;\r
11617         break;\r
11618       case TwoMachinesPlay:\r
11619         if (WhiteOnMove(forwardMostMove) ==\r
11620             (first.twoMachinesColor[0] == 'w')) {\r
11621             cps = &first;\r
11622         } else {\r
11623             cps = &second;\r
11624         }\r
11625         break;\r
11626       case BeginningOfGame:\r
11627       default:\r
11628         return;\r
11629     }\r
11630     SendToProgram("?\n", cps);\r
11631 }\r
11632 \r
11633 void\r
11634 TruncateGameEvent()\r
11635 {\r
11636     EditGameEvent();\r
11637     if (gameMode != EditGame) return;\r
11638     TruncateGame();\r
11639 }\r
11640 \r
11641 void\r
11642 TruncateGame()\r
11643 {\r
11644     if (forwardMostMove > currentMove) {\r
11645         if (gameInfo.resultDetails != NULL) {\r
11646             free(gameInfo.resultDetails);\r
11647             gameInfo.resultDetails = NULL;\r
11648             gameInfo.result = GameUnfinished;\r
11649         }\r
11650         forwardMostMove = currentMove;\r
11651         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
11652                    currentMove-1);\r
11653     }\r
11654 }\r
11655 \r
11656 void\r
11657 HintEvent()\r
11658 {\r
11659     if (appData.noChessProgram) return;\r
11660     switch (gameMode) {\r
11661       case MachinePlaysWhite:\r
11662         if (WhiteOnMove(forwardMostMove)) {\r
11663             DisplayError(_("Wait until your turn"), 0);\r
11664             return;\r
11665         }\r
11666         break;\r
11667       case BeginningOfGame:\r
11668       case MachinePlaysBlack:\r
11669         if (!WhiteOnMove(forwardMostMove)) {\r
11670             DisplayError(_("Wait until your turn"), 0);\r
11671             return;\r
11672         }\r
11673         break;\r
11674       default:\r
11675         DisplayError(_("No hint available"), 0);\r
11676         return;\r
11677     }\r
11678     SendToProgram("hint\n", &first);\r
11679     hintRequested = TRUE;\r
11680 }\r
11681 \r
11682 void\r
11683 BookEvent()\r
11684 {\r
11685     if (appData.noChessProgram) return;\r
11686     switch (gameMode) {\r
11687       case MachinePlaysWhite:\r
11688         if (WhiteOnMove(forwardMostMove)) {\r
11689             DisplayError(_("Wait until your turn"), 0);\r
11690             return;\r
11691         }\r
11692         break;\r
11693       case BeginningOfGame:\r
11694       case MachinePlaysBlack:\r
11695         if (!WhiteOnMove(forwardMostMove)) {\r
11696             DisplayError(_("Wait until your turn"), 0);\r
11697             return;\r
11698         }\r
11699         break;\r
11700       case EditPosition:\r
11701         EditPositionDone();\r
11702         break;\r
11703       case TwoMachinesPlay:\r
11704         return;\r
11705       default:\r
11706         break;\r
11707     }\r
11708     SendToProgram("bk\n", &first);\r
11709     bookOutput[0] = NULLCHAR;\r
11710     bookRequested = TRUE;\r
11711 }\r
11712 \r
11713 void\r
11714 AboutGameEvent()\r
11715 {\r
11716     char *tags = PGNTags(&gameInfo);\r
11717     TagsPopUp(tags, CmailMsg());\r
11718     free(tags);\r
11719 }\r
11720 \r
11721 /* end button procedures */\r
11722 \r
11723 void\r
11724 PrintPosition(fp, move)\r
11725      FILE *fp;\r
11726      int move;\r
11727 {\r
11728     int i, j;\r
11729     \r
11730     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11731         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11732             char c = PieceToChar(boards[move][i][j]);\r
11733             fputc(c == 'x' ? '.' : c, fp);\r
11734             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
11735         }\r
11736     }\r
11737     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
11738       fprintf(fp, "white to play\n");\r
11739     else\r
11740       fprintf(fp, "black to play\n");\r
11741 }\r
11742 \r
11743 void\r
11744 PrintOpponents(fp)\r
11745      FILE *fp;\r
11746 {\r
11747     if (gameInfo.white != NULL) {\r
11748         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
11749     } else {\r
11750         fprintf(fp, "\n");\r
11751     }\r
11752 }\r
11753 \r
11754 /* Find last component of program's own name, using some heuristics */\r
11755 void\r
11756 TidyProgramName(prog, host, buf)\r
11757      char *prog, *host, buf[MSG_SIZ];\r
11758 {\r
11759     char *p, *q;\r
11760     int local = (strcmp(host, "localhost") == 0);\r
11761     while (!local && (p = strchr(prog, ';')) != NULL) {\r
11762         p++;\r
11763         while (*p == ' ') p++;\r
11764         prog = p;\r
11765     }\r
11766     if (*prog == '"' || *prog == '\'') {\r
11767         q = strchr(prog + 1, *prog);\r
11768     } else {\r
11769         q = strchr(prog, ' ');\r
11770     }\r
11771     if (q == NULL) q = prog + strlen(prog);\r
11772     p = q;\r
11773     while (p >= prog && *p != '/' && *p != '\\') p--;\r
11774     p++;\r
11775     if(p == prog && *p == '"') p++;\r
11776     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
11777     memcpy(buf, p, q - p);\r
11778     buf[q - p] = NULLCHAR;\r
11779     if (!local) {\r
11780         strcat(buf, "@");\r
11781         strcat(buf, host);\r
11782     }\r
11783 }\r
11784 \r
11785 char *\r
11786 TimeControlTagValue()\r
11787 {\r
11788     char buf[MSG_SIZ];\r
11789     if (!appData.clockMode) {\r
11790         strcpy(buf, "-");\r
11791     } else if (movesPerSession > 0) {\r
11792         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
11793     } else if (timeIncrement == 0) {\r
11794         sprintf(buf, "%ld", timeControl/1000);\r
11795     } else {\r
11796         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
11797     }\r
11798     return StrSave(buf);\r
11799 }\r
11800 \r
11801 void\r
11802 SetGameInfo()\r
11803 {\r
11804     /* This routine is used only for certain modes */\r
11805     VariantClass v = gameInfo.variant;\r
11806     ClearGameInfo(&gameInfo);\r
11807     gameInfo.variant = v;\r
11808 \r
11809     switch (gameMode) {\r
11810       case MachinePlaysWhite:\r
11811         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11812         gameInfo.site = StrSave(HostName());\r
11813         gameInfo.date = PGNDate();\r
11814         gameInfo.round = StrSave("-");\r
11815         gameInfo.white = StrSave(first.tidy);\r
11816         gameInfo.black = StrSave(UserName());\r
11817         gameInfo.timeControl = TimeControlTagValue();\r
11818         break;\r
11819 \r
11820       case MachinePlaysBlack:\r
11821         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11822         gameInfo.site = StrSave(HostName());\r
11823         gameInfo.date = PGNDate();\r
11824         gameInfo.round = StrSave("-");\r
11825         gameInfo.white = StrSave(UserName());\r
11826         gameInfo.black = StrSave(first.tidy);\r
11827         gameInfo.timeControl = TimeControlTagValue();\r
11828         break;\r
11829 \r
11830       case TwoMachinesPlay:\r
11831         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11832         gameInfo.site = StrSave(HostName());\r
11833         gameInfo.date = PGNDate();\r
11834         if (matchGame > 0) {\r
11835             char buf[MSG_SIZ];\r
11836             sprintf(buf, "%d", matchGame);\r
11837             gameInfo.round = StrSave(buf);\r
11838         } else {\r
11839             gameInfo.round = StrSave("-");\r
11840         }\r
11841         if (first.twoMachinesColor[0] == 'w') {\r
11842             gameInfo.white = StrSave(first.tidy);\r
11843             gameInfo.black = StrSave(second.tidy);\r
11844         } else {\r
11845             gameInfo.white = StrSave(second.tidy);\r
11846             gameInfo.black = StrSave(first.tidy);\r
11847         }\r
11848         gameInfo.timeControl = TimeControlTagValue();\r
11849         break;\r
11850 \r
11851       case EditGame:\r
11852         gameInfo.event = StrSave("Edited game");\r
11853         gameInfo.site = StrSave(HostName());\r
11854         gameInfo.date = PGNDate();\r
11855         gameInfo.round = StrSave("-");\r
11856         gameInfo.white = StrSave("-");\r
11857         gameInfo.black = StrSave("-");\r
11858         break;\r
11859 \r
11860       case EditPosition:\r
11861         gameInfo.event = StrSave("Edited position");\r
11862         gameInfo.site = StrSave(HostName());\r
11863         gameInfo.date = PGNDate();\r
11864         gameInfo.round = StrSave("-");\r
11865         gameInfo.white = StrSave("-");\r
11866         gameInfo.black = StrSave("-");\r
11867         break;\r
11868 \r
11869       case IcsPlayingWhite:\r
11870       case IcsPlayingBlack:\r
11871       case IcsObserving:\r
11872       case IcsExamining:\r
11873         break;\r
11874 \r
11875       case PlayFromGameFile:\r
11876         gameInfo.event = StrSave("Game from non-PGN file");\r
11877         gameInfo.site = StrSave(HostName());\r
11878         gameInfo.date = PGNDate();\r
11879         gameInfo.round = StrSave("-");\r
11880         gameInfo.white = StrSave("?");\r
11881         gameInfo.black = StrSave("?");\r
11882         break;\r
11883 \r
11884       default:\r
11885         break;\r
11886     }\r
11887 }\r
11888 \r
11889 void\r
11890 ReplaceComment(index, text)\r
11891      int index;\r
11892      char *text;\r
11893 {\r
11894     int len;\r
11895 \r
11896     while (*text == '\n') text++;\r
11897     len = strlen(text);\r
11898     while (len > 0 && text[len - 1] == '\n') len--;\r
11899 \r
11900     if (commentList[index] != NULL)\r
11901       free(commentList[index]);\r
11902 \r
11903     if (len == 0) {\r
11904         commentList[index] = NULL;\r
11905         return;\r
11906     }\r
11907     commentList[index] = (char *) malloc(len + 2);\r
11908     strncpy(commentList[index], text, len);\r
11909     commentList[index][len] = '\n';\r
11910     commentList[index][len + 1] = NULLCHAR;\r
11911 }\r
11912 \r
11913 void\r
11914 CrushCRs(text)\r
11915      char *text;\r
11916 {\r
11917   char *p = text;\r
11918   char *q = text;\r
11919   char ch;\r
11920 \r
11921   do {\r
11922     ch = *p++;\r
11923     if (ch == '\r') continue;\r
11924     *q++ = ch;\r
11925   } while (ch != '\0');\r
11926 }\r
11927 \r
11928 void\r
11929 AppendComment(index, text)\r
11930      int index;\r
11931      char *text;\r
11932 {\r
11933     int oldlen, len;\r
11934     char *old;\r
11935 \r
11936     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11937 \r
11938     CrushCRs(text);\r
11939     while (*text == '\n') text++;\r
11940     len = strlen(text);\r
11941     while (len > 0 && text[len - 1] == '\n') len--;\r
11942 \r
11943     if (len == 0) return;\r
11944 \r
11945     if (commentList[index] != NULL) {\r
11946         old = commentList[index];\r
11947         oldlen = strlen(old);\r
11948         commentList[index] = (char *) malloc(oldlen + len + 2);\r
11949         strcpy(commentList[index], old);\r
11950         free(old);\r
11951         strncpy(&commentList[index][oldlen], text, len);\r
11952         commentList[index][oldlen + len] = '\n';\r
11953         commentList[index][oldlen + len + 1] = NULLCHAR;\r
11954     } else {\r
11955         commentList[index] = (char *) malloc(len + 2);\r
11956         strncpy(commentList[index], text, len);\r
11957         commentList[index][len] = '\n';\r
11958         commentList[index][len + 1] = NULLCHAR;\r
11959     }\r
11960 }\r
11961 \r
11962 static char * FindStr( char * text, char * sub_text )\r
11963 {\r
11964     char * result = strstr( text, sub_text );\r
11965 \r
11966     if( result != NULL ) {\r
11967         result += strlen( sub_text );\r
11968     }\r
11969 \r
11970     return result;\r
11971 }\r
11972 \r
11973 /* [AS] Try to extract PV info from PGN comment */\r
11974 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
11975 char *GetInfoFromComment( int index, char * text )\r
11976 {\r
11977     char * sep = text;\r
11978 \r
11979     if( text != NULL && index > 0 ) {\r
11980         int score = 0;\r
11981         int depth = 0;\r
11982         int time = -1, sec = 0, deci;\r
11983         char * s_eval = FindStr( text, "[%eval " );\r
11984         char * s_emt = FindStr( text, "[%emt " );\r
11985 \r
11986         if( s_eval != NULL || s_emt != NULL ) {\r
11987             /* New style */\r
11988             char delim;\r
11989 \r
11990             if( s_eval != NULL ) {\r
11991                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
11992                     return text;\r
11993                 }\r
11994 \r
11995                 if( delim != ']' ) {\r
11996                     return text;\r
11997                 }\r
11998             }\r
11999 \r
12000             if( s_emt != NULL ) {\r
12001             }\r
12002         }\r
12003         else {\r
12004             /* We expect something like: [+|-]nnn.nn/dd */\r
12005             int score_lo = 0;\r
12006 \r
12007             sep = strchr( text, '/' );\r
12008             if( sep == NULL || sep < (text+4) ) {\r
12009                 return text;\r
12010             }\r
12011 \r
12012             time = -1; sec = -1; deci = -1;\r
12013             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
12014                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&\r
12015                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
12016                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
12017                 return text;\r
12018             }\r
12019 \r
12020             if( score_lo < 0 || score_lo >= 100 ) {\r
12021                 return text;\r
12022             }\r
12023 \r
12024             if(sec >= 0) time = 600*time + 10*sec; else\r
12025             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec\r
12026 \r
12027             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
12028 \r
12029             /* [HGM] PV time: now locate end of PV info */\r
12030             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
12031             if(time >= 0)\r
12032             while( *++sep >= '0' && *sep <= '9'); // strip time\r
12033             if(sec >= 0)\r
12034             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
12035             if(deci >= 0)\r
12036             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds\r
12037             while(*sep == ' ') sep++;\r
12038         }\r
12039 \r
12040         if( depth <= 0 ) {\r
12041             return text;\r
12042         }\r
12043 \r
12044         if( time < 0 ) {\r
12045             time = -1;\r
12046         }\r
12047 \r
12048         pvInfoList[index-1].depth = depth;\r
12049         pvInfoList[index-1].score = score;\r
12050         pvInfoList[index-1].time  = 10*time; // centi-sec\r
12051     }\r
12052     return sep;\r
12053 }\r
12054 \r
12055 void\r
12056 SendToProgram(message, cps)\r
12057      char *message;\r
12058      ChessProgramState *cps;\r
12059 {\r
12060     int count, outCount, error;\r
12061     char buf[MSG_SIZ];\r
12062 \r
12063     if (cps->pr == NULL) return;\r
12064     Attention(cps);\r
12065     \r
12066     if (appData.debugMode) {\r
12067         TimeMark now;\r
12068         GetTimeMark(&now);\r
12069         fprintf(debugFP, "%ld >%-6s: %s", \r
12070                 SubtractTimeMarks(&now, &programStartTime),\r
12071                 cps->which, message);\r
12072     }\r
12073     \r
12074     count = strlen(message);\r
12075     outCount = OutputToProcess(cps->pr, message, count, &error);\r
12076     if (outCount < count && !exiting \r
12077                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
12078         sprintf(buf, _("Error writing to %s chess program"), cps->which);\r
12079         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12080             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12081                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12082                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
12083             } else {\r
12084                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12085             }\r
12086             gameInfo.resultDetails = buf;\r
12087         }\r
12088         DisplayFatalError(buf, error, 1);\r
12089     }\r
12090 }\r
12091 \r
12092 void\r
12093 ReceiveFromProgram(isr, closure, message, count, error)\r
12094      InputSourceRef isr;\r
12095      VOIDSTAR closure;\r
12096      char *message;\r
12097      int count;\r
12098      int error;\r
12099 {\r
12100     char *end_str;\r
12101     char buf[MSG_SIZ];\r
12102     ChessProgramState *cps = (ChessProgramState *)closure;\r
12103 \r
12104     if (isr != cps->isr) return; /* Killed intentionally */\r
12105     if (count <= 0) {\r
12106         if (count == 0) {\r
12107             sprintf(buf,\r
12108                     _("Error: %s chess program (%s) exited unexpectedly"),\r
12109                     cps->which, cps->program);\r
12110         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12111                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12112                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12113                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);\r
12114                 } else {\r
12115                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12116                 }\r
12117                 gameInfo.resultDetails = buf;\r
12118             }\r
12119             RemoveInputSource(cps->isr);\r
12120             DisplayFatalError(buf, 0, 1);\r
12121         } else {\r
12122             sprintf(buf,\r
12123                     _("Error reading from %s chess program (%s)"),\r
12124                     cps->which, cps->program);\r
12125             RemoveInputSource(cps->isr);\r
12126 \r
12127             /* [AS] Program is misbehaving badly... kill it */\r
12128             if( count == -2 ) {\r
12129                 DestroyChildProcess( cps->pr, 9 );\r
12130                 cps->pr = NoProc;\r
12131             }\r
12132 \r
12133             DisplayFatalError(buf, error, 1);\r
12134         }\r
12135         return;\r
12136     }\r
12137     \r
12138     if ((end_str = strchr(message, '\r')) != NULL)\r
12139       *end_str = NULLCHAR;\r
12140     if ((end_str = strchr(message, '\n')) != NULL)\r
12141       *end_str = NULLCHAR;\r
12142     \r
12143     if (appData.debugMode) {\r
12144         TimeMark now; int print = 1;\r
12145         char *quote = ""; char c; int i;\r
12146 \r
12147         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
12148                 char start = message[0];\r
12149                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
12150                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
12151                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&\r
12152                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
12153                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
12154                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&\r
12155                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
12156                         { quote = "# "; print = (appData.engineComments == 2); }\r
12157                 message[0] = start; // restore original message\r
12158         }\r
12159         if(print) {\r
12160                 GetTimeMark(&now);\r
12161                 fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
12162                         SubtractTimeMarks(&now, &programStartTime), cps->which, \r
12163                         quote,\r
12164                         message);\r
12165         }\r
12166     }\r
12167 \r
12168     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */\r
12169     if (appData.icsEngineAnalyze) {\r
12170         if (strstr(message, "whisper") != NULL ||\r
12171              strstr(message, "kibitz") != NULL || \r
12172             strstr(message, "tellics") != NULL) return;\r
12173     }\r
12174 \r
12175     HandleMachineMove(message, cps);\r
12176 }\r
12177 \r
12178 \r
12179 void\r
12180 SendTimeControl(cps, mps, tc, inc, sd, st)\r
12181      ChessProgramState *cps;\r
12182      int mps, inc, sd, st;\r
12183      long tc;\r
12184 {\r
12185     char buf[MSG_SIZ];\r
12186     int seconds;\r
12187 \r
12188     if( timeControl_2 > 0 ) {\r
12189         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
12190             tc = timeControl_2;\r
12191         }\r
12192     }\r
12193     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
12194     inc /= cps->timeOdds;\r
12195     st  /= cps->timeOdds;\r
12196 \r
12197     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
12198 \r
12199     if (st > 0) {\r
12200       /* Set exact time per move, normally using st command */\r
12201       if (cps->stKludge) {\r
12202         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
12203         seconds = st % 60;\r
12204         if (seconds == 0) {\r
12205           sprintf(buf, "level 1 %d\n", st/60);\r
12206         } else {\r
12207           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
12208         }\r
12209       } else {\r
12210         sprintf(buf, "st %d\n", st);\r
12211       }\r
12212     } else {\r
12213       /* Set conventional or incremental time control, using level command */\r
12214       if (seconds == 0) {\r
12215         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
12216            Fixed in later versions, but still avoid :seconds\r
12217            when seconds is 0. */\r
12218         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
12219       } else {\r
12220         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
12221                 seconds, inc/1000);\r
12222       }\r
12223     }\r
12224     SendToProgram(buf, cps);\r
12225 \r
12226     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
12227     /* Orthogonally, limit search to given depth */\r
12228     if (sd > 0) {\r
12229       if (cps->sdKludge) {\r
12230         sprintf(buf, "depth\n%d\n", sd);\r
12231       } else {\r
12232         sprintf(buf, "sd %d\n", sd);\r
12233       }\r
12234       SendToProgram(buf, cps);\r
12235     }\r
12236 \r
12237     if(cps->nps > 0) { /* [HGM] nps */\r
12238         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
12239         else {\r
12240                 sprintf(buf, "nps %d\n", cps->nps);\r
12241               SendToProgram(buf, cps);\r
12242         }\r
12243     }\r
12244 }\r
12245 \r
12246 ChessProgramState *WhitePlayer()\r
12247 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
12248 {\r
12249     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || \r
12250        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)\r
12251         return &second;\r
12252     return &first;\r
12253 }\r
12254 \r
12255 void\r
12256 SendTimeRemaining(cps, machineWhite)\r
12257      ChessProgramState *cps;\r
12258      int /*boolean*/ machineWhite;\r
12259 {\r
12260     char message[MSG_SIZ];\r
12261     long time, otime;\r
12262 \r
12263     /* Note: this routine must be called when the clocks are stopped\r
12264        or when they have *just* been set or switched; otherwise\r
12265        it will be off by the time since the current tick started.\r
12266     */\r
12267     if (machineWhite) {\r
12268         time = whiteTimeRemaining / 10;\r
12269         otime = blackTimeRemaining / 10;\r
12270     } else {\r
12271         time = blackTimeRemaining / 10;\r
12272         otime = whiteTimeRemaining / 10;\r
12273     }\r
12274     /* [HGM] translate opponent's time by time-odds factor */\r
12275     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
12276     if (appData.debugMode) {\r
12277         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
12278     }\r
12279 \r
12280     if (time <= 0) time = 1;\r
12281     if (otime <= 0) otime = 1;\r
12282     \r
12283     sprintf(message, "time %ld\n", time);\r
12284     SendToProgram(message, cps);\r
12285 \r
12286     sprintf(message, "otim %ld\n", otime);\r
12287     SendToProgram(message, cps);\r
12288 }\r
12289 \r
12290 int\r
12291 BoolFeature(p, name, loc, cps)\r
12292      char **p;\r
12293      char *name;\r
12294      int *loc;\r
12295      ChessProgramState *cps;\r
12296 {\r
12297   char buf[MSG_SIZ];\r
12298   int len = strlen(name);\r
12299   int val;\r
12300   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12301     (*p) += len + 1;\r
12302     sscanf(*p, "%d", &val);\r
12303     *loc = (val != 0);\r
12304     while (**p && **p != ' ') (*p)++;\r
12305     sprintf(buf, "accepted %s\n", name);\r
12306     SendToProgram(buf, cps);\r
12307     return TRUE;\r
12308   }\r
12309   return FALSE;\r
12310 }\r
12311 \r
12312 int\r
12313 IntFeature(p, name, loc, cps)\r
12314      char **p;\r
12315      char *name;\r
12316      int *loc;\r
12317      ChessProgramState *cps;\r
12318 {\r
12319   char buf[MSG_SIZ];\r
12320   int len = strlen(name);\r
12321   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12322     (*p) += len + 1;\r
12323     sscanf(*p, "%d", loc);\r
12324     while (**p && **p != ' ') (*p)++;\r
12325     sprintf(buf, "accepted %s\n", name);\r
12326     SendToProgram(buf, cps);\r
12327     return TRUE;\r
12328   }\r
12329   return FALSE;\r
12330 }\r
12331 \r
12332 int\r
12333 StringFeature(p, name, loc, cps)\r
12334      char **p;\r
12335      char *name;\r
12336      char loc[];\r
12337      ChessProgramState *cps;\r
12338 {\r
12339   char buf[MSG_SIZ];\r
12340   int len = strlen(name);\r
12341   if (strncmp((*p), name, len) == 0\r
12342       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
12343     (*p) += len + 2;\r
12344     sscanf(*p, "%[^\"]", loc);\r
12345     while (**p && **p != '\"') (*p)++;\r
12346     if (**p == '\"') (*p)++;\r
12347     sprintf(buf, "accepted %s\n", name);\r
12348     SendToProgram(buf, cps);\r
12349     return TRUE;\r
12350   }\r
12351   return FALSE;\r
12352 }\r
12353 \r
12354 int \r
12355 ParseOption(Option *opt, ChessProgramState *cps)\r
12356 // [HGM] options: process the string that defines an engine option, and determine\r
12357 // name, type, default value, and allowed value range\r
12358 {\r
12359         char *p, *q, buf[MSG_SIZ];\r
12360         int n, min = (-1)<<31, max = 1<<31, def;\r
12361 \r
12362         if(p = strstr(opt->name, " -spin ")) {\r
12363             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;\r
12364             if(max < min) max = min; // enforce consistency\r
12365             if(def < min) def = min;\r
12366             if(def > max) def = max;\r
12367             opt->value = def;\r
12368             opt->min = min;\r
12369             opt->max = max;\r
12370             opt->type = Spin;\r
12371         } else if(p = strstr(opt->name, " -string ")) {\r
12372             opt->textValue = p+9;\r
12373             opt->type = TextBox;\r
12374         } else if(p = strstr(opt->name, " -check ")) {\r
12375             if(sscanf(p, " -check %d", &def) < 1) return FALSE;\r
12376             opt->value = (def != 0);\r
12377             opt->type = CheckBox;\r
12378         } else if(p = strstr(opt->name, " -combo ")) {\r
12379             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type\r
12380             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices\r
12381             opt->value = n = 0;\r
12382             while(q = StrStr(q, " /// ")) {\r
12383                 n++; *q = 0;    // count choices, and null-terminate each of them\r
12384                 q += 5;\r
12385                 if(*q == '*') { // remember default, which is marked with * prefix\r
12386                     q++;\r
12387                     opt->value = n;\r
12388                 }\r
12389                 cps->comboList[cps->comboCnt++] = q;\r
12390             }\r
12391             cps->comboList[cps->comboCnt++] = NULL;\r
12392             opt->max = n + 1;\r
12393             opt->type = ComboBox;\r
12394         } else if(p = strstr(opt->name, " -button")) {\r
12395             opt->type = Button;\r
12396         } else if(p = strstr(opt->name, " -save")) {\r
12397             opt->type = SaveButton;\r
12398         } else return FALSE;\r
12399         *p = 0; // terminate option name\r
12400         // now look if the command-line options define a setting for this engine option.\r
12401         p = strstr(cps->optionSettings, opt->name);\r
12402         if(p == cps->optionSettings || p[-1] == ',') {\r
12403                 sprintf(buf, "option %s", p);\r
12404                 if(p = strstr(buf, ",")) *p = 0;\r
12405                 strcat(buf, "\n");\r
12406                 SendToProgram(buf, cps);\r
12407         }\r
12408         return TRUE;\r
12409 }\r
12410 \r
12411 void\r
12412 FeatureDone(cps, val)\r
12413      ChessProgramState* cps;\r
12414      int val;\r
12415 {\r
12416   DelayedEventCallback cb = GetDelayedEvent();\r
12417   if ((cb == InitBackEnd3 && cps == &first) ||\r
12418       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
12419     CancelDelayedEvent();\r
12420     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
12421   }\r
12422   cps->initDone = val;\r
12423 }\r
12424 \r
12425 /* Parse feature command from engine */\r
12426 void\r
12427 ParseFeatures(args, cps)\r
12428      char* args;\r
12429      ChessProgramState *cps;  \r
12430 {\r
12431   char *p = args;\r
12432   char *q;\r
12433   int val;\r
12434   char buf[MSG_SIZ];\r
12435 \r
12436   for (;;) {\r
12437     while (*p == ' ') p++;\r
12438     if (*p == NULLCHAR) return;\r
12439 \r
12440     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
12441     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
12442     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
12443     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
12444     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
12445     if (BoolFeature(&p, "reuse", &val, cps)) {\r
12446       /* Engine can disable reuse, but can't enable it if user said no */\r
12447       if (!val) cps->reuse = FALSE;\r
12448       continue;\r
12449     }\r
12450     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
12451     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
12452       if (gameMode == TwoMachinesPlay) {\r
12453         DisplayTwoMachinesTitle();\r
12454       } else {\r
12455         DisplayTitle("");\r
12456       }\r
12457       continue;\r
12458     }\r
12459     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
12460     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
12461     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
12462     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
12463     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
12464     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
12465     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
12466     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
12467     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
12468     if (IntFeature(&p, "done", &val, cps)) {\r
12469       FeatureDone(cps, val);\r
12470       continue;\r
12471     }\r
12472     /* Added by Tord: */\r
12473     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
12474     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
12475     /* End of additions by Tord */\r
12476 \r
12477     /* [HGM] added features: */\r
12478     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
12479     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
12480     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
12481     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
12482     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12483     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
12484     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {\r
12485         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature\r
12486         if(cps->nrOptions >= MAX_OPTIONS) {\r
12487             cps->nrOptions--;\r
12488             sprintf(buf, "%s engine has too many options\n", cps->which);\r
12489             DisplayError(buf, 0);\r
12490         }\r
12491         continue;\r
12492     }\r
12493     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12494     /* End of additions by HGM */\r
12495 \r
12496     /* unknown feature: complain and skip */\r
12497     q = p;\r
12498     while (*q && *q != '=') q++;\r
12499     sprintf(buf, "rejected %.*s\n", q-p, p);\r
12500     SendToProgram(buf, cps);\r
12501     p = q;\r
12502     if (*p == '=') {\r
12503       p++;\r
12504       if (*p == '\"') {\r
12505         p++;\r
12506         while (*p && *p != '\"') p++;\r
12507         if (*p == '\"') p++;\r
12508       } else {\r
12509         while (*p && *p != ' ') p++;\r
12510       }\r
12511     }\r
12512   }\r
12513 \r
12514 }\r
12515 \r
12516 void\r
12517 PeriodicUpdatesEvent(newState)\r
12518      int newState;\r
12519 {\r
12520     if (newState == appData.periodicUpdates)\r
12521       return;\r
12522 \r
12523     appData.periodicUpdates=newState;\r
12524 \r
12525     /* Display type changes, so update it now */\r
12526     DisplayAnalysis();\r
12527 \r
12528     /* Get the ball rolling again... */\r
12529     if (newState) {\r
12530         AnalysisPeriodicEvent(1);\r
12531         StartAnalysisClock();\r
12532     }\r
12533 }\r
12534 \r
12535 void\r
12536 PonderNextMoveEvent(newState)\r
12537      int newState;\r
12538 {\r
12539     if (newState == appData.ponderNextMove) return;\r
12540     if (gameMode == EditPosition) EditPositionDone();\r
12541     if (newState) {\r
12542         SendToProgram("hard\n", &first);\r
12543         if (gameMode == TwoMachinesPlay) {\r
12544             SendToProgram("hard\n", &second);\r
12545         }\r
12546     } else {\r
12547         SendToProgram("easy\n", &first);\r
12548         thinkOutput[0] = NULLCHAR;\r
12549         if (gameMode == TwoMachinesPlay) {\r
12550             SendToProgram("easy\n", &second);\r
12551         }\r
12552     }\r
12553     appData.ponderNextMove = newState;\r
12554 }\r
12555 \r
12556 void\r
12557 NewSettingEvent(option, command, value)\r
12558      char *command;\r
12559      int option, value;\r
12560 {\r
12561     char buf[MSG_SIZ];\r
12562 \r
12563     if (gameMode == EditPosition) EditPositionDone();\r
12564     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);\r
12565     SendToProgram(buf, &first);\r
12566     if (gameMode == TwoMachinesPlay) {\r
12567         SendToProgram(buf, &second);\r
12568     }\r
12569 }\r
12570 \r
12571 void\r
12572 ShowThinkingEvent()\r
12573 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup\r
12574 {\r
12575     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated\r
12576     int newState = appData.showThinking\r
12577         // [HGM] thinking: other features now need thinking output as well\r
12578         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();\r
12579     \r
12580     if (oldState == newState) return;\r
12581     oldState = newState;\r
12582     if (gameMode == EditPosition) EditPositionDone();\r
12583     if (oldState) {\r
12584         SendToProgram("post\n", &first);\r
12585         if (gameMode == TwoMachinesPlay) {\r
12586             SendToProgram("post\n", &second);\r
12587         }\r
12588     } else {\r
12589         SendToProgram("nopost\n", &first);\r
12590         thinkOutput[0] = NULLCHAR;\r
12591         if (gameMode == TwoMachinesPlay) {\r
12592             SendToProgram("nopost\n", &second);\r
12593         }\r
12594     }\r
12595 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!\r
12596 }\r
12597 \r
12598 void\r
12599 AskQuestionEvent(title, question, replyPrefix, which)\r
12600      char *title; char *question; char *replyPrefix; char *which;\r
12601 {\r
12602   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
12603   if (pr == NoProc) return;\r
12604   AskQuestion(title, question, replyPrefix, pr);\r
12605 }\r
12606 \r
12607 void\r
12608 DisplayMove(moveNumber)\r
12609      int moveNumber;\r
12610 {\r
12611     char message[MSG_SIZ];\r
12612     char res[MSG_SIZ];\r
12613     char cpThinkOutput[MSG_SIZ];\r
12614 \r
12615     if(appData.noGUI) return; // [HGM] fast: suppress display of moves\r
12616     \r
12617     if (moveNumber == forwardMostMove - 1 || \r
12618         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12619 \r
12620         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
12621 \r
12622         if (strchr(cpThinkOutput, '\n')) {\r
12623             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
12624         }\r
12625     } else {\r
12626         *cpThinkOutput = NULLCHAR;\r
12627     }\r
12628 \r
12629     /* [AS] Hide thinking from human user */\r
12630     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
12631         *cpThinkOutput = NULLCHAR;\r
12632         if( thinkOutput[0] != NULLCHAR ) {\r
12633             int i;\r
12634 \r
12635             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
12636                 cpThinkOutput[i] = '.';\r
12637             }\r
12638             cpThinkOutput[i] = NULLCHAR;\r
12639             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
12640         }\r
12641     }\r
12642 \r
12643     if (moveNumber == forwardMostMove - 1 &&\r
12644         gameInfo.resultDetails != NULL) {\r
12645         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
12646             sprintf(res, " %s", PGNResult(gameInfo.result));\r
12647         } else {\r
12648             sprintf(res, " {%s} %s",\r
12649                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
12650         }\r
12651     } else {\r
12652         res[0] = NULLCHAR;\r
12653     }\r
12654 \r
12655     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12656         DisplayMessage(res, cpThinkOutput);\r
12657     } else {\r
12658         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
12659                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
12660                 parseList[moveNumber], res);\r
12661         DisplayMessage(message, cpThinkOutput);\r
12662     }\r
12663 }\r
12664 \r
12665 void\r
12666 DisplayAnalysisText(text)\r
12667      char *text;\r
12668 {\r
12669     char buf[MSG_SIZ];\r
12670 \r
12671     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile \r
12672                || appData.icsEngineAnalyze) {\r
12673         sprintf(buf, "Analysis (%s)", first.tidy);\r
12674         AnalysisPopUp(buf, text);\r
12675     }\r
12676 }\r
12677 \r
12678 static int\r
12679 only_one_move(str)\r
12680      char *str;\r
12681 {\r
12682     while (*str && isspace(*str)) ++str;\r
12683     while (*str && !isspace(*str)) ++str;\r
12684     if (!*str) return 1;\r
12685     while (*str && isspace(*str)) ++str;\r
12686     if (!*str) return 1;\r
12687     return 0;\r
12688 }\r
12689 \r
12690 void\r
12691 DisplayAnalysis()\r
12692 {\r
12693     char buf[MSG_SIZ];\r
12694     char lst[MSG_SIZ / 2];\r
12695     double nps;\r
12696     static char *xtra[] = { "", " (--)", " (++)" };\r
12697     int h, m, s, cs;\r
12698   \r
12699     if (programStats.time == 0) {\r
12700         programStats.time = 1;\r
12701     }\r
12702   \r
12703     if (programStats.got_only_move) {\r
12704         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
12705     } else {\r
12706         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
12707 \r
12708         nps = (u64ToDouble(programStats.nodes) /\r
12709              ((double)programStats.time /100.0));\r
12710 \r
12711         cs = programStats.time % 100;\r
12712         s = programStats.time / 100;\r
12713         h = (s / (60*60));\r
12714         s = s - h*60*60;\r
12715         m = (s/60);\r
12716         s = s - m*60;\r
12717 \r
12718         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
12719           if (programStats.move_name[0] != NULLCHAR) {\r
12720             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12721                     programStats.depth,\r
12722                     programStats.nr_moves-programStats.moves_left,\r
12723                     programStats.nr_moves, programStats.move_name,\r
12724                     ((float)programStats.score)/100.0, lst,\r
12725                     only_one_move(lst)?\r
12726                     xtra[programStats.got_fail] : "",\r
12727                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12728           } else {\r
12729             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12730                     programStats.depth,\r
12731                     programStats.nr_moves-programStats.moves_left,\r
12732                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
12733                     lst,\r
12734                     only_one_move(lst)?\r
12735                     xtra[programStats.got_fail] : "",\r
12736                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12737           }\r
12738         } else {\r
12739             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12740                     programStats.depth,\r
12741                     ((float)programStats.score)/100.0,\r
12742                     lst,\r
12743                     only_one_move(lst)?\r
12744                     xtra[programStats.got_fail] : "",\r
12745                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12746         }\r
12747     }\r
12748     DisplayAnalysisText(buf);\r
12749 }\r
12750 \r
12751 void\r
12752 DisplayComment(moveNumber, text)\r
12753      int moveNumber;\r
12754      char *text;\r
12755 {\r
12756     char title[MSG_SIZ];\r
12757     char buf[8000]; // comment can be long!\r
12758     int score, depth;\r
12759 \r
12760     if( appData.autoDisplayComment ) {\r
12761         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12762             strcpy(title, "Comment");\r
12763         } else {\r
12764             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
12765                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
12766                     parseList[moveNumber]);\r
12767         }\r
12768     } else title[0] = 0;\r
12769 \r
12770     // [HGM] PV info: display PV info together with (or as) comment\r
12771     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
12772         if(text == NULL) text = "";                                           \r
12773         score = pvInfoList[moveNumber].score;\r
12774         sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
12775                               depth, (pvInfoList[moveNumber].time+50)/100, text);\r
12776         CommentPopUp(title, buf);\r
12777     } else\r
12778     if (text != NULL)\r
12779         CommentPopUp(title, text);\r
12780 }\r
12781 \r
12782 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
12783  * might be busy thinking or pondering.  It can be omitted if your\r
12784  * gnuchess is configured to stop thinking immediately on any user\r
12785  * input.  However, that gnuchess feature depends on the FIONREAD\r
12786  * ioctl, which does not work properly on some flavors of Unix.\r
12787  */\r
12788 void\r
12789 Attention(cps)\r
12790      ChessProgramState *cps;\r
12791 {\r
12792 #if ATTENTION\r
12793     if (!cps->useSigint) return;\r
12794     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
12795     switch (gameMode) {\r
12796       case MachinePlaysWhite:\r
12797       case MachinePlaysBlack:\r
12798       case TwoMachinesPlay:\r
12799       case IcsPlayingWhite:\r
12800       case IcsPlayingBlack:\r
12801       case AnalyzeMode:\r
12802       case AnalyzeFile:\r
12803         /* Skip if we know it isn't thinking */\r
12804         if (!cps->maybeThinking) return;\r
12805         if (appData.debugMode)\r
12806           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
12807         InterruptChildProcess(cps->pr);\r
12808         cps->maybeThinking = FALSE;\r
12809         break;\r
12810       default:\r
12811         break;\r
12812     }\r
12813 #endif /*ATTENTION*/\r
12814 }\r
12815 \r
12816 int\r
12817 CheckFlags()\r
12818 {\r
12819     if (whiteTimeRemaining <= 0) {\r
12820         if (!whiteFlag) {\r
12821             whiteFlag = TRUE;\r
12822             if (appData.icsActive) {\r
12823                 if (appData.autoCallFlag &&\r
12824                     gameMode == IcsPlayingBlack && !blackFlag) {\r
12825                   SendToICS(ics_prefix);\r
12826                   SendToICS("flag\n");\r
12827                 }\r
12828             } else {\r
12829                 if (blackFlag) {\r
12830                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12831                 } else {\r
12832                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));\r
12833                     if (appData.autoCallFlag) {\r
12834                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
12835                         return TRUE;\r
12836                     }\r
12837                 }\r
12838             }\r
12839         }\r
12840     }\r
12841     if (blackTimeRemaining <= 0) {\r
12842         if (!blackFlag) {\r
12843             blackFlag = TRUE;\r
12844             if (appData.icsActive) {\r
12845                 if (appData.autoCallFlag &&\r
12846                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
12847                   SendToICS(ics_prefix);\r
12848                   SendToICS("flag\n");\r
12849                 }\r
12850             } else {\r
12851                 if (whiteFlag) {\r
12852                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12853                 } else {\r
12854                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));\r
12855                     if (appData.autoCallFlag) {\r
12856                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
12857                         return TRUE;\r
12858                     }\r
12859                 }\r
12860             }\r
12861         }\r
12862     }\r
12863     return FALSE;\r
12864 }\r
12865 \r
12866 void\r
12867 CheckTimeControl()\r
12868 {\r
12869     if (!appData.clockMode || appData.icsActive ||\r
12870         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
12871 \r
12872     /*\r
12873      * add time to clocks when time control is achieved ([HGM] now also used for increment)\r
12874      */\r
12875     if ( !WhiteOnMove(forwardMostMove) )\r
12876         /* White made time control */\r
12877         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12878         /* [HGM] time odds: correct new time quota for time odds! */\r
12879                                             / WhitePlayer()->timeOdds;\r
12880       else\r
12881         /* Black made time control */\r
12882         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12883                                             / WhitePlayer()->other->timeOdds;\r
12884 }\r
12885 \r
12886 void\r
12887 DisplayBothClocks()\r
12888 {\r
12889     int wom = gameMode == EditPosition ?\r
12890       !blackPlaysFirst : WhiteOnMove(currentMove);\r
12891     DisplayWhiteClock(whiteTimeRemaining, wom);\r
12892     DisplayBlackClock(blackTimeRemaining, !wom);\r
12893 }\r
12894 \r
12895 \r
12896 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
12897    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
12898    to use other calls if you don't.  Clocks will be less accurate if\r
12899    you have neither ftime nor gettimeofday.\r
12900 */\r
12901 \r
12902 /* VS 2008 requires the #include outside of the function */\r
12903 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME\r
12904 #include <sys/timeb.h>\r
12905 #endif\r
12906 \r
12907 /* Get the current time as a TimeMark */\r
12908 void\r
12909 GetTimeMark(tm)\r
12910      TimeMark *tm;\r
12911 {\r
12912 #if HAVE_GETTIMEOFDAY\r
12913 \r
12914     struct timeval timeVal;\r
12915     struct timezone timeZone;\r
12916 \r
12917     gettimeofday(&timeVal, &timeZone);\r
12918     tm->sec = (long) timeVal.tv_sec; \r
12919     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
12920 \r
12921 #else /*!HAVE_GETTIMEOFDAY*/\r
12922 #if HAVE_FTIME\r
12923 \r
12924 #include <sys/timeb.h>\r
12925     struct timeb timeB;\r
12926 \r
12927     ftime(&timeB);\r
12928     tm->sec = (long) timeB.time;\r
12929     tm->ms = (int) timeB.millitm;\r
12930 \r
12931 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
12932     tm->sec = (long) time(NULL);\r
12933     tm->ms = 0;\r
12934 #endif\r
12935 #endif\r
12936 }\r
12937 \r
12938 /* Return the difference in milliseconds between two\r
12939    time marks.  We assume the difference will fit in a long!\r
12940 */\r
12941 long\r
12942 SubtractTimeMarks(tm2, tm1)\r
12943      TimeMark *tm2, *tm1;\r
12944 {\r
12945     return 1000L*(tm2->sec - tm1->sec) +\r
12946            (long) (tm2->ms - tm1->ms);\r
12947 }\r
12948 \r
12949 \r
12950 /*\r
12951  * Code to manage the game clocks.\r
12952  *\r
12953  * In tournament play, black starts the clock and then white makes a move.\r
12954  * We give the human user a slight advantage if he is playing white---the\r
12955  * clocks don't run until he makes his first move, so it takes zero time.\r
12956  * Also, we don't account for network lag, so we could get out of sync\r
12957  * with GNU Chess's clock -- but then, referees are always right.  \r
12958  */\r
12959 \r
12960 static TimeMark tickStartTM;\r
12961 static long intendedTickLength;\r
12962 \r
12963 long\r
12964 NextTickLength(timeRemaining)\r
12965      long timeRemaining;\r
12966 {\r
12967     long nominalTickLength, nextTickLength;\r
12968 \r
12969     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
12970       nominalTickLength = 100L;\r
12971     else\r
12972       nominalTickLength = 1000L;\r
12973     nextTickLength = timeRemaining % nominalTickLength;\r
12974     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
12975 \r
12976     return nextTickLength;\r
12977 }\r
12978 \r
12979 /* Adjust clock one minute up or down */\r
12980 void\r
12981 AdjustClock(Boolean which, int dir)\r
12982 {\r
12983     if(which) blackTimeRemaining += 60000*dir;\r
12984     else      whiteTimeRemaining += 60000*dir;\r
12985     DisplayBothClocks();\r
12986 }\r
12987 \r
12988 /* Stop clocks and reset to a fresh time control */\r
12989 void\r
12990 ResetClocks() \r
12991 {\r
12992     (void) StopClockTimer();\r
12993     if (appData.icsActive) {\r
12994         whiteTimeRemaining = blackTimeRemaining = 0;\r
12995     } else { /* [HGM] correct new time quote for time odds */\r
12996         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
12997         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
12998     }\r
12999     if (whiteFlag || blackFlag) {\r
13000         DisplayTitle("");\r
13001         whiteFlag = blackFlag = FALSE;\r
13002     }\r
13003     DisplayBothClocks();\r
13004 }\r
13005 \r
13006 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
13007 \r
13008 /* Decrement running clock by amount of time that has passed */\r
13009 void\r
13010 DecrementClocks()\r
13011 {\r
13012     long timeRemaining;\r
13013     long lastTickLength, fudge;\r
13014     TimeMark now;\r
13015 \r
13016     if (!appData.clockMode) return;\r
13017     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
13018         \r
13019     GetTimeMark(&now);\r
13020 \r
13021     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13022 \r
13023     /* Fudge if we woke up a little too soon */\r
13024     fudge = intendedTickLength - lastTickLength;\r
13025     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
13026 \r
13027     if (WhiteOnMove(forwardMostMove)) {\r
13028         if(whiteNPS >= 0) lastTickLength = 0;\r
13029         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
13030         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
13031                           WhiteOnMove(currentMove));\r
13032     } else {\r
13033         if(blackNPS >= 0) lastTickLength = 0;\r
13034         timeRemaining = blackTimeRemaining -= lastTickLength;\r
13035         DisplayBlackClock(blackTimeRemaining - fudge,\r
13036                           !WhiteOnMove(currentMove));\r
13037     }\r
13038 \r
13039     if (CheckFlags()) return;\r
13040         \r
13041     tickStartTM = now;\r
13042     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
13043     StartClockTimer(intendedTickLength);\r
13044 \r
13045     /* if the time remaining has fallen below the alarm threshold, sound the\r
13046      * alarm. if the alarm has sounded and (due to a takeback or time control\r
13047      * with increment) the time remaining has increased to a level above the\r
13048      * threshold, reset the alarm so it can sound again. \r
13049      */\r
13050     \r
13051     if (appData.icsActive && appData.icsAlarm) {\r
13052 \r
13053         /* make sure we are dealing with the user's clock */\r
13054         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
13055                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
13056            )) return;\r
13057 \r
13058         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
13059             alarmSounded = FALSE;\r
13060         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
13061             PlayAlarmSound();\r
13062             alarmSounded = TRUE;\r
13063         }\r
13064     }\r
13065 }\r
13066 \r
13067 \r
13068 /* A player has just moved, so stop the previously running\r
13069    clock and (if in clock mode) start the other one.\r
13070    We redisplay both clocks in case we're in ICS mode, because\r
13071    ICS gives us an update to both clocks after every move.\r
13072    Note that this routine is called *after* forwardMostMove\r
13073    is updated, so the last fractional tick must be subtracted\r
13074    from the color that is *not* on move now.\r
13075 */\r
13076 void\r
13077 SwitchClocks()\r
13078 {\r
13079     long lastTickLength;\r
13080     TimeMark now;\r
13081     int flagged = FALSE;\r
13082 \r
13083     GetTimeMark(&now);\r
13084 \r
13085     if (StopClockTimer() && appData.clockMode) {\r
13086         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13087         if (WhiteOnMove(forwardMostMove)) {\r
13088             if(blackNPS >= 0) lastTickLength = 0;\r
13089             blackTimeRemaining -= lastTickLength;\r
13090            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13091 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13092                  pvInfoList[forwardMostMove-1].time =               // use GUI time\r
13093                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
13094         } else {\r
13095            if(whiteNPS >= 0) lastTickLength = 0;\r
13096            whiteTimeRemaining -= lastTickLength;\r
13097            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13098 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13099                  pvInfoList[forwardMostMove-1].time = \r
13100                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
13101         }\r
13102         flagged = CheckFlags();\r
13103     }\r
13104     CheckTimeControl();\r
13105 \r
13106     if (flagged || !appData.clockMode) return;\r
13107 \r
13108     switch (gameMode) {\r
13109       case MachinePlaysBlack:\r
13110       case MachinePlaysWhite:\r
13111       case BeginningOfGame:\r
13112         if (pausing) return;\r
13113         break;\r
13114 \r
13115       case EditGame:\r
13116       case PlayFromGameFile:\r
13117       case IcsExamining:\r
13118         return;\r
13119 \r
13120       default:\r
13121         break;\r
13122     }\r
13123 \r
13124     tickStartTM = now;\r
13125     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13126       whiteTimeRemaining : blackTimeRemaining);\r
13127     StartClockTimer(intendedTickLength);\r
13128 }\r
13129         \r
13130 \r
13131 /* Stop both clocks */\r
13132 void\r
13133 StopClocks()\r
13134 {       \r
13135     long lastTickLength;\r
13136     TimeMark now;\r
13137 \r
13138     if (!StopClockTimer()) return;\r
13139     if (!appData.clockMode) return;\r
13140 \r
13141     GetTimeMark(&now);\r
13142 \r
13143     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13144     if (WhiteOnMove(forwardMostMove)) {\r
13145         if(whiteNPS >= 0) lastTickLength = 0;\r
13146         whiteTimeRemaining -= lastTickLength;\r
13147         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
13148     } else {\r
13149         if(blackNPS >= 0) lastTickLength = 0;\r
13150         blackTimeRemaining -= lastTickLength;\r
13151         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
13152     }\r
13153     CheckFlags();\r
13154 }\r
13155         \r
13156 /* Start clock of player on move.  Time may have been reset, so\r
13157    if clock is already running, stop and restart it. */\r
13158 void\r
13159 StartClocks()\r
13160 {\r
13161     (void) StopClockTimer(); /* in case it was running already */\r
13162     DisplayBothClocks();\r
13163     if (CheckFlags()) return;\r
13164 \r
13165     if (!appData.clockMode) return;\r
13166     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
13167 \r
13168     GetTimeMark(&tickStartTM);\r
13169     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13170       whiteTimeRemaining : blackTimeRemaining);\r
13171 \r
13172    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
13173     whiteNPS = blackNPS = -1; \r
13174     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
13175        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
13176         whiteNPS = first.nps;\r
13177     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
13178        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
13179         blackNPS = first.nps;\r
13180     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
13181         whiteNPS = second.nps;\r
13182     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
13183         blackNPS = second.nps;\r
13184     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
13185 \r
13186     StartClockTimer(intendedTickLength);\r
13187 }\r
13188 \r
13189 char *\r
13190 TimeString(ms)\r
13191      long ms;\r
13192 {\r
13193     long second, minute, hour, day;\r
13194     char *sign = "";\r
13195     static char buf[32];\r
13196     \r
13197     if (ms > 0 && ms <= 9900) {\r
13198       /* convert milliseconds to tenths, rounding up */\r
13199       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
13200 \r
13201       sprintf(buf, " %03.1f ", tenths/10.0);\r
13202       return buf;\r
13203     }\r
13204 \r
13205     /* convert milliseconds to seconds, rounding up */\r
13206     /* use floating point to avoid strangeness of integer division\r
13207        with negative dividends on many machines */\r
13208     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
13209 \r
13210     if (second < 0) {\r
13211         sign = "-";\r
13212         second = -second;\r
13213     }\r
13214     \r
13215     day = second / (60 * 60 * 24);\r
13216     second = second % (60 * 60 * 24);\r
13217     hour = second / (60 * 60);\r
13218     second = second % (60 * 60);\r
13219     minute = second / 60;\r
13220     second = second % 60;\r
13221     \r
13222     if (day > 0)\r
13223       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
13224               sign, day, hour, minute, second);\r
13225     else if (hour > 0)\r
13226       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
13227     else\r
13228       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
13229     \r
13230     return buf;\r
13231 }\r
13232 \r
13233 \r
13234 /*\r
13235  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
13236  */\r
13237 char *\r
13238 StrStr(string, match)\r
13239      char *string, *match;\r
13240 {\r
13241     int i, length;\r
13242     \r
13243     length = strlen(match);\r
13244     \r
13245     for (i = strlen(string) - length; i >= 0; i--, string++)\r
13246       if (!strncmp(match, string, length))\r
13247         return string;\r
13248     \r
13249     return NULL;\r
13250 }\r
13251 \r
13252 char *\r
13253 StrCaseStr(string, match)\r
13254      char *string, *match;\r
13255 {\r
13256     int i, j, length;\r
13257     \r
13258     length = strlen(match);\r
13259     \r
13260     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
13261         for (j = 0; j < length; j++) {\r
13262             if (ToLower(match[j]) != ToLower(string[j]))\r
13263               break;\r
13264         }\r
13265         if (j == length) return string;\r
13266     }\r
13267 \r
13268     return NULL;\r
13269 }\r
13270 \r
13271 #ifndef _amigados\r
13272 int\r
13273 StrCaseCmp(s1, s2)\r
13274      char *s1, *s2;\r
13275 {\r
13276     char c1, c2;\r
13277     \r
13278     for (;;) {\r
13279         c1 = ToLower(*s1++);\r
13280         c2 = ToLower(*s2++);\r
13281         if (c1 > c2) return 1;\r
13282         if (c1 < c2) return -1;\r
13283         if (c1 == NULLCHAR) return 0;\r
13284     }\r
13285 }\r
13286 \r
13287 \r
13288 int\r
13289 ToLower(c)\r
13290      int c;\r
13291 {\r
13292     return isupper(c) ? tolower(c) : c;\r
13293 }\r
13294 \r
13295 \r
13296 int\r
13297 ToUpper(c)\r
13298      int c;\r
13299 {\r
13300     return islower(c) ? toupper(c) : c;\r
13301 }\r
13302 #endif /* !_amigados    */\r
13303 \r
13304 char *\r
13305 StrSave(s)\r
13306      char *s;\r
13307 {\r
13308     char *ret;\r
13309 \r
13310     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
13311         strcpy(ret, s);\r
13312     }\r
13313     return ret;\r
13314 }\r
13315 \r
13316 char *\r
13317 StrSavePtr(s, savePtr)\r
13318      char *s, **savePtr;\r
13319 {\r
13320     if (*savePtr) {\r
13321         free(*savePtr);\r
13322     }\r
13323     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
13324         strcpy(*savePtr, s);\r
13325     }\r
13326     return(*savePtr);\r
13327 }\r
13328 \r
13329 char *\r
13330 PGNDate()\r
13331 {\r
13332     time_t clock;\r
13333     struct tm *tm;\r
13334     char buf[MSG_SIZ];\r
13335 \r
13336     clock = time((time_t *)NULL);\r
13337     tm = localtime(&clock);\r
13338     sprintf(buf, "%04d.%02d.%02d",\r
13339             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
13340     return StrSave(buf);\r
13341 }\r
13342 \r
13343 \r
13344 char *\r
13345 PositionToFEN(move, useFEN960)\r
13346      int move;\r
13347      int useFEN960;\r
13348 {\r
13349     int i, j, fromX, fromY, toX, toY;\r
13350     int whiteToPlay;\r
13351     char buf[128];\r
13352     char *p, *q;\r
13353     int emptycount;\r
13354     ChessSquare piece;\r
13355 \r
13356     whiteToPlay = (gameMode == EditPosition) ?\r
13357       !blackPlaysFirst : (move % 2 == 0);\r
13358     p = buf;\r
13359 \r
13360     /* Piece placement data */\r
13361     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13362         emptycount = 0;\r
13363         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
13364             if (boards[move][i][j] == EmptySquare) {\r
13365                 emptycount++;\r
13366             } else { ChessSquare piece = boards[move][i][j];\r
13367                 if (emptycount > 0) {\r
13368                     if(emptycount<10) /* [HGM] can be >= 10 */\r
13369                         *p++ = '0' + emptycount;\r
13370                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13371                     emptycount = 0;\r
13372                 }\r
13373                 if(PieceToChar(piece) == '+') {\r
13374                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
13375                     *p++ = '+';\r
13376                     piece = (ChessSquare)(DEMOTED piece);\r
13377                 } \r
13378                 *p++ = PieceToChar(piece);\r
13379                 if(p[-1] == '~') {\r
13380                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
13381                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
13382                     *p++ = '~';\r
13383                 }\r
13384             }\r
13385         }\r
13386         if (emptycount > 0) {\r
13387             if(emptycount<10) /* [HGM] can be >= 10 */\r
13388                 *p++ = '0' + emptycount;\r
13389             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13390             emptycount = 0;\r
13391         }\r
13392         *p++ = '/';\r
13393     }\r
13394     *(p - 1) = ' ';\r
13395 \r
13396     /* [HGM] print Crazyhouse or Shogi holdings */\r
13397     if( gameInfo.holdingsWidth ) {\r
13398         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
13399         q = p;\r
13400         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
13401             piece = boards[move][i][BOARD_WIDTH-1];\r
13402             if( piece != EmptySquare )\r
13403               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
13404                   *p++ = PieceToChar(piece);\r
13405         }\r
13406         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
13407             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
13408             if( piece != EmptySquare )\r
13409               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
13410                   *p++ = PieceToChar(piece);\r
13411         }\r
13412 \r
13413         if( q == p ) *p++ = '-';\r
13414         *p++ = ']';\r
13415         *p++ = ' ';\r
13416     }\r
13417 \r
13418     /* Active color */\r
13419     *p++ = whiteToPlay ? 'w' : 'b';\r
13420     *p++ = ' ';\r
13421 \r
13422   if(nrCastlingRights) {\r
13423      q = p;\r
13424      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
13425        /* [HGM] write directly from rights */\r
13426            if(castlingRights[move][2] >= 0 &&\r
13427               castlingRights[move][0] >= 0   )\r
13428                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
13429            if(castlingRights[move][2] >= 0 &&\r
13430               castlingRights[move][1] >= 0   )\r
13431                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
13432            if(castlingRights[move][5] >= 0 &&\r
13433               castlingRights[move][3] >= 0   )\r
13434                 *p++ = castlingRights[move][3] + AAA;\r
13435            if(castlingRights[move][5] >= 0 &&\r
13436               castlingRights[move][4] >= 0   )\r
13437                 *p++ = castlingRights[move][4] + AAA;\r
13438      } else {\r
13439 \r
13440         /* [HGM] write true castling rights */\r
13441         if( nrCastlingRights == 6 ) {\r
13442             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
13443                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
13444             if(castlingRights[move][1] == BOARD_LEFT &&\r
13445                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
13446             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
13447                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
13448             if(castlingRights[move][4] == BOARD_LEFT &&\r
13449                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
13450         }\r
13451      }\r
13452      if (q == p) *p++ = '-'; /* No castling rights */\r
13453      *p++ = ' ';\r
13454   }\r
13455 \r
13456   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13457      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13458     /* En passant target square */\r
13459     if (move > backwardMostMove) {\r
13460         fromX = moveList[move - 1][0] - AAA;\r
13461         fromY = moveList[move - 1][1] - ONE;\r
13462         toX = moveList[move - 1][2] - AAA;\r
13463         toY = moveList[move - 1][3] - ONE;\r
13464         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
13465             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
13466             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
13467             fromX == toX) {\r
13468             /* 2-square pawn move just happened */\r
13469             *p++ = toX + AAA;\r
13470             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
13471         } else {\r
13472             *p++ = '-';\r
13473         }\r
13474     } else {\r
13475         *p++ = '-';\r
13476     }\r
13477     *p++ = ' ';\r
13478   }\r
13479 \r
13480     /* [HGM] find reversible plies */\r
13481     {   int i = 0, j=move;\r
13482 \r
13483         if (appData.debugMode) { int k;\r
13484             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
13485             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
13486                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
13487 \r
13488         }\r
13489 \r
13490         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
13491         if( j == backwardMostMove ) i += initialRulePlies;\r
13492         sprintf(p, "%d ", i);\r
13493         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
13494     }\r
13495     /* Fullmove number */\r
13496     sprintf(p, "%d", (move / 2) + 1);\r
13497     \r
13498     return StrSave(buf);\r
13499 }\r
13500 \r
13501 Boolean\r
13502 ParseFEN(board, blackPlaysFirst, fen)\r
13503     Board board;\r
13504      int *blackPlaysFirst;\r
13505      char *fen;\r
13506 {\r
13507     int i, j;\r
13508     char *p;\r
13509     int emptycount;\r
13510     ChessSquare piece;\r
13511 \r
13512     p = fen;\r
13513 \r
13514     /* [HGM] by default clear Crazyhouse holdings, if present */\r
13515     if(gameInfo.holdingsWidth) {\r
13516        for(i=0; i<BOARD_HEIGHT; i++) {\r
13517            board[i][0]             = EmptySquare; /* black holdings */\r
13518            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
13519            board[i][1]             = (ChessSquare) 0; /* black counts */\r
13520            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
13521        }\r
13522     }\r
13523 \r
13524     /* Piece placement data */\r
13525     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13526         j = 0;\r
13527         for (;;) {\r
13528             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
13529                 if (*p == '/') p++;\r
13530                 emptycount = gameInfo.boardWidth - j;\r
13531                 while (emptycount--)\r
13532                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13533                 break;\r
13534 #if(BOARD_SIZE >= 10)\r
13535             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
13536                 p++; emptycount=10;\r
13537                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13538                 while (emptycount--)\r
13539                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13540 #endif\r
13541             } else if (isdigit(*p)) {\r
13542                 emptycount = *p++ - '0';\r
13543                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
13544                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13545                 while (emptycount--)\r
13546                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13547             } else if (*p == '+' || isalpha(*p)) {\r
13548                 if (j >= gameInfo.boardWidth) return FALSE;\r
13549                 if(*p=='+') {\r
13550                     piece = CharToPiece(*++p);\r
13551                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
13552                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
13553                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
13554                 } else piece = CharToPiece(*p++);\r
13555 \r
13556                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
13557                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
13558                     piece = (ChessSquare) (PROMOTED piece);\r
13559                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
13560                     p++;\r
13561                 }\r
13562                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
13563             } else {\r
13564                 return FALSE;\r
13565             }\r
13566         }\r
13567     }\r
13568     while (*p == '/' || *p == ' ') p++;\r
13569 \r
13570     /* [HGM] look for Crazyhouse holdings here */\r
13571     while(*p==' ') p++;\r
13572     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
13573         if(*p == '[') p++;\r
13574         if(*p == '-' ) *p++; /* empty holdings */ else {\r
13575             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
13576             /* if we would allow FEN reading to set board size, we would   */\r
13577             /* have to add holdings and shift the board read so far here   */\r
13578             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
13579                 *p++;\r
13580                 if((int) piece >= (int) BlackPawn ) {\r
13581                     i = (int)piece - (int)BlackPawn;\r
13582                     i = PieceToNumber((ChessSquare)i);\r
13583                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13584                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
13585                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
13586                 } else {\r
13587                     i = (int)piece - (int)WhitePawn;\r
13588                     i = PieceToNumber((ChessSquare)i);\r
13589                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13590                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
13591                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
13592                 }\r
13593             }\r
13594         }\r
13595         if(*p == ']') *p++;\r
13596     }\r
13597 \r
13598     while(*p == ' ') p++;\r
13599 \r
13600     /* Active color */\r
13601     switch (*p++) {\r
13602       case 'w':\r
13603         *blackPlaysFirst = FALSE;\r
13604         break;\r
13605       case 'b': \r
13606         *blackPlaysFirst = TRUE;\r
13607         break;\r
13608       default:\r
13609         return FALSE;\r
13610     }\r
13611 \r
13612     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
13613     /* return the extra info in global variiables             */\r
13614 \r
13615     /* set defaults in case FEN is incomplete */\r
13616     FENepStatus = EP_UNKNOWN;\r
13617     for(i=0; i<nrCastlingRights; i++ ) {\r
13618         FENcastlingRights[i] =\r
13619             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
13620     }   /* assume possible unless obviously impossible */\r
13621     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
13622     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
13623     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
13624     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
13625     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
13626     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
13627     FENrulePlies = 0;\r
13628 \r
13629     while(*p==' ') p++;\r
13630     if(nrCastlingRights) {\r
13631       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
13632           /* castling indicator present, so default becomes no castlings */\r
13633           for(i=0; i<nrCastlingRights; i++ ) {\r
13634                  FENcastlingRights[i] = -1;\r
13635           }\r
13636       }\r
13637       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
13638              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
13639              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
13640              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
13641         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
13642 \r
13643         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
13644             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
13645             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
13646         }\r
13647         switch(c) {\r
13648           case'K':\r
13649               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
13650               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
13651               FENcastlingRights[2] = whiteKingFile;\r
13652               break;\r
13653           case'Q':\r
13654               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
13655               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
13656               FENcastlingRights[2] = whiteKingFile;\r
13657               break;\r
13658           case'k':\r
13659               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
13660               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
13661               FENcastlingRights[5] = blackKingFile;\r
13662               break;\r
13663           case'q':\r
13664               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
13665               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
13666               FENcastlingRights[5] = blackKingFile;\r
13667           case '-':\r
13668               break;\r
13669           default: /* FRC castlings */\r
13670               if(c >= 'a') { /* black rights */\r
13671                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13672                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
13673                   if(i == BOARD_RGHT) break;\r
13674                   FENcastlingRights[5] = i;\r
13675                   c -= AAA;\r
13676                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
13677                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
13678                   if(c > i)\r
13679                       FENcastlingRights[3] = c;\r
13680                   else\r
13681                       FENcastlingRights[4] = c;\r
13682               } else { /* white rights */\r
13683                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13684                     if(board[0][i] == WhiteKing) break;\r
13685                   if(i == BOARD_RGHT) break;\r
13686                   FENcastlingRights[2] = i;\r
13687                   c -= AAA - 'a' + 'A';\r
13688                   if(board[0][c] >= WhiteKing) break;\r
13689                   if(c > i)\r
13690                       FENcastlingRights[0] = c;\r
13691                   else\r
13692                       FENcastlingRights[1] = c;\r
13693               }\r
13694         }\r
13695       }\r
13696     if (appData.debugMode) {\r
13697         fprintf(debugFP, "FEN castling rights:");\r
13698         for(i=0; i<nrCastlingRights; i++)\r
13699         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
13700         fprintf(debugFP, "\n");\r
13701     }\r
13702 \r
13703       while(*p==' ') p++;\r
13704     }\r
13705 \r
13706     /* read e.p. field in games that know e.p. capture */\r
13707     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13708        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13709       if(*p=='-') {\r
13710         p++; FENepStatus = EP_NONE;\r
13711       } else {\r
13712          char c = *p++ - AAA;\r
13713 \r
13714          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
13715          if(*p >= '0' && *p <='9') *p++;\r
13716          FENepStatus = c;\r
13717       }\r
13718     }\r
13719 \r
13720 \r
13721     if(sscanf(p, "%d", &i) == 1) {\r
13722         FENrulePlies = i; /* 50-move ply counter */\r
13723         /* (The move number is still ignored)    */\r
13724     }\r
13725 \r
13726     return TRUE;\r
13727 }\r
13728       \r
13729 void\r
13730 EditPositionPasteFEN(char *fen)\r
13731 {\r
13732   if (fen != NULL) {\r
13733     Board initial_position;\r
13734 \r
13735     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
13736       DisplayError(_("Bad FEN position in clipboard"), 0);\r
13737       return ;\r
13738     } else {\r
13739       int savedBlackPlaysFirst = blackPlaysFirst;\r
13740       EditPositionEvent();\r
13741       blackPlaysFirst = savedBlackPlaysFirst;\r
13742       CopyBoard(boards[0], initial_position);\r
13743           /* [HGM] copy FEN attributes as well */\r
13744           {   int i;\r
13745               initialRulePlies = FENrulePlies;\r
13746               epStatus[0] = FENepStatus;\r
13747               for( i=0; i<nrCastlingRights; i++ )\r
13748                   castlingRights[0][i] = FENcastlingRights[i];\r
13749           }\r
13750       EditPositionDone();\r
13751       DisplayBothClocks();\r
13752       DrawPosition(FALSE, boards[currentMove]);\r
13753     }\r
13754   }\r
13755 }\r