source clean up. Fix compiler warning, removed unused variables, etc.
[xboard.git] / backend.c
1 /*\r
2  * backend.c -- Common back end for X and Windows NT versions of\r
3  * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $\r
4  *\r
5  * Copyright 1991 by Digital Equipment Corporation, Maynard,\r
6  * Massachusetts.  Enhancements Copyright\r
7  * 1992-2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software\r
8  * Foundation, Inc.\r
9  *\r
10  * The following terms apply to Digital Equipment Corporation's copyright\r
11  * interest in XBoard:\r
12  * ------------------------------------------------------------------------\r
13  * All Rights Reserved\r
14  *\r
15  * Permission to use, copy, modify, and distribute this software and its\r
16  * documentation for any purpose and without fee is hereby granted,\r
17  * provided that the above copyright notice appear in all copies and that\r
18  * both that copyright notice and this permission notice appear in\r
19  * supporting documentation, and that the name of Digital not be\r
20  * used in advertising or publicity pertaining to distribution of the\r
21  * software without specific, written prior permission.\r
22  *\r
23  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING\r
24  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL\r
25  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR\r
26  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,\r
27  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\r
28  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS\r
29  * SOFTWARE.\r
30  * ------------------------------------------------------------------------\r
31  *\r
32  * The following terms apply to the enhanced version of XBoard\r
33  * distributed by the Free Software Foundation:\r
34  * ------------------------------------------------------------------------\r
35  *\r
36  * GNU XBoard is free software: you can redistribute it and/or modify\r
37  * it under the terms of the GNU General Public License as published by\r
38  * the Free Software Foundation, either version 3 of the License, or (at\r
39  * your option) any later version.\r
40  *\r
41  * GNU XBoard is distributed in the hope that it will be useful, but\r
42  * WITHOUT ANY WARRANTY; without even the implied warranty of\r
43  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
44  * General Public License for more details.\r
45  *\r
46  * You should have received a copy of the GNU General Public License\r
47  * along with this program. If not, see http://www.gnu.org/licenses/.  *\r
48  *\r
49  *------------------------------------------------------------------------\r
50  ** See the file ChangeLog for a revision history.  */\r
51 \r
52 /* [AS] Also useful here for debugging */\r
53 #ifdef WIN32\r
54 #include <windows.h>\r
55 \r
56 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );\r
57 \r
58 #else\r
59 \r
60 #define DoSleep( n ) if( (n) >= 0) sleep(n)\r
61 \r
62 #endif\r
63 \r
64 #include "config.h"\r
65 \r
66 #include <assert.h>\r
67 #include <stdio.h>\r
68 #include <ctype.h>\r
69 #include <errno.h>\r
70 #include <sys/types.h>\r
71 #include <sys/stat.h>\r
72 #include <math.h>\r
73 #include <ctype.h>\r
74 \r
75 #if STDC_HEADERS\r
76 # include <stdlib.h>\r
77 # include <string.h>\r
78 #else /* not STDC_HEADERS */\r
79 # if HAVE_STRING_H\r
80 #  include <string.h>\r
81 # else /* not HAVE_STRING_H */\r
82 #  include <strings.h>\r
83 # endif /* not HAVE_STRING_H */\r
84 #endif /* not STDC_HEADERS */\r
85 \r
86 #if HAVE_SYS_FCNTL_H\r
87 # include <sys/fcntl.h>\r
88 #else /* not HAVE_SYS_FCNTL_H */\r
89 # if HAVE_FCNTL_H\r
90 #  include <fcntl.h>\r
91 # endif /* HAVE_FCNTL_H */\r
92 #endif /* not HAVE_SYS_FCNTL_H */\r
93 \r
94 #if TIME_WITH_SYS_TIME\r
95 # include <sys/time.h>\r
96 # include <time.h>\r
97 #else\r
98 # if HAVE_SYS_TIME_H\r
99 #  include <sys/time.h>\r
100 # else\r
101 #  include <time.h>\r
102 # endif\r
103 #endif\r
104 \r
105 #if defined(_amigados) && !defined(__GNUC__)\r
106 struct timezone {\r
107     int tz_minuteswest;\r
108     int tz_dsttime;\r
109 };\r
110 extern int gettimeofday(struct timeval *, struct timezone *);\r
111 #endif\r
112 \r
113 #if HAVE_UNISTD_H\r
114 # include <unistd.h>\r
115 #endif\r
116 \r
117 #include "common.h"\r
118 #include "frontend.h"\r
119 #include "backend.h"\r
120 #include "parser.h"\r
121 #include "moves.h"\r
122 #if ZIPPY\r
123 # include "zippy.h"\r
124 #endif\r
125 #include "backendz.h"\r
126 #include "gettext.h" \r
127  \r
128 #ifdef ENABLE_NLS \r
129 # define _(s) gettext (s) \r
130 # define N_(s) gettext_noop (s) \r
131 #else \r
132 # define _(s) (s) \r
133 # define N_(s) s \r
134 #endif \r
135 \r
136 \r
137 /* A point in time */\r
138 typedef struct {\r
139     long sec;  /* Assuming this is >= 32 bits */\r
140     int ms;    /* Assuming this is >= 16 bits */\r
141 } TimeMark;\r
142 \r
143 int establish P((void));\r
144 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,\r
145                          char *buf, int count, int error));\r
146 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,\r
147                       char *buf, int count, int error));\r
148 void SendToICS P((char *s));\r
149 void SendToICSDelayed P((char *s, long msdelay));\r
150 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,\r
151                       int toX, int toY));\r
152 void InitPosition P((int redraw));\r
153 void HandleMachineMove P((char *message, ChessProgramState *cps));\r
154 int AutoPlayOneMove P((void));\r
155 int LoadGameOneMove P((ChessMove readAhead));\r
156 int LoadGameFromFile P((char *filename, int n, char *title, int useList));\r
157 int LoadPositionFromFile P((char *filename, int n, char *title));\r
158 int SavePositionToFile P((char *filename));\r
159 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,\r
160                   Board board));\r
161 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));\r
162 void ShowMove P((int fromX, int fromY, int toX, int toY));\r
163 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,\r
164                    /*char*/int promoChar));\r
165 void BackwardInner P((int target));\r
166 void ForwardInner P((int target));\r
167 void GameEnds P((ChessMove result, char *resultDetails, int whosays));\r
168 void EditPositionDone P((void));\r
169 void PrintOpponents P((FILE *fp));\r
170 void PrintPosition P((FILE *fp, int move));\r
171 void StartChessProgram P((ChessProgramState *cps));\r
172 void SendToProgram P((char *message, ChessProgramState *cps));\r
173 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));\r
174 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,\r
175                            char *buf, int count, int error));\r
176 void SendTimeControl P((ChessProgramState *cps,\r
177                         int mps, long tc, int inc, int sd, int st));\r
178 char *TimeControlTagValue P((void));\r
179 void Attention P((ChessProgramState *cps));\r
180 void FeedMovesToProgram P((ChessProgramState *cps, int upto));\r
181 void ResurrectChessProgram P((void));\r
182 void DisplayComment P((int moveNumber, char *text));\r
183 void DisplayMove P((int moveNumber));\r
184 void DisplayAnalysis P((void));\r
185 \r
186 void ParseGameHistory P((char *game));\r
187 void ParseBoard12 P((char *string));\r
188 void StartClocks P((void));\r
189 void SwitchClocks P((void));\r
190 void StopClocks P((void));\r
191 void ResetClocks P((void));\r
192 char *PGNDate P((void));\r
193 void SetGameInfo P((void));\r
194 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));\r
195 int RegisterMove P((void));\r
196 void MakeRegisteredMove P((void));\r
197 void TruncateGame P((void));\r
198 int looking_at P((char *, int *, char *));\r
199 void CopyPlayerNameIntoFileName P((char **, char *));\r
200 char *SavePart P((char *));\r
201 int SaveGameOldStyle P((FILE *));\r
202 int SaveGamePGN P((FILE *));\r
203 void GetTimeMark P((TimeMark *));\r
204 long SubtractTimeMarks P((TimeMark *, TimeMark *));\r
205 int CheckFlags P((void));\r
206 long NextTickLength P((long));\r
207 void CheckTimeControl P((void));\r
208 void show_bytes P((FILE *, char *, int));\r
209 int string_to_rating P((char *str));\r
210 void ParseFeatures P((char* args, ChessProgramState *cps));\r
211 void InitBackEnd3 P((void));\r
212 void FeatureDone P((ChessProgramState* cps, int val));\r
213 void InitChessProgram P((ChessProgramState *cps, int setup));\r
214 void OutputKibitz(int window, char *text);\r
215 int PerpetualChase(int first, int last);\r
216 int EngineOutputIsUp();\r
217 void InitDrawingSizes(int x, int y);\r
218 \r
219 #ifdef WIN32\r
220        extern void ConsoleCreate();\r
221 #endif\r
222 \r
223 ChessProgramState *WhitePlayer();\r
224 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c\r
225 int VerifyDisplayMode P(());\r
226 \r
227 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment\r
228 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c\r
229 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move\r
230 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book\r
231 extern char installDir[MSG_SIZ];\r
232 \r
233 extern int tinyLayout, smallLayout;\r
234 ChessProgramStats programStats;\r
235 static int exiting = 0; /* [HGM] moved to top */\r
236 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;\r
237 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */\r
238 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */\r
239 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */\r
240 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */\r
241 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */\r
242 int opponentKibitzes;\r
243 \r
244 /* States for ics_getting_history */\r
245 #define H_FALSE 0\r
246 #define H_REQUESTED 1\r
247 #define H_GOT_REQ_HEADER 2\r
248 #define H_GOT_UNREQ_HEADER 3\r
249 #define H_GETTING_MOVES 4\r
250 #define H_GOT_UNWANTED_HEADER 5\r
251 \r
252 /* whosays values for GameEnds */\r
253 #define GE_ICS 0\r
254 #define GE_ENGINE 1\r
255 #define GE_PLAYER 2\r
256 #define GE_FILE 3\r
257 #define GE_XBOARD 4\r
258 #define GE_ENGINE1 5\r
259 #define GE_ENGINE2 6\r
260 \r
261 /* Maximum number of games in a cmail message */\r
262 #define CMAIL_MAX_GAMES 20\r
263 \r
264 /* Different types of move when calling RegisterMove */\r
265 #define CMAIL_MOVE   0\r
266 #define CMAIL_RESIGN 1\r
267 #define CMAIL_DRAW   2\r
268 #define CMAIL_ACCEPT 3\r
269 \r
270 /* Different types of result to remember for each game */\r
271 #define CMAIL_NOT_RESULT 0\r
272 #define CMAIL_OLD_RESULT 1\r
273 #define CMAIL_NEW_RESULT 2\r
274 \r
275 /* Telnet protocol constants */\r
276 #define TN_WILL 0373\r
277 #define TN_WONT 0374\r
278 #define TN_DO   0375\r
279 #define TN_DONT 0376\r
280 #define TN_IAC  0377\r
281 #define TN_ECHO 0001\r
282 #define TN_SGA  0003\r
283 #define TN_PORT 23\r
284 \r
285 /* [AS] */\r
286 static char * safeStrCpy( char * dst, const char * src, size_t count )\r
287 {\r
288     assert( dst != NULL );\r
289     assert( src != NULL );\r
290     assert( count > 0 );\r
291 \r
292     strncpy( dst, src, count );\r
293     dst[ count-1 ] = '\0';\r
294     return dst;\r
295 }\r
296 \r
297 #if 0\r
298 //[HGM] for future use? Conditioned out for now to suppress warning.\r
299 static char * safeStrCat( char * dst, const char * src, size_t count )\r
300 {\r
301     size_t  dst_len;\r
302 \r
303     assert( dst != NULL );\r
304     assert( src != NULL );\r
305     assert( count > 0 );\r
306 \r
307     dst_len = strlen(dst);\r
308 \r
309     assert( count > dst_len ); /* Buffer size must be greater than current length */\r
310 \r
311     safeStrCpy( dst + dst_len, src, count - dst_len );\r
312 \r
313     return dst;\r
314 }\r
315 #endif\r
316 \r
317 /* Some compiler can't cast u64 to double\r
318  * This function do the job for us:\r
319 \r
320  * We use the highest bit for cast, this only\r
321  * works if the highest bit is not\r
322  * in use (This should not happen)\r
323  *\r
324  * We used this for all compiler\r
325  */\r
326 double\r
327 u64ToDouble(u64 value)\r
328 {\r
329   double r;\r
330   u64 tmp = value & u64Const(0x7fffffffffffffff);\r
331   r = (double)(s64)tmp;\r
332   if (value & u64Const(0x8000000000000000))\r
333        r +=  9.2233720368547758080e18; /* 2^63 */\r
334  return r;\r
335 }\r
336 \r
337 /* Fake up flags for now, as we aren't keeping track of castling\r
338    availability yet. [HGM] Change of logic: the flag now only\r
339    indicates the type of castlings allowed by the rule of the game.\r
340    The actual rights themselves are maintained in the array\r
341    castlingRights, as part of the game history, and are not probed\r
342    by this function.\r
343  */\r
344 int\r
345 PosFlags(index)\r
346 {\r
347   int flags = F_ALL_CASTLE_OK;\r
348   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;\r
349   switch (gameInfo.variant) {\r
350   case VariantSuicide:\r
351     flags &= ~F_ALL_CASTLE_OK;\r
352   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!\r
353     flags |= F_IGNORE_CHECK;\r
354     break;\r
355   case VariantAtomic:\r
356     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;\r
357     break;\r
358   case VariantKriegspiel:\r
359     flags |= F_KRIEGSPIEL_CAPTURE;\r
360     break;\r
361   case VariantCapaRandom: \r
362   case VariantFischeRandom:\r
363     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */\r
364   case VariantNoCastle:\r
365   case VariantShatranj:\r
366   case VariantCourier:\r
367     flags &= ~F_ALL_CASTLE_OK;\r
368     break;\r
369   default:\r
370     break;\r
371   }\r
372   return flags;\r
373 }\r
374 \r
375 FILE *gameFileFP, *debugFP;\r
376 \r
377 /* \r
378     [AS] Note: sometimes, the sscanf() function is used to parse the input\r
379     into a fixed-size buffer. Because of this, we must be prepared to\r
380     receive strings as long as the size of the input buffer, which is currently\r
381     set to 4K for Windows and 8K for the rest.\r
382     So, we must either allocate sufficiently large buffers here, or\r
383     reduce the size of the input buffer in the input reading part.\r
384 */\r
385 \r
386 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];\r
387 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];\r
388 char thinkOutput1[MSG_SIZ*10];\r
389 \r
390 ChessProgramState first, second;\r
391 \r
392 /* premove variables */\r
393 int premoveToX = 0;\r
394 int premoveToY = 0;\r
395 int premoveFromX = 0;\r
396 int premoveFromY = 0;\r
397 int premovePromoChar = 0;\r
398 int gotPremove = 0;\r
399 Boolean alarmSounded;\r
400 /* end premove variables */\r
401 \r
402 char *ics_prefix = "$";\r
403 int ics_type = ICS_GENERIC;\r
404 \r
405 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;\r
406 int pauseExamForwardMostMove = 0;\r
407 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;\r
408 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];\r
409 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;\r
410 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;\r
411 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;\r
412 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;\r
413 int whiteFlag = FALSE, blackFlag = FALSE;\r
414 int userOfferedDraw = FALSE;\r
415 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;\r
416 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;\r
417 int cmailMoveType[CMAIL_MAX_GAMES];\r
418 long ics_clock_paused = 0;\r
419 ProcRef icsPR = NoProc, cmailPR = NoProc;\r
420 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;\r
421 GameMode gameMode = BeginningOfGame;\r
422 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];\r
423 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];\r
424 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */\r
425 int hiddenThinkOutputState = 0; /* [AS] */\r
426 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */\r
427 int adjudicateLossPlies = 6;\r
428 char white_holding[64], black_holding[64];\r
429 TimeMark lastNodeCountTime;\r
430 long lastNodeCount=0;\r
431 int have_sent_ICS_logon = 0;\r
432 int movesPerSession;\r
433 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;\r
434 long timeControl_2; /* [AS] Allow separate time controls */\r
435 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */\r
436 long timeRemaining[2][MAX_MOVES];\r
437 int matchGame = 0;\r
438 TimeMark programStartTime;\r
439 char ics_handle[MSG_SIZ];\r
440 int have_set_title = 0;\r
441 \r
442 /* animateTraining preserves the state of appData.animate\r
443  * when Training mode is activated. This allows the\r
444  * response to be animated when appData.animate == TRUE and\r
445  * appData.animateDragging == TRUE.\r
446  */\r
447 Boolean animateTraining;\r
448 \r
449 GameInfo gameInfo;\r
450 \r
451 AppData appData;\r
452 \r
453 Board boards[MAX_MOVES];\r
454 /* [HGM] Following 7 needed for accurate legality tests: */\r
455 char  epStatus[MAX_MOVES];\r
456 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1\r
457 char  castlingRank[BOARD_SIZE]; // and corresponding ranks\r
458 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];\r
459 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status\r
460 int   initialRulePlies, FENrulePlies;\r
461 char  FENepStatus;\r
462 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)\r
463 int loadFlag = 0; \r
464 int shuffleOpenings;\r
465 \r
466 ChessSquare  FIDEArray[2][BOARD_SIZE] = {\r
467     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
468         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
469     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
470         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
471 };\r
472 \r
473 ChessSquare twoKingsArray[2][BOARD_SIZE] = {\r
474     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
475         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },\r
476     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
477         BlackKing, BlackKing, BlackKnight, BlackRook }\r
478 };\r
479 \r
480 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {\r
481     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,\r
482         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },\r
483     { BlackRook, BlackMan, BlackBishop, BlackQueen,\r
484         BlackUnicorn, BlackBishop, BlackMan, BlackRook }\r
485 };\r
486 \r
487 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */\r
488     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,\r
489         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
490     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,\r
491         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
492 };\r
493 \r
494 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */\r
495     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,\r
496         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
497     { BlackRook, BlackKnight, BlackAlfil, BlackKing,\r
498         BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
499 };\r
500 \r
501 \r
502 #if (BOARD_SIZE>=10)\r
503 ChessSquare ShogiArray[2][BOARD_SIZE] = {\r
504     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,\r
505         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },\r
506     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,\r
507         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }\r
508 };\r
509 \r
510 ChessSquare XiangqiArray[2][BOARD_SIZE] = {\r
511     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,\r
512         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
513     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,\r
514         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
515 };\r
516 \r
517 ChessSquare CapablancaArray[2][BOARD_SIZE] = {\r
518     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, \r
519         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },\r
520     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, \r
521         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
522 };\r
523 \r
524 ChessSquare GreatArray[2][BOARD_SIZE] = {\r
525     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, \r
526         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },\r
527     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, \r
528         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },\r
529 };\r
530 \r
531 ChessSquare JanusArray[2][BOARD_SIZE] = {\r
532     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, \r
533         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },\r
534     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, \r
535         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }\r
536 };\r
537 \r
538 #ifdef GOTHIC\r
539 ChessSquare GothicArray[2][BOARD_SIZE] = {\r
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, \r
541         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },\r
542     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, \r
543         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }\r
544 };\r
545 #else // !GOTHIC\r
546 #define GothicArray CapablancaArray\r
547 #endif // !GOTHIC\r
548 \r
549 #ifdef FALCON\r
550 ChessSquare FalconArray[2][BOARD_SIZE] = {\r
551     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, \r
552         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },\r
553     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, \r
554         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }\r
555 };\r
556 #else // !FALCON\r
557 #define FalconArray CapablancaArray\r
558 #endif // !FALCON\r
559 \r
560 #else // !(BOARD_SIZE>=10)\r
561 #define XiangqiPosition FIDEArray\r
562 #define CapablancaArray FIDEArray\r
563 #define GothicArray FIDEArray\r
564 #define GreatArray FIDEArray\r
565 #endif // !(BOARD_SIZE>=10)\r
566 \r
567 #if (BOARD_SIZE>=12)\r
568 ChessSquare CourierArray[2][BOARD_SIZE] = {\r
569     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,\r
570         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },\r
571     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,\r
572         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }\r
573 };\r
574 #else // !(BOARD_SIZE>=12)\r
575 #define CourierArray CapablancaArray\r
576 #endif // !(BOARD_SIZE>=12)\r
577 \r
578 \r
579 Board initialPosition;\r
580 \r
581 \r
582 /* Convert str to a rating. Checks for special cases of "----",\r
583 \r
584    "++++", etc. Also strips ()'s */\r
585 int\r
586 string_to_rating(str)\r
587   char *str;\r
588 {\r
589   while(*str && !isdigit(*str)) ++str;\r
590   if (!*str)\r
591     return 0;   /* One of the special "no rating" cases */\r
592   else\r
593     return atoi(str);\r
594 }\r
595 \r
596 void\r
597 ClearProgramStats()\r
598 {\r
599     /* Init programStats */\r
600     programStats.movelist[0] = 0;\r
601     programStats.depth = 0;\r
602     programStats.nr_moves = 0;\r
603     programStats.moves_left = 0;\r
604     programStats.nodes = 0;\r
605     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output\r
606     programStats.score = 0;\r
607     programStats.got_only_move = 0;\r
608     programStats.got_fail = 0;\r
609     programStats.line_is_book = 0;\r
610 }\r
611 \r
612 void\r
613 InitBackEnd1()\r
614 {\r
615     int matched, min, sec;\r
616 \r
617     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options\r
618 \r
619     GetTimeMark(&programStartTime);\r
620 \r
621     ClearProgramStats();\r
622     programStats.ok_to_send = 1;\r
623     programStats.seen_stat = 0;\r
624 \r
625     /*\r
626      * Initialize game list\r
627      */\r
628     ListNew(&gameList);\r
629 \r
630 \r
631     /*\r
632      * Internet chess server status\r
633      */\r
634     if (appData.icsActive) {\r
635         appData.matchMode = FALSE;\r
636         appData.matchGames = 0;\r
637 #if ZIPPY       \r
638         appData.noChessProgram = !appData.zippyPlay;\r
639 #else\r
640         appData.zippyPlay = FALSE;\r
641         appData.zippyTalk = FALSE;\r
642         appData.noChessProgram = TRUE;\r
643 #endif\r
644         if (*appData.icsHelper != NULLCHAR) {\r
645             appData.useTelnet = TRUE;\r
646             appData.telnetProgram = appData.icsHelper;\r
647         }\r
648     } else {\r
649         appData.zippyTalk = appData.zippyPlay = FALSE;\r
650     }\r
651 \r
652     /* [AS] Initialize pv info list [HGM] and game state */\r
653     {\r
654         int i, j;\r
655 \r
656         for( i=0; i<MAX_MOVES; i++ ) {\r
657             pvInfoList[i].depth = -1;\r
658             epStatus[i]=EP_NONE;\r
659             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
660         }\r
661     }\r
662 \r
663     /*\r
664      * Parse timeControl resource\r
665      */\r
666     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,\r
667                           appData.movesPerSession)) {\r
668         char buf[MSG_SIZ];\r
669         sprintf(buf, _("bad timeControl option %s"), appData.timeControl);\r
670         DisplayFatalError(buf, 0, 2);\r
671     }\r
672 \r
673     /*\r
674      * Parse searchTime resource\r
675      */\r
676     if (*appData.searchTime != NULLCHAR) {\r
677         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);\r
678         if (matched == 1) {\r
679             searchTime = min * 60;\r
680         } else if (matched == 2) {\r
681             searchTime = min * 60 + sec;\r
682         } else {\r
683             char buf[MSG_SIZ];\r
684             sprintf(buf, _("bad searchTime option %s"), appData.searchTime);\r
685             DisplayFatalError(buf, 0, 2);\r
686         }\r
687     }\r
688 \r
689     /* [AS] Adjudication threshold */\r
690     adjudicateLossThreshold = appData.adjudicateLossThreshold;\r
691     \r
692     first.which = "first";\r
693     second.which = "second";\r
694     first.maybeThinking = second.maybeThinking = FALSE;\r
695     first.pr = second.pr = NoProc;\r
696     first.isr = second.isr = NULL;\r
697     first.sendTime = second.sendTime = 2;\r
698     first.sendDrawOffers = 1;\r
699     if (appData.firstPlaysBlack) {\r
700         first.twoMachinesColor = "black\n";\r
701         second.twoMachinesColor = "white\n";\r
702     } else {\r
703         first.twoMachinesColor = "white\n";\r
704         second.twoMachinesColor = "black\n";\r
705     }\r
706     first.program = appData.firstChessProgram;\r
707     second.program = appData.secondChessProgram;\r
708     first.host = appData.firstHost;\r
709     second.host = appData.secondHost;\r
710     first.dir = appData.firstDirectory;\r
711     second.dir = appData.secondDirectory;\r
712     first.other = &second;\r
713     second.other = &first;\r
714     first.initString = appData.initString;\r
715     second.initString = appData.secondInitString;\r
716     first.computerString = appData.firstComputerString;\r
717     second.computerString = appData.secondComputerString;\r
718     first.useSigint = second.useSigint = TRUE;\r
719     first.useSigterm = second.useSigterm = TRUE;\r
720     first.reuse = appData.reuseFirst;\r
721     second.reuse = appData.reuseSecond;\r
722     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second\r
723     second.nps = appData.secondNPS;\r
724     first.useSetboard = second.useSetboard = FALSE;\r
725     first.useSAN = second.useSAN = FALSE;\r
726     first.usePing = second.usePing = FALSE;\r
727     first.lastPing = second.lastPing = 0;\r
728     first.lastPong = second.lastPong = 0;\r
729     first.usePlayother = second.usePlayother = FALSE;\r
730     first.useColors = second.useColors = TRUE;\r
731     first.useUsermove = second.useUsermove = FALSE;\r
732     first.sendICS = second.sendICS = FALSE;\r
733     first.sendName = second.sendName = appData.icsActive;\r
734     first.sdKludge = second.sdKludge = FALSE;\r
735     first.stKludge = second.stKludge = FALSE;\r
736     TidyProgramName(first.program, first.host, first.tidy);\r
737     TidyProgramName(second.program, second.host, second.tidy);\r
738     first.matchWins = second.matchWins = 0;\r
739     strcpy(first.variants, appData.variant);\r
740     strcpy(second.variants, appData.variant);\r
741     first.analysisSupport = second.analysisSupport = 2; /* detect */\r
742     first.analyzing = second.analyzing = FALSE;\r
743     first.initDone = second.initDone = FALSE;\r
744 \r
745     /* New features added by Tord: */\r
746     first.useFEN960 = FALSE; second.useFEN960 = FALSE;\r
747     first.useOOCastle = TRUE; second.useOOCastle = TRUE;\r
748     /* End of new features added by Tord. */\r
749 \r
750     /* [HGM] time odds: set factor for each machine */\r
751     first.timeOdds  = appData.firstTimeOdds;\r
752     second.timeOdds = appData.secondTimeOdds;\r
753     { int norm = 1;\r
754         if(appData.timeOddsMode) {\r
755             norm = first.timeOdds;\r
756             if(norm > second.timeOdds) norm = second.timeOdds;\r
757         }\r
758         first.timeOdds /= norm;\r
759         second.timeOdds /= norm;\r
760     }\r
761 \r
762     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/\r
763     first.accumulateTC = appData.firstAccumulateTC;\r
764     second.accumulateTC = appData.secondAccumulateTC;\r
765     first.maxNrOfSessions = second.maxNrOfSessions = 1;\r
766 \r
767     /* [HGM] debug */\r
768     first.debug = second.debug = FALSE;\r
769     first.supportsNPS = second.supportsNPS = UNKNOWN;\r
770 \r
771     /* [HGM] options */\r
772     first.optionSettings  = appData.firstOptions;\r
773     second.optionSettings = appData.secondOptions;\r
774 \r
775     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
776     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
777     first.isUCI = appData.firstIsUCI; /* [AS] */\r
778     second.isUCI = appData.secondIsUCI; /* [AS] */\r
779     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
780     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
781 \r
782     if (appData.firstProtocolVersion > PROTOVER ||\r
783         appData.firstProtocolVersion < 1) {\r
784       char buf[MSG_SIZ];\r
785       sprintf(buf, _("protocol version %d not supported"),\r
786               appData.firstProtocolVersion);\r
787       DisplayFatalError(buf, 0, 2);\r
788     } else {\r
789       first.protocolVersion = appData.firstProtocolVersion;\r
790     }\r
791 \r
792     if (appData.secondProtocolVersion > PROTOVER ||\r
793         appData.secondProtocolVersion < 1) {\r
794       char buf[MSG_SIZ];\r
795       sprintf(buf, _("protocol version %d not supported"),\r
796               appData.secondProtocolVersion);\r
797       DisplayFatalError(buf, 0, 2);\r
798     } else {\r
799       second.protocolVersion = appData.secondProtocolVersion;\r
800     }\r
801 \r
802     if (appData.icsActive) {\r
803         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */\r
804     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
805         appData.clockMode = FALSE;\r
806         first.sendTime = second.sendTime = 0;\r
807     }\r
808     \r
809 #if ZIPPY\r
810     /* Override some settings from environment variables, for backward\r
811        compatibility.  Unfortunately it's not feasible to have the env\r
812        vars just set defaults, at least in xboard.  Ugh.\r
813     */\r
814     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
815       ZippyInit();\r
816     }\r
817 #endif\r
818     \r
819     if (appData.noChessProgram) {\r
820         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
821                                         + strlen(PATCHLEVEL));\r
822         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
823     } else {\r
824 #if 0\r
825         char *p, *q;\r
826         q = first.program;\r
827         while (*q != ' ' && *q != NULLCHAR) q++;\r
828         p = q;\r
829         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */\r
830         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
831                                         + strlen(PATCHLEVEL) + (q - p));\r
832         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
833         strncat(programVersion, p, q - p);\r
834 #else\r
835         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */\r
836         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
837                                         + strlen(PATCHLEVEL) + strlen(first.tidy));\r
838         sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);\r
839 #endif\r
840     }\r
841 \r
842     if (!appData.icsActive) {\r
843       char buf[MSG_SIZ];\r
844       /* Check for variants that are supported only in ICS mode,\r
845          or not at all.  Some that are accepted here nevertheless\r
846          have bugs; see comments below.\r
847       */\r
848       VariantClass variant = StringToVariant(appData.variant);\r
849       switch (variant) {\r
850       case VariantBughouse:     /* need four players and two boards */\r
851       case VariantKriegspiel:   /* need to hide pieces and move details */\r
852       /* case VariantFischeRandom: (Fabien: moved below) */\r
853         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);\r
854         DisplayFatalError(buf, 0, 2);\r
855         return;\r
856 \r
857       case VariantUnknown:\r
858       case VariantLoadable:\r
859       case Variant29:\r
860       case Variant30:\r
861       case Variant31:\r
862       case Variant32:\r
863       case Variant33:\r
864       case Variant34:\r
865       case Variant35:\r
866       case Variant36:\r
867       default:\r
868         sprintf(buf, _("Unknown variant name %s"), appData.variant);\r
869         DisplayFatalError(buf, 0, 2);\r
870         return;\r
871 \r
872       case VariantXiangqi:    /* [HGM] repetition rules not implemented */\r
873       case VariantFairy:      /* [HGM] TestLegality definitely off! */\r
874       case VariantGothic:     /* [HGM] should work */\r
875       case VariantCapablanca: /* [HGM] should work */\r
876       case VariantCourier:    /* [HGM] initial forced moves not implemented */\r
877       case VariantShogi:      /* [HGM] drops not tested for legality */\r
878       case VariantKnightmate: /* [HGM] should work */\r
879       case VariantCylinder:   /* [HGM] untested */\r
880       case VariantFalcon:     /* [HGM] untested */\r
881       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)\r
882                                  offboard interposition not understood */\r
883       case VariantNormal:     /* definitely works! */\r
884       case VariantWildCastle: /* pieces not automatically shuffled */\r
885       case VariantNoCastle:   /* pieces not automatically shuffled */\r
886       case VariantFischeRandom: /* [HGM] works and shuffles pieces */\r
887       case VariantLosers:     /* should work except for win condition,\r
888                                  and doesn't know captures are mandatory */\r
889       case VariantSuicide:    /* should work except for win condition,\r
890                                  and doesn't know captures are mandatory */\r
891       case VariantGiveaway:   /* should work except for win condition,\r
892                                  and doesn't know captures are mandatory */\r
893       case VariantTwoKings:   /* should work */\r
894       case VariantAtomic:     /* should work except for win condition */\r
895       case Variant3Check:     /* should work except for win condition */\r
896       case VariantShatranj:   /* should work except for all win conditions */\r
897       case VariantBerolina:   /* might work if TestLegality is off */\r
898       case VariantCapaRandom: /* should work */\r
899       case VariantJanus:      /* should work */\r
900       case VariantSuper:      /* experimental */\r
901       case VariantGreat:      /* experimental, requires legality testing to be off */\r
902         break;\r
903       }\r
904     }\r
905 \r
906     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard\r
907     InitEngineUCI( installDir, &second );\r
908 }\r
909 \r
910 int NextIntegerFromString( char ** str, long * value )\r
911 {\r
912     int result = -1;\r
913     char * s = *str;\r
914 \r
915     while( *s == ' ' || *s == '\t' ) {\r
916         s++;\r
917     }\r
918 \r
919     *value = 0;\r
920 \r
921     if( *s >= '0' && *s <= '9' ) {\r
922         while( *s >= '0' && *s <= '9' ) {\r
923             *value = *value * 10 + (*s - '0');\r
924             s++;\r
925         }\r
926 \r
927         result = 0;\r
928     }\r
929 \r
930     *str = s;\r
931 \r
932     return result;\r
933 }\r
934 \r
935 int NextTimeControlFromString( char ** str, long * value )\r
936 {\r
937     long temp;\r
938     int result = NextIntegerFromString( str, &temp );\r
939 \r
940     if( result == 0 ) {\r
941         *value = temp * 60; /* Minutes */\r
942         if( **str == ':' ) {\r
943             (*str)++;\r
944             result = NextIntegerFromString( str, &temp );\r
945             *value += temp; /* Seconds */\r
946         }\r
947     }\r
948 \r
949     return result;\r
950 }\r
951 \r
952 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)\r
953 {   /* [HGM] routine added to read '+moves/time' for secondary time control */\r
954     int result = -1; long temp, temp2;\r
955 \r
956     if(**str != '+') return -1; // old params remain in force!\r
957     (*str)++;\r
958     if( NextTimeControlFromString( str, &temp ) ) return -1;\r
959 \r
960     if(**str != '/') {\r
961         /* time only: incremental or sudden-death time control */\r
962         if(**str == '+') { /* increment follows; read it */\r
963             (*str)++;\r
964             if(result = NextIntegerFromString( str, &temp2)) return -1;\r
965             *inc = temp2 * 1000;\r
966         } else *inc = 0;\r
967         *moves = 0; *tc = temp * 1000; \r
968         return 0;\r
969     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */\r
970 \r
971     (*str)++; /* classical time control */\r
972     result = NextTimeControlFromString( str, &temp2);\r
973     if(result == 0) {\r
974         *moves = temp/60;\r
975         *tc    = temp2 * 1000;\r
976         *inc   = 0;\r
977     }\r
978     return result;\r
979 }\r
980 \r
981 int GetTimeQuota(int movenr)\r
982 {   /* [HGM] get time to add from the multi-session time-control string */\r
983     int moves=1; /* kludge to force reading of first session */\r
984     long time, increment;\r
985     char *s = fullTimeControlString;\r
986 \r
987     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);\r
988     do {\r
989         if(moves) NextSessionFromString(&s, &moves, &time, &increment);\r
990         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);\r
991         if(movenr == -1) return time;    /* last move before new session     */\r
992         if(!moves) return increment;     /* current session is incremental   */\r
993         if(movenr >= 0) movenr -= moves; /* we already finished this session */\r
994     } while(movenr >= -1);               /* try again for next session       */\r
995 \r
996     return 0; // no new time quota on this move\r
997 }\r
998 \r
999 int\r
1000 ParseTimeControl(tc, ti, mps)\r
1001      char *tc;\r
1002      int ti;\r
1003      int mps;\r
1004 {\r
1005 #if 0\r
1006     int matched, min, sec;\r
1007 \r
1008     matched = sscanf(tc, "%d:%d", &min, &sec);\r
1009     if (matched == 1) {\r
1010         timeControl = min * 60 * 1000;\r
1011     } else if (matched == 2) {\r
1012         timeControl = (min * 60 + sec) * 1000;\r
1013     } else {\r
1014         return FALSE;\r
1015     }\r
1016 #else\r
1017     long tc1;\r
1018     long tc2;\r
1019     char buf[MSG_SIZ];\r
1020 \r
1021     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;\r
1022     if(ti > 0) {\r
1023         if(mps)\r
1024              sprintf(buf, "+%d/%s+%d", mps, tc, ti);\r
1025         else sprintf(buf, "+%s+%d", tc, ti);\r
1026     } else {\r
1027         if(mps)\r
1028              sprintf(buf, "+%d/%s", mps, tc);\r
1029         else sprintf(buf, "+%s", tc);\r
1030     }\r
1031     fullTimeControlString = StrSave(buf);\r
1032 \r
1033     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
1034         return FALSE;\r
1035     }\r
1036 \r
1037     if( *tc == '/' ) {\r
1038         /* Parse second time control */\r
1039         tc++;\r
1040 \r
1041         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
1042             return FALSE;\r
1043         }\r
1044 \r
1045         if( tc2 == 0 ) {\r
1046             return FALSE;\r
1047         }\r
1048 \r
1049         timeControl_2 = tc2 * 1000;\r
1050     }\r
1051     else {\r
1052         timeControl_2 = 0;\r
1053     }\r
1054 \r
1055     if( tc1 == 0 ) {\r
1056         return FALSE;\r
1057     }\r
1058 \r
1059     timeControl = tc1 * 1000;\r
1060 #endif\r
1061 \r
1062     if (ti >= 0) {\r
1063         timeIncrement = ti * 1000;  /* convert to ms */\r
1064         movesPerSession = 0;\r
1065     } else {\r
1066         timeIncrement = 0;\r
1067         movesPerSession = mps;\r
1068     }\r
1069     return TRUE;\r
1070 }\r
1071 \r
1072 void\r
1073 InitBackEnd2()\r
1074 {\r
1075     if (appData.debugMode) {\r
1076         fprintf(debugFP, "%s\n", programVersion);\r
1077     }\r
1078 \r
1079     if (appData.matchGames > 0) {\r
1080         appData.matchMode = TRUE;\r
1081     } else if (appData.matchMode) {\r
1082         appData.matchGames = 1;\r
1083     }\r
1084     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */\r
1085         appData.matchGames = appData.sameColorGames;\r
1086     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */\r
1087         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;\r
1088         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;\r
1089     }\r
1090     Reset(TRUE, FALSE);\r
1091     if (appData.noChessProgram || first.protocolVersion == 1) {\r
1092       InitBackEnd3();\r
1093     } else {\r
1094       /* kludge: allow timeout for initial "feature" commands */\r
1095       FreezeUI();\r
1096       DisplayMessage("", _("Starting chess program"));\r
1097       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
1098     }\r
1099 }\r
1100 \r
1101 void\r
1102 InitBackEnd3 P((void))\r
1103 {\r
1104     GameMode initialMode;\r
1105     char buf[MSG_SIZ];\r
1106     int err;\r
1107 \r
1108     InitChessProgram(&first, startedFromSetupPosition);\r
1109 \r
1110 \r
1111     if (appData.icsActive) {\r
1112 #ifdef WIN32\r
1113         /* [DM] Make a console window if needed [HGM] merged ifs */\r
1114         ConsoleCreate(); \r
1115 #endif\r
1116         err = establish();\r
1117         if (err != 0) {\r
1118             if (*appData.icsCommPort != NULLCHAR) {\r
1119                 sprintf(buf, _("Could not open comm port %s"),  \r
1120                         appData.icsCommPort);\r
1121             } else {\r
1122                 sprintf(buf, _("Could not connect to host %s, port %s"),  \r
1123                         appData.icsHost, appData.icsPort);\r
1124             }\r
1125             DisplayFatalError(buf, err, 1);\r
1126             return;\r
1127         }\r
1128         SetICSMode();\r
1129         telnetISR =\r
1130           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
1131         fromUserISR =\r
1132           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
1133     } else if (appData.noChessProgram) {\r
1134         SetNCPMode();\r
1135     } else {\r
1136         SetGNUMode();\r
1137     }\r
1138 \r
1139     if (*appData.cmailGameName != NULLCHAR) {\r
1140         SetCmailMode();\r
1141         OpenLoopback(&cmailPR);\r
1142         cmailISR =\r
1143           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
1144     }\r
1145     \r
1146     ThawUI();\r
1147     DisplayMessage("", "");\r
1148     if (StrCaseCmp(appData.initialMode, "") == 0) {\r
1149       initialMode = BeginningOfGame;\r
1150     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
1151       initialMode = TwoMachinesPlay;\r
1152     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
1153       initialMode = AnalyzeFile; \r
1154     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
1155       initialMode = AnalyzeMode;\r
1156     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
1157       initialMode = MachinePlaysWhite;\r
1158     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
1159       initialMode = MachinePlaysBlack;\r
1160     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
1161       initialMode = EditGame;\r
1162     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
1163       initialMode = EditPosition;\r
1164     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
1165       initialMode = Training;\r
1166     } else {\r
1167       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);\r
1168       DisplayFatalError(buf, 0, 2);\r
1169       return;\r
1170     }\r
1171 \r
1172     if (appData.matchMode) {\r
1173         /* Set up machine vs. machine match */\r
1174         if (appData.noChessProgram) {\r
1175             DisplayFatalError(_("Can't have a match with no chess programs"),\r
1176                               0, 2);\r
1177             return;\r
1178         }\r
1179         matchMode = TRUE;\r
1180         matchGame = 1;\r
1181         if (*appData.loadGameFile != NULLCHAR) {\r
1182             int index = appData.loadGameIndex; // [HGM] autoinc\r
1183             if(index<0) lastIndex = index = 1;\r
1184             if (!LoadGameFromFile(appData.loadGameFile,\r
1185                                   index,\r
1186                                   appData.loadGameFile, FALSE)) {\r
1187                 DisplayFatalError(_("Bad game file"), 0, 1);\r
1188                 return;\r
1189             }\r
1190         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1191             int index = appData.loadPositionIndex; // [HGM] autoinc\r
1192             if(index<0) lastIndex = index = 1;\r
1193             if (!LoadPositionFromFile(appData.loadPositionFile,\r
1194                                       index,\r
1195                                       appData.loadPositionFile)) {\r
1196                 DisplayFatalError(_("Bad position file"), 0, 1);\r
1197                 return;\r
1198             }\r
1199         }\r
1200         TwoMachinesEvent();\r
1201     } else if (*appData.cmailGameName != NULLCHAR) {\r
1202         /* Set up cmail mode */\r
1203         ReloadCmailMsgEvent(TRUE);\r
1204     } else {\r
1205         /* Set up other modes */\r
1206         if (initialMode == AnalyzeFile) {\r
1207           if (*appData.loadGameFile == NULLCHAR) {\r
1208             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);\r
1209             return;\r
1210           }\r
1211         }\r
1212         if (*appData.loadGameFile != NULLCHAR) {\r
1213             (void) LoadGameFromFile(appData.loadGameFile,\r
1214                                     appData.loadGameIndex,\r
1215                                     appData.loadGameFile, TRUE);\r
1216         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1217             (void) LoadPositionFromFile(appData.loadPositionFile,\r
1218                                         appData.loadPositionIndex,\r
1219                                         appData.loadPositionFile);\r
1220             /* [HGM] try to make self-starting even after FEN load */\r
1221             /* to allow automatic setup of fairy variants with wtm */\r
1222             if(initialMode == BeginningOfGame && !blackPlaysFirst) {\r
1223                 gameMode = BeginningOfGame;\r
1224                 setboardSpoiledMachineBlack = 1;\r
1225             }\r
1226             /* [HGM] loadPos: make that every new game uses the setup */\r
1227             /* from file as long as we do not switch variant          */\r
1228             if(!blackPlaysFirst) { int i;\r
1229                 startedFromPositionFile = TRUE;\r
1230                 CopyBoard(filePosition, boards[0]);\r
1231                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];\r
1232             }\r
1233         }\r
1234         if (initialMode == AnalyzeMode) {\r
1235           if (appData.noChessProgram) {\r
1236             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);\r
1237             return;\r
1238           }\r
1239           if (appData.icsActive) {\r
1240             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);\r
1241             return;\r
1242           }\r
1243           AnalyzeModeEvent();\r
1244         } else if (initialMode == AnalyzeFile) {\r
1245           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent\r
1246           ShowThinkingEvent();\r
1247           AnalyzeFileEvent();\r
1248           AnalysisPeriodicEvent(1);\r
1249         } else if (initialMode == MachinePlaysWhite) {\r
1250           if (appData.noChessProgram) {\r
1251             DisplayFatalError(_("MachineWhite mode requires a chess engine"),\r
1252                               0, 2);\r
1253             return;\r
1254           }\r
1255           if (appData.icsActive) {\r
1256             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),\r
1257                               0, 2);\r
1258             return;\r
1259           }\r
1260           MachineWhiteEvent();\r
1261         } else if (initialMode == MachinePlaysBlack) {\r
1262           if (appData.noChessProgram) {\r
1263             DisplayFatalError(_("MachineBlack mode requires a chess engine"),\r
1264                               0, 2);\r
1265             return;\r
1266           }\r
1267           if (appData.icsActive) {\r
1268             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),\r
1269                               0, 2);\r
1270             return;\r
1271           }\r
1272           MachineBlackEvent();\r
1273         } else if (initialMode == TwoMachinesPlay) {\r
1274           if (appData.noChessProgram) {\r
1275             DisplayFatalError(_("TwoMachines mode requires a chess engine"),\r
1276                               0, 2);\r
1277             return;\r
1278           }\r
1279           if (appData.icsActive) {\r
1280             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),\r
1281                               0, 2);\r
1282             return;\r
1283           }\r
1284           TwoMachinesEvent();\r
1285         } else if (initialMode == EditGame) {\r
1286           EditGameEvent();\r
1287         } else if (initialMode == EditPosition) {\r
1288           EditPositionEvent();\r
1289         } else if (initialMode == Training) {\r
1290           if (*appData.loadGameFile == NULLCHAR) {\r
1291             DisplayFatalError(_("Training mode requires a game file"), 0, 2);\r
1292             return;\r
1293           }\r
1294           TrainingEvent();\r
1295         }\r
1296     }\r
1297 }\r
1298 \r
1299 /*\r
1300  * Establish will establish a contact to a remote host.port.\r
1301  * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
1302  *  used to talk to the host.\r
1303  * Returns 0 if okay, error code if not.\r
1304  */\r
1305 int\r
1306 establish()\r
1307 {\r
1308     char buf[MSG_SIZ];\r
1309 \r
1310     if (*appData.icsCommPort != NULLCHAR) {\r
1311         /* Talk to the host through a serial comm port */\r
1312         return OpenCommPort(appData.icsCommPort, &icsPR);\r
1313 \r
1314     } else if (*appData.gateway != NULLCHAR) {\r
1315         if (*appData.remoteShell == NULLCHAR) {\r
1316             /* Use the rcmd protocol to run telnet program on a gateway host */\r
1317             sprintf(buf, "%s %s %s",\r
1318                     appData.telnetProgram, appData.icsHost, appData.icsPort);\r
1319             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
1320 \r
1321         } else {\r
1322             /* Use the rsh program to run telnet program on a gateway host */\r
1323             if (*appData.remoteUser == NULLCHAR) {\r
1324                 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,\r
1325                         appData.gateway, appData.telnetProgram,\r
1326                         appData.icsHost, appData.icsPort);\r
1327             } else {\r
1328                 sprintf(buf, "%s %s -l %s %s %s %s",\r
1329                         appData.remoteShell, appData.gateway, \r
1330                         appData.remoteUser, appData.telnetProgram,\r
1331                         appData.icsHost, appData.icsPort);\r
1332             }\r
1333             return StartChildProcess(buf, "", &icsPR);\r
1334 \r
1335         }\r
1336     } else if (appData.useTelnet) {\r
1337         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
1338 \r
1339     } else {\r
1340         /* TCP socket interface differs somewhat between\r
1341            Unix and NT; handle details in the front end.\r
1342            */\r
1343         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
1344     }\r
1345 }\r
1346 \r
1347 void\r
1348 show_bytes(fp, buf, count)\r
1349      FILE *fp;\r
1350      char *buf;\r
1351      int count;\r
1352 {\r
1353     while (count--) {\r
1354         if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
1355             fprintf(fp, "\\%03o", *buf & 0xff);\r
1356         } else {\r
1357             putc(*buf, fp);\r
1358         }\r
1359         buf++;\r
1360     }\r
1361     fflush(fp);\r
1362 }\r
1363 \r
1364 /* Returns an errno value */\r
1365 int\r
1366 OutputMaybeTelnet(pr, message, count, outError)\r
1367      ProcRef pr;\r
1368      char *message;\r
1369      int count;\r
1370      int *outError;\r
1371 {\r
1372     char buf[8192], *p, *q, *buflim;\r
1373     int left, newcount, outcount;\r
1374 \r
1375     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
1376         *appData.gateway != NULLCHAR) {\r
1377         if (appData.debugMode) {\r
1378             fprintf(debugFP, ">ICS: ");\r
1379             show_bytes(debugFP, message, count);\r
1380             fprintf(debugFP, "\n");\r
1381         }\r
1382         return OutputToProcess(pr, message, count, outError);\r
1383     }\r
1384 \r
1385     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
1386     p = message;\r
1387     q = buf;\r
1388     left = count;\r
1389     newcount = 0;\r
1390     while (left) {\r
1391         if (q >= buflim) {\r
1392             if (appData.debugMode) {\r
1393                 fprintf(debugFP, ">ICS: ");\r
1394                 show_bytes(debugFP, buf, newcount);\r
1395                 fprintf(debugFP, "\n");\r
1396             }\r
1397             outcount = OutputToProcess(pr, buf, newcount, outError);\r
1398             if (outcount < newcount) return -1; /* to be sure */\r
1399             q = buf;\r
1400             newcount = 0;\r
1401         }\r
1402         if (*p == '\n') {\r
1403             *q++ = '\r';\r
1404             newcount++;\r
1405         } else if (((unsigned char) *p) == TN_IAC) {\r
1406             *q++ = (char) TN_IAC;\r
1407             newcount ++;\r
1408         }\r
1409         *q++ = *p++;\r
1410         newcount++;\r
1411         left--;\r
1412     }\r
1413     if (appData.debugMode) {\r
1414         fprintf(debugFP, ">ICS: ");\r
1415         show_bytes(debugFP, buf, newcount);\r
1416         fprintf(debugFP, "\n");\r
1417     }\r
1418     outcount = OutputToProcess(pr, buf, newcount, outError);\r
1419     if (outcount < newcount) return -1; /* to be sure */\r
1420     return count;\r
1421 }\r
1422 \r
1423 void\r
1424 read_from_player(isr, closure, message, count, error)\r
1425      InputSourceRef isr;\r
1426      VOIDSTAR closure;\r
1427      char *message;\r
1428      int count;\r
1429      int error;\r
1430 {\r
1431     int outError, outCount;\r
1432     static int gotEof = 0;\r
1433 \r
1434     /* Pass data read from player on to ICS */\r
1435     if (count > 0) {\r
1436         gotEof = 0;\r
1437         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
1438         if (outCount < count) {\r
1439             DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1440         }\r
1441     } else if (count < 0) {\r
1442         RemoveInputSource(isr);\r
1443         DisplayFatalError(_("Error reading from keyboard"), error, 1);\r
1444     } else if (gotEof++ > 0) {\r
1445         RemoveInputSource(isr);\r
1446         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);\r
1447     }\r
1448 }\r
1449 \r
1450 void\r
1451 SendToICS(s)\r
1452      char *s;\r
1453 {\r
1454     int count, outCount, outError;\r
1455 \r
1456     if (icsPR == NULL) return;\r
1457 \r
1458     count = strlen(s);\r
1459     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
1460     if (outCount < count) {\r
1461         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1462     }\r
1463 }\r
1464 \r
1465 /* This is used for sending logon scripts to the ICS. Sending\r
1466    without a delay causes problems when using timestamp on ICC\r
1467    (at least on my machine). */\r
1468 void\r
1469 SendToICSDelayed(s,msdelay)\r
1470      char *s;\r
1471      long msdelay;\r
1472 {\r
1473     int count, outCount, outError;\r
1474 \r
1475     if (icsPR == NULL) return;\r
1476 \r
1477     count = strlen(s);\r
1478     if (appData.debugMode) {\r
1479         fprintf(debugFP, ">ICS: ");\r
1480         show_bytes(debugFP, s, count);\r
1481         fprintf(debugFP, "\n");\r
1482     }\r
1483     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
1484                                       msdelay);\r
1485     if (outCount < count) {\r
1486         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1487     }\r
1488 }\r
1489 \r
1490 \r
1491 /* Remove all highlighting escape sequences in s\r
1492    Also deletes any suffix starting with '(' \r
1493    */\r
1494 char *\r
1495 StripHighlightAndTitle(s)\r
1496      char *s;\r
1497 {\r
1498     static char retbuf[MSG_SIZ];\r
1499     char *p = retbuf;\r
1500 \r
1501     while (*s != NULLCHAR) {\r
1502         while (*s == '\033') {\r
1503             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1504             if (*s != NULLCHAR) s++;\r
1505         }\r
1506         while (*s != NULLCHAR && *s != '\033') {\r
1507             if (*s == '(' || *s == '[') {\r
1508                 *p = NULLCHAR;\r
1509                 return retbuf;\r
1510             }\r
1511             *p++ = *s++;\r
1512         }\r
1513     }\r
1514     *p = NULLCHAR;\r
1515     return retbuf;\r
1516 }\r
1517 \r
1518 /* Remove all highlighting escape sequences in s */\r
1519 char *\r
1520 StripHighlight(s)\r
1521      char *s;\r
1522 {\r
1523     static char retbuf[MSG_SIZ];\r
1524     char *p = retbuf;\r
1525 \r
1526     while (*s != NULLCHAR) {\r
1527         while (*s == '\033') {\r
1528             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1529             if (*s != NULLCHAR) s++;\r
1530         }\r
1531         while (*s != NULLCHAR && *s != '\033') {\r
1532             *p++ = *s++;\r
1533         }\r
1534     }\r
1535     *p = NULLCHAR;\r
1536     return retbuf;\r
1537 }\r
1538 \r
1539 char *variantNames[] = VARIANT_NAMES;\r
1540 char *\r
1541 VariantName(v)\r
1542      VariantClass v;\r
1543 {\r
1544     return variantNames[v];\r
1545 }\r
1546 \r
1547 \r
1548 /* Identify a variant from the strings the chess servers use or the\r
1549    PGN Variant tag names we use. */\r
1550 VariantClass\r
1551 StringToVariant(e)\r
1552      char *e;\r
1553 {\r
1554     char *p;\r
1555     int wnum = -1;\r
1556     VariantClass v = VariantNormal;\r
1557     int i, found = FALSE;\r
1558     char buf[MSG_SIZ];\r
1559 \r
1560     if (!e) return v;\r
1561 \r
1562     /* [HGM] skip over optional board-size prefixes */\r
1563     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||\r
1564         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
1565         while( *e++ != '_');\r
1566     }\r
1567 \r
1568     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1569       if (StrCaseStr(e, variantNames[i])) {\r
1570         v = (VariantClass) i;\r
1571         found = TRUE;\r
1572         break;\r
1573       }\r
1574     }\r
1575 \r
1576     if (!found) {\r
1577       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1578           || StrCaseStr(e, "wild/fr") \r
1579           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {\r
1580         v = VariantFischeRandom;\r
1581       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1582                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1583         p += i;\r
1584         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1585         if (isdigit(*p)) {\r
1586           wnum = atoi(p);\r
1587         } else {\r
1588           wnum = -1;\r
1589         }\r
1590         switch (wnum) {\r
1591         case 0: /* FICS only, actually */\r
1592         case 1:\r
1593           /* Castling legal even if K starts on d-file */\r
1594           v = VariantWildCastle;\r
1595           break;\r
1596         case 2:\r
1597         case 3:\r
1598         case 4:\r
1599           /* Castling illegal even if K & R happen to start in\r
1600              normal positions. */\r
1601           v = VariantNoCastle;\r
1602           break;\r
1603         case 5:\r
1604         case 7:\r
1605         case 8:\r
1606         case 10:\r
1607         case 11:\r
1608         case 12:\r
1609         case 13:\r
1610         case 14:\r
1611         case 15:\r
1612         case 18:\r
1613         case 19:\r
1614           /* Castling legal iff K & R start in normal positions */\r
1615           v = VariantNormal;\r
1616           break;\r
1617         case 6:\r
1618         case 20:\r
1619         case 21:\r
1620           /* Special wilds for position setup; unclear what to do here */\r
1621           v = VariantLoadable;\r
1622           break;\r
1623         case 9:\r
1624           /* Bizarre ICC game */\r
1625           v = VariantTwoKings;\r
1626           break;\r
1627         case 16:\r
1628           v = VariantKriegspiel;\r
1629           break;\r
1630         case 17:\r
1631           v = VariantLosers;\r
1632           break;\r
1633         case 22:\r
1634           v = VariantFischeRandom;\r
1635           break;\r
1636         case 23:\r
1637           v = VariantCrazyhouse;\r
1638           break;\r
1639         case 24:\r
1640           v = VariantBughouse;\r
1641           break;\r
1642         case 25:\r
1643           v = Variant3Check;\r
1644           break;\r
1645         case 26:\r
1646           /* Not quite the same as FICS suicide! */\r
1647           v = VariantGiveaway;\r
1648           break;\r
1649         case 27:\r
1650           v = VariantAtomic;\r
1651           break;\r
1652         case 28:\r
1653           v = VariantShatranj;\r
1654           break;\r
1655 \r
1656         /* Temporary names for future ICC types.  The name *will* change in \r
1657            the next xboard/WinBoard release after ICC defines it. */\r
1658         case 29:\r
1659           v = Variant29;\r
1660           break;\r
1661         case 30:\r
1662           v = Variant30;\r
1663           break;\r
1664         case 31:\r
1665           v = Variant31;\r
1666           break;\r
1667         case 32:\r
1668           v = Variant32;\r
1669           break;\r
1670         case 33:\r
1671           v = Variant33;\r
1672           break;\r
1673         case 34:\r
1674           v = Variant34;\r
1675           break;\r
1676         case 35:\r
1677           v = Variant35;\r
1678           break;\r
1679         case 36:\r
1680           v = Variant36;\r
1681           break;\r
1682         case 37:\r
1683           v = VariantShogi;\r
1684           break;\r
1685         case 38:\r
1686           v = VariantXiangqi;\r
1687           break;\r
1688         case 39:\r
1689           v = VariantCourier;\r
1690           break;\r
1691         case 40:\r
1692           v = VariantGothic;\r
1693           break;\r
1694         case 41:\r
1695           v = VariantCapablanca;\r
1696           break;\r
1697         case 42:\r
1698           v = VariantKnightmate;\r
1699           break;\r
1700         case 43:\r
1701           v = VariantFairy;\r
1702           break;\r
1703         case 44:\r
1704           v = VariantCylinder;\r
1705           break;\r
1706         case 45:\r
1707           v = VariantFalcon;\r
1708           break;\r
1709         case 46:\r
1710           v = VariantCapaRandom;\r
1711           break;\r
1712         case 47:\r
1713           v = VariantBerolina;\r
1714           break;\r
1715         case 48:\r
1716           v = VariantJanus;\r
1717           break;\r
1718         case 49:\r
1719           v = VariantSuper;\r
1720           break;\r
1721         case 50:\r
1722           v = VariantGreat;\r
1723           break;\r
1724         case -1:\r
1725           /* Found "wild" or "w" in the string but no number;\r
1726              must assume it's normal chess. */\r
1727           v = VariantNormal;\r
1728           break;\r
1729         default:\r
1730           sprintf(buf, _("Unknown wild type %d"), wnum);\r
1731           DisplayError(buf, 0);\r
1732           v = VariantUnknown;\r
1733           break;\r
1734         }\r
1735       }\r
1736     }\r
1737     if (appData.debugMode) {\r
1738       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),\r
1739               e, wnum, VariantName(v));\r
1740     }\r
1741     return v;\r
1742 }\r
1743 \r
1744 static int leftover_start = 0, leftover_len = 0;\r
1745 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1746 \r
1747 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1748    advance *index beyond it, and set leftover_start to the new value of\r
1749    *index; else return FALSE.  If pattern contains the character '*', it\r
1750    matches any sequence of characters not containing '\r', '\n', or the\r
1751    character following the '*' (if any), and the matched sequence(s) are\r
1752    copied into star_match.\r
1753    */\r
1754 int\r
1755 looking_at(buf, index, pattern)\r
1756      char *buf;\r
1757      int *index;\r
1758      char *pattern;\r
1759 {\r
1760     char *bufp = &buf[*index], *patternp = pattern;\r
1761     int star_count = 0;\r
1762     char *matchp = star_match[0];\r
1763     \r
1764     for (;;) {\r
1765         if (*patternp == NULLCHAR) {\r
1766             *index = leftover_start = bufp - buf;\r
1767             *matchp = NULLCHAR;\r
1768             return TRUE;\r
1769         }\r
1770         if (*bufp == NULLCHAR) return FALSE;\r
1771         if (*patternp == '*') {\r
1772             if (*bufp == *(patternp + 1)) {\r
1773                 *matchp = NULLCHAR;\r
1774                 matchp = star_match[++star_count];\r
1775                 patternp += 2;\r
1776                 bufp++;\r
1777                 continue;\r
1778             } else if (*bufp == '\n' || *bufp == '\r') {\r
1779                 patternp++;\r
1780                 if (*patternp == NULLCHAR)\r
1781                   continue;\r
1782                 else\r
1783                   return FALSE;\r
1784             } else {\r
1785                 *matchp++ = *bufp++;\r
1786                 continue;\r
1787             }\r
1788         }\r
1789         if (*patternp != *bufp) return FALSE;\r
1790         patternp++;\r
1791         bufp++;\r
1792     }\r
1793 }\r
1794 \r
1795 void\r
1796 SendToPlayer(data, length)\r
1797      char *data;\r
1798      int length;\r
1799 {\r
1800     int error, outCount;\r
1801     outCount = OutputToProcess(NoProc, data, length, &error);\r
1802     if (outCount < length) {\r
1803         DisplayFatalError(_("Error writing to display"), error, 1);\r
1804     }\r
1805 }\r
1806 \r
1807 void\r
1808 PackHolding(packed, holding)\r
1809      char packed[];\r
1810      char *holding;\r
1811 {\r
1812     char *p = holding;\r
1813     char *q = packed;\r
1814     int runlength = 0;\r
1815     int curr = 9999;\r
1816     do {\r
1817         if (*p == curr) {\r
1818             runlength++;\r
1819         } else {\r
1820             switch (runlength) {\r
1821               case 0:\r
1822                 break;\r
1823               case 1:\r
1824                 *q++ = curr;\r
1825                 break;\r
1826               case 2:\r
1827                 *q++ = curr;\r
1828                 *q++ = curr;\r
1829                 break;\r
1830               default:\r
1831                 sprintf(q, "%d", runlength);\r
1832                 while (*q) q++;\r
1833                 *q++ = curr;\r
1834                 break;\r
1835             }\r
1836             runlength = 1;\r
1837             curr = *p;\r
1838         }\r
1839     } while (*p++);\r
1840     *q = NULLCHAR;\r
1841 }\r
1842 \r
1843 /* Telnet protocol requests from the front end */\r
1844 void\r
1845 TelnetRequest(ddww, option)\r
1846      unsigned char ddww, option;\r
1847 {\r
1848     unsigned char msg[3];\r
1849     int outCount, outError;\r
1850 \r
1851     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1852 \r
1853     if (appData.debugMode) {\r
1854         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1855         switch (ddww) {\r
1856           case TN_DO:\r
1857             ddwwStr = "DO";\r
1858             break;\r
1859           case TN_DONT:\r
1860             ddwwStr = "DONT";\r
1861             break;\r
1862           case TN_WILL:\r
1863             ddwwStr = "WILL";\r
1864             break;\r
1865           case TN_WONT:\r
1866             ddwwStr = "WONT";\r
1867             break;\r
1868           default:\r
1869             ddwwStr = buf1;\r
1870             sprintf(buf1, "%d", ddww);\r
1871             break;\r
1872         }\r
1873         switch (option) {\r
1874           case TN_ECHO:\r
1875             optionStr = "ECHO";\r
1876             break;\r
1877           default:\r
1878             optionStr = buf2;\r
1879             sprintf(buf2, "%d", option);\r
1880             break;\r
1881         }\r
1882         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1883     }\r
1884     msg[0] = TN_IAC;\r
1885     msg[1] = ddww;\r
1886     msg[2] = option;\r
1887     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1888     if (outCount < 3) {\r
1889         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1890     }\r
1891 }\r
1892 \r
1893 void\r
1894 DoEcho()\r
1895 {\r
1896     if (!appData.icsActive) return;\r
1897     TelnetRequest(TN_DO, TN_ECHO);\r
1898 }\r
1899 \r
1900 void\r
1901 DontEcho()\r
1902 {\r
1903     if (!appData.icsActive) return;\r
1904     TelnetRequest(TN_DONT, TN_ECHO);\r
1905 }\r
1906 \r
1907 void\r
1908 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
1909 {\r
1910     /* put the holdings sent to us by the server on the board holdings area */\r
1911     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
1912     char p;\r
1913     ChessSquare piece;\r
1914 \r
1915     if(gameInfo.holdingsWidth < 2)  return;\r
1916 \r
1917     if( (int)lowestPiece >= BlackPawn ) {\r
1918         holdingsColumn = 0;\r
1919         countsColumn = 1;\r
1920         holdingsStartRow = BOARD_HEIGHT-1;\r
1921         direction = -1;\r
1922     } else {\r
1923         holdingsColumn = BOARD_WIDTH-1;\r
1924         countsColumn = BOARD_WIDTH-2;\r
1925         holdingsStartRow = 0;\r
1926         direction = 1;\r
1927     }\r
1928 \r
1929     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
1930         board[i][holdingsColumn] = EmptySquare;\r
1931         board[i][countsColumn]   = (ChessSquare) 0;\r
1932     }\r
1933     while( (p=*holdings++) != NULLCHAR ) {\r
1934         piece = CharToPiece( ToUpper(p) );\r
1935         if(piece == EmptySquare) continue;\r
1936         /*j = (int) piece - (int) WhitePawn;*/\r
1937         j = PieceToNumber(piece);\r
1938         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1939         if(j < 0) continue;               /* should not happen */\r
1940         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );\r
1941         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
1942         board[holdingsStartRow+j*direction][countsColumn]++;\r
1943     }\r
1944 \r
1945 }\r
1946 \r
1947 \r
1948 void\r
1949 VariantSwitch(Board board, VariantClass newVariant)\r
1950 {\r
1951    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;\r
1952    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;\r
1953 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;\r
1954 \r
1955    startedFromPositionFile = FALSE;\r
1956    if(gameInfo.variant == newVariant) return;\r
1957 \r
1958    /* [HGM] This routine is called each time an assignment is made to\r
1959     * gameInfo.variant during a game, to make sure the board sizes\r
1960     * are set to match the new variant. If that means adding or deleting\r
1961     * holdings, we shift the playing board accordingly\r
1962     * This kludge is needed because in ICS observe mode, we get boards\r
1963     * of an ongoing game without knowing the variant, and learn about the\r
1964     * latter only later. This can be because of the move list we requested,\r
1965     * in which case the game history is refilled from the beginning anyway,\r
1966     * but also when receiving holdings of a crazyhouse game. In the latter\r
1967     * case we want to add those holdings to the already received position.\r
1968     */\r
1969 \r
1970 \r
1971   if (appData.debugMode) {\r
1972     fprintf(debugFP, "Switch board from %s to %s\n",\r
1973                VariantName(gameInfo.variant), VariantName(newVariant));\r
1974     setbuf(debugFP, NULL);\r
1975   }\r
1976     shuffleOpenings = 0;       /* [HGM] shuffle */\r
1977     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
1978     switch(newVariant) {\r
1979             case VariantShogi:\r
1980               newWidth = 9;  newHeight = 9;\r
1981               gameInfo.holdingsSize = 7;\r
1982             case VariantBughouse:\r
1983             case VariantCrazyhouse:\r
1984               newHoldingsWidth = 2; break;\r
1985             default:\r
1986               newHoldingsWidth = gameInfo.holdingsSize = 0;\r
1987     }\r
1988 \r
1989     if(newWidth  != gameInfo.boardWidth  ||\r
1990        newHeight != gameInfo.boardHeight ||\r
1991        newHoldingsWidth != gameInfo.holdingsWidth ) {\r
1992 \r
1993         /* shift position to new playing area, if needed */\r
1994         if(newHoldingsWidth > gameInfo.holdingsWidth) {\r
1995            for(i=0; i<BOARD_HEIGHT; i++) \r
1996                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)\r
1997                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
1998                                                      board[i][j];\r
1999            for(i=0; i<newHeight; i++) {\r
2000                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;\r
2001                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;\r
2002            }\r
2003         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {\r
2004            for(i=0; i<BOARD_HEIGHT; i++)\r
2005                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
2006                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
2007                                                  board[i][j];\r
2008         }\r
2009 \r
2010         gameInfo.boardWidth  = newWidth;\r
2011         gameInfo.boardHeight = newHeight;\r
2012         gameInfo.holdingsWidth = newHoldingsWidth;\r
2013         gameInfo.variant = newVariant;\r
2014         InitDrawingSizes(-2, 0);\r
2015 \r
2016         /* [HGM] The following should definitely be solved in a better way */\r
2017 #if 0\r
2018         CopyBoard(board, tempBoard); /* save position in case it is board[0] */\r
2019         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];\r
2020         saveEP = epStatus[0];\r
2021 #endif\r
2022         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */\r
2023 #if 0\r
2024         epStatus[0] = saveEP;\r
2025         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];\r
2026         CopyBoard(tempBoard, board); /* restore position received from ICS   */\r
2027 #endif\r
2028     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }\r
2029 \r
2030     forwardMostMove = oldForwardMostMove;\r
2031     backwardMostMove = oldBackwardMostMove;\r
2032     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */\r
2033 }\r
2034 \r
2035 static int loggedOn = FALSE;\r
2036 \r
2037 /*-- Game start info cache: --*/\r
2038 int gs_gamenum;\r
2039 char gs_kind[MSG_SIZ];\r
2040 static char player1Name[128] = "";\r
2041 static char player2Name[128] = "";\r
2042 static int player1Rating = -1;\r
2043 static int player2Rating = -1;\r
2044 /*----------------------------*/\r
2045 \r
2046 ColorClass curColor = ColorNormal;\r
2047 int suppressKibitz = 0;\r
2048 \r
2049 void\r
2050 read_from_ics(isr, closure, data, count, error)\r
2051      InputSourceRef isr;\r
2052      VOIDSTAR closure;\r
2053      char *data;\r
2054      int count;\r
2055      int error;\r
2056 {\r
2057 #define BUF_SIZE 8192\r
2058 #define STARTED_NONE 0\r
2059 #define STARTED_MOVES 1\r
2060 #define STARTED_BOARD 2\r
2061 #define STARTED_OBSERVE 3\r
2062 #define STARTED_HOLDINGS 4\r
2063 #define STARTED_CHATTER 5\r
2064 #define STARTED_COMMENT 6\r
2065 #define STARTED_MOVES_NOHIDE 7\r
2066     \r
2067     static int started = STARTED_NONE;\r
2068     static char parse[20000];\r
2069     static int parse_pos = 0;\r
2070     static char buf[BUF_SIZE + 1];\r
2071     static int firstTime = TRUE, intfSet = FALSE;\r
2072     static ColorClass prevColor = ColorNormal;\r
2073     static int savingComment = FALSE;\r
2074     char str[500];\r
2075     int i, oldi;\r
2076     int buf_len;\r
2077     int next_out;\r
2078     int tkind;\r
2079     int backup;    /* [DM] For zippy color lines */\r
2080     char *p;\r
2081 \r
2082     if (appData.debugMode) {\r
2083       if (!error) {\r
2084         fprintf(debugFP, "<ICS: ");\r
2085         show_bytes(debugFP, data, count);\r
2086         fprintf(debugFP, "\n");\r
2087       }\r
2088     }\r
2089 \r
2090     if (appData.debugMode) { int f = forwardMostMove;\r
2091         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,\r
2092                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
2093     }\r
2094     if (count > 0) {\r
2095         /* If last read ended with a partial line that we couldn't parse,\r
2096            prepend it to the new read and try again. */\r
2097         if (leftover_len > 0) {\r
2098             for (i=0; i<leftover_len; i++)\r
2099               buf[i] = buf[leftover_start + i];\r
2100         }\r
2101 \r
2102         /* Copy in new characters, removing nulls and \r's */\r
2103         buf_len = leftover_len;\r
2104         for (i = 0; i < count; i++) {\r
2105             if (data[i] != NULLCHAR && data[i] != '\r')\r
2106               buf[buf_len++] = data[i];\r
2107             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && \r
2108                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') \r
2109                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous\r
2110         }\r
2111 \r
2112         buf[buf_len] = NULLCHAR;\r
2113         next_out = leftover_len;\r
2114         leftover_start = 0;\r
2115         \r
2116         i = 0;\r
2117         while (i < buf_len) {\r
2118             /* Deal with part of the TELNET option negotiation\r
2119                protocol.  We refuse to do anything beyond the\r
2120                defaults, except that we allow the WILL ECHO option,\r
2121                which ICS uses to turn off password echoing when we are\r
2122                directly connected to it.  We reject this option\r
2123                if localLineEditing mode is on (always on in xboard)\r
2124                and we are talking to port 23, which might be a real\r
2125                telnet server that will try to keep WILL ECHO on permanently.\r
2126              */\r
2127             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
2128                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
2129                 unsigned char option;\r
2130                 oldi = i;\r
2131                 switch ((unsigned char) buf[++i]) {\r
2132                   case TN_WILL:\r
2133                     if (appData.debugMode)\r
2134                       fprintf(debugFP, "\n<WILL ");\r
2135                     switch (option = (unsigned char) buf[++i]) {\r
2136                       case TN_ECHO:\r
2137                         if (appData.debugMode)\r
2138                           fprintf(debugFP, "ECHO ");\r
2139                         /* Reply only if this is a change, according\r
2140                            to the protocol rules. */\r
2141                         if (remoteEchoOption) break;\r
2142                         if (appData.localLineEditing &&\r
2143                             atoi(appData.icsPort) == TN_PORT) {\r
2144                             TelnetRequest(TN_DONT, TN_ECHO);\r
2145                         } else {\r
2146                             EchoOff();\r
2147                             TelnetRequest(TN_DO, TN_ECHO);\r
2148                             remoteEchoOption = TRUE;\r
2149                         }\r
2150                         break;\r
2151                       default:\r
2152                         if (appData.debugMode)\r
2153                           fprintf(debugFP, "%d ", option);\r
2154                         /* Whatever this is, we don't want it. */\r
2155                         TelnetRequest(TN_DONT, option);\r
2156                         break;\r
2157                     }\r
2158                     break;\r
2159                   case TN_WONT:\r
2160                     if (appData.debugMode)\r
2161                       fprintf(debugFP, "\n<WONT ");\r
2162                     switch (option = (unsigned char) buf[++i]) {\r
2163                       case TN_ECHO:\r
2164                         if (appData.debugMode)\r
2165                           fprintf(debugFP, "ECHO ");\r
2166                         /* Reply only if this is a change, according\r
2167                            to the protocol rules. */\r
2168                         if (!remoteEchoOption) break;\r
2169                         EchoOn();\r
2170                         TelnetRequest(TN_DONT, TN_ECHO);\r
2171                         remoteEchoOption = FALSE;\r
2172                         break;\r
2173                       default:\r
2174                         if (appData.debugMode)\r
2175                           fprintf(debugFP, "%d ", (unsigned char) option);\r
2176                         /* Whatever this is, it must already be turned\r
2177                            off, because we never agree to turn on\r
2178                            anything non-default, so according to the\r
2179                            protocol rules, we don't reply. */\r
2180                         break;\r
2181                     }\r
2182                     break;\r
2183                   case TN_DO:\r
2184                     if (appData.debugMode)\r
2185                       fprintf(debugFP, "\n<DO ");\r
2186                     switch (option = (unsigned char) buf[++i]) {\r
2187                       default:\r
2188                         /* Whatever this is, we refuse to do it. */\r
2189                         if (appData.debugMode)\r
2190                           fprintf(debugFP, "%d ", option);\r
2191                         TelnetRequest(TN_WONT, option);\r
2192                         break;\r
2193                     }\r
2194                     break;\r
2195                   case TN_DONT:\r
2196                     if (appData.debugMode)\r
2197                       fprintf(debugFP, "\n<DONT ");\r
2198                     switch (option = (unsigned char) buf[++i]) {\r
2199                       default:\r
2200                         if (appData.debugMode)\r
2201                           fprintf(debugFP, "%d ", option);\r
2202                         /* Whatever this is, we are already not doing\r
2203                            it, because we never agree to do anything\r
2204                            non-default, so according to the protocol\r
2205                            rules, we don't reply. */\r
2206                         break;\r
2207                     }\r
2208                     break;\r
2209                   case TN_IAC:\r
2210                     if (appData.debugMode)\r
2211                       fprintf(debugFP, "\n<IAC ");\r
2212                     /* Doubled IAC; pass it through */\r
2213                     i--;\r
2214                     break;\r
2215                   default:\r
2216                     if (appData.debugMode)\r
2217                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
2218                     /* Drop all other telnet commands on the floor */\r
2219                     break;\r
2220                 }\r
2221                 if (oldi > next_out)\r
2222                   SendToPlayer(&buf[next_out], oldi - next_out);\r
2223                 if (++i > next_out)\r
2224                   next_out = i;\r
2225                 continue;\r
2226             }\r
2227                 \r
2228             /* OK, this at least will *usually* work */\r
2229             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
2230                 loggedOn = TRUE;\r
2231             }\r
2232             \r
2233             if (loggedOn && !intfSet) {\r
2234                 if (ics_type == ICS_ICC) {\r
2235                   sprintf(str,\r
2236                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
2237                           programVersion);\r
2238 \r
2239                 } else if (ics_type == ICS_CHESSNET) {\r
2240                   sprintf(str, "/style 12\n");\r
2241                 } else {\r
2242                   strcpy(str, "alias $ @\n$set interface ");\r
2243                   strcat(str, programVersion);\r
2244                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
2245 #ifdef WIN32\r
2246                   strcat(str, "$iset nohighlight 1\n");\r
2247 #endif\r
2248                   strcat(str, "$iset lock 1\n$style 12\n");\r
2249                 }\r
2250                 SendToICS(str);\r
2251                 intfSet = TRUE;\r
2252             }\r
2253 \r
2254             if (started == STARTED_COMMENT) {\r
2255                 /* Accumulate characters in comment */\r
2256                 parse[parse_pos++] = buf[i];\r
2257                 if (buf[i] == '\n') {\r
2258                     parse[parse_pos] = NULLCHAR;\r
2259                     if(!suppressKibitz) // [HGM] kibitz\r
2260                         AppendComment(forwardMostMove, StripHighlight(parse));\r
2261                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window\r
2262                         int nrDigit = 0, nrAlph = 0, i;\r
2263                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input\r
2264                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }\r
2265                         parse[parse_pos] = NULLCHAR;\r
2266                         // try to be smart: if it does not look like search info, it should go to\r
2267                         // ICS interaction window after all, not to engine-output window.\r
2268                         for(i=0; i<parse_pos; i++) { // count letters and digits\r
2269                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');\r
2270                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');\r
2271                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');\r
2272                         }\r
2273                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info\r
2274                             OutputKibitz(suppressKibitz, parse);\r
2275                         } else {\r
2276                             char tmp[MSG_SIZ];\r
2277                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);\r
2278                             SendToPlayer(tmp, strlen(tmp));\r
2279                         }\r
2280                     }\r
2281                     started = STARTED_NONE;\r
2282                 } else {\r
2283                     /* Don't match patterns against characters in chatter */\r
2284                     i++;\r
2285                     continue;\r
2286                 }\r
2287             }\r
2288             if (started == STARTED_CHATTER) {\r
2289                 if (buf[i] != '\n') {\r
2290                     /* Don't match patterns against characters in chatter */\r
2291                     i++;\r
2292                     continue;\r
2293                 }\r
2294                 started = STARTED_NONE;\r
2295             }\r
2296 \r
2297             /* Kludge to deal with rcmd protocol */\r
2298             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2299                 DisplayFatalError(&buf[1], 0, 1);\r
2300                 continue;\r
2301             } else {\r
2302                 firstTime = FALSE;\r
2303             }\r
2304 \r
2305             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2306                 ics_type = ICS_ICC;\r
2307                 ics_prefix = "/";\r
2308                 if (appData.debugMode)\r
2309                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2310                 continue;\r
2311             }\r
2312             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2313                 ics_type = ICS_FICS;\r
2314                 ics_prefix = "$";\r
2315                 if (appData.debugMode)\r
2316                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2317                 continue;\r
2318             }\r
2319             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2320                 ics_type = ICS_CHESSNET;\r
2321                 ics_prefix = "/";\r
2322                 if (appData.debugMode)\r
2323                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2324                 continue;\r
2325             }\r
2326 \r
2327             if (!loggedOn &&\r
2328                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2329                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2330                  looking_at(buf, &i, "will be \"*\""))) {\r
2331               strcpy(ics_handle, star_match[0]);\r
2332               continue;\r
2333             }\r
2334 \r
2335             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2336               char buf[MSG_SIZ];\r
2337               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2338               DisplayIcsInteractionTitle(buf);\r
2339               have_set_title = TRUE;\r
2340             }\r
2341 \r
2342             /* skip finger notes */\r
2343             if (started == STARTED_NONE &&\r
2344                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2345                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2346                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2347               started = STARTED_CHATTER;\r
2348               i += 3;\r
2349               continue;\r
2350             }\r
2351 \r
2352             /* skip formula vars */\r
2353             if (started == STARTED_NONE &&\r
2354                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2355               started = STARTED_CHATTER;\r
2356               i += 3;\r
2357               continue;\r
2358             }\r
2359 \r
2360             oldi = i;\r
2361             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window\r
2362             if (appData.autoKibitz && started == STARTED_NONE && \r
2363                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze\r
2364                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {\r
2365                 if(looking_at(buf, &i, "* kibitzes: ") &&\r
2366                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || \r
2367                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent\r
2368                         suppressKibitz = TRUE;\r
2369                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]\r
2370                                 && (gameMode == IcsPlayingWhite)) ||\r
2371                            (StrStr(star_match[0], gameInfo.black) == star_match[0]\r
2372                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz\r
2373                             started = STARTED_CHATTER; // own kibitz we simply discard\r
2374                         else {\r
2375                             started = STARTED_COMMENT; // make sure it will be collected in parse[]\r
2376                             parse_pos = 0; parse[0] = NULLCHAR;\r
2377                             savingComment = TRUE;\r
2378                             suppressKibitz = gameMode != IcsObserving ? 2 :\r
2379                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;\r
2380                         } \r
2381                         continue;\r
2382                 } else\r
2383                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz\r
2384                     started = STARTED_CHATTER;\r
2385                     suppressKibitz = TRUE;\r
2386                 }\r
2387             } // [HGM] kibitz: end of patch\r
2388 \r
2389             if (appData.zippyTalk || appData.zippyPlay) {\r
2390                 /* [DM] Backup address for color zippy lines */\r
2391                 backup = i;\r
2392 #if ZIPPY\r
2393        #ifdef WIN32\r
2394                if (loggedOn == TRUE)\r
2395                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||\r
2396                           (appData.zippyPlay && ZippyMatch(buf, &backup)));\r
2397        #else\r
2398                 if (ZippyControl(buf, &i) ||\r
2399                     ZippyConverse(buf, &i) ||\r
2400                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2401                       loggedOn = TRUE;\r
2402                       if (!appData.colorize) continue;\r
2403                 }\r
2404        #endif\r
2405 #endif\r
2406             } // [DM] 'else { ' deleted\r
2407                 if (/* Don't color "message" or "messages" output */\r
2408                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2409                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2410                     looking_at(buf, &i, "--* (*:*): ") ||\r
2411                     /* Regular tells and says */\r
2412                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2413                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2414                     looking_at(buf, &i, "* says: ") ||\r
2415                     /* Message notifications (same color as tells) */\r
2416                     looking_at(buf, &i, "* has left a message ") ||\r
2417                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2418                     /* Whispers and kibitzes */\r
2419                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2420                     looking_at(buf, &i, "* kibitzes: ") ||\r
2421                     /* Channel tells */\r
2422                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2423 \r
2424                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2425                       /* Avoid "tells you:" spoofs in channels */\r
2426                      tkind = 3;\r
2427                   }\r
2428                   if (star_match[0][0] == NULLCHAR ||\r
2429                       strchr(star_match[0], ' ') ||\r
2430                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2431                     /* Reject bogus matches */\r
2432                     i = oldi;\r
2433                   } else {\r
2434                     if (appData.colorize) {\r
2435                       if (oldi > next_out) {\r
2436                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2437                         next_out = oldi;\r
2438                       }\r
2439                       switch (tkind) {\r
2440                       case 1:\r
2441                         Colorize(ColorTell, FALSE);\r
2442                         curColor = ColorTell;\r
2443                         break;\r
2444                       case 2:\r
2445                         Colorize(ColorKibitz, FALSE);\r
2446                         curColor = ColorKibitz;\r
2447                         break;\r
2448                       case 3:\r
2449                         p = strrchr(star_match[1], '(');\r
2450                         if (p == NULL) {\r
2451                           p = star_match[1];\r
2452                         } else {\r
2453                           p++;\r
2454                         }\r
2455                         if (atoi(p) == 1) {\r
2456                           Colorize(ColorChannel1, FALSE);\r
2457                           curColor = ColorChannel1;\r
2458                         } else {\r
2459                           Colorize(ColorChannel, FALSE);\r
2460                           curColor = ColorChannel;\r
2461                         }\r
2462                         break;\r
2463                       case 5:\r
2464                         curColor = ColorNormal;\r
2465                         break;\r
2466                       }\r
2467                     }\r
2468                     if (started == STARTED_NONE && appData.autoComment &&\r
2469                         (gameMode == IcsObserving ||\r
2470                          gameMode == IcsPlayingWhite ||\r
2471                          gameMode == IcsPlayingBlack)) {\r
2472                       parse_pos = i - oldi;\r
2473                       memcpy(parse, &buf[oldi], parse_pos);\r
2474                       parse[parse_pos] = NULLCHAR;\r
2475                       started = STARTED_COMMENT;\r
2476                       savingComment = TRUE;\r
2477                     } else {\r
2478                       started = STARTED_CHATTER;\r
2479                       savingComment = FALSE;\r
2480                     }\r
2481                     loggedOn = TRUE;\r
2482                     continue;\r
2483                   }\r
2484                 }\r
2485 \r
2486                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2487                     looking_at(buf, &i, "* c-shouts: ")) {\r
2488                     if (appData.colorize) {\r
2489                         if (oldi > next_out) {\r
2490                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2491                             next_out = oldi;\r
2492                         }\r
2493                         Colorize(ColorSShout, FALSE);\r
2494                         curColor = ColorSShout;\r
2495                     }\r
2496                     loggedOn = TRUE;\r
2497                     started = STARTED_CHATTER;\r
2498                     continue;\r
2499                 }\r
2500 \r
2501                 if (looking_at(buf, &i, "--->")) {\r
2502                     loggedOn = TRUE;\r
2503                     continue;\r
2504                 }\r
2505 \r
2506                 if (looking_at(buf, &i, "* shouts: ") ||\r
2507                     looking_at(buf, &i, "--> ")) {\r
2508                     if (appData.colorize) {\r
2509                         if (oldi > next_out) {\r
2510                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2511                             next_out = oldi;\r
2512                         }\r
2513                         Colorize(ColorShout, FALSE);\r
2514                         curColor = ColorShout;\r
2515                     }\r
2516                     loggedOn = TRUE;\r
2517                     started = STARTED_CHATTER;\r
2518                     continue;\r
2519                 }\r
2520 \r
2521                 if (looking_at( buf, &i, "Challenge:")) {\r
2522                     if (appData.colorize) {\r
2523                         if (oldi > next_out) {\r
2524                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2525                             next_out = oldi;\r
2526                         }\r
2527                         Colorize(ColorChallenge, FALSE);\r
2528                         curColor = ColorChallenge;\r
2529                     }\r
2530                     loggedOn = TRUE;\r
2531                     continue;\r
2532                 }\r
2533 \r
2534                 if (looking_at(buf, &i, "* offers you") ||\r
2535                     looking_at(buf, &i, "* offers to be") ||\r
2536                     looking_at(buf, &i, "* would like to") ||\r
2537                     looking_at(buf, &i, "* requests to") ||\r
2538                     looking_at(buf, &i, "Your opponent offers") ||\r
2539                     looking_at(buf, &i, "Your opponent requests")) {\r
2540 \r
2541                     if (appData.colorize) {\r
2542                         if (oldi > next_out) {\r
2543                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2544                             next_out = oldi;\r
2545                         }\r
2546                         Colorize(ColorRequest, FALSE);\r
2547                         curColor = ColorRequest;\r
2548                     }\r
2549                     continue;\r
2550                 }\r
2551 \r
2552                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2553                     if (appData.colorize) {\r
2554                         if (oldi > next_out) {\r
2555                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2556                             next_out = oldi;\r
2557                         }\r
2558                         Colorize(ColorSeek, FALSE);\r
2559                         curColor = ColorSeek;\r
2560                     }\r
2561                     continue;\r
2562             }\r
2563 \r
2564             if (looking_at(buf, &i, "\\   ")) {\r
2565                 if (prevColor != ColorNormal) {\r
2566                     if (oldi > next_out) {\r
2567                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2568                         next_out = oldi;\r
2569                     }\r
2570                     Colorize(prevColor, TRUE);\r
2571                     curColor = prevColor;\r
2572                 }\r
2573                 if (savingComment) {\r
2574                     parse_pos = i - oldi;\r
2575                     memcpy(parse, &buf[oldi], parse_pos);\r
2576                     parse[parse_pos] = NULLCHAR;\r
2577                     started = STARTED_COMMENT;\r
2578                 } else {\r
2579                     started = STARTED_CHATTER;\r
2580                 }\r
2581                 continue;\r
2582             }\r
2583 \r
2584             if (looking_at(buf, &i, "Black Strength :") ||\r
2585                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2586                 looking_at(buf, &i, "<10>") ||\r
2587                 looking_at(buf, &i, "#@#")) {\r
2588                 /* Wrong board style */\r
2589                 loggedOn = TRUE;\r
2590                 SendToICS(ics_prefix);\r
2591                 SendToICS("set style 12\n");\r
2592                 SendToICS(ics_prefix);\r
2593                 SendToICS("refresh\n");\r
2594                 continue;\r
2595             }\r
2596             \r
2597             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2598                 ICSInitScript();\r
2599                 have_sent_ICS_logon = 1;\r
2600                 continue;\r
2601             }\r
2602               \r
2603             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2604                 (looking_at(buf, &i, "\n<12> ") ||\r
2605                  looking_at(buf, &i, "<12> "))) {\r
2606                 loggedOn = TRUE;\r
2607                 if (oldi > next_out) {\r
2608                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2609                 }\r
2610                 next_out = i;\r
2611                 started = STARTED_BOARD;\r
2612                 parse_pos = 0;\r
2613                 continue;\r
2614             }\r
2615 \r
2616             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2617                 looking_at(buf, &i, "<b1> ")) {\r
2618                 if (oldi > next_out) {\r
2619                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2620                 }\r
2621                 next_out = i;\r
2622                 started = STARTED_HOLDINGS;\r
2623                 parse_pos = 0;\r
2624                 continue;\r
2625             }\r
2626 \r
2627             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2628                 loggedOn = TRUE;\r
2629                 /* Header for a move list -- first line */\r
2630 \r
2631                 switch (ics_getting_history) {\r
2632                   case H_FALSE:\r
2633                     switch (gameMode) {\r
2634                       case IcsIdle:\r
2635                       case BeginningOfGame:\r
2636                         /* User typed "moves" or "oldmoves" while we\r
2637                            were idle.  Pretend we asked for these\r
2638                            moves and soak them up so user can step\r
2639                            through them and/or save them.\r
2640                            */\r
2641                         Reset(FALSE, TRUE);\r
2642                         gameMode = IcsObserving;\r
2643                         ModeHighlight();\r
2644                         ics_gamenum = -1;\r
2645                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2646                         break;\r
2647                       case EditGame: /*?*/\r
2648                       case EditPosition: /*?*/\r
2649                         /* Should above feature work in these modes too? */\r
2650                         /* For now it doesn't */\r
2651                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2652                         break;\r
2653                       default:\r
2654                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2655                         break;\r
2656                     }\r
2657                     break;\r
2658                   case H_REQUESTED:\r
2659                     /* Is this the right one? */\r
2660                     if (gameInfo.white && gameInfo.black &&\r
2661                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2662                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2663                         /* All is well */\r
2664                         ics_getting_history = H_GOT_REQ_HEADER;\r
2665                     }\r
2666                     break;\r
2667                   case H_GOT_REQ_HEADER:\r
2668                   case H_GOT_UNREQ_HEADER:\r
2669                   case H_GOT_UNWANTED_HEADER:\r
2670                   case H_GETTING_MOVES:\r
2671                     /* Should not happen */\r
2672                     DisplayError(_("Error gathering move list: two headers"), 0);\r
2673                     ics_getting_history = H_FALSE;\r
2674                     break;\r
2675                 }\r
2676 \r
2677                 /* Save player ratings into gameInfo if needed */\r
2678                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2679                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2680                     (gameInfo.whiteRating == -1 ||\r
2681                      gameInfo.blackRating == -1)) {\r
2682 \r
2683                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2684                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2685                     if (appData.debugMode)\r
2686                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), \r
2687                               gameInfo.whiteRating, gameInfo.blackRating);\r
2688                 }\r
2689                 continue;\r
2690             }\r
2691 \r
2692             if (looking_at(buf, &i,\r
2693               "* * match, initial time: * minute*, increment: * second")) {\r
2694                 /* Header for a move list -- second line */\r
2695                 /* Initial board will follow if this is a wild game */\r
2696                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2697                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2698                 gameInfo.event = StrSave(str);\r
2699                 /* [HGM] we switched variant. Translate boards if needed. */\r
2700                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2701                 continue;\r
2702             }\r
2703 \r
2704             if (looking_at(buf, &i, "Move  ")) {\r
2705                 /* Beginning of a move list */\r
2706                 switch (ics_getting_history) {\r
2707                   case H_FALSE:\r
2708                     /* Normally should not happen */\r
2709                     /* Maybe user hit reset while we were parsing */\r
2710                     break;\r
2711                   case H_REQUESTED:\r
2712                     /* Happens if we are ignoring a move list that is not\r
2713                      * the one we just requested.  Common if the user\r
2714                      * tries to observe two games without turning off\r
2715                      * getMoveList */\r
2716                     break;\r
2717                   case H_GETTING_MOVES:\r
2718                     /* Should not happen */\r
2719                     DisplayError(_("Error gathering move list: nested"), 0);\r
2720                     ics_getting_history = H_FALSE;\r
2721                     break;\r
2722                   case H_GOT_REQ_HEADER:\r
2723                     ics_getting_history = H_GETTING_MOVES;\r
2724                     started = STARTED_MOVES;\r
2725                     parse_pos = 0;\r
2726                     if (oldi > next_out) {\r
2727                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2728                     }\r
2729                     break;\r
2730                   case H_GOT_UNREQ_HEADER:\r
2731                     ics_getting_history = H_GETTING_MOVES;\r
2732                     started = STARTED_MOVES_NOHIDE;\r
2733                     parse_pos = 0;\r
2734                     break;\r
2735                   case H_GOT_UNWANTED_HEADER:\r
2736                     ics_getting_history = H_FALSE;\r
2737                     break;\r
2738                 }\r
2739                 continue;\r
2740             }                           \r
2741             \r
2742             if (looking_at(buf, &i, "% ") ||\r
2743                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2744                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book\r
2745                 savingComment = FALSE;\r
2746                 switch (started) {\r
2747                   case STARTED_MOVES:\r
2748                   case STARTED_MOVES_NOHIDE:\r
2749                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2750                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2751                     ParseGameHistory(parse);\r
2752 #if ZIPPY\r
2753                     if (appData.zippyPlay && first.initDone) {\r
2754                         FeedMovesToProgram(&first, forwardMostMove);\r
2755                         if (gameMode == IcsPlayingWhite) {\r
2756                             if (WhiteOnMove(forwardMostMove)) {\r
2757                                 if (first.sendTime) {\r
2758                                   if (first.useColors) {\r
2759                                     SendToProgram("black\n", &first); \r
2760                                   }\r
2761                                   SendTimeRemaining(&first, TRUE);\r
2762                                 }\r
2763 #if 0\r
2764                                 if (first.useColors) {\r
2765                                   SendToProgram("white\ngo\n", &first);\r
2766                                 } else {\r
2767                                   SendToProgram("go\n", &first);\r
2768                                 }\r
2769 #else\r
2770                                 if (first.useColors) {\r
2771                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent\r
2772                                 }\r
2773                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos\r
2774 #endif\r
2775                                 first.maybeThinking = TRUE;\r
2776                             } else {\r
2777                                 if (first.usePlayother) {\r
2778                                   if (first.sendTime) {\r
2779                                     SendTimeRemaining(&first, TRUE);\r
2780                                   }\r
2781                                   SendToProgram("playother\n", &first);\r
2782                                   firstMove = FALSE;\r
2783                                 } else {\r
2784                                   firstMove = TRUE;\r
2785                                 }\r
2786                             }\r
2787                         } else if (gameMode == IcsPlayingBlack) {\r
2788                             if (!WhiteOnMove(forwardMostMove)) {\r
2789                                 if (first.sendTime) {\r
2790                                   if (first.useColors) {\r
2791                                     SendToProgram("white\n", &first);\r
2792                                   }\r
2793                                   SendTimeRemaining(&first, FALSE);\r
2794                                 }\r
2795 #if 0\r
2796                                 if (first.useColors) {\r
2797                                   SendToProgram("black\ngo\n", &first);\r
2798                                 } else {\r
2799                                   SendToProgram("go\n", &first);\r
2800                                 }\r
2801 #else\r
2802                                 if (first.useColors) {\r
2803                                   SendToProgram("black\n", &first);\r
2804                                 }\r
2805                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);\r
2806 #endif\r
2807                                 first.maybeThinking = TRUE;\r
2808                             } else {\r
2809                                 if (first.usePlayother) {\r
2810                                   if (first.sendTime) {\r
2811                                     SendTimeRemaining(&first, FALSE);\r
2812                                   }\r
2813                                   SendToProgram("playother\n", &first);\r
2814                                   firstMove = FALSE;\r
2815                                 } else {\r
2816                                   firstMove = TRUE;\r
2817                                 }\r
2818                             }\r
2819                         }                       \r
2820                     }\r
2821 #endif\r
2822                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2823                         /* Moves came from oldmoves or moves command\r
2824                            while we weren't doing anything else.\r
2825                            */\r
2826                         currentMove = forwardMostMove;\r
2827                         ClearHighlights();/*!!could figure this out*/\r
2828                         flipView = appData.flipView;\r
2829                         DrawPosition(FALSE, boards[currentMove]);\r
2830                         DisplayBothClocks();\r
2831                         sprintf(str, "%s vs. %s",\r
2832                                 gameInfo.white, gameInfo.black);\r
2833                         DisplayTitle(str);\r
2834                         gameMode = IcsIdle;\r
2835                     } else {\r
2836                         /* Moves were history of an active game */\r
2837                         if (gameInfo.resultDetails != NULL) {\r
2838                             free(gameInfo.resultDetails);\r
2839                             gameInfo.resultDetails = NULL;\r
2840                         }\r
2841                     }\r
2842                     HistorySet(parseList, backwardMostMove,\r
2843                                forwardMostMove, currentMove-1);\r
2844                     DisplayMove(currentMove - 1);\r
2845                     if (started == STARTED_MOVES) next_out = i;\r
2846                     started = STARTED_NONE;\r
2847                     ics_getting_history = H_FALSE;\r
2848                     break;\r
2849 \r
2850                   case STARTED_OBSERVE:\r
2851                     started = STARTED_NONE;\r
2852                     SendToICS(ics_prefix);\r
2853                     SendToICS("refresh\n");\r
2854                     break;\r
2855 \r
2856                   default:\r
2857                     break;\r
2858                 }\r
2859                 if(bookHit) { // [HGM] book: simulate book reply\r
2860                     static char bookMove[MSG_SIZ]; // a bit generous?\r
2861 \r
2862                     programStats.nodes = programStats.depth = programStats.time = \r
2863                     programStats.score = programStats.got_only_move = 0;\r
2864                     sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
2865 \r
2866                     strcpy(bookMove, "move ");\r
2867                     strcat(bookMove, bookHit);\r
2868                     HandleMachineMove(bookMove, &first);\r
2869                 }\r
2870                 continue;\r
2871             }\r
2872             \r
2873             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2874                  started == STARTED_HOLDINGS ||\r
2875                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2876                 /* Accumulate characters in move list or board */\r
2877                 parse[parse_pos++] = buf[i];\r
2878             }\r
2879             \r
2880             /* Start of game messages.  Mostly we detect start of game\r
2881                when the first board image arrives.  On some versions\r
2882                of the ICS, though, we need to do a "refresh" after starting\r
2883                to observe in order to get the current board right away. */\r
2884             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2885                 started = STARTED_OBSERVE;\r
2886                 continue;\r
2887             }\r
2888 \r
2889             /* Handle auto-observe */\r
2890             if (appData.autoObserve &&\r
2891                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2892                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2893                 char *player;\r
2894                 /* Choose the player that was highlighted, if any. */\r
2895                 if (star_match[0][0] == '\033' ||\r
2896                     star_match[1][0] != '\033') {\r
2897                     player = star_match[0];\r
2898                 } else {\r
2899                     player = star_match[2];\r
2900                 }\r
2901                 sprintf(str, "%sobserve %s\n",\r
2902                         ics_prefix, StripHighlightAndTitle(player));\r
2903                 SendToICS(str);\r
2904 \r
2905                 /* Save ratings from notify string */\r
2906                 strcpy(player1Name, star_match[0]);\r
2907                 player1Rating = string_to_rating(star_match[1]);\r
2908                 strcpy(player2Name, star_match[2]);\r
2909                 player2Rating = string_to_rating(star_match[3]);\r
2910 \r
2911                 if (appData.debugMode)\r
2912                   fprintf(debugFP, \r
2913                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2914                           player1Name, player1Rating,\r
2915                           player2Name, player2Rating);\r
2916 \r
2917                 continue;\r
2918             }\r
2919 \r
2920             /* Deal with automatic examine mode after a game,\r
2921                and with IcsObserving -> IcsExamining transition */\r
2922             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2923                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2924 \r
2925                 int gamenum = atoi(star_match[0]);\r
2926                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2927                     gamenum == ics_gamenum) {\r
2928                     /* We were already playing or observing this game;\r
2929                        no need to refetch history */\r
2930                     gameMode = IcsExamining;\r
2931                     if (pausing) {\r
2932                         pauseExamForwardMostMove = forwardMostMove;\r
2933                     } else if (currentMove < forwardMostMove) {\r
2934                         ForwardInner(forwardMostMove);\r
2935                     }\r
2936                 } else {\r
2937                     /* I don't think this case really can happen */\r
2938                     SendToICS(ics_prefix);\r
2939                     SendToICS("refresh\n");\r
2940                 }\r
2941                 continue;\r
2942             }    \r
2943             \r
2944             /* Error messages */\r
2945             if (ics_user_moved) {\r
2946                 if (looking_at(buf, &i, "Illegal move") ||\r
2947                     looking_at(buf, &i, "Not a legal move") ||\r
2948                     looking_at(buf, &i, "Your king is in check") ||\r
2949                     looking_at(buf, &i, "It isn't your turn") ||\r
2950                     looking_at(buf, &i, "It is not your move")) {\r
2951                     /* Illegal move */\r
2952                     ics_user_moved = 0;\r
2953                     if (forwardMostMove > backwardMostMove) {\r
2954                         currentMove = --forwardMostMove;\r
2955                         DisplayMove(currentMove - 1); /* before DMError */\r
2956                         DisplayMoveError(_("Illegal move (rejected by ICS)"));\r
2957                         DrawPosition(FALSE, boards[currentMove]);\r
2958                         SwitchClocks();\r
2959                         DisplayBothClocks();\r
2960                     }\r
2961                     continue;\r
2962                 }\r
2963             }\r
2964 \r
2965             if (looking_at(buf, &i, "still have time") ||\r
2966                 looking_at(buf, &i, "not out of time") ||\r
2967                 looking_at(buf, &i, "either player is out of time") ||\r
2968                 looking_at(buf, &i, "has timeseal; checking")) {\r
2969                 /* We must have called his flag a little too soon */\r
2970                 whiteFlag = blackFlag = FALSE;\r
2971                 continue;\r
2972             }\r
2973 \r
2974             if (looking_at(buf, &i, "added * seconds to") ||\r
2975                 looking_at(buf, &i, "seconds were added to")) {\r
2976                 /* Update the clocks */\r
2977                 SendToICS(ics_prefix);\r
2978                 SendToICS("refresh\n");\r
2979                 continue;\r
2980             }\r
2981 \r
2982             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2983                 ics_clock_paused = TRUE;\r
2984                 StopClocks();\r
2985                 continue;\r
2986             }\r
2987 \r
2988             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2989                 ics_clock_paused = FALSE;\r
2990                 StartClocks();\r
2991                 continue;\r
2992             }\r
2993 \r
2994             /* Grab player ratings from the Creating: message.\r
2995                Note we have to check for the special case when\r
2996                the ICS inserts things like [white] or [black]. */\r
2997             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
2998                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
2999                 /* star_matches:\r
3000                    0    player 1 name (not necessarily white)\r
3001                    1    player 1 rating\r
3002                    2    empty, white, or black (IGNORED)\r
3003                    3    player 2 name (not necessarily black)\r
3004                    4    player 2 rating\r
3005                    \r
3006                    The names/ratings are sorted out when the game\r
3007                    actually starts (below).\r
3008                 */\r
3009                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
3010                 player1Rating = string_to_rating(star_match[1]);\r
3011                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
3012                 player2Rating = string_to_rating(star_match[4]);\r
3013 \r
3014                 if (appData.debugMode)\r
3015                   fprintf(debugFP, \r
3016                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
3017                           player1Name, player1Rating,\r
3018                           player2Name, player2Rating);\r
3019 \r
3020                 continue;\r
3021             }\r
3022             \r
3023             /* Improved generic start/end-of-game messages */\r
3024             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
3025                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
3026                 /* If tkind == 0: */\r
3027                 /* star_match[0] is the game number */\r
3028                 /*           [1] is the white player's name */\r
3029                 /*           [2] is the black player's name */\r
3030                 /* For end-of-game: */\r
3031                 /*           [3] is the reason for the game end */\r
3032                 /*           [4] is a PGN end game-token, preceded by " " */\r
3033                 /* For start-of-game: */\r
3034                 /*           [3] begins with "Creating" or "Continuing" */\r
3035                 /*           [4] is " *" or empty (don't care). */\r
3036                 int gamenum = atoi(star_match[0]);\r
3037                 char *whitename, *blackname, *why, *endtoken;\r
3038                 ChessMove endtype = (ChessMove) 0;\r
3039 \r
3040                 if (tkind == 0) {\r
3041                   whitename = star_match[1];\r
3042                   blackname = star_match[2];\r
3043                   why = star_match[3];\r
3044                   endtoken = star_match[4];\r
3045                 } else {\r
3046                   whitename = star_match[1];\r
3047                   blackname = star_match[3];\r
3048                   why = star_match[5];\r
3049                   endtoken = star_match[6];\r
3050                 }\r
3051 \r
3052                 /* Game start messages */\r
3053                 if (strncmp(why, "Creating ", 9) == 0 ||\r
3054                     strncmp(why, "Continuing ", 11) == 0) {\r
3055                     gs_gamenum = gamenum;\r
3056                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
3057 #if ZIPPY\r
3058                     if (appData.zippyPlay) {\r
3059                         ZippyGameStart(whitename, blackname);\r
3060                     }\r
3061 #endif /*ZIPPY*/\r
3062                     continue;\r
3063                 }\r
3064 \r
3065                 /* Game end messages */\r
3066                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
3067                     ics_gamenum != gamenum) {\r
3068                     continue;\r
3069                 }\r
3070                 while (endtoken[0] == ' ') endtoken++;\r
3071                 switch (endtoken[0]) {\r
3072                   case '*':\r
3073                   default:\r
3074                     endtype = GameUnfinished;\r
3075                     break;\r
3076                   case '0':\r
3077                     endtype = BlackWins;\r
3078                     break;\r
3079                   case '1':\r
3080                     if (endtoken[1] == '/')\r
3081                       endtype = GameIsDrawn;\r
3082                     else\r
3083                       endtype = WhiteWins;\r
3084                     break;\r
3085                 }\r
3086                 GameEnds(endtype, why, GE_ICS);\r
3087 #if ZIPPY\r
3088                 if (appData.zippyPlay && first.initDone) {\r
3089                     ZippyGameEnd(endtype, why);\r
3090                     if (first.pr == NULL) {\r
3091                       /* Start the next process early so that we'll\r
3092                          be ready for the next challenge */\r
3093                       StartChessProgram(&first);\r
3094                     }\r
3095                     /* Send "new" early, in case this command takes\r
3096                        a long time to finish, so that we'll be ready\r
3097                        for the next challenge. */\r
3098                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'\r
3099                     Reset(TRUE, TRUE);\r
3100                 }\r
3101 #endif /*ZIPPY*/\r
3102                 continue;\r
3103             }\r
3104 \r
3105             if (looking_at(buf, &i, "Removing game * from observation") ||\r
3106                 looking_at(buf, &i, "no longer observing game *") ||\r
3107                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
3108                 if (gameMode == IcsObserving &&\r
3109                     atoi(star_match[0]) == ics_gamenum)\r
3110                   {\r
3111                       /* icsEngineAnalyze */\r
3112                       if (appData.icsEngineAnalyze) {\r
3113                             ExitAnalyzeMode();\r
3114                             ModeHighlight();\r
3115                       }\r
3116                       StopClocks();\r
3117                       gameMode = IcsIdle;\r
3118                       ics_gamenum = -1;\r
3119                       ics_user_moved = FALSE;\r
3120                   }\r
3121                 continue;\r
3122             }\r
3123 \r
3124             if (looking_at(buf, &i, "no longer examining game *")) {\r
3125                 if (gameMode == IcsExamining &&\r
3126                     atoi(star_match[0]) == ics_gamenum)\r
3127                   {\r
3128                       gameMode = IcsIdle;\r
3129                       ics_gamenum = -1;\r
3130                       ics_user_moved = FALSE;\r
3131                   }\r
3132                 continue;\r
3133             }\r
3134 \r
3135             /* Advance leftover_start past any newlines we find,\r
3136                so only partial lines can get reparsed */\r
3137             if (looking_at(buf, &i, "\n")) {\r
3138                 prevColor = curColor;\r
3139                 if (curColor != ColorNormal) {\r
3140                     if (oldi > next_out) {\r
3141                         SendToPlayer(&buf[next_out], oldi - next_out);\r
3142                         next_out = oldi;\r
3143                     }\r
3144                     Colorize(ColorNormal, FALSE);\r
3145                     curColor = ColorNormal;\r
3146                 }\r
3147                 if (started == STARTED_BOARD) {\r
3148                     started = STARTED_NONE;\r
3149                     parse[parse_pos] = NULLCHAR;\r
3150                     ParseBoard12(parse);\r
3151                     ics_user_moved = 0;\r
3152 \r
3153                     /* Send premove here */\r
3154                     if (appData.premove) {\r
3155                       char str[MSG_SIZ];\r
3156                       if (currentMove == 0 &&\r
3157                           gameMode == IcsPlayingWhite &&\r
3158                           appData.premoveWhite) {\r
3159                         sprintf(str, "%s%s\n", ics_prefix,\r
3160                                 appData.premoveWhiteText);\r
3161                         if (appData.debugMode)\r
3162                           fprintf(debugFP, "Sending premove:\n");\r
3163                         SendToICS(str);\r
3164                       } else if (currentMove == 1 &&\r
3165                                  gameMode == IcsPlayingBlack &&\r
3166                                  appData.premoveBlack) {\r
3167                         sprintf(str, "%s%s\n", ics_prefix,\r
3168                                 appData.premoveBlackText);\r
3169                         if (appData.debugMode)\r
3170                           fprintf(debugFP, "Sending premove:\n");\r
3171                         SendToICS(str);\r
3172                       } else if (gotPremove) {\r
3173                         gotPremove = 0;\r
3174                         ClearPremoveHighlights();\r
3175                         if (appData.debugMode)\r
3176                           fprintf(debugFP, "Sending premove:\n");\r
3177                           UserMoveEvent(premoveFromX, premoveFromY, \r
3178                                         premoveToX, premoveToY, \r
3179                                         premovePromoChar);\r
3180                       }\r
3181                     }\r
3182 \r
3183                     /* Usually suppress following prompt */\r
3184                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
3185                         if (looking_at(buf, &i, "*% ")) {\r
3186                             savingComment = FALSE;\r
3187                         }\r
3188                     }\r
3189                     next_out = i;\r
3190                 } else if (started == STARTED_HOLDINGS) {\r
3191                     int gamenum;\r
3192                     char new_piece[MSG_SIZ];\r
3193                     started = STARTED_NONE;\r
3194                     parse[parse_pos] = NULLCHAR;\r
3195                     if (appData.debugMode)\r
3196                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
3197                                                         parse, currentMove);\r
3198                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
3199                         gamenum == ics_gamenum) {\r
3200                         if (gameInfo.variant == VariantNormal) {\r
3201                           /* [HGM] We seem to switch variant during a game!\r
3202                            * Presumably no holdings were displayed, so we have\r
3203                            * to move the position two files to the right to\r
3204                            * create room for them!\r
3205                            */\r
3206                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
3207                           /* Get a move list just to see the header, which\r
3208                              will tell us whether this is really bug or zh */\r
3209                           if (ics_getting_history == H_FALSE) {\r
3210                             ics_getting_history = H_REQUESTED;\r
3211                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3212                             SendToICS(str);\r
3213                           }\r
3214                         }\r
3215                         new_piece[0] = NULLCHAR;\r
3216                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
3217                                &gamenum, white_holding, black_holding,\r
3218                                new_piece);\r
3219                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
3220                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
3221                         /* [HGM] copy holdings to board holdings area */\r
3222                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
3223                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
3224 #if ZIPPY\r
3225                         if (appData.zippyPlay && first.initDone) {\r
3226                             ZippyHoldings(white_holding, black_holding,\r
3227                                           new_piece);\r
3228                         }\r
3229 #endif /*ZIPPY*/\r
3230                         if (tinyLayout || smallLayout) {\r
3231                             char wh[16], bh[16];\r
3232                             PackHolding(wh, white_holding);\r
3233                             PackHolding(bh, black_holding);\r
3234                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
3235                                     gameInfo.white, gameInfo.black);\r
3236                         } else {\r
3237                             sprintf(str, "%s [%s] vs. %s [%s]",\r
3238                                     gameInfo.white, white_holding,\r
3239                                     gameInfo.black, black_holding);\r
3240                         }\r
3241 \r
3242                         DrawPosition(FALSE, boards[currentMove]);\r
3243                         DisplayTitle(str);\r
3244                     }\r
3245                     /* Suppress following prompt */\r
3246                     if (looking_at(buf, &i, "*% ")) {\r
3247                         savingComment = FALSE;\r
3248                     }\r
3249                     next_out = i;\r
3250                 }\r
3251                 continue;\r
3252             }\r
3253 \r
3254             i++;                /* skip unparsed character and loop back */\r
3255         }\r
3256         \r
3257         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window\r
3258             started != STARTED_HOLDINGS && i > next_out) {\r
3259             SendToPlayer(&buf[next_out], i - next_out);\r
3260             next_out = i;\r
3261         }\r
3262         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above\r
3263         \r
3264         leftover_len = buf_len - leftover_start;\r
3265         /* if buffer ends with something we couldn't parse,\r
3266            reparse it after appending the next read */\r
3267         \r
3268     } else if (count == 0) {\r
3269         RemoveInputSource(isr);\r
3270         DisplayFatalError(_("Connection closed by ICS"), 0, 0);\r
3271     } else {\r
3272         DisplayFatalError(_("Error reading from ICS"), error, 1);\r
3273     }\r
3274 }\r
3275 \r
3276 \r
3277 /* Board style 12 looks like this:\r
3278    \r
3279    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0\r
3280    \r
3281  * The "<12> " is stripped before it gets to this routine.  The two\r
3282  * trailing 0's (flip state and clock ticking) are later addition, and\r
3283  * some chess servers may not have them, or may have only the first.\r
3284  * Additional trailing fields may be added in the future.  \r
3285  */\r
3286 \r
3287 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"\r
3288 \r
3289 #define RELATION_OBSERVING_PLAYED    0\r
3290 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
3291 #define RELATION_PLAYING_MYMOVE      1\r
3292 #define RELATION_PLAYING_NOTMYMOVE  -1\r
3293 #define RELATION_EXAMINING           2\r
3294 #define RELATION_ISOLATED_BOARD     -3\r
3295 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
3296 \r
3297 void\r
3298 ParseBoard12(string)\r
3299      char *string;\r
3300\r
3301     GameMode newGameMode;\r
3302     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;\r
3303     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;\r
3304     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
3305     char to_play, board_chars[200];\r
3306     char move_str[500], str[500], elapsed_time[500];\r
3307     char black[32], white[32];\r
3308     Board board;\r
3309     int prevMove = currentMove;\r
3310     int ticking = 2;\r
3311     ChessMove moveType;\r
3312     int fromX, fromY, toX, toY;\r
3313     char promoChar;\r
3314     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */\r
3315     char *bookHit = NULL; // [HGM] book\r
3316 \r
3317     fromX = fromY = toX = toY = -1;\r
3318     \r
3319     newGame = FALSE;\r
3320 \r
3321     if (appData.debugMode)\r
3322       fprintf(debugFP, _("Parsing board: %s\n"), string);\r
3323 \r
3324     move_str[0] = NULLCHAR;\r
3325     elapsed_time[0] = NULLCHAR;\r
3326     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */\r
3327         int  i = 0, j;\r
3328         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {\r
3329             if(string[i] == ' ') { ranks++; files = 0; }\r
3330             else files++;\r
3331             i++;\r
3332         }\r
3333         for(j = 0; j <i; j++) board_chars[j] = string[j];\r
3334         board_chars[i] = '\0';\r
3335         string += i + 1;\r
3336     }\r
3337     n = sscanf(string, PATTERN, &to_play, &double_push,\r
3338                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
3339                &gamenum, white, black, &relation, &basetime, &increment,\r
3340                &white_stren, &black_stren, &white_time, &black_time,\r
3341                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
3342                &ticking);\r
3343 \r
3344     if (n < 21) {\r
3345         sprintf(str, _("Failed to parse board string:\n\"%s\""), string);\r
3346         DisplayError(str, 0);\r
3347         return;\r
3348     }\r
3349 \r
3350     /* Convert the move number to internal form */\r
3351     moveNum = (moveNum - 1) * 2;\r
3352     if (to_play == 'B') moveNum++;\r
3353     if (moveNum >= MAX_MOVES) {\r
3354       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
3355                         0, 1);\r
3356       return;\r
3357     }\r
3358     \r
3359     switch (relation) {\r
3360       case RELATION_OBSERVING_PLAYED:\r
3361       case RELATION_OBSERVING_STATIC:\r
3362         if (gamenum == -1) {\r
3363             /* Old ICC buglet */\r
3364             relation = RELATION_OBSERVING_STATIC;\r
3365         }\r
3366         newGameMode = IcsObserving;\r
3367         break;\r
3368       case RELATION_PLAYING_MYMOVE:\r
3369       case RELATION_PLAYING_NOTMYMOVE:\r
3370         newGameMode =\r
3371           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3372             IcsPlayingWhite : IcsPlayingBlack;\r
3373         break;\r
3374       case RELATION_EXAMINING:\r
3375         newGameMode = IcsExamining;\r
3376         break;\r
3377       case RELATION_ISOLATED_BOARD:\r
3378       default:\r
3379         /* Just display this board.  If user was doing something else,\r
3380            we will forget about it until the next board comes. */ \r
3381         newGameMode = IcsIdle;\r
3382         break;\r
3383       case RELATION_STARTING_POSITION:\r
3384         newGameMode = gameMode;\r
3385         break;\r
3386     }\r
3387     \r
3388     /* Modify behavior for initial board display on move listing\r
3389        of wild games.\r
3390        */\r
3391     switch (ics_getting_history) {\r
3392       case H_FALSE:\r
3393       case H_REQUESTED:\r
3394         break;\r
3395       case H_GOT_REQ_HEADER:\r
3396       case H_GOT_UNREQ_HEADER:\r
3397         /* This is the initial position of the current game */\r
3398         gamenum = ics_gamenum;\r
3399         moveNum = 0;            /* old ICS bug workaround */\r
3400         if (to_play == 'B') {\r
3401           startedFromSetupPosition = TRUE;\r
3402           blackPlaysFirst = TRUE;\r
3403           moveNum = 1;\r
3404           if (forwardMostMove == 0) forwardMostMove = 1;\r
3405           if (backwardMostMove == 0) backwardMostMove = 1;\r
3406           if (currentMove == 0) currentMove = 1;\r
3407         }\r
3408         newGameMode = gameMode;\r
3409         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3410         break;\r
3411       case H_GOT_UNWANTED_HEADER:\r
3412         /* This is an initial board that we don't want */\r
3413         return;\r
3414       case H_GETTING_MOVES:\r
3415         /* Should not happen */\r
3416         DisplayError(_("Error gathering move list: extra board"), 0);\r
3417         ics_getting_history = H_FALSE;\r
3418         return;\r
3419     }\r
3420     \r
3421     /* Take action if this is the first board of a new game, or of a\r
3422        different game than is currently being displayed.  */\r
3423     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3424         relation == RELATION_ISOLATED_BOARD) {\r
3425         \r
3426         /* Forget the old game and get the history (if any) of the new one */\r
3427         if (gameMode != BeginningOfGame) {\r
3428           Reset(FALSE, TRUE);\r
3429         }\r
3430         newGame = TRUE;\r
3431         if (appData.autoRaiseBoard) BoardToTop();\r
3432         prevMove = -3;\r
3433         if (gamenum == -1) {\r
3434             newGameMode = IcsIdle;\r
3435         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3436                    appData.getMoveList) {\r
3437             /* Need to get game history */\r
3438             ics_getting_history = H_REQUESTED;\r
3439             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3440             SendToICS(str);\r
3441         }\r
3442         \r
3443         /* Initially flip the board to have black on the bottom if playing\r
3444            black or if the ICS flip flag is set, but let the user change\r
3445            it with the Flip View button. */\r
3446         flipView = appData.autoFlipView ? \r
3447           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3448           appData.flipView;\r
3449         \r
3450         /* Done with values from previous mode; copy in new ones */\r
3451         gameMode = newGameMode;\r
3452         ModeHighlight();\r
3453         ics_gamenum = gamenum;\r
3454         if (gamenum == gs_gamenum) {\r
3455             int klen = strlen(gs_kind);\r
3456             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3457             sprintf(str, "ICS %s", gs_kind);\r
3458             gameInfo.event = StrSave(str);\r
3459         } else {\r
3460             gameInfo.event = StrSave("ICS game");\r
3461         }\r
3462         gameInfo.site = StrSave(appData.icsHost);\r
3463         gameInfo.date = PGNDate();\r
3464         gameInfo.round = StrSave("-");\r
3465         gameInfo.white = StrSave(white);\r
3466         gameInfo.black = StrSave(black);\r
3467         timeControl = basetime * 60 * 1000;\r
3468         timeControl_2 = 0;\r
3469         timeIncrement = increment * 1000;\r
3470         movesPerSession = 0;\r
3471         gameInfo.timeControl = TimeControlTagValue();\r
3472         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3473   if (appData.debugMode) {\r
3474     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3475     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3476     setbuf(debugFP, NULL);\r
3477   }\r
3478 \r
3479         gameInfo.outOfBook = NULL;\r
3480         \r
3481         /* Do we have the ratings? */\r
3482         if (strcmp(player1Name, white) == 0 &&\r
3483             strcmp(player2Name, black) == 0) {\r
3484             if (appData.debugMode)\r
3485               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3486                       player1Rating, player2Rating);\r
3487             gameInfo.whiteRating = player1Rating;\r
3488             gameInfo.blackRating = player2Rating;\r
3489         } else if (strcmp(player2Name, white) == 0 &&\r
3490                    strcmp(player1Name, black) == 0) {\r
3491             if (appData.debugMode)\r
3492               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3493                       player2Rating, player1Rating);\r
3494             gameInfo.whiteRating = player2Rating;\r
3495             gameInfo.blackRating = player1Rating;\r
3496         }\r
3497         player1Name[0] = player2Name[0] = NULLCHAR;\r
3498 \r
3499         /* Silence shouts if requested */\r
3500         if (appData.quietPlay &&\r
3501             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3502             SendToICS(ics_prefix);\r
3503             SendToICS("set shout 0\n");\r
3504         }\r
3505     }\r
3506     \r
3507     /* Deal with midgame name changes */\r
3508     if (!newGame) {\r
3509         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3510             if (gameInfo.white) free(gameInfo.white);\r
3511             gameInfo.white = StrSave(white);\r
3512         }\r
3513         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3514             if (gameInfo.black) free(gameInfo.black);\r
3515             gameInfo.black = StrSave(black);\r
3516         }\r
3517     }\r
3518     \r
3519     /* Throw away game result if anything actually changes in examine mode */\r
3520     if (gameMode == IcsExamining && !newGame) {\r
3521         gameInfo.result = GameUnfinished;\r
3522         if (gameInfo.resultDetails != NULL) {\r
3523             free(gameInfo.resultDetails);\r
3524             gameInfo.resultDetails = NULL;\r
3525         }\r
3526     }\r
3527     \r
3528     /* In pausing && IcsExamining mode, we ignore boards coming\r
3529        in if they are in a different variation than we are. */\r
3530     if (pauseExamInvalid) return;\r
3531     if (pausing && gameMode == IcsExamining) {\r
3532         if (moveNum <= pauseExamForwardMostMove) {\r
3533             pauseExamInvalid = TRUE;\r
3534             forwardMostMove = pauseExamForwardMostMove;\r
3535             return;\r
3536         }\r
3537     }\r
3538     \r
3539   if (appData.debugMode) {\r
3540     fprintf(debugFP, "load %dx%d board\n", files, ranks);\r
3541   }\r
3542     /* Parse the board */\r
3543     for (k = 0; k < ranks; k++) {\r
3544       for (j = 0; j < files; j++)\r
3545         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);\r
3546       if(gameInfo.holdingsWidth > 1) {\r
3547            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3548            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3549       }\r
3550     }\r
3551     CopyBoard(boards[moveNum], board);\r
3552     if (moveNum == 0) {\r
3553         startedFromSetupPosition =\r
3554           !CompareBoards(board, initialPosition);\r
3555         if(startedFromSetupPosition)\r
3556             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */\r
3557     }\r
3558 \r
3559     /* [HGM] Set castling rights. Take the outermost Rooks,\r
3560        to make it also work for FRC opening positions. Note that board12\r
3561        is really defective for later FRC positions, as it has no way to\r
3562        indicate which Rook can castle if they are on the same side of King.\r
3563        For the initial position we grant rights to the outermost Rooks,\r
3564        and remember thos rights, and we then copy them on positions\r
3565        later in an FRC game. This means WB might not recognize castlings with\r
3566        Rooks that have moved back to their original position as illegal,\r
3567        but in ICS mode that is not its job anyway.\r
3568     */\r
3569     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)\r
3570     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;\r
3571 \r
3572         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3573             if(board[0][i] == WhiteRook) j = i;\r
3574         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3575         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3576             if(board[0][i] == WhiteRook) j = i;\r
3577         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3578         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3579             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3580         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3581         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3582             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3583         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3584 \r
3585         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }\r
3586         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3587             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;\r
3588         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3589             if(board[BOARD_HEIGHT-1][k] == bKing)\r
3590                 initialRights[5] = castlingRights[moveNum][5] = k;\r
3591     } else { int r;\r
3592         r = castlingRights[moveNum][0] = initialRights[0];\r
3593         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;\r
3594         r = castlingRights[moveNum][1] = initialRights[1];\r
3595         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;\r
3596         r = castlingRights[moveNum][3] = initialRights[3];\r
3597         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;\r
3598         r = castlingRights[moveNum][4] = initialRights[4];\r
3599         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;\r
3600         /* wildcastle kludge: always assume King has rights */\r
3601         r = castlingRights[moveNum][2] = initialRights[2];\r
3602         r = castlingRights[moveNum][5] = initialRights[5];\r
3603     }\r
3604     /* [HGM] e.p. rights. Assume that ICS sends file number here? */\r
3605     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;\r
3606 \r
3607     \r
3608     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3609         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3610         /* This was an initial position from a move list, not\r
3611            the current position */\r
3612         return;\r
3613     }\r
3614     \r
3615     /* Update currentMove and known move number limits */\r
3616     newMove = newGame || moveNum > forwardMostMove;\r
3617 \r
3618     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */\r
3619     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {\r
3620         takeback = forwardMostMove - moveNum;\r
3621         for (i = 0; i < takeback; i++) {\r
3622              if (appData.debugMode) fprintf(debugFP, "take back move\n");\r
3623              SendToProgram("undo\n", &first);\r
3624         }\r
3625     }\r
3626 \r
3627     if (newGame) {\r
3628         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3629         if (gameMode == IcsExamining && moveNum == 0) {\r
3630           /* Workaround for ICS limitation: we are not told the wild\r
3631              type when starting to examine a game.  But if we ask for\r
3632              the move list, the move list header will tell us */\r
3633             ics_getting_history = H_REQUESTED;\r
3634             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3635             SendToICS(str);\r
3636         }\r
3637     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3638                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3639         forwardMostMove = moveNum;\r
3640         if (!pausing || currentMove > forwardMostMove)\r
3641           currentMove = forwardMostMove;\r
3642     } else {\r
3643         /* New part of history that is not contiguous with old part */ \r
3644         if (pausing && gameMode == IcsExamining) {\r
3645             pauseExamInvalid = TRUE;\r
3646             forwardMostMove = pauseExamForwardMostMove;\r
3647             return;\r
3648         }\r
3649         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3650         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3651             ics_getting_history = H_REQUESTED;\r
3652             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3653             SendToICS(str);\r
3654         }\r
3655     }\r
3656     \r
3657     /* Update the clocks */\r
3658     if (strchr(elapsed_time, '.')) {\r
3659       /* Time is in ms */\r
3660       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3661       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3662     } else {\r
3663       /* Time is in seconds */\r
3664       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3665       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3666     }\r
3667       \r
3668 \r
3669 #if ZIPPY\r
3670     if (appData.zippyPlay && newGame &&\r
3671         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3672         gameMode != IcsExamining)\r
3673       ZippyFirstBoard(moveNum, basetime, increment);\r
3674 #endif\r
3675     \r
3676     /* Put the move on the move list, first converting\r
3677        to canonical algebraic form. */\r
3678     if (moveNum > 0) {\r
3679   if (appData.debugMode) {\r
3680     if (appData.debugMode) { int f = forwardMostMove;\r
3681         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,\r
3682                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
3683     }\r
3684     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3685     fprintf(debugFP, "moveNum = %d\n", moveNum);\r
3686     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3687     setbuf(debugFP, NULL);\r
3688   }\r
3689         if (moveNum <= backwardMostMove) {\r
3690             /* We don't know what the board looked like before\r
3691                this move.  Punt. */\r
3692             strcpy(parseList[moveNum - 1], move_str);\r
3693             strcat(parseList[moveNum - 1], " ");\r
3694             strcat(parseList[moveNum - 1], elapsed_time);\r
3695             moveList[moveNum - 1][0] = NULLCHAR;\r
3696         } else if (strcmp(move_str, "none") == 0) {\r
3697             // [HGM] long SAN: swapped order; test for 'none' before parsing move\r
3698             /* Again, we don't know what the board looked like;\r
3699                this is really the start of the game. */\r
3700             parseList[moveNum - 1][0] = NULLCHAR;\r
3701             moveList[moveNum - 1][0] = NULLCHAR;\r
3702             backwardMostMove = moveNum;\r
3703             startedFromSetupPosition = TRUE;\r
3704             fromX = fromY = toX = toY = -1;\r
3705         } else {\r
3706           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. \r
3707           //                 So we parse the long-algebraic move string in stead of the SAN move\r
3708           int valid; char buf[MSG_SIZ], *prom;\r
3709 \r
3710           // str looks something like "Q/a1-a2"; kill the slash\r
3711           if(str[1] == '/') \r
3712                 sprintf(buf, "%c%s", str[0], str+2);\r
3713           else  strcpy(buf, str); // might be castling\r
3714           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) \r
3715                 strcat(buf, prom); // long move lacks promo specification!\r
3716           if(!appData.testLegality) {\r
3717                 if(appData.debugMode) \r
3718                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);\r
3719                 strcpy(move_str, buf);\r
3720           }\r
3721           valid = ParseOneMove(move_str, moveNum - 1, &moveType,\r
3722                                 &fromX, &fromY, &toX, &toY, &promoChar)\r
3723                || ParseOneMove(buf, moveNum - 1, &moveType,\r
3724                                 &fromX, &fromY, &toX, &toY, &promoChar);\r
3725           // end of long SAN patch\r
3726           if (valid) {\r
3727             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3728                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3729                                      fromY, fromX, toY, toX, promoChar,\r
3730                                      parseList[moveNum-1]);\r
3731             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3732                              castlingRights[moveNum]) ) {\r
3733               case MT_NONE:\r
3734               case MT_STALEMATE:\r
3735               default:\r
3736                 break;\r
3737               case MT_CHECK:\r
3738                 if(gameInfo.variant != VariantShogi)\r
3739                     strcat(parseList[moveNum - 1], "+");\r
3740                 break;\r
3741               case MT_CHECKMATE:\r
3742                 strcat(parseList[moveNum - 1], "#");\r
3743                 break;\r
3744             }\r
3745             strcat(parseList[moveNum - 1], " ");\r
3746             strcat(parseList[moveNum - 1], elapsed_time);\r
3747             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3748             strcpy(moveList[moveNum - 1], currentMoveString);\r
3749             strcat(moveList[moveNum - 1], "\n");\r
3750           } else {\r
3751             /* Move from ICS was illegal!?  Punt. */\r
3752   if (appData.debugMode) {\r
3753     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3754     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3755   }\r
3756 #if 0\r
3757             if (appData.testLegality && appData.debugMode) {\r
3758                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3759                 DisplayError(str, 0);\r
3760             }\r
3761 #endif\r
3762             strcpy(parseList[moveNum - 1], move_str);\r
3763             strcat(parseList[moveNum - 1], " ");\r
3764             strcat(parseList[moveNum - 1], elapsed_time);\r
3765             moveList[moveNum - 1][0] = NULLCHAR;\r
3766             fromX = fromY = toX = toY = -1;\r
3767           }\r
3768         }\r
3769   if (appData.debugMode) {\r
3770     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3771     setbuf(debugFP, NULL);\r
3772   }\r
3773 \r
3774 #if ZIPPY\r
3775         /* Send move to chess program (BEFORE animating it). */\r
3776         if (appData.zippyPlay && !newGame && newMove && \r
3777            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3778 \r
3779             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3780                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3781                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3782                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),\r
3783                             move_str);\r
3784                     DisplayError(str, 0);\r
3785                 } else {\r
3786                     if (first.sendTime) {\r
3787                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3788                     }\r
3789                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book\r
3790                     if (firstMove && !bookHit) {\r
3791                         firstMove = FALSE;\r
3792                         if (first.useColors) {\r
3793                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3794                                         "white\ngo\n" :\r
3795                                         "black\ngo\n", &first);\r
3796                         } else {\r
3797                           SendToProgram("go\n", &first);\r
3798                         }\r
3799                         first.maybeThinking = TRUE;\r
3800                     }\r
3801                 }\r
3802             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3803               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3804                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);\r
3805                 DisplayError(str, 0);\r
3806               } else {\r
3807                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!\r
3808                 SendMoveToProgram(moveNum - 1, &first);\r
3809               }\r
3810             }\r
3811         }\r
3812 #endif\r
3813     }\r
3814 \r
3815     if (moveNum > 0 && !gotPremove) {\r
3816         /* If move comes from a remote source, animate it.  If it\r
3817            isn't remote, it will have already been animated. */\r
3818         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3819             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3820         }\r
3821         if (!pausing && appData.highlightLastMove) {\r
3822             SetHighlights(fromX, fromY, toX, toY);\r
3823         }\r
3824     }\r
3825     \r
3826     /* Start the clocks */\r
3827     whiteFlag = blackFlag = FALSE;\r
3828     appData.clockMode = !(basetime == 0 && increment == 0);\r
3829     if (ticking == 0) {\r
3830       ics_clock_paused = TRUE;\r
3831       StopClocks();\r
3832     } else if (ticking == 1) {\r
3833       ics_clock_paused = FALSE;\r
3834     }\r
3835     if (gameMode == IcsIdle ||\r
3836         relation == RELATION_OBSERVING_STATIC ||\r
3837         relation == RELATION_EXAMINING ||\r
3838         ics_clock_paused)\r
3839       DisplayBothClocks();\r
3840     else\r
3841       StartClocks();\r
3842     \r
3843     /* Display opponents and material strengths */\r
3844     if (gameInfo.variant != VariantBughouse &&\r
3845         gameInfo.variant != VariantCrazyhouse) {\r
3846         if (tinyLayout || smallLayout) {\r
3847             if(gameInfo.variant == VariantNormal)\r
3848                 sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3849                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3850                     basetime, increment);\r
3851             else\r
3852                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", \r
3853                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3854                     basetime, increment, (int) gameInfo.variant);\r
3855         } else {\r
3856             if(gameInfo.variant == VariantNormal)\r
3857                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3858                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3859                     basetime, increment);\r
3860             else\r
3861                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", \r
3862                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3863                     basetime, increment, VariantName(gameInfo.variant));\r
3864         }\r
3865         DisplayTitle(str);\r
3866   if (appData.debugMode) {\r
3867     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);\r
3868   }\r
3869     }\r
3870 \r
3871    \r
3872     /* Display the board */\r
3873     if (!pausing) {\r
3874       \r
3875       if (appData.premove)\r
3876           if (!gotPremove || \r
3877              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3878              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3879               ClearPremoveHighlights();\r
3880 \r
3881       DrawPosition(FALSE, boards[currentMove]);\r
3882       DisplayMove(moveNum - 1);\r
3883       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3884         RingBell();\r
3885     }\r
3886 \r
3887     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3888 #if ZIPPY\r
3889     if(bookHit) { // [HGM] book: simulate book reply\r
3890         static char bookMove[MSG_SIZ]; // a bit generous?\r
3891 \r
3892         programStats.nodes = programStats.depth = programStats.time = \r
3893         programStats.score = programStats.got_only_move = 0;\r
3894         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
3895 \r
3896         strcpy(bookMove, "move ");\r
3897         strcat(bookMove, bookHit);\r
3898         HandleMachineMove(bookMove, &first);\r
3899     }\r
3900 #endif\r
3901 }\r
3902 \r
3903 void\r
3904 GetMoveListEvent()\r
3905 {\r
3906     char buf[MSG_SIZ];\r
3907     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3908         ics_getting_history = H_REQUESTED;\r
3909         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3910         SendToICS(buf);\r
3911     }\r
3912 }\r
3913 \r
3914 void\r
3915 AnalysisPeriodicEvent(force)\r
3916      int force;\r
3917 {\r
3918     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3919          && !force) || !appData.periodicUpdates)\r
3920       return;\r
3921 \r
3922     /* Send . command to Crafty to collect stats */\r
3923     SendToProgram(".\n", &first);\r
3924 \r
3925     /* Don't send another until we get a response (this makes\r
3926        us stop sending to old Crafty's which don't understand\r
3927        the "." command (sending illegal cmds resets node count & time,\r
3928        which looks bad)) */\r
3929     programStats.ok_to_send = 0;\r
3930 }\r
3931 \r
3932 void\r
3933 SendMoveToProgram(moveNum, cps)\r
3934      int moveNum;\r
3935      ChessProgramState *cps;\r
3936 {\r
3937     char buf[MSG_SIZ];\r
3938 \r
3939     if (cps->useUsermove) {\r
3940       SendToProgram("usermove ", cps);\r
3941     }\r
3942     if (cps->useSAN) {\r
3943       char *space;\r
3944       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3945         int len = space - parseList[moveNum];\r
3946         memcpy(buf, parseList[moveNum], len);\r
3947         buf[len++] = '\n';\r
3948         buf[len] = NULLCHAR;\r
3949       } else {\r
3950         sprintf(buf, "%s\n", parseList[moveNum]);\r
3951       }\r
3952       SendToProgram(buf, cps);\r
3953     } else {\r
3954       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */\r
3955         AlphaRank(moveList[moveNum], 4);\r
3956         SendToProgram(moveList[moveNum], cps);\r
3957         AlphaRank(moveList[moveNum], 4); // and back\r
3958       } else\r
3959       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3960        * the engine. It would be nice to have a better way to identify castle \r
3961        * moves here. */\r
3962       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)\r
3963                                                                          && cps->useOOCastle) {\r
3964         int fromX = moveList[moveNum][0] - AAA; \r
3965         int fromY = moveList[moveNum][1] - ONE;\r
3966         int toX = moveList[moveNum][2] - AAA; \r
3967         int toY = moveList[moveNum][3] - ONE;\r
3968         if((boards[moveNum][fromY][fromX] == WhiteKing \r
3969             && boards[moveNum][toY][toX] == WhiteRook)\r
3970            || (boards[moveNum][fromY][fromX] == BlackKing \r
3971                && boards[moveNum][toY][toX] == BlackRook)) {\r
3972           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3973           else SendToProgram("O-O-O\n", cps);\r
3974         }\r
3975         else SendToProgram(moveList[moveNum], cps);\r
3976       }\r
3977       else SendToProgram(moveList[moveNum], cps);\r
3978       /* End of additions by Tord */\r
3979     }\r
3980 \r
3981     /* [HGM] setting up the opening has brought engine in force mode! */\r
3982     /*       Send 'go' if we are in a mode where machine should play. */\r
3983     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
3984         (gameMode == TwoMachinesPlay   ||\r
3985 #ifdef ZIPPY\r
3986          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||\r
3987 #endif\r
3988          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
3989         SendToProgram("go\n", cps);\r
3990   if (appData.debugMode) {\r
3991     fprintf(debugFP, "(extra)\n");\r
3992   }\r
3993     }\r
3994     setboardSpoiledMachineBlack = 0;\r
3995 }\r
3996 \r
3997 void\r
3998 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
3999      ChessMove moveType;\r
4000      int fromX, fromY, toX, toY;\r
4001 {\r
4002     char user_move[MSG_SIZ];\r
4003 \r
4004     switch (moveType) {\r
4005       default:\r
4006         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),\r
4007                 (int)moveType, fromX, fromY, toX, toY);\r
4008         DisplayError(user_move + strlen("say "), 0);\r
4009         break;\r
4010       case WhiteKingSideCastle:\r
4011       case BlackKingSideCastle:\r
4012       case WhiteQueenSideCastleWild:\r
4013       case BlackQueenSideCastleWild:\r
4014       /* PUSH Fabien */\r
4015       case WhiteHSideCastleFR:\r
4016       case BlackHSideCastleFR:\r
4017       /* POP Fabien */\r
4018         sprintf(user_move, "o-o\n");\r
4019         break;\r
4020       case WhiteQueenSideCastle:\r
4021       case BlackQueenSideCastle:\r
4022       case WhiteKingSideCastleWild:\r
4023       case BlackKingSideCastleWild:\r
4024       /* PUSH Fabien */\r
4025       case WhiteASideCastleFR:\r
4026       case BlackASideCastleFR:\r
4027       /* POP Fabien */\r
4028         sprintf(user_move, "o-o-o\n");\r
4029         break;\r
4030       case WhitePromotionQueen:\r
4031       case BlackPromotionQueen:\r
4032       case WhitePromotionRook:\r
4033       case BlackPromotionRook:\r
4034       case WhitePromotionBishop:\r
4035       case BlackPromotionBishop:\r
4036       case WhitePromotionKnight:\r
4037       case BlackPromotionKnight:\r
4038       case WhitePromotionKing:\r
4039       case BlackPromotionKing:\r
4040       case WhitePromotionChancellor:\r
4041       case BlackPromotionChancellor:\r
4042       case WhitePromotionArchbishop:\r
4043       case BlackPromotionArchbishop:\r
4044         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)\r
4045             sprintf(user_move, "%c%c%c%c=%c\n",\r
4046                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4047                 PieceToChar(WhiteFerz));\r
4048         else if(gameInfo.variant == VariantGreat)\r
4049             sprintf(user_move, "%c%c%c%c=%c\n",\r
4050                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4051                 PieceToChar(WhiteMan));\r
4052         else\r
4053             sprintf(user_move, "%c%c%c%c=%c\n",\r
4054                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4055                 PieceToChar(PromoPiece(moveType)));\r
4056         break;\r
4057       case WhiteDrop:\r
4058       case BlackDrop:\r
4059         sprintf(user_move, "%c@%c%c\n",\r
4060                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
4061                 AAA + toX, ONE + toY);\r
4062         break;\r
4063       case NormalMove:\r
4064       case WhiteCapturesEnPassant:\r
4065       case BlackCapturesEnPassant:\r
4066       case IllegalMove:  /* could be a variant we don't quite understand */\r
4067         sprintf(user_move, "%c%c%c%c\n",\r
4068                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
4069         break;\r
4070     }\r
4071     SendToICS(user_move);\r
4072 }\r
4073 \r
4074 void\r
4075 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
4076      int rf, ff, rt, ft;\r
4077      char promoChar;\r
4078      char move[7];\r
4079 {\r
4080     if (rf == DROP_RANK) {\r
4081         sprintf(move, "%c@%c%c\n",\r
4082                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
4083     } else {\r
4084         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
4085             sprintf(move, "%c%c%c%c\n",\r
4086                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
4087         } else {\r
4088             sprintf(move, "%c%c%c%c%c\n",\r
4089                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
4090         }\r
4091     }\r
4092 }\r
4093 \r
4094 void\r
4095 ProcessICSInitScript(f)\r
4096      FILE *f;\r
4097 {\r
4098     char buf[MSG_SIZ];\r
4099 \r
4100     while (fgets(buf, MSG_SIZ, f)) {\r
4101         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
4102     }\r
4103 \r
4104     fclose(f);\r
4105 }\r
4106 \r
4107 \r
4108 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
4109 void\r
4110 AlphaRank(char *move, int n)\r
4111 {\r
4112 //    char *p = move, c; int x, y;\r
4113 \r
4114     if (appData.debugMode) {\r
4115         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
4116     }\r
4117 \r
4118     if(move[1]=='*' && \r
4119        move[2]>='0' && move[2]<='9' &&\r
4120        move[3]>='a' && move[3]<='x'    ) {\r
4121         move[1] = '@';\r
4122         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4123         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4124     } else\r
4125     if(move[0]>='0' && move[0]<='9' &&\r
4126        move[1]>='a' && move[1]<='x' &&\r
4127        move[2]>='0' && move[2]<='9' &&\r
4128        move[3]>='a' && move[3]<='x'    ) {\r
4129         /* input move, Shogi -> normal */\r
4130         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;\r
4131         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\r
4132         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4133         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4134     } else\r
4135     if(move[1]=='@' &&\r
4136        move[3]>='0' && move[3]<='9' &&\r
4137        move[2]>='a' && move[2]<='x'    ) {\r
4138         move[1] = '*';\r
4139         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4140         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4141     } else\r
4142     if(\r
4143        move[0]>='a' && move[0]<='x' &&\r
4144        move[3]>='0' && move[3]<='9' &&\r
4145        move[2]>='a' && move[2]<='x'    ) {\r
4146          /* output move, normal -> Shogi */\r
4147         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
4148         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
4149         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4150         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4151         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
4152     }\r
4153     if (appData.debugMode) {\r
4154         fprintf(debugFP, "   out = '%s'\n", move);\r
4155     }\r
4156 }\r
4157 \r
4158 /* Parser for moves from gnuchess, ICS, or user typein box */\r
4159 Boolean\r
4160 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
4161      char *move;\r
4162      int moveNum;\r
4163      ChessMove *moveType;\r
4164      int *fromX, *fromY, *toX, *toY;\r
4165      char *promoChar;\r
4166 {       \r
4167     if (appData.debugMode) {\r
4168         fprintf(debugFP, "move to parse: %s\n", move);\r
4169     }\r
4170     *moveType = yylexstr(moveNum, move);\r
4171 \r
4172     switch (*moveType) {\r
4173       case WhitePromotionChancellor:\r
4174       case BlackPromotionChancellor:\r
4175       case WhitePromotionArchbishop:\r
4176       case BlackPromotionArchbishop:\r
4177       case WhitePromotionQueen:\r
4178       case BlackPromotionQueen:\r
4179       case WhitePromotionRook:\r
4180       case BlackPromotionRook:\r
4181       case WhitePromotionBishop:\r
4182       case BlackPromotionBishop:\r
4183       case WhitePromotionKnight:\r
4184       case BlackPromotionKnight:\r
4185       case WhitePromotionKing:\r
4186       case BlackPromotionKing:\r
4187       case NormalMove:\r
4188       case WhiteCapturesEnPassant:\r
4189       case BlackCapturesEnPassant:\r
4190       case WhiteKingSideCastle:\r
4191       case WhiteQueenSideCastle:\r
4192       case BlackKingSideCastle:\r
4193       case BlackQueenSideCastle:\r
4194       case WhiteKingSideCastleWild:\r
4195       case WhiteQueenSideCastleWild:\r
4196       case BlackKingSideCastleWild:\r
4197       case BlackQueenSideCastleWild:\r
4198       /* Code added by Tord: */\r
4199       case WhiteHSideCastleFR:\r
4200       case WhiteASideCastleFR:\r
4201       case BlackHSideCastleFR:\r
4202       case BlackASideCastleFR:\r
4203       /* End of code added by Tord */\r
4204       case IllegalMove:         /* bug or odd chess variant */\r
4205         *fromX = currentMoveString[0] - AAA;\r
4206         *fromY = currentMoveString[1] - ONE;\r
4207         *toX = currentMoveString[2] - AAA;\r
4208         *toY = currentMoveString[3] - ONE;\r
4209         *promoChar = currentMoveString[4];\r
4210         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
4211             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
4212     if (appData.debugMode) {\r
4213         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
4214     }\r
4215             *fromX = *fromY = *toX = *toY = 0;\r
4216             return FALSE;\r
4217         }\r
4218         if (appData.testLegality) {\r
4219           return (*moveType != IllegalMove);\r
4220         } else {\r
4221           return !(fromX == fromY && toX == toY);\r
4222         }\r
4223 \r
4224       case WhiteDrop:\r
4225       case BlackDrop:\r
4226         *fromX = *moveType == WhiteDrop ?\r
4227           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
4228           (int) CharToPiece(ToLower(currentMoveString[0]));\r
4229         *fromY = DROP_RANK;\r
4230         *toX = currentMoveString[2] - AAA;\r
4231         *toY = currentMoveString[3] - ONE;\r
4232         *promoChar = NULLCHAR;\r
4233         return TRUE;\r
4234 \r
4235       case AmbiguousMove:\r
4236       case ImpossibleMove:\r
4237       case (ChessMove) 0:       /* end of file */\r
4238       case ElapsedTime:\r
4239       case Comment:\r
4240       case PGNTag:\r
4241       case NAG:\r
4242       case WhiteWins:\r
4243       case BlackWins:\r
4244       case GameIsDrawn:\r
4245       default:\r
4246     if (appData.debugMode) {\r
4247         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
4248     }\r
4249         /* bug? */\r
4250         *fromX = *fromY = *toX = *toY = 0;\r
4251         *promoChar = NULLCHAR;\r
4252         return FALSE;\r
4253     }\r
4254 }\r
4255 \r
4256 #if 0\r
4257 /* [AS] FRC game initialization */\r
4258 static int FindEmptySquare( Board board, int n )\r
4259 {\r
4260     int i = 0;\r
4261 \r
4262     while( 1 ) {\r
4263         while( board[0][i] != EmptySquare ) i++;\r
4264         if( n == 0 )\r
4265             break;\r
4266         n--;\r
4267         i++;\r
4268     }\r
4269 \r
4270     return i;\r
4271 }\r
4272 \r
4273 static void ShuffleFRC( Board board )\r
4274 {\r
4275     int i;\r
4276 \r
4277     srand( time(0) );\r
4278     \r
4279     for( i=0; i<8; i++ ) {\r
4280         board[0][i] = EmptySquare;\r
4281     }\r
4282 \r
4283     board[0][(rand() % 4)*2  ] = WhiteBishop; /* On dark square */\r
4284     board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */\r
4285     board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;\r
4286     board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;\r
4287     board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;\r
4288     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4289     initialRights[1]  = initialRights[4]  =\r
4290     castlingRights[0][1] = castlingRights[0][4] = i;\r
4291     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4292     initialRights[2]  = initialRights[5]  =\r
4293     castlingRights[0][2] = castlingRights[0][5] = i;\r
4294     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4295     initialRights[0]  = initialRights[3]  =\r
4296     castlingRights[0][0] = castlingRights[0][3] = i;\r
4297 \r
4298     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4299         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4300     }\r
4301 }\r
4302 \r
4303 static unsigned char FRC_KnightTable[10] = {\r
4304     0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33\r
4305 };\r
4306 \r
4307 static void SetupFRC( Board board, int pos_index )\r
4308 {\r
4309     int i;\r
4310     unsigned char knights;\r
4311 \r
4312     /* Bring the position index into a safe range (just in case...) */\r
4313     if( pos_index < 0 ) pos_index = 0;\r
4314 \r
4315     pos_index %= 960;\r
4316 \r
4317     /* Clear the board */\r
4318     for( i=0; i<8; i++ ) {\r
4319         board[0][i] = EmptySquare;\r
4320     }\r
4321 \r
4322     /* Place bishops and queen */\r
4323     board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */\r
4324     pos_index /= 4;\r
4325     \r
4326     board[0][ (pos_index % 4)*2     ] = WhiteBishop; /* On dark square */\r
4327     pos_index /= 4;\r
4328 \r
4329     board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;\r
4330     pos_index /= 6;\r
4331 \r
4332     /* Place knigths */\r
4333     knights = FRC_KnightTable[ pos_index ];\r
4334 \r
4335     board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;\r
4336     board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;\r
4337 \r
4338     /* Place rooks and king */\r
4339     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4340     initialRights[1]  = initialRights[4]  =\r
4341     castlingRights[0][1] = castlingRights[0][4] = i;\r
4342     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4343     initialRights[2]  = initialRights[5]  =\r
4344     castlingRights[0][2] = castlingRights[0][5] = i;\r
4345     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4346     initialRights[0]  = initialRights[3]  =\r
4347     castlingRights[0][0] = castlingRights[0][3] = i;\r
4348 \r
4349     /* Mirror piece placement for black */\r
4350     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4351         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4352     }\r
4353 }\r
4354 #else\r
4355 // [HGM] shuffle: a more general way to suffle opening setups, applicable to arbitrry variants.\r
4356 // All positions will have equal probability, but the current method will not provide a unique\r
4357 // numbering scheme for arrays that contain 3 or more pieces of the same kind.\r
4358 #define DARK 1\r
4359 #define LITE 2\r
4360 #define ANY 3\r
4361 \r
4362 int squaresLeft[4];\r
4363 int piecesLeft[(int)BlackPawn];\r
4364 u64 seed, nrOfShuffles;\r
4365 \r
4366 void GetPositionNumber()\r
4367 {       // sets global variable seed\r
4368         int i;\r
4369 \r
4370         seed = appData.defaultFrcPosition;\r
4371         if(seed < 0) { // randomize based on time for negative FRC position numbers\r
4372                 srandom(time(0)); \r
4373                 for(i=0; i<50; i++) seed += random();\r
4374                 seed = random() ^ random() >> 8 ^ random() << 8;\r
4375                 if(seed<0) seed = -seed;\r
4376         }\r
4377 }\r
4378 \r
4379 int put(Board board, int pieceType, int rank, int n, int shade)\r
4380 // put the piece on the (n-1)-th empty squares of the given shade\r
4381 {\r
4382         int i;\r
4383 \r
4384         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
4385                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {\r
4386                         board[rank][i] = (ChessSquare) pieceType;\r
4387                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;\r
4388                         squaresLeft[ANY]--;\r
4389                         piecesLeft[pieceType]--; \r
4390                         return i;\r
4391                 }\r
4392         }\r
4393         return -1;\r
4394 }\r
4395 \r
4396 \r
4397 void AddOnePiece(Board board, int pieceType, int rank, int shade)\r
4398 // calculate where the next piece goes, (any empty square), and put it there\r
4399 {\r
4400         int i;\r
4401 \r
4402         i = seed % squaresLeft[shade];\r
4403         nrOfShuffles *= squaresLeft[shade];\r
4404         seed /= squaresLeft[shade];\r
4405         put(board, pieceType, rank, i, shade);\r
4406 }\r
4407 \r
4408 void AddTwoPieces(Board board, int pieceType, int rank)\r
4409 // calculate where the next 2 identical pieces go, (any empty square), and put it there\r
4410 {\r
4411         int i, n=squaresLeft[ANY], j=n-1, k;\r
4412 \r
4413         k = n*(n-1)/2; // nr of possibilities, not counting permutations\r
4414         i = seed % k;  // pick one\r
4415         nrOfShuffles *= k;\r
4416         seed /= k;\r
4417         while(i >= j) i -= j--;\r
4418         j = n - 1 - j; i += j;\r
4419         put(board, pieceType, rank, j, ANY);\r
4420         put(board, pieceType, rank, i, ANY);\r
4421 }\r
4422 \r
4423 void SetUpShuffle(Board board, int number)\r
4424 {\r
4425         int i, p, first=1;\r
4426 \r
4427         GetPositionNumber(); nrOfShuffles = 1;\r
4428 \r
4429         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;\r
4430         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;\r
4431         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];\r
4432 \r
4433         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;\r
4434 \r
4435         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board\r
4436             p = (int) board[0][i];\r
4437             if(p < (int) BlackPawn) piecesLeft[p] ++;\r
4438             board[0][i] = EmptySquare;\r
4439         }\r
4440 \r
4441         if(PosFlags(0) & F_ALL_CASTLE_OK) {\r
4442             // shuffles restricted to allow normal castling put KRR first\r
4443             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle\r
4444                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4445             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles\r
4446                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4447             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling\r
4448                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);\r
4449             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling\r
4450                 put(board, WhiteRook, 0, 0, ANY);\r
4451             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle\r
4452         }\r
4453 \r
4454         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)\r
4455             // only for even boards make effort to put pairs of colorbound pieces on opposite colors\r
4456             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {\r
4457                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;\r
4458                 while(piecesLeft[p] >= 2) {\r
4459                     AddOnePiece(board, p, 0, LITE);\r
4460                     AddOnePiece(board, p, 0, DARK);\r
4461                 }\r
4462                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)\r
4463             }\r
4464 \r
4465         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {\r
4466             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere\r
4467             // but we leave King and Rooks for last, to possibly obey FRC restriction\r
4468             if(p == (int)WhiteRook) continue;\r
4469             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations\r
4470             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece\r
4471         }\r
4472 \r
4473         // now everything is placed, except perhaps King (Unicorn) and Rooks\r
4474 \r
4475         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {\r
4476             // Last King gets castling rights\r
4477             while(piecesLeft[(int)WhiteUnicorn]) {\r
4478                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4479                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4480             }\r
4481 \r
4482             while(piecesLeft[(int)WhiteKing]) {\r
4483                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4484                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4485             }\r
4486 \r
4487 \r
4488         } else {\r
4489             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);\r
4490             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);\r
4491         }\r
4492 \r
4493         // Only Rooks can be left; simply place them all\r
4494         while(piecesLeft[(int)WhiteRook]) {\r
4495                 i = put(board, WhiteRook, 0, 0, ANY);\r
4496                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights\r
4497                         if(first) {\r
4498                                 first=0;\r
4499                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;\r
4500                         }\r
4501                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;\r
4502                 }\r
4503         }\r
4504         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white\r
4505             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;\r
4506         }\r
4507 \r
4508         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize\r
4509 }\r
4510 \r
4511 #endif\r
4512 \r
4513 int SetCharTable( char *table, const char * map )\r
4514 /* [HGM] moved here from winboard.c because of its general usefulness */\r
4515 /*       Basically a safe strcpy that uses the last character as King */\r
4516 {\r
4517     int result = FALSE; int NrPieces;\r
4518 \r
4519     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
4520                     && NrPieces >= 12 && !(NrPieces&1)) {\r
4521         int i; /* [HGM] Accept even length from 12 to 34 */\r
4522 \r
4523         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
4524         for( i=0; i<NrPieces/2-1; i++ ) {\r
4525             table[i] = map[i];\r
4526             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
4527         }\r
4528         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
4529         table[(int) BlackKing]  = map[NrPieces-1];\r
4530 \r
4531         result = TRUE;\r
4532     }\r
4533 \r
4534     return result;\r
4535 }\r
4536 \r
4537 void Prelude(Board board)\r
4538 {       // [HGM] superchess: random selection of exo-pieces\r
4539         int i, j, k; ChessSquare p; \r
4540         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };\r
4541 \r
4542         GetPositionNumber(); // use FRC position number\r
4543 \r
4544         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table\r
4545             SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4546             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) \r
4547                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;\r
4548         }\r
4549 \r
4550         j = seed%4;                 seed /= 4; \r
4551         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4552         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4553         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4554         j = seed%3 + (seed%3 >= j); seed /= 3; \r
4555         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4556         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4557         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4558         j = seed%3;                 seed /= 3; \r
4559         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4560         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4561         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4562         j = seed%2 + (seed%2 >= j); seed /= 2; \r
4563         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4564         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4565         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4566         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);\r
4567         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);\r
4568         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);\r
4569         put(board, exoPieces[0],    0, 0, ANY);\r
4570         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];\r
4571 }\r
4572 \r
4573 void\r
4574 InitPosition(redraw)\r
4575      int redraw;\r
4576 {\r
4577     ChessSquare (* pieces)[BOARD_SIZE];\r
4578     int i, j, pawnRow, overrule,\r
4579     oldx = gameInfo.boardWidth,\r
4580     oldy = gameInfo.boardHeight,\r
4581     oldh = gameInfo.holdingsWidth,\r
4582     oldv = gameInfo.variant;\r
4583 \r
4584     currentMove = forwardMostMove = backwardMostMove = 0;\r
4585     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request\r
4586 \r
4587     /* [AS] Initialize pv info list [HGM] and game status */\r
4588     {\r
4589         for( i=0; i<MAX_MOVES; i++ ) {\r
4590             pvInfoList[i].depth = 0;\r
4591             epStatus[i]=EP_NONE;\r
4592             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
4593         }\r
4594 \r
4595         initialRulePlies = 0; /* 50-move counter start */\r
4596 \r
4597         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4598         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4599     }\r
4600 \r
4601     \r
4602     /* [HGM] logic here is completely changed. In stead of full positions */\r
4603     /* the initialized data only consist of the two backranks. The switch */\r
4604     /* selects which one we will use, which is than copied to the Board   */\r
4605     /* initialPosition, which for the rest is initialized by Pawns and    */\r
4606     /* empty squares. This initial position is then copied to boards[0],  */\r
4607     /* possibly after shuffling, so that it remains available.            */\r
4608 \r
4609     gameInfo.holdingsWidth = 0; /* default board sizes */\r
4610     gameInfo.boardWidth    = 8;\r
4611     gameInfo.boardHeight   = 8;\r
4612     gameInfo.holdingsSize  = 0;\r
4613     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
4614     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
4615     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
4616 \r
4617     switch (gameInfo.variant) {\r
4618     case VariantFischeRandom:\r
4619       shuffleOpenings = TRUE;\r
4620     default:\r
4621       pieces = FIDEArray;\r
4622       break;\r
4623     case VariantShatranj:\r
4624       pieces = ShatranjArray;\r
4625       nrCastlingRights = 0;\r
4626       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
4627       break;\r
4628     case VariantTwoKings:\r
4629       pieces = twoKingsArray;\r
4630       nrCastlingRights = 8;                 /* add rights for second King */\r
4631       castlingRights[0][6] = initialRights[2] = 5;\r
4632       castlingRights[0][7] = initialRights[5] = 5;\r
4633       castlingRank[6] = 0;\r
4634       castlingRank[7] = BOARD_HEIGHT-1;\r
4635       break;\r
4636     case VariantCapaRandom:\r
4637       shuffleOpenings = TRUE;\r
4638     case VariantCapablanca:\r
4639       pieces = CapablancaArray;\r
4640       gameInfo.boardWidth = 10;\r
4641       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4642       break;\r
4643     case VariantGothic:\r
4644       pieces = GothicArray;\r
4645       gameInfo.boardWidth = 10;\r
4646       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4647       break;\r
4648     case VariantJanus:\r
4649       pieces = JanusArray;\r
4650       gameInfo.boardWidth = 10;\r
4651       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
4652       nrCastlingRights = 6;\r
4653         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4654         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4655         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;\r
4656         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4657         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4658         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;\r
4659       break;\r
4660     case VariantFalcon:\r
4661       pieces = FalconArray;\r
4662       gameInfo.boardWidth = 10;\r
4663       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4664       break;\r
4665     case VariantXiangqi:\r
4666       pieces = XiangqiArray;\r
4667       gameInfo.boardWidth  = 9;\r
4668       gameInfo.boardHeight = 10;\r
4669       nrCastlingRights = 0;\r
4670       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4671       break;\r
4672     case VariantShogi:\r
4673       pieces = ShogiArray;\r
4674       gameInfo.boardWidth  = 9;\r
4675       gameInfo.boardHeight = 9;\r
4676       gameInfo.holdingsSize = 7;\r
4677       nrCastlingRights = 0;\r
4678       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4679       break;\r
4680     case VariantCourier:\r
4681       pieces = CourierArray;\r
4682       gameInfo.boardWidth  = 12;\r
4683       nrCastlingRights = 0;\r
4684       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4685       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4686       break;\r
4687     case VariantKnightmate:\r
4688       pieces = KnightmateArray;\r
4689       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4690       break;\r
4691     case VariantFairy:\r
4692       pieces = fairyArray;\r
4693       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4694       break;\r
4695     case VariantGreat:\r
4696       pieces = GreatArray;\r
4697       gameInfo.boardWidth = 10;\r
4698       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");\r
4699       gameInfo.holdingsSize = 8;\r
4700       break;\r
4701     case VariantSuper:\r
4702       pieces = FIDEArray;\r
4703       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
4704       gameInfo.holdingsSize = 8;\r
4705       startedFromSetupPosition = TRUE;\r
4706       break;\r
4707     case VariantCrazyhouse:\r
4708     case VariantBughouse:\r
4709       pieces = FIDEArray;\r
4710       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4711       gameInfo.holdingsSize = 5;\r
4712       break;\r
4713     case VariantWildCastle:\r
4714       pieces = FIDEArray;\r
4715       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4716       shuffleOpenings = 1;\r
4717       break;\r
4718     case VariantNoCastle:\r
4719       pieces = FIDEArray;\r
4720       nrCastlingRights = 0;\r
4721       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4722       /* !!?unconstrained back-rank shuffle */\r
4723       shuffleOpenings = 1;\r
4724       break;\r
4725     }\r
4726 \r
4727     overrule = 0;\r
4728     if(appData.NrFiles >= 0) {\r
4729         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4730         gameInfo.boardWidth = appData.NrFiles;\r
4731     }\r
4732     if(appData.NrRanks >= 0) {\r
4733         gameInfo.boardHeight = appData.NrRanks;\r
4734     }\r
4735     if(appData.holdingsSize >= 0) {\r
4736         i = appData.holdingsSize;\r
4737         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4738         gameInfo.holdingsSize = i;\r
4739     }\r
4740     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4741     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4742         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);\r
4743 \r
4744     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4745     if(pawnRow < 1) pawnRow = 1;\r
4746 \r
4747     /* User pieceToChar list overrules defaults */\r
4748     if(appData.pieceToCharTable != NULL)\r
4749         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4750 \r
4751     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4752 \r
4753         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4754             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4755         for( i=0; i<BOARD_HEIGHT; i++ )\r
4756             initialPosition[i][j] = s;\r
4757 \r
4758         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4759         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4760         initialPosition[pawnRow][j] = WhitePawn;\r
4761         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4762         if(gameInfo.variant == VariantXiangqi) {\r
4763             if(j&1) {\r
4764                 initialPosition[pawnRow][j] = \r
4765                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4766                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4767                    initialPosition[2][j] = WhiteCannon;\r
4768                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4769                 }\r
4770             }\r
4771         }\r
4772         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4773     }\r
4774     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4775 \r
4776             j=BOARD_LEFT+1;\r
4777             initialPosition[1][j] = WhiteBishop;\r
4778             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4779             j=BOARD_RGHT-2;\r
4780             initialPosition[1][j] = WhiteRook;\r
4781             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4782     }\r
4783 \r
4784     if( nrCastlingRights == -1) {\r
4785         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4786         /*       This sets default castling rights from none to normal corners   */\r
4787         /* Variants with other castling rights must set them themselves above    */\r
4788         nrCastlingRights = 6;\r
4789        \r
4790         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4791         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4792         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4793         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4794         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4795         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4796      }\r
4797 \r
4798      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
4799      if(gameInfo.variant == VariantGreat) { // promotion commoners\r
4800         initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-1] = WhiteMan;\r
4801         initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-2] = 9;\r
4802         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;\r
4803         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;\r
4804      }\r
4805 #if 0\r
4806     if(gameInfo.variant == VariantFischeRandom) {\r
4807       if( appData.defaultFrcPosition < 0 ) {\r
4808         ShuffleFRC( initialPosition );\r
4809       }\r
4810       else {\r
4811         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4812       }\r
4813       startedFromSetupPosition = TRUE;\r
4814     } else \r
4815 #else\r
4816   if (appData.debugMode) {\r
4817     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
4818   }\r
4819     if(shuffleOpenings) {\r
4820         SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
4821         startedFromSetupPosition = TRUE;\r
4822     }\r
4823 #endif\r
4824     if(startedFromPositionFile) {\r
4825       /* [HGM] loadPos: use PositionFile for every new game */\r
4826       CopyBoard(initialPosition, filePosition);\r
4827       for(i=0; i<nrCastlingRights; i++)\r
4828           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4829       startedFromSetupPosition = TRUE;\r
4830     }\r
4831 \r
4832     CopyBoard(boards[0], initialPosition);\r
4833 \r
4834     if(oldx != gameInfo.boardWidth ||\r
4835        oldy != gameInfo.boardHeight ||\r
4836        oldh != gameInfo.holdingsWidth\r
4837 #ifdef GOTHIC\r
4838        || oldv == VariantGothic ||        // For licensing popups\r
4839        gameInfo.variant == VariantGothic\r
4840 #endif\r
4841 #ifdef FALCON\r
4842        || oldv == VariantFalcon ||\r
4843        gameInfo.variant == VariantFalcon\r
4844 #endif\r
4845                                          )\r
4846             InitDrawingSizes(-2 ,0);\r
4847 \r
4848     if (redraw)\r
4849       DrawPosition(TRUE, boards[currentMove]);\r
4850 }\r
4851 \r
4852 void\r
4853 SendBoard(cps, moveNum)\r
4854      ChessProgramState *cps;\r
4855      int moveNum;\r
4856 {\r
4857     char message[MSG_SIZ];\r
4858     \r
4859     if (cps->useSetboard) {\r
4860       char* fen = PositionToFEN(moveNum, cps->useFEN960);\r
4861       sprintf(message, "setboard %s\n", fen);\r
4862       SendToProgram(message, cps);\r
4863       free(fen);\r
4864 \r
4865     } else {\r
4866       ChessSquare *bp;\r
4867       int i, j;\r
4868       /* Kludge to set black to move, avoiding the troublesome and now\r
4869        * deprecated "black" command.\r
4870        */\r
4871       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4872 \r
4873       SendToProgram("edit\n", cps);\r
4874       SendToProgram("#\n", cps);\r
4875       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4876         bp = &boards[moveNum][i][BOARD_LEFT];\r
4877         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4878           if ((int) *bp < (int) BlackPawn) {\r
4879             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4880                     AAA + j, ONE + i);\r
4881             if(message[0] == '+' || message[0] == '~') {\r
4882                 sprintf(message, "%c%c%c+\n",\r
4883                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4884                         AAA + j, ONE + i);\r
4885             }\r
4886             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4887                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4888                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4889             }\r
4890             SendToProgram(message, cps);\r
4891           }\r
4892         }\r
4893       }\r
4894     \r
4895       SendToProgram("c\n", cps);\r
4896       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4897         bp = &boards[moveNum][i][BOARD_LEFT];\r
4898         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4899           if (((int) *bp != (int) EmptySquare)\r
4900               && ((int) *bp >= (int) BlackPawn)) {\r
4901             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4902                     AAA + j, ONE + i);\r
4903             if(message[0] == '+' || message[0] == '~') {\r
4904                 sprintf(message, "%c%c%c+\n",\r
4905                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4906                         AAA + j, ONE + i);\r
4907             }\r
4908             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4909                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4910                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4911             }\r
4912             SendToProgram(message, cps);\r
4913           }\r
4914         }\r
4915       }\r
4916     \r
4917       SendToProgram(".\n", cps);\r
4918     }\r
4919     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4920 }\r
4921 \r
4922 int\r
4923 IsPromotion(fromX, fromY, toX, toY)\r
4924      int fromX, fromY, toX, toY;\r
4925 {\r
4926     /* [HGM] add Shogi promotions */\r
4927     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4928     ChessSquare piece;\r
4929 \r
4930     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4931       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4932    /* [HGM] Note to self: line above also weeds out drops */\r
4933     piece = boards[currentMove][fromY][fromX];\r
4934     if(gameInfo.variant == VariantShogi) {\r
4935         promotionZoneSize = 3;\r
4936         highestPromotingPiece = (int)WhiteKing;\r
4937         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4938            and if in normal chess we then allow promotion to King, why not\r
4939            allow promotion of other piece in Shogi?                         */\r
4940     }\r
4941     if((int)piece >= BlackPawn) {\r
4942         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4943              return FALSE;\r
4944         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4945     } else {\r
4946         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4947            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4948     }\r
4949     return ( (int)piece <= highestPromotingPiece );\r
4950 }\r
4951 \r
4952 int\r
4953 InPalace(row, column)\r
4954      int row, column;\r
4955 {   /* [HGM] for Xiangqi */\r
4956     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4957          column < (BOARD_WIDTH + 4)/2 &&\r
4958          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4959     return FALSE;\r
4960 }\r
4961 \r
4962 int\r
4963 PieceForSquare (x, y)\r
4964      int x;\r
4965      int y;\r
4966 {\r
4967   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
4968      return -1;\r
4969   else\r
4970      return boards[currentMove][y][x];\r
4971 }\r
4972 \r
4973 int\r
4974 OKToStartUserMove(x, y)\r
4975      int x, y;\r
4976 {\r
4977     ChessSquare from_piece;\r
4978     int white_piece;\r
4979 \r
4980     if (matchMode) return FALSE;\r
4981     if (gameMode == EditPosition) return TRUE;\r
4982 \r
4983     if (x >= 0 && y >= 0)\r
4984       from_piece = boards[currentMove][y][x];\r
4985     else\r
4986       from_piece = EmptySquare;\r
4987 \r
4988     if (from_piece == EmptySquare) return FALSE;\r
4989 \r
4990     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4991       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4992 \r
4993     switch (gameMode) {\r
4994       case PlayFromGameFile:\r
4995       case AnalyzeFile:\r
4996       case TwoMachinesPlay:\r
4997       case EndOfGame:\r
4998         return FALSE;\r
4999 \r
5000       case IcsObserving:\r
5001       case IcsIdle:\r
5002         return FALSE;\r
5003 \r
5004       case MachinePlaysWhite:\r
5005       case IcsPlayingBlack:\r
5006         if (appData.zippyPlay) return FALSE;\r
5007         if (white_piece) {\r
5008             DisplayMoveError(_("You are playing Black"));\r
5009             return FALSE;\r
5010         }\r
5011         break;\r
5012 \r
5013       case MachinePlaysBlack:\r
5014       case IcsPlayingWhite:\r
5015         if (appData.zippyPlay) return FALSE;\r
5016         if (!white_piece) {\r
5017             DisplayMoveError(_("You are playing White"));\r
5018             return FALSE;\r
5019         }\r
5020         break;\r
5021 \r
5022       case EditGame:\r
5023         if (!white_piece && WhiteOnMove(currentMove)) {\r
5024             DisplayMoveError(_("It is White's turn"));\r
5025             return FALSE;\r
5026         }           \r
5027         if (white_piece && !WhiteOnMove(currentMove)) {\r
5028             DisplayMoveError(_("It is Black's turn"));\r
5029             return FALSE;\r
5030         }           \r
5031         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
5032             /* Editing correspondence game history */\r
5033             /* Could disallow this or prompt for confirmation */\r
5034             cmailOldMove = -1;\r
5035         }\r
5036         if (currentMove < forwardMostMove) {\r
5037             /* Discarding moves */\r
5038             /* Could prompt for confirmation here,\r
5039                but I don't think that's such a good idea */\r
5040             forwardMostMove = currentMove;\r
5041         }\r
5042         break;\r
5043 \r
5044       case BeginningOfGame:\r
5045         if (appData.icsActive) return FALSE;\r
5046         if (!appData.noChessProgram) {\r
5047             if (!white_piece) {\r
5048                 DisplayMoveError(_("You are playing White"));\r
5049                 return FALSE;\r
5050             }\r
5051         }\r
5052         break;\r
5053         \r
5054       case Training:\r
5055         if (!white_piece && WhiteOnMove(currentMove)) {\r
5056             DisplayMoveError(_("It is White's turn"));\r
5057             return FALSE;\r
5058         }           \r
5059         if (white_piece && !WhiteOnMove(currentMove)) {\r
5060             DisplayMoveError(_("It is Black's turn"));\r
5061             return FALSE;\r
5062         }           \r
5063         break;\r
5064 \r
5065       default:\r
5066       case IcsExamining:\r
5067         break;\r
5068     }\r
5069     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
5070         && gameMode != AnalyzeFile && gameMode != Training) {\r
5071         DisplayMoveError(_("Displayed position is not current"));\r
5072         return FALSE;\r
5073     }\r
5074     return TRUE;\r
5075 }\r
5076 \r
5077 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
5078 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
5079 int lastLoadGameUseList = FALSE;\r
5080 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
5081 ChessMove lastLoadGameStart = (ChessMove) 0;\r
5082 \r
5083 \r
5084 ChessMove\r
5085 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
5086      int fromX, fromY, toX, toY;\r
5087      int promoChar;\r
5088 {\r
5089     ChessMove moveType;\r
5090     ChessSquare pdown, pup;\r
5091 \r
5092     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
5093     if ((fromX == toX) && (fromY == toY)) {\r
5094         return ImpossibleMove;\r
5095     }\r
5096 \r
5097     /* [HGM] suppress all moves into holdings area and guard band */\r
5098     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
5099             return ImpossibleMove;\r
5100 \r
5101     /* [HGM] <sameColor> moved to here from winboard.c */\r
5102     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
5103     pdown = boards[currentMove][fromY][fromX];\r
5104     pup = boards[currentMove][toY][toX];\r
5105     if (    gameMode != EditPosition &&\r
5106             (WhitePawn <= pdown && pdown < BlackPawn &&\r
5107              WhitePawn <= pup && pup < BlackPawn  ||\r
5108              BlackPawn <= pdown && pdown < EmptySquare &&\r
5109              BlackPawn <= pup && pup < EmptySquare \r
5110             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
5111                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
5112                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
5113         )           )\r
5114          return ImpossibleMove;\r
5115 \r
5116     /* Check if the user is playing in turn.  This is complicated because we\r
5117        let the user "pick up" a piece before it is his turn.  So the piece he\r
5118        tried to pick up may have been captured by the time he puts it down!\r
5119        Therefore we use the color the user is supposed to be playing in this\r
5120        test, not the color of the piece that is currently on the starting\r
5121        square---except in EditGame mode, where the user is playing both\r
5122        sides; fortunately there the capture race can't happen.  (It can\r
5123        now happen in IcsExamining mode, but that's just too bad.  The user\r
5124        will get a somewhat confusing message in that case.)\r
5125        */\r
5126 \r
5127     switch (gameMode) {\r
5128       case PlayFromGameFile:\r
5129       case AnalyzeFile:\r
5130       case TwoMachinesPlay:\r
5131       case EndOfGame:\r
5132       case IcsObserving:\r
5133       case IcsIdle:\r
5134         /* We switched into a game mode where moves are not accepted,\r
5135            perhaps while the mouse button was down. */\r
5136         return ImpossibleMove;\r
5137 \r
5138       case MachinePlaysWhite:\r
5139         /* User is moving for Black */\r
5140         if (WhiteOnMove(currentMove)) {\r
5141             DisplayMoveError(_("It is White's turn"));\r
5142             return ImpossibleMove;\r
5143         }\r
5144         break;\r
5145 \r
5146       case MachinePlaysBlack:\r
5147         /* User is moving for White */\r
5148         if (!WhiteOnMove(currentMove)) {\r
5149             DisplayMoveError(_("It is Black's turn"));\r
5150             return ImpossibleMove;\r
5151         }\r
5152         break;\r
5153 \r
5154       case EditGame:\r
5155       case IcsExamining:\r
5156       case BeginningOfGame:\r
5157       case AnalyzeMode:\r
5158       case Training:\r
5159         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
5160             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
5161             /* User is moving for Black */\r
5162             if (WhiteOnMove(currentMove)) {\r
5163                 DisplayMoveError(_("It is White's turn"));\r
5164                 return ImpossibleMove;\r
5165             }\r
5166         } else {\r
5167             /* User is moving for White */\r
5168             if (!WhiteOnMove(currentMove)) {\r
5169                 DisplayMoveError(_("It is Black's turn"));\r
5170                 return ImpossibleMove;\r
5171             }\r
5172         }\r
5173         break;\r
5174 \r
5175       case IcsPlayingBlack:\r
5176         /* User is moving for Black */\r
5177         if (WhiteOnMove(currentMove)) {\r
5178             if (!appData.premove) {\r
5179                 DisplayMoveError(_("It is White's turn"));\r
5180             } else if (toX >= 0 && toY >= 0) {\r
5181                 premoveToX = toX;\r
5182                 premoveToY = toY;\r
5183                 premoveFromX = fromX;\r
5184                 premoveFromY = fromY;\r
5185                 premovePromoChar = promoChar;\r
5186                 gotPremove = 1;\r
5187                 if (appData.debugMode) \r
5188                     fprintf(debugFP, "Got premove: fromX %d,"\r
5189                             "fromY %d, toX %d, toY %d\n",\r
5190                             fromX, fromY, toX, toY);\r
5191             }\r
5192             return ImpossibleMove;\r
5193         }\r
5194         break;\r
5195 \r
5196       case IcsPlayingWhite:\r
5197         /* User is moving for White */\r
5198         if (!WhiteOnMove(currentMove)) {\r
5199             if (!appData.premove) {\r
5200                 DisplayMoveError(_("It is Black's turn"));\r
5201             } else if (toX >= 0 && toY >= 0) {\r
5202                 premoveToX = toX;\r
5203                 premoveToY = toY;\r
5204                 premoveFromX = fromX;\r
5205                 premoveFromY = fromY;\r
5206                 premovePromoChar = promoChar;\r
5207                 gotPremove = 1;\r
5208                 if (appData.debugMode) \r
5209                     fprintf(debugFP, "Got premove: fromX %d,"\r
5210                             "fromY %d, toX %d, toY %d\n",\r
5211                             fromX, fromY, toX, toY);\r
5212             }\r
5213             return ImpossibleMove;\r
5214         }\r
5215         break;\r
5216 \r
5217       default:\r
5218         break;\r
5219 \r
5220       case EditPosition:\r
5221         /* EditPosition, empty square, or different color piece;\r
5222            click-click move is possible */\r
5223         if (toX == -2 || toY == -2) {\r
5224             boards[0][fromY][fromX] = EmptySquare;\r
5225             return AmbiguousMove;\r
5226         } else if (toX >= 0 && toY >= 0) {\r
5227             boards[0][toY][toX] = boards[0][fromY][fromX];\r
5228             boards[0][fromY][fromX] = EmptySquare;\r
5229             return AmbiguousMove;\r
5230         }\r
5231         return ImpossibleMove;\r
5232     }\r
5233 \r
5234     /* [HGM] If move started in holdings, it means a drop */\r
5235     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
5236          if( pup != EmptySquare ) return ImpossibleMove;\r
5237          if(appData.testLegality) {\r
5238              /* it would be more logical if LegalityTest() also figured out\r
5239               * which drops are legal. For now we forbid pawns on back rank.\r
5240               * Shogi is on its own here...\r
5241               */\r
5242              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
5243                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
5244                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
5245          }\r
5246          return WhiteDrop; /* Not needed to specify white or black yet */\r
5247     }\r
5248 \r
5249     userOfferedDraw = FALSE;\r
5250         \r
5251     /* [HGM] always test for legality, to get promotion info */\r
5252     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
5253                           epStatus[currentMove], castlingRights[currentMove],\r
5254                                          fromY, fromX, toY, toX, promoChar);\r
5255 \r
5256     /* [HGM] but possibly ignore an IllegalMove result */\r
5257     if (appData.testLegality) {\r
5258         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
5259             DisplayMoveError(_("Illegal move"));\r
5260             return ImpossibleMove;\r
5261         }\r
5262     }\r
5263 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);\r
5264     return moveType;\r
5265     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
5266        function is made into one that returns an OK move type if FinishMove\r
5267        should be called. This to give the calling driver routine the\r
5268        opportunity to finish the userMove input with a promotion popup,\r
5269        without bothering the user with this for invalid or illegal moves */\r
5270 \r
5271 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
5272 }\r
5273 \r
5274 /* Common tail of UserMoveEvent and DropMenuEvent */\r
5275 int\r
5276 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
5277      ChessMove moveType;\r
5278      int fromX, fromY, toX, toY;\r
5279      /*char*/int promoChar;\r
5280 {\r
5281     char *bookHit = 0;\r
5282 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);\r
5283     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { \r
5284         // [HGM] superchess: suppress promotions to non-available piece\r
5285         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5286         if(WhiteOnMove(currentMove)) {\r
5287             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;\r
5288         } else {\r
5289             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;\r
5290         }\r
5291     }\r
5292 \r
5293     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion\r
5294        move type in caller when we know the move is a legal promotion */\r
5295     if(moveType == NormalMove && promoChar)\r
5296         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
5297 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);\r
5298     /* [HGM] convert drag-and-drop piece drops to standard form */\r
5299     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
5300          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
5301          fromX = boards[currentMove][fromY][fromX];\r
5302          fromY = DROP_RANK;\r
5303     }\r
5304 \r
5305     /* [HGM] <popupFix> The following if has been moved here from\r
5306        UserMoveEvent(). Because it seemed to belon here (why not allow\r
5307        piece drops in training games?), and because it can only be\r
5308        performed after it is known to what we promote. */\r
5309     if (gameMode == Training) {\r
5310       /* compare the move played on the board to the next move in the\r
5311        * game. If they match, display the move and the opponent's response. \r
5312        * If they don't match, display an error message.\r
5313        */\r
5314       int saveAnimate;\r
5315       Board testBoard;\r
5316       CopyBoard(testBoard, boards[currentMove]);\r
5317       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
5318 \r
5319       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
5320         ForwardInner(currentMove+1);\r
5321 \r
5322         /* Autoplay the opponent's response.\r
5323          * if appData.animate was TRUE when Training mode was entered,\r
5324          * the response will be animated.\r
5325          */\r
5326         saveAnimate = appData.animate;\r
5327         appData.animate = animateTraining;\r
5328         ForwardInner(currentMove+1);\r
5329         appData.animate = saveAnimate;\r
5330 \r
5331         /* check for the end of the game */\r
5332         if (currentMove >= forwardMostMove) {\r
5333           gameMode = PlayFromGameFile;\r
5334           ModeHighlight();\r
5335           SetTrainingModeOff();\r
5336           DisplayInformation(_("End of game"));\r
5337         }\r
5338       } else {\r
5339         DisplayError(_("Incorrect move"), 0);\r
5340       }\r
5341       return 1;\r
5342     }\r
5343 \r
5344   /* Ok, now we know that the move is good, so we can kill\r
5345      the previous line in Analysis Mode */\r
5346   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
5347     forwardMostMove = currentMove;\r
5348   }\r
5349 \r
5350   /* If we need the chess program but it's dead, restart it */\r
5351   ResurrectChessProgram();\r
5352 \r
5353   /* A user move restarts a paused game*/\r
5354   if (pausing)\r
5355     PauseEvent();\r
5356 \r
5357   thinkOutput[0] = NULLCHAR;\r
5358 \r
5359   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
5360 \r
5361     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) \r
5362                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
5363         // [HGM] superchess: take promotion piece out of holdings\r
5364         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5365         if(WhiteOnMove(forwardMostMove-1)) {\r
5366             if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])\r
5367                 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;\r
5368         } else {\r
5369             if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])\r
5370                 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;\r
5371         }\r
5372     }\r
5373 \r
5374   if (gameMode == BeginningOfGame) {\r
5375     if (appData.noChessProgram) {\r
5376       gameMode = EditGame;\r
5377       SetGameInfo();\r
5378     } else {\r
5379       char buf[MSG_SIZ];\r
5380       gameMode = MachinePlaysBlack;\r
5381       StartClocks();\r
5382       SetGameInfo();\r
5383       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
5384       DisplayTitle(buf);\r
5385       if (first.sendName) {\r
5386         sprintf(buf, "name %s\n", gameInfo.white);\r
5387         SendToProgram(buf, &first);\r
5388       }\r
5389       StartClocks();\r
5390     }\r
5391     ModeHighlight();\r
5392   }\r
5393 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);\r
5394   /* Relay move to ICS or chess engine */\r
5395   if (appData.icsActive) {\r
5396     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
5397         gameMode == IcsExamining) {\r
5398       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5399       ics_user_moved = 1;\r
5400     }\r
5401   } else {\r
5402     if (first.sendTime && (gameMode == BeginningOfGame ||\r
5403                            gameMode == MachinePlaysWhite ||\r
5404                            gameMode == MachinePlaysBlack)) {\r
5405       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
5406     }\r
5407     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
5408          // [HGM] book: if program might be playing, let it use book\r
5409         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);\r
5410         first.maybeThinking = TRUE;\r
5411     } else SendMoveToProgram(forwardMostMove-1, &first);\r
5412     if (currentMove == cmailOldMove + 1) {\r
5413       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
5414     }\r
5415   }\r
5416 \r
5417   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5418 \r
5419   switch (gameMode) {\r
5420   case EditGame:\r
5421     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
5422                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
5423     case MT_NONE:\r
5424     case MT_CHECK:\r
5425       break;\r
5426     case MT_CHECKMATE:\r
5427       if (WhiteOnMove(currentMove)) {\r
5428         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
5429       } else {\r
5430         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
5431       }\r
5432       break;\r
5433     case MT_STALEMATE:\r
5434       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
5435       break;\r
5436     }\r
5437     break;\r
5438     \r
5439   case MachinePlaysBlack:\r
5440   case MachinePlaysWhite:\r
5441     /* disable certain menu options while machine is thinking */\r
5442     SetMachineThinkingEnables();\r
5443     break;\r
5444 \r
5445   default:\r
5446     break;\r
5447   }\r
5448 \r
5449   if(bookHit) { // [HGM] book: simulate book reply\r
5450         static char bookMove[MSG_SIZ]; // a bit generous?\r
5451 \r
5452         programStats.nodes = programStats.depth = programStats.time = \r
5453         programStats.score = programStats.got_only_move = 0;\r
5454         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
5455 \r
5456         strcpy(bookMove, "move ");\r
5457         strcat(bookMove, bookHit);\r
5458         HandleMachineMove(bookMove, &first);\r
5459   }\r
5460   return 1;\r
5461 }\r
5462 \r
5463 void\r
5464 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
5465      int fromX, fromY, toX, toY;\r
5466      int promoChar;\r
5467 {\r
5468     /* [HGM] This routine was added to allow calling of its two logical\r
5469        parts from other modules in the old way. Before, UserMoveEvent()\r
5470        automatically called FinishMove() if the move was OK, and returned\r
5471        otherwise. I separated the two, in order to make it possible to\r
5472        slip a promotion popup in between. But that it always needs two\r
5473        calls, to the first part, (now called UserMoveTest() ), and to\r
5474        FinishMove if the first part succeeded. Calls that do not need\r
5475        to do anything in between, can call this routine the old way. \r
5476     */\r
5477     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
5478 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);\r
5479     if(moveType != ImpossibleMove)\r
5480         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
5481 }\r
5482 \r
5483 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
5484 {\r
5485 //    char * hint = lastHint;\r
5486     FrontEndProgramStats stats;\r
5487 \r
5488     stats.which = cps == &first ? 0 : 1;\r
5489     stats.depth = cpstats->depth;\r
5490     stats.nodes = cpstats->nodes;\r
5491     stats.score = cpstats->score;\r
5492     stats.time = cpstats->time;\r
5493     stats.pv = cpstats->movelist;\r
5494     stats.hint = lastHint;\r
5495     stats.an_move_index = 0;\r
5496     stats.an_move_count = 0;\r
5497 \r
5498     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
5499         stats.hint = cpstats->move_name;\r
5500         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
5501         stats.an_move_count = cpstats->nr_moves;\r
5502     }\r
5503 \r
5504     SetProgramStats( &stats );\r
5505 }\r
5506 \r
5507 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)\r
5508 {   // [HGM] book: this routine intercepts moves to simulate book replies\r
5509     char *bookHit = NULL;\r
5510 \r
5511     //first determine if the incoming move brings opponent into his book\r
5512     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
5513         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
5514     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");\r
5515     if(bookHit != NULL && !cps->bookSuspend) {\r
5516         // make sure opponent is not going to reply after receiving move to book position\r
5517         SendToProgram("force\n", cps);\r
5518         cps->bookSuspend = TRUE; // flag indicating it has to be restarted\r
5519     }\r
5520     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move\r
5521     // now arrange restart after book miss\r
5522     if(bookHit) {\r
5523         // after a book hit we never send 'go', and the code after the call to this routine\r
5524         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').\r
5525         char buf[MSG_SIZ];\r
5526         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(\r
5527         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it\r
5528         SendToProgram(buf, cps);\r
5529         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'\r
5530     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine\r
5531         SendToProgram("go\n", cps);\r
5532         cps->bookSuspend = FALSE; // after a 'go' we are never suspended\r
5533     } else { // 'go' might be sent based on 'firstMove' after this routine returns\r
5534         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return\r
5535             SendToProgram("go\n", cps); \r
5536         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss\r
5537     }\r
5538     return bookHit; // notify caller of hit, so it can take action to send move to opponent\r
5539 }\r
5540 \r
5541 char *savedMessage;\r
5542 ChessProgramState *savedState;\r
5543 void DeferredBookMove(void)\r
5544 {\r
5545         if(savedState->lastPing != savedState->lastPong)\r
5546                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
5547         else\r
5548         HandleMachineMove(savedMessage, savedState);\r
5549 }\r
5550 \r
5551 void\r
5552 HandleMachineMove(message, cps)\r
5553      char *message;\r
5554      ChessProgramState *cps;\r
5555 {\r
5556     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
5557     char realname[MSG_SIZ];\r
5558     int fromX, fromY, toX, toY;\r
5559     ChessMove moveType;\r
5560     char promoChar;\r
5561     char *p;\r
5562     int machineWhite;\r
5563     char *bookHit;\r
5564 \r
5565 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit\r
5566     /*\r
5567      * Kludge to ignore BEL characters\r
5568      */\r
5569     while (*message == '\007') message++;\r
5570 \r
5571     /*\r
5572      * [HGM] engine debug message: ignore lines starting with '#' character\r
5573      */\r
5574     if(cps->debug && *message == '#') return;\r
5575 \r
5576     /*\r
5577      * Look for book output\r
5578      */\r
5579     if (cps == &first && bookRequested) {\r
5580         if (message[0] == '\t' || message[0] == ' ') {\r
5581             /* Part of the book output is here; append it */\r
5582             strcat(bookOutput, message);\r
5583             strcat(bookOutput, "  \n");\r
5584             return;\r
5585         } else if (bookOutput[0] != NULLCHAR) {\r
5586             /* All of book output has arrived; display it */\r
5587             char *p = bookOutput;\r
5588             while (*p != NULLCHAR) {\r
5589                 if (*p == '\t') *p = ' ';\r
5590                 p++;\r
5591             }\r
5592             DisplayInformation(bookOutput);\r
5593             bookRequested = FALSE;\r
5594             /* Fall through to parse the current output */\r
5595         }\r
5596     }\r
5597 \r
5598     /*\r
5599      * Look for machine move.\r
5600      */\r
5601     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
5602         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
5603     {\r
5604         /* This method is only useful on engines that support ping */\r
5605         if (cps->lastPing != cps->lastPong) {\r
5606           if (gameMode == BeginningOfGame) {\r
5607             /* Extra move from before last new; ignore */\r
5608             if (appData.debugMode) {\r
5609                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5610             }\r
5611           } else {\r
5612             if (appData.debugMode) {\r
5613                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5614                         cps->which, gameMode);\r
5615             }\r
5616 \r
5617             SendToProgram("undo\n", cps);\r
5618           }\r
5619           return;\r
5620         }\r
5621 \r
5622         switch (gameMode) {\r
5623           case BeginningOfGame:\r
5624             /* Extra move from before last reset; ignore */\r
5625             if (appData.debugMode) {\r
5626                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5627             }\r
5628             return;\r
5629 \r
5630           case EndOfGame:\r
5631           case IcsIdle:\r
5632           default:\r
5633             /* Extra move after we tried to stop.  The mode test is\r
5634                not a reliable way of detecting this problem, but it's\r
5635                the best we can do on engines that don't support ping.\r
5636             */\r
5637             if (appData.debugMode) {\r
5638                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5639                         cps->which, gameMode);\r
5640             }\r
5641             SendToProgram("undo\n", cps);\r
5642             return;\r
5643 \r
5644           case MachinePlaysWhite:\r
5645           case IcsPlayingWhite:\r
5646             machineWhite = TRUE;\r
5647             break;\r
5648 \r
5649           case MachinePlaysBlack:\r
5650           case IcsPlayingBlack:\r
5651             machineWhite = FALSE;\r
5652             break;\r
5653 \r
5654           case TwoMachinesPlay:\r
5655             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5656             break;\r
5657         }\r
5658         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5659             if (appData.debugMode) {\r
5660                 fprintf(debugFP,\r
5661                         "Ignoring move out of turn by %s, gameMode %d"\r
5662                         ", forwardMost %d\n",\r
5663                         cps->which, gameMode, forwardMostMove);\r
5664             }\r
5665             return;\r
5666         }\r
5667 \r
5668     if (appData.debugMode) { int f = forwardMostMove;\r
5669         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5670                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5671     }\r
5672         if(cps->alphaRank) AlphaRank(machineMove, 4);\r
5673         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5674                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5675             /* Machine move could not be parsed; ignore it. */\r
5676             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),\r
5677                     machineMove, cps->which);\r
5678             DisplayError(buf1, 0);\r
5679             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",\r
5680                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);\r
5681             if (gameMode == TwoMachinesPlay) {\r
5682               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5683                        buf1, GE_XBOARD);\r
5684             }\r
5685             return;\r
5686         }\r
5687 \r
5688         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5689         /* So we have to redo legality test with true e.p. status here,  */\r
5690         /* to make sure an illegal e.p. capture does not slip through,   */\r
5691         /* to cause a forfeit on a justified illegal-move complaint      */\r
5692         /* of the opponent.                                              */\r
5693         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5694             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5695                                                               ) {\r
5696            ChessMove moveType;\r
5697            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5698                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5699                              fromY, fromX, toY, toX, promoChar);\r
5700             if (appData.debugMode) {\r
5701                 int i;\r
5702                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5703                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5704                 fprintf(debugFP, "castling rights\n");\r
5705             }\r
5706             if(moveType == IllegalMove) {\r
5707                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5708                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5709                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5710                            buf1, GE_XBOARD);\r
5711            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
5712            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5713               when they shouldn't (like TSCP-Gothic) */\r
5714            switch(moveType) {\r
5715              case WhiteASideCastleFR:\r
5716              case BlackASideCastleFR:\r
5717                toX+=2;\r
5718                currentMoveString[2]++;\r
5719                break;\r
5720              case WhiteHSideCastleFR:\r
5721              case BlackHSideCastleFR:\r
5722                toX--;\r
5723                currentMoveString[2]--;\r
5724                break;\r
5725              default: ; // nothing to do, but suppresses warning of pedantic compilers\r
5726            }\r
5727         }\r
5728         hintRequested = FALSE;\r
5729         lastHint[0] = NULLCHAR;\r
5730         bookRequested = FALSE;\r
5731         /* Program may be pondering now */\r
5732         cps->maybeThinking = TRUE;\r
5733         if (cps->sendTime == 2) cps->sendTime = 1;\r
5734         if (cps->offeredDraw) cps->offeredDraw--;\r
5735 \r
5736 #if ZIPPY\r
5737         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5738             first.initDone) {\r
5739           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5740           ics_user_moved = 1;\r
5741           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
5742                 char buf[3*MSG_SIZ];\r
5743 \r
5744                 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",\r
5745                         programStats.depth,\r
5746                         programStats.score / 100.,\r
5747                         programStats.time / 100.,\r
5748                         u64ToDouble(programStats.nodes),\r
5749                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),\r
5750                         programStats.movelist);\r
5751                 SendToICS(buf);\r
5752           }\r
5753         }\r
5754 #endif\r
5755         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5756         strcpy(machineMove, currentMoveString);\r
5757         strcat(machineMove, "\n");\r
5758         strcpy(moveList[forwardMostMove], machineMove);\r
5759 \r
5760         /* [AS] Save move info and clear stats for next move */\r
5761         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5762         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5763         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5764         ClearProgramStats();\r
5765         thinkOutput[0] = NULLCHAR;\r
5766         hiddenThinkOutputState = 0;\r
5767 \r
5768         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5769 \r
5770         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5771         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5772             int count = 0;\r
5773 \r
5774             while( count < adjudicateLossPlies ) {\r
5775                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5776 \r
5777                 if( count & 1 ) {\r
5778                     score = -score; /* Flip score for winning side */\r
5779                 }\r
5780 \r
5781                 if( score > adjudicateLossThreshold ) {\r
5782                     break;\r
5783                 }\r
5784 \r
5785                 count++;\r
5786             }\r
5787 \r
5788             if( count >= adjudicateLossPlies ) {\r
5789                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5790 \r
5791                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5792                     "Xboard adjudication", \r
5793                     GE_XBOARD );\r
5794 \r
5795                 return;\r
5796             }\r
5797         }\r
5798 \r
5799         if( gameMode == TwoMachinesPlay ) {\r
5800           // [HGM] some adjudications useful with buggy engines\r
5801             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
5802           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {\r
5803 \r
5804             if(appData.testLegality)\r
5805             // don't wait for engine to announce game end if we can judge ourselves\r
5806             switch (MateTest(boards[forwardMostMove],\r
5807                                  PosFlags(forwardMostMove), epFile,\r
5808                                        castlingRights[forwardMostMove]) ) {\r
5809               case MT_NONE:\r
5810               case MT_CHECK:\r
5811               default:\r
5812                 break;\r
5813               case MT_STALEMATE:\r
5814                 epStatus[forwardMostMove] = EP_STALEMATE;\r
5815                 if(appData.checkMates) {\r
5816                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5817                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5818                     GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",\r
5819                         GE_XBOARD );\r
5820                 }\r
5821                 break;\r
5822               case MT_CHECKMATE:\r
5823                 epStatus[forwardMostMove] = EP_CHECKMATE;\r
5824                 if(appData.checkMates) {\r
5825                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5826                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5827                     GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5828                     "Xboard adjudication: Checkmate", \r
5829                     GE_XBOARD );\r
5830                 }\r
5831                 break;\r
5832             }\r
5833 \r
5834             if( appData.testLegality )\r
5835             {   /* [HGM] Some more adjudications for obstinate engines */\r
5836                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5837                     NrWQ=0, NrBQ=0, NrW=0, bishopsColor = 0,\r
5838                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;\r
5839                 static int moveCount = 6;\r
5840 \r
5841                 /* First absolutely insufficient mating material. Count what is on board. */\r
5842                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5843                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5844                     int m=i;\r
5845 \r
5846                     switch((int) p)\r
5847                     {   /* count B,N,R and other of each side */\r
5848                         case WhiteKnight:\r
5849                              NrWN++; break;\r
5850                         case WhiteBishop:\r
5851                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5852                              bishopsColor |= 1 << ((i^j)&1);\r
5853                              NrWB++; break;\r
5854                         case BlackKnight:\r
5855                              NrBN++; break;\r
5856                         case BlackBishop:\r
5857                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5858                              bishopsColor |= 1 << ((i^j)&1);\r
5859                              NrBB++; break;\r
5860                         case WhiteRook:\r
5861                              NrWR++; break;\r
5862                         case BlackRook:\r
5863                              NrBR++; break;\r
5864                         case WhiteQueen:\r
5865                              NrWQ++; break;\r
5866                         case BlackQueen:\r
5867                              NrBQ++; break;\r
5868                         case EmptySquare: \r
5869                              break;\r
5870                         case BlackPawn:\r
5871                              m = 7-i;\r
5872                         case WhitePawn:\r
5873                              PawnAdvance += m; NrPawns++;\r
5874                     }\r
5875                     NrPieces += (p != EmptySquare);\r
5876                     NrW += ((int)p < (int)BlackPawn);\r
5877                     if(gameInfo.variant == VariantXiangqi && \r
5878                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {\r
5879                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces\r
5880                         NrW -= ((int)p < (int)BlackPawn);\r
5881                     }\r
5882                 }\r
5883 \r
5884                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&\r
5885                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||\r
5886                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color\r
5887                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5888 \r
5889                      /* always flag draws, for judging claims */\r
5890                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5891 \r
5892                      if(appData.materialDraws) {\r
5893                          /* but only adjudicate them if adjudication enabled */\r
5894                          SendToProgram("force\n", cps->other); // suppress reply\r
5895                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */\r
5896                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5897                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5898                          return;\r
5899                      }\r
5900                 }\r
5901 \r
5902                 /* Shatranj baring rule */\r
5903                 if( gameInfo.variant == VariantShatranj && (NrW == 1 || NrPieces - NrW == 1) )\r
5904                 {    /* bare King */\r
5905 \r
5906                      if(--bare < 0 && appData.checkMates) {\r
5907                          /* but only adjudicate them if adjudication enabled */\r
5908                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5909                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5910                          GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
5911                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5912                          return;\r
5913                      }\r
5914                 } else bare = 1;\r
5915 \r
5916                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5917                 if(NrPieces == 4 && \r
5918                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5919                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5920                    || NrWN==2 || NrBN==2     /* KNNK */\r
5921                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5922                   ) ) {\r
5923                      if(--moveCount < 0 && appData.trivialDraws)\r
5924                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5925                           SendToProgram("force\n", cps->other); // suppress reply\r
5926                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5927                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5928                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5929                           return;\r
5930                      }\r
5931                 } else moveCount = 6;\r
5932             }\r
5933           }\r
5934 #if 1\r
5935     if (appData.debugMode) { int i;\r
5936       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5937               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5938               appData.drawRepeats);\r
5939       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5940            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5941 \r
5942     }\r
5943 #endif\r
5944                 /* Check for rep-draws */\r
5945                 count = 0;\r
5946                 for(k = forwardMostMove-2;\r
5947                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5948                         epStatus[k] < EP_UNKNOWN &&\r
5949                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5950                     k-=2)\r
5951                 {   int rights=0;\r
5952 #if 0\r
5953     if (appData.debugMode) {\r
5954       fprintf(debugFP, " loop\n");\r
5955     }\r
5956 #endif\r
5957                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5958 #if 0\r
5959     if (appData.debugMode) {\r
5960       fprintf(debugFP, "match\n");\r
5961     }\r
5962 #endif\r
5963                         /* compare castling rights */\r
5964                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5965                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5966                                 rights++; /* King lost rights, while rook still had them */\r
5967                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5968                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5969                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5970                                    rights++; /* but at least one rook lost them */\r
5971                         }\r
5972                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5973                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5974                                 rights++; \r
5975                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5976                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5977                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5978                                    rights++;\r
5979                         }\r
5980 #if 0\r
5981     if (appData.debugMode) {\r
5982       for(i=0; i<nrCastlingRights; i++)\r
5983       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5984     }\r
5985 \r
5986     if (appData.debugMode) {\r
5987       fprintf(debugFP, " %d %d\n", rights, k);\r
5988     }\r
5989 #endif\r
5990                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5991                             && appData.drawRepeats > 1) {\r
5992                              /* adjudicate after user-specified nr of repeats */\r
5993                              SendToProgram("force\n", cps->other); // suppress reply\r
5994                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5995                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5996                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { \r
5997                                 // [HGM] xiangqi: check for forbidden perpetuals\r
5998                                 int m, ourPerpetual = 1, hisPerpetual = 1;\r
5999                                 for(m=forwardMostMove; m>k; m-=2) {\r
6000                                     if(MateTest(boards[m], PosFlags(m), \r
6001                                                         EP_NONE, castlingRights[m]) != MT_CHECK)\r
6002                                         ourPerpetual = 0; // the current mover did not always check\r
6003                                     if(MateTest(boards[m-1], PosFlags(m-1), \r
6004                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)\r
6005                                         hisPerpetual = 0; // the opponent did not always check\r
6006                                 }\r
6007                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",\r
6008                                                                         ourPerpetual, hisPerpetual);\r
6009                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
6010                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
6011                                            "Xboard adjudication: perpetual checking", GE_XBOARD );\r
6012                                     return;\r
6013                                 }\r
6014                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet\r
6015                                     break; // (or we would have caught him before). Abort repetition-checking loop.\r
6016                                 // Now check for perpetual chases\r
6017                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase\r
6018                                     hisPerpetual = PerpetualChase(k, forwardMostMove);\r
6019                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);\r
6020                                     if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
6021                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
6022                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );\r
6023                                         return;\r
6024                                     }\r
6025                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet\r
6026                                         break; // Abort repetition-checking loop.\r
6027                                 }\r
6028                                 // if neither of us is checking or chasing all the time, or both are, it is draw\r
6029                              }\r
6030                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
6031                              return;\r
6032                         }\r
6033                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
6034                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
6035                     }\r
6036                 }\r
6037 \r
6038                 /* Now we test for 50-move draws. Determine ply count */\r
6039                 count = forwardMostMove;\r
6040                 /* look for last irreversble move */\r
6041                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
6042                     count--;\r
6043                 /* if we hit starting position, add initial plies */\r
6044                 if( count == backwardMostMove )\r
6045                     count -= initialRulePlies;\r
6046                 count = forwardMostMove - count; \r
6047                 if( count >= 100)\r
6048                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
6049                          /* this is used to judge if draw claims are legal */\r
6050                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
6051                          SendToProgram("force\n", cps->other); // suppress reply\r
6052                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6053                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6054                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
6055                          return;\r
6056                 }\r
6057 \r
6058                 /* if draw offer is pending, treat it as a draw claim\r
6059                  * when draw condition present, to allow engines a way to\r
6060                  * claim draws before making their move to avoid a race\r
6061                  * condition occurring after their move\r
6062                  */\r
6063                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
6064                          char *p = NULL;\r
6065                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
6066                              p = "Draw claim: 50-move rule";\r
6067                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
6068                              p = "Draw claim: 3-fold repetition";\r
6069                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
6070                              p = "Draw claim: insufficient mating material";\r
6071                          if( p != NULL ) {\r
6072                              SendToProgram("force\n", cps->other); // suppress reply\r
6073                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6074                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
6075                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6076                              return;\r
6077                          }\r
6078                 }\r
6079 \r
6080 \r
6081                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
6082                     SendToProgram("force\n", cps->other); // suppress reply\r
6083                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6084                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6085 \r
6086                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
6087 \r
6088                     return;\r
6089                 }\r
6090         }\r
6091 \r
6092         bookHit = NULL;\r
6093         if (gameMode == TwoMachinesPlay) {\r
6094             /* [HGM] relaying draw offers moved to after reception of move */\r
6095             /* and interpreting offer as claim if it brings draw condition */\r
6096             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
6097                 SendToProgram("draw\n", cps->other);\r
6098             }\r
6099             if (cps->other->sendTime) {\r
6100                 SendTimeRemaining(cps->other,\r
6101                                   cps->other->twoMachinesColor[0] == 'w');\r
6102             }\r
6103             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);\r
6104             if (firstMove && !bookHit) {\r
6105                 firstMove = FALSE;\r
6106                 if (cps->other->useColors) {\r
6107                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
6108                 }\r
6109                 SendToProgram("go\n", cps->other);\r
6110             }\r
6111             cps->other->maybeThinking = TRUE;\r
6112         }\r
6113 \r
6114         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6115         \r
6116         if (!pausing && appData.ringBellAfterMoves) {\r
6117             RingBell();\r
6118         }\r
6119 \r
6120         /* \r
6121          * Reenable menu items that were disabled while\r
6122          * machine was thinking\r
6123          */\r
6124         if (gameMode != TwoMachinesPlay)\r
6125             SetUserThinkingEnables();\r
6126 \r
6127         // [HGM] book: after book hit opponent has received move and is now in force mode\r
6128         // force the book reply into it, and then fake that it outputted this move by jumping\r
6129         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move\r
6130         if(bookHit) {\r
6131                 static char bookMove[MSG_SIZ]; // a bit generous?\r
6132 \r
6133                 strcpy(bookMove, "move ");\r
6134                 strcat(bookMove, bookHit);\r
6135                 message = bookMove;\r
6136                 cps = cps->other;\r
6137                 programStats.nodes = programStats.depth = programStats.time = \r
6138                 programStats.score = programStats.got_only_move = 0;\r
6139                 sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
6140 \r
6141                 if(cps->lastPing != cps->lastPong) {\r
6142                     savedMessage = message; // args for deferred call\r
6143                     savedState = cps;\r
6144                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
6145                     return;\r
6146                 }\r
6147                 goto FakeBookMove;\r
6148         }\r
6149 \r
6150         return;\r
6151     }\r
6152 \r
6153     /* Set special modes for chess engines.  Later something general\r
6154      *  could be added here; for now there is just one kludge feature,\r
6155      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
6156      *  when "xboard" is given as an interactive command.\r
6157      */\r
6158     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
6159         cps->useSigint = FALSE;\r
6160         cps->useSigterm = FALSE;\r
6161     }\r
6162 \r
6163     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
6164      * want this, I was asked to put it in, and obliged.\r
6165      */\r
6166     if (!strncmp(message, "setboard ", 9)) {\r
6167         Board initial_position; int i;\r
6168 \r
6169         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
6170 \r
6171         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
6172             DisplayError(_("Bad FEN received from engine"), 0);\r
6173             return ;\r
6174         } else {\r
6175            Reset(FALSE, FALSE);\r
6176            CopyBoard(boards[0], initial_position);\r
6177            initialRulePlies = FENrulePlies;\r
6178            epStatus[0] = FENepStatus;\r
6179            for( i=0; i<nrCastlingRights; i++ )\r
6180                 castlingRights[0][i] = FENcastlingRights[i];\r
6181            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
6182            else gameMode = MachinePlaysBlack;                 \r
6183            DrawPosition(FALSE, boards[currentMove]);\r
6184         }\r
6185         return;\r
6186     }\r
6187 \r
6188     /*\r
6189      * Look for communication commands\r
6190      */\r
6191     if (!strncmp(message, "telluser ", 9)) {\r
6192         DisplayNote(message + 9);\r
6193         return;\r
6194     }\r
6195     if (!strncmp(message, "tellusererror ", 14)) {\r
6196         DisplayError(message + 14, 0);\r
6197         return;\r
6198     }\r
6199     if (!strncmp(message, "tellopponent ", 13)) {\r
6200       if (appData.icsActive) {\r
6201         if (loggedOn) {\r
6202           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
6203           SendToICS(buf1);\r
6204         }\r
6205       } else {\r
6206         DisplayNote(message + 13);\r
6207       }\r
6208       return;\r
6209     }\r
6210     if (!strncmp(message, "tellothers ", 11)) {\r
6211       if (appData.icsActive) {\r
6212         if (loggedOn) {\r
6213           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
6214           SendToICS(buf1);\r
6215         }\r
6216       }\r
6217       return;\r
6218     }\r
6219     if (!strncmp(message, "tellall ", 8)) {\r
6220       if (appData.icsActive) {\r
6221         if (loggedOn) {\r
6222           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
6223           SendToICS(buf1);\r
6224         }\r
6225       } else {\r
6226         DisplayNote(message + 8);\r
6227       }\r
6228       return;\r
6229     }\r
6230     if (strncmp(message, "warning", 7) == 0) {\r
6231         /* Undocumented feature, use tellusererror in new code */\r
6232         DisplayError(message, 0);\r
6233         return;\r
6234     }\r
6235     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
6236         strcpy(realname, cps->tidy);\r
6237         strcat(realname, " query");\r
6238         AskQuestion(realname, buf2, buf1, cps->pr);\r
6239         return;\r
6240     }\r
6241     /* Commands from the engine directly to ICS.  We don't allow these to be \r
6242      *  sent until we are logged on. Crafty kibitzes have been known to \r
6243      *  interfere with the login process.\r
6244      */\r
6245     if (loggedOn) {\r
6246         if (!strncmp(message, "tellics ", 8)) {\r
6247             SendToICS(message + 8);\r
6248             SendToICS("\n");\r
6249             return;\r
6250         }\r
6251         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
6252             SendToICS(ics_prefix);\r
6253             SendToICS(message + 15);\r
6254             SendToICS("\n");\r
6255             return;\r
6256         }\r
6257         /* The following are for backward compatibility only */\r
6258         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
6259             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
6260             SendToICS(ics_prefix);\r
6261             SendToICS(message);\r
6262             SendToICS("\n");\r
6263             return;\r
6264         }\r
6265     }\r
6266     if (strncmp(message, "feature ", 8) == 0) {\r
6267       ParseFeatures(message+8, cps);\r
6268     }\r
6269     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
6270         return;\r
6271     }\r
6272     /*\r
6273      * If the move is illegal, cancel it and redraw the board.\r
6274      * Also deal with other error cases.  Matching is rather loose\r
6275      * here to accommodate engines written before the spec.\r
6276      */\r
6277     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
6278         strncmp(message, "Error", 5) == 0) {\r
6279         if (StrStr(message, "name") || \r
6280             StrStr(message, "rating") || StrStr(message, "?") ||\r
6281             StrStr(message, "result") || StrStr(message, "board") ||\r
6282             StrStr(message, "bk") || StrStr(message, "computer") ||\r
6283             StrStr(message, "variant") || StrStr(message, "hint") ||\r
6284             StrStr(message, "random") || StrStr(message, "depth") ||\r
6285             StrStr(message, "accepted")) {\r
6286             return;\r
6287         }\r
6288         if (StrStr(message, "protover")) {\r
6289           /* Program is responding to input, so it's apparently done\r
6290              initializing, and this error message indicates it is\r
6291              protocol version 1.  So we don't need to wait any longer\r
6292              for it to initialize and send feature commands. */\r
6293           FeatureDone(cps, 1);\r
6294           cps->protocolVersion = 1;\r
6295           return;\r
6296         }\r
6297         cps->maybeThinking = FALSE;\r
6298 \r
6299         if (StrStr(message, "draw")) {\r
6300             /* Program doesn't have "draw" command */\r
6301             cps->sendDrawOffers = 0;\r
6302             return;\r
6303         }\r
6304         if (cps->sendTime != 1 &&\r
6305             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
6306           /* Program apparently doesn't have "time" or "otim" command */\r
6307           cps->sendTime = 0;\r
6308           return;\r
6309         }\r
6310         if (StrStr(message, "analyze")) {\r
6311             cps->analysisSupport = FALSE;\r
6312             cps->analyzing = FALSE;\r
6313             Reset(FALSE, TRUE);\r
6314             sprintf(buf2, _("%s does not support analysis"), cps->tidy);\r
6315             DisplayError(buf2, 0);\r
6316             return;\r
6317         }\r
6318         if (StrStr(message, "(no matching move)st")) {\r
6319           /* Special kludge for GNU Chess 4 only */\r
6320           cps->stKludge = 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, "(no matching move)sd")) {\r
6327           /* Special kludge for GNU Chess 4 only */\r
6328           cps->sdKludge = TRUE;\r
6329           SendTimeControl(cps, movesPerSession, timeControl,\r
6330                           timeIncrement, appData.searchDepth,\r
6331                           searchTime);\r
6332           return;\r
6333         }\r
6334         if (!StrStr(message, "llegal")) {\r
6335             return;\r
6336         }\r
6337         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6338             gameMode == IcsIdle) return;\r
6339         if (forwardMostMove <= backwardMostMove) return;\r
6340 #if 0\r
6341         /* Following removed: it caused a bug where a real illegal move\r
6342            message in analyze mored would be ignored. */\r
6343         if (cps == &first && programStats.ok_to_send == 0) {\r
6344             /* Bogus message from Crafty responding to "."  This filtering\r
6345                can miss some of the bad messages, but fortunately the bug \r
6346                is fixed in current Crafty versions, so it doesn't matter. */\r
6347             return;\r
6348         }\r
6349 #endif\r
6350         if (pausing) PauseEvent();\r
6351         if (gameMode == PlayFromGameFile) {\r
6352             /* Stop reading this game file */\r
6353             gameMode = EditGame;\r
6354             ModeHighlight();\r
6355         }\r
6356         currentMove = --forwardMostMove;\r
6357         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
6358         SwitchClocks();\r
6359         DisplayBothClocks();\r
6360         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),\r
6361                 parseList[currentMove], cps->which);\r
6362         DisplayMoveError(buf1);\r
6363         DrawPosition(FALSE, boards[currentMove]);\r
6364 \r
6365         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
6366         /* only passes fully legal moves                            */\r
6367         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
6368             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
6369                                 "False illegal-move claim", GE_XBOARD );\r
6370         }\r
6371         return;\r
6372     }\r
6373     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
6374         /* Program has a broken "time" command that\r
6375            outputs a string not ending in newline.\r
6376            Don't use it. */\r
6377         cps->sendTime = 0;\r
6378     }\r
6379     \r
6380     /*\r
6381      * If chess program startup fails, exit with an error message.\r
6382      * Attempts to recover here are futile.\r
6383      */\r
6384     if ((StrStr(message, "unknown host") != NULL)\r
6385         || (StrStr(message, "No remote directory") != NULL)\r
6386         || (StrStr(message, "not found") != NULL)\r
6387         || (StrStr(message, "No such file") != NULL)\r
6388         || (StrStr(message, "can't alloc") != NULL)\r
6389         || (StrStr(message, "Permission denied") != NULL)) {\r
6390 \r
6391         cps->maybeThinking = FALSE;\r
6392         sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),\r
6393                 cps->which, cps->program, cps->host, message);\r
6394         RemoveInputSource(cps->isr);\r
6395         DisplayFatalError(buf1, 0, 1);\r
6396         return;\r
6397     }\r
6398     \r
6399     /* \r
6400      * Look for hint output\r
6401      */\r
6402     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
6403         if (cps == &first && hintRequested) {\r
6404             hintRequested = FALSE;\r
6405             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
6406                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
6407                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
6408                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
6409                                     fromY, fromX, toY, toX, promoChar, buf1);\r
6410                 sprintf(buf2, _("Hint: %s"), buf1);\r
6411                 DisplayInformation(buf2);\r
6412             } else {\r
6413                 /* Hint move could not be parsed!? */\r
6414                 sprintf(buf2,\r
6415                         _("Illegal hint move \"%s\"\nfrom %s chess program"),\r
6416                         buf1, cps->which);\r
6417                 DisplayError(buf2, 0);\r
6418             }\r
6419         } else {\r
6420             strcpy(lastHint, buf1);\r
6421         }\r
6422         return;\r
6423     }\r
6424 \r
6425     /*\r
6426      * Ignore other messages if game is not in progress\r
6427      */\r
6428     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6429         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
6430 \r
6431     /*\r
6432      * look for win, lose, draw, or draw offer\r
6433      */\r
6434     if (strncmp(message, "1-0", 3) == 0) {\r
6435         char *p, *q, *r = "";\r
6436         p = strchr(message, '{');\r
6437         if (p) {\r
6438             q = strchr(p, '}');\r
6439             if (q) {\r
6440                 *q = NULLCHAR;\r
6441                 r = p + 1;\r
6442             }\r
6443         }\r
6444         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
6445         return;\r
6446     } else if (strncmp(message, "0-1", 3) == 0) {\r
6447         char *p, *q, *r = "";\r
6448         p = strchr(message, '{');\r
6449         if (p) {\r
6450             q = strchr(p, '}');\r
6451             if (q) {\r
6452                 *q = NULLCHAR;\r
6453                 r = p + 1;\r
6454             }\r
6455         }\r
6456         /* Kludge for Arasan 4.1 bug */\r
6457         if (strcmp(r, "Black resigns") == 0) {\r
6458             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
6459             return;\r
6460         }\r
6461         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
6462         return;\r
6463     } else if (strncmp(message, "1/2", 3) == 0) {\r
6464         char *p, *q, *r = "";\r
6465         p = strchr(message, '{');\r
6466         if (p) {\r
6467             q = strchr(p, '}');\r
6468             if (q) {\r
6469                 *q = NULLCHAR;\r
6470                 r = p + 1;\r
6471             }\r
6472         }\r
6473             \r
6474         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
6475         return;\r
6476 \r
6477     } else if (strncmp(message, "White resign", 12) == 0) {\r
6478         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6479         return;\r
6480     } else if (strncmp(message, "Black resign", 12) == 0) {\r
6481         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6482         return;\r
6483     } else if (strncmp(message, "White matches", 13) == 0 ||\r
6484                strncmp(message, "Black matches", 13) == 0   ) {\r
6485         /* [HGM] ignore GNUShogi noises */\r
6486         return;\r
6487     } else if (strncmp(message, "White", 5) == 0 &&\r
6488                message[5] != '(' &&\r
6489                StrStr(message, "Black") == NULL) {\r
6490         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6491         return;\r
6492     } else if (strncmp(message, "Black", 5) == 0 &&\r
6493                message[5] != '(') {\r
6494         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6495         return;\r
6496     } else if (strcmp(message, "resign") == 0 ||\r
6497                strcmp(message, "computer resigns") == 0) {\r
6498         switch (gameMode) {\r
6499           case MachinePlaysBlack:\r
6500           case IcsPlayingBlack:\r
6501             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
6502             break;\r
6503           case MachinePlaysWhite:\r
6504           case IcsPlayingWhite:\r
6505             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
6506             break;\r
6507           case TwoMachinesPlay:\r
6508             if (cps->twoMachinesColor[0] == 'w')\r
6509               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6510             else\r
6511               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6512             break;\r
6513           default:\r
6514             /* can't happen */\r
6515             break;\r
6516         }\r
6517         return;\r
6518     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
6519         switch (gameMode) {\r
6520           case MachinePlaysBlack:\r
6521           case IcsPlayingBlack:\r
6522             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6523             break;\r
6524           case MachinePlaysWhite:\r
6525           case IcsPlayingWhite:\r
6526             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
6527             break;\r
6528           case TwoMachinesPlay:\r
6529             if (cps->twoMachinesColor[0] == 'w')\r
6530               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6531             else\r
6532               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6533             break;\r
6534           default:\r
6535             /* can't happen */\r
6536             break;\r
6537         }\r
6538         return;\r
6539     } else if (strncmp(message, "computer mates", 14) == 0) {\r
6540         switch (gameMode) {\r
6541           case MachinePlaysBlack:\r
6542           case IcsPlayingBlack:\r
6543             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
6544             break;\r
6545           case MachinePlaysWhite:\r
6546           case IcsPlayingWhite:\r
6547             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6548             break;\r
6549           case TwoMachinesPlay:\r
6550             if (cps->twoMachinesColor[0] == 'w')\r
6551               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6552             else\r
6553               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6554             break;\r
6555           default:\r
6556             /* can't happen */\r
6557             break;\r
6558         }\r
6559         return;\r
6560     } else if (strncmp(message, "checkmate", 9) == 0) {\r
6561         if (WhiteOnMove(forwardMostMove)) {\r
6562             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6563         } else {\r
6564             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6565         }\r
6566         return;\r
6567     } else if (strstr(message, "Draw") != NULL ||\r
6568                strstr(message, "game is a draw") != NULL) {\r
6569         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
6570         return;\r
6571     } else if (strstr(message, "offer") != NULL &&\r
6572                strstr(message, "draw") != NULL) {\r
6573 #if ZIPPY\r
6574         if (appData.zippyPlay && first.initDone) {\r
6575             /* Relay offer to ICS */\r
6576             SendToICS(ics_prefix);\r
6577             SendToICS("draw\n");\r
6578         }\r
6579 #endif\r
6580         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
6581         if (gameMode == TwoMachinesPlay) {\r
6582             if (cps->other->offeredDraw) {\r
6583                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6584             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
6585             /* until after we also have move, to see if it is really claim */\r
6586             }\r
6587 #if 0\r
6588               else {\r
6589                 if (cps->other->sendDrawOffers) {\r
6590                     SendToProgram("draw\n", cps->other);\r
6591                 }\r
6592             }\r
6593 #endif\r
6594         } else if (gameMode == MachinePlaysWhite ||\r
6595                    gameMode == MachinePlaysBlack) {\r
6596           if (userOfferedDraw) {\r
6597             DisplayInformation(_("Machine accepts your draw offer"));\r
6598             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6599           } else {\r
6600             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));\r
6601           }\r
6602         }\r
6603     }\r
6604 \r
6605     \r
6606     /*\r
6607      * Look for thinking output\r
6608      */\r
6609     if ( appData.showThinking // [HGM] thinking: test all options that cause this output\r
6610           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
6611                                 ) {\r
6612         int plylev, mvleft, mvtot, curscore, time;\r
6613         char mvname[MOVE_LEN];\r
6614         u64 nodes; // [DM]\r
6615         char plyext;\r
6616         int ignore = FALSE;\r
6617         int prefixHint = FALSE;\r
6618         mvname[0] = NULLCHAR;\r
6619 \r
6620         switch (gameMode) {\r
6621           case MachinePlaysBlack:\r
6622           case IcsPlayingBlack:\r
6623             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6624             break;\r
6625           case MachinePlaysWhite:\r
6626           case IcsPlayingWhite:\r
6627             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6628             break;\r
6629           case AnalyzeMode:\r
6630           case AnalyzeFile:\r
6631             break;\r
6632           case IcsObserving: /* [DM] icsEngineAnalyze */\r
6633             if (!appData.icsEngineAnalyze) ignore = TRUE;\r
6634             break;\r
6635           case TwoMachinesPlay:\r
6636             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
6637                 ignore = TRUE;\r
6638             }\r
6639             break;\r
6640           default:\r
6641             ignore = TRUE;\r
6642             break;\r
6643         }\r
6644 \r
6645         if (!ignore) {\r
6646             buf1[0] = NULLCHAR;\r
6647             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6648                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
6649 \r
6650                 if (plyext != ' ' && plyext != '\t') {\r
6651                     time *= 100;\r
6652                 }\r
6653 \r
6654                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6655                 if( cps->scoreIsAbsolute && \r
6656                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
6657                 {\r
6658                     curscore = -curscore;\r
6659                 }\r
6660 \r
6661 \r
6662                 programStats.depth = plylev;\r
6663                 programStats.nodes = nodes;\r
6664                 programStats.time = time;\r
6665                 programStats.score = curscore;\r
6666                 programStats.got_only_move = 0;\r
6667 \r
6668                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
6669                         int ticklen;\r
6670 \r
6671                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time\r
6672                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time\r
6673                         if(WhiteOnMove(forwardMostMove)) \r
6674                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
6675                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
6676                 }\r
6677 \r
6678                 /* Buffer overflow protection */\r
6679                 if (buf1[0] != NULLCHAR) {\r
6680                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
6681                         && appData.debugMode) {\r
6682                         fprintf(debugFP,\r
6683                                 "PV is too long; using the first %d bytes.\n",\r
6684                                 sizeof(programStats.movelist) - 1);\r
6685                     }\r
6686 \r
6687                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
6688                 } else {\r
6689                     sprintf(programStats.movelist, " no PV\n");\r
6690                 }\r
6691 \r
6692                 if (programStats.seen_stat) {\r
6693                     programStats.ok_to_send = 1;\r
6694                 }\r
6695 \r
6696                 if (strchr(programStats.movelist, '(') != NULL) {\r
6697                     programStats.line_is_book = 1;\r
6698                     programStats.nr_moves = 0;\r
6699                     programStats.moves_left = 0;\r
6700                 } else {\r
6701                     programStats.line_is_book = 0;\r
6702                 }\r
6703 \r
6704                 SendProgramStatsToFrontend( cps, &programStats );\r
6705 \r
6706                 /* \r
6707                     [AS] Protect the thinkOutput buffer from overflow... this\r
6708                     is only useful if buf1 hasn't overflowed first!\r
6709                 */\r
6710                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
6711                         plylev, \r
6712                         (gameMode == TwoMachinesPlay ?\r
6713                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
6714                         ((double) curscore) / 100.0,\r
6715                         prefixHint ? lastHint : "",\r
6716                         prefixHint ? " " : "" );\r
6717 \r
6718                 if( buf1[0] != NULLCHAR ) {\r
6719                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
6720 \r
6721                     if( strlen(buf1) > max_len ) {\r
6722                         if( appData.debugMode) {\r
6723                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
6724                         }\r
6725                         buf1[max_len+1] = '\0';\r
6726                     }\r
6727 \r
6728                     strcat( thinkOutput, buf1 );\r
6729                 }\r
6730 \r
6731                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode\r
6732                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6733                     DisplayMove(currentMove - 1);\r
6734                     DisplayAnalysis();\r
6735                 }\r
6736                 return;\r
6737 \r
6738             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6739                 /* crafty (9.25+) says "(only move) <move>"\r
6740                  * if there is only 1 legal move\r
6741                  */\r
6742                 sscanf(p, "(only move) %s", buf1);\r
6743                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6744                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6745                 programStats.depth = 1;\r
6746                 programStats.nr_moves = 1;\r
6747                 programStats.moves_left = 1;\r
6748                 programStats.nodes = 1;\r
6749                 programStats.time = 1;\r
6750                 programStats.got_only_move = 1;\r
6751 \r
6752                 /* Not really, but we also use this member to\r
6753                    mean "line isn't going to change" (Crafty\r
6754                    isn't searching, so stats won't change) */\r
6755                 programStats.line_is_book = 1;\r
6756 \r
6757                 SendProgramStatsToFrontend( cps, &programStats );\r
6758                 \r
6759                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || \r
6760                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6761                     DisplayMove(currentMove - 1);\r
6762                     DisplayAnalysis();\r
6763                 }\r
6764                 return;\r
6765             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",\r
6766                               &time, &nodes, &plylev, &mvleft,\r
6767                               &mvtot, mvname) >= 5) {\r
6768                 /* The stat01: line is from Crafty (9.29+) in response\r
6769                    to the "." command */\r
6770                 programStats.seen_stat = 1;\r
6771                 cps->maybeThinking = TRUE;\r
6772 \r
6773                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6774                   return;\r
6775 \r
6776                 programStats.depth = plylev;\r
6777                 programStats.time = time;\r
6778                 programStats.nodes = nodes;\r
6779                 programStats.moves_left = mvleft;\r
6780                 programStats.nr_moves = mvtot;\r
6781                 strcpy(programStats.move_name, mvname);\r
6782                 programStats.ok_to_send = 1;\r
6783                 programStats.movelist[0] = '\0';\r
6784 \r
6785                 SendProgramStatsToFrontend( cps, &programStats );\r
6786 \r
6787                 DisplayAnalysis();\r
6788                 return;\r
6789 \r
6790             } else if (strncmp(message,"++",2) == 0) {\r
6791                 /* Crafty 9.29+ outputs this */\r
6792                 programStats.got_fail = 2;\r
6793                 return;\r
6794 \r
6795             } else if (strncmp(message,"--",2) == 0) {\r
6796                 /* Crafty 9.29+ outputs this */\r
6797                 programStats.got_fail = 1;\r
6798                 return;\r
6799 \r
6800             } else if (thinkOutput[0] != NULLCHAR &&\r
6801                        strncmp(message, "    ", 4) == 0) {\r
6802                 unsigned message_len;\r
6803 \r
6804                 p = message;\r
6805                 while (*p && *p == ' ') p++;\r
6806 \r
6807                 message_len = strlen( p );\r
6808 \r
6809                 /* [AS] Avoid buffer overflow */\r
6810                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6811                     strcat(thinkOutput, " ");\r
6812                     strcat(thinkOutput, p);\r
6813                 }\r
6814 \r
6815                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6816                     strcat(programStats.movelist, " ");\r
6817                     strcat(programStats.movelist, p);\r
6818                 }\r
6819 \r
6820                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||\r
6821                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6822                     DisplayMove(currentMove - 1);\r
6823                     DisplayAnalysis();\r
6824                 }\r
6825                 return;\r
6826             }\r
6827         }\r
6828         else {\r
6829             buf1[0] = NULLCHAR;\r
6830 \r
6831             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6832                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6833             {\r
6834                 ChessProgramStats cpstats;\r
6835 \r
6836                 if (plyext != ' ' && plyext != '\t') {\r
6837                     time *= 100;\r
6838                 }\r
6839 \r
6840                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6841                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6842                     curscore = -curscore;\r
6843                 }\r
6844 \r
6845                 cpstats.depth = plylev;\r
6846                 cpstats.nodes = nodes;\r
6847                 cpstats.time = time;\r
6848                 cpstats.score = curscore;\r
6849                 cpstats.got_only_move = 0;\r
6850                 cpstats.movelist[0] = '\0';\r
6851 \r
6852                 if (buf1[0] != NULLCHAR) {\r
6853                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6854                 }\r
6855 \r
6856                 cpstats.ok_to_send = 0;\r
6857                 cpstats.line_is_book = 0;\r
6858                 cpstats.nr_moves = 0;\r
6859                 cpstats.moves_left = 0;\r
6860 \r
6861                 SendProgramStatsToFrontend( cps, &cpstats );\r
6862             }\r
6863         }\r
6864     }\r
6865 }\r
6866 \r
6867 \r
6868 /* Parse a game score from the character string "game", and\r
6869    record it as the history of the current game.  The game\r
6870    score is NOT assumed to start from the standard position. \r
6871    The display is not updated in any way.\r
6872    */\r
6873 void\r
6874 ParseGameHistory(game)\r
6875      char *game;\r
6876 {\r
6877     ChessMove moveType;\r
6878     int fromX, fromY, toX, toY, boardIndex;\r
6879     char promoChar;\r
6880     char *p, *q;\r
6881     char buf[MSG_SIZ];\r
6882 \r
6883     if (appData.debugMode)\r
6884       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6885 \r
6886     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6887     gameInfo.site = StrSave(appData.icsHost);\r
6888     gameInfo.date = PGNDate();\r
6889     gameInfo.round = StrSave("-");\r
6890 \r
6891     /* Parse out names of players */\r
6892     while (*game == ' ') game++;\r
6893     p = buf;\r
6894     while (*game != ' ') *p++ = *game++;\r
6895     *p = NULLCHAR;\r
6896     gameInfo.white = StrSave(buf);\r
6897     while (*game == ' ') game++;\r
6898     p = buf;\r
6899     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6900     *p = NULLCHAR;\r
6901     gameInfo.black = StrSave(buf);\r
6902 \r
6903     /* Parse moves */\r
6904     boardIndex = blackPlaysFirst ? 1 : 0;\r
6905     yynewstr(game);\r
6906     for (;;) {\r
6907         yyboardindex = boardIndex;\r
6908         moveType = (ChessMove) yylex();\r
6909         switch (moveType) {\r
6910           case IllegalMove:             /* maybe suicide chess, etc. */\r
6911   if (appData.debugMode) {\r
6912     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6913     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6914     setbuf(debugFP, NULL);\r
6915   }\r
6916           case WhitePromotionChancellor:\r
6917           case BlackPromotionChancellor:\r
6918           case WhitePromotionArchbishop:\r
6919           case BlackPromotionArchbishop:\r
6920           case WhitePromotionQueen:\r
6921           case BlackPromotionQueen:\r
6922           case WhitePromotionRook:\r
6923           case BlackPromotionRook:\r
6924           case WhitePromotionBishop:\r
6925           case BlackPromotionBishop:\r
6926           case WhitePromotionKnight:\r
6927           case BlackPromotionKnight:\r
6928           case WhitePromotionKing:\r
6929           case BlackPromotionKing:\r
6930           case NormalMove:\r
6931           case WhiteCapturesEnPassant:\r
6932           case BlackCapturesEnPassant:\r
6933           case WhiteKingSideCastle:\r
6934           case WhiteQueenSideCastle:\r
6935           case BlackKingSideCastle:\r
6936           case BlackQueenSideCastle:\r
6937           case WhiteKingSideCastleWild:\r
6938           case WhiteQueenSideCastleWild:\r
6939           case BlackKingSideCastleWild:\r
6940           case BlackQueenSideCastleWild:\r
6941           /* PUSH Fabien */\r
6942           case WhiteHSideCastleFR:\r
6943           case WhiteASideCastleFR:\r
6944           case BlackHSideCastleFR:\r
6945           case BlackASideCastleFR:\r
6946           /* POP Fabien */\r
6947             fromX = currentMoveString[0] - AAA;\r
6948             fromY = currentMoveString[1] - ONE;\r
6949             toX = currentMoveString[2] - AAA;\r
6950             toY = currentMoveString[3] - ONE;\r
6951             promoChar = currentMoveString[4];\r
6952             break;\r
6953           case WhiteDrop:\r
6954           case BlackDrop:\r
6955             fromX = moveType == WhiteDrop ?\r
6956               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6957             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6958             fromY = DROP_RANK;\r
6959             toX = currentMoveString[2] - AAA;\r
6960             toY = currentMoveString[3] - ONE;\r
6961             promoChar = NULLCHAR;\r
6962             break;\r
6963           case AmbiguousMove:\r
6964             /* bug? */\r
6965             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);\r
6966   if (appData.debugMode) {\r
6967     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
6968     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6969     setbuf(debugFP, NULL);\r
6970   }\r
6971             DisplayError(buf, 0);\r
6972             return;\r
6973           case ImpossibleMove:\r
6974             /* bug? */\r
6975             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);\r
6976   if (appData.debugMode) {\r
6977     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
6978     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6979     setbuf(debugFP, NULL);\r
6980   }\r
6981             DisplayError(buf, 0);\r
6982             return;\r
6983           case (ChessMove) 0:   /* end of file */\r
6984             if (boardIndex < backwardMostMove) {\r
6985                 /* Oops, gap.  How did that happen? */\r
6986                 DisplayError(_("Gap in move list"), 0);\r
6987                 return;\r
6988             }\r
6989             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
6990             if (boardIndex > forwardMostMove) {\r
6991                 forwardMostMove = boardIndex;\r
6992             }\r
6993             return;\r
6994           case ElapsedTime:\r
6995             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
6996                 strcat(parseList[boardIndex-1], " ");\r
6997                 strcat(parseList[boardIndex-1], yy_text);\r
6998             }\r
6999             continue;\r
7000           case Comment:\r
7001           case PGNTag:\r
7002           case NAG:\r
7003           default:\r
7004             /* ignore */\r
7005             continue;\r
7006           case WhiteWins:\r
7007           case BlackWins:\r
7008           case GameIsDrawn:\r
7009           case GameUnfinished:\r
7010             if (gameMode == IcsExamining) {\r
7011                 if (boardIndex < backwardMostMove) {\r
7012                     /* Oops, gap.  How did that happen? */\r
7013                     return;\r
7014                 }\r
7015                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
7016                 return;\r
7017             }\r
7018             gameInfo.result = moveType;\r
7019             p = strchr(yy_text, '{');\r
7020             if (p == NULL) p = strchr(yy_text, '(');\r
7021             if (p == NULL) {\r
7022                 p = yy_text;\r
7023                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
7024             } else {\r
7025                 q = strchr(p, *p == '{' ? '}' : ')');\r
7026                 if (q != NULL) *q = NULLCHAR;\r
7027                 p++;\r
7028             }\r
7029             gameInfo.resultDetails = StrSave(p);\r
7030             continue;\r
7031         }\r
7032         if (boardIndex >= forwardMostMove &&\r
7033             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
7034             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
7035             return;\r
7036         }\r
7037         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
7038                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
7039                                  parseList[boardIndex]);\r
7040         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
7041         /* currentMoveString is set as a side-effect of yylex */\r
7042         strcpy(moveList[boardIndex], currentMoveString);\r
7043         strcat(moveList[boardIndex], "\n");\r
7044         boardIndex++;\r
7045         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
7046         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
7047                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
7048           case MT_NONE:\r
7049           case MT_STALEMATE:\r
7050           default:\r
7051             break;\r
7052           case MT_CHECK:\r
7053             if(gameInfo.variant != VariantShogi)\r
7054                 strcat(parseList[boardIndex - 1], "+");\r
7055             break;\r
7056           case MT_CHECKMATE:\r
7057             strcat(parseList[boardIndex - 1], "#");\r
7058             break;\r
7059         }\r
7060     }\r
7061 }\r
7062 \r
7063 \r
7064 /* Apply a move to the given board  */\r
7065 void\r
7066 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
7067      int fromX, fromY, toX, toY;\r
7068      int promoChar;\r
7069      Board board;\r
7070 {\r
7071   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
7072 \r
7073     /* [HGM] compute & store e.p. status and castling rights for new position */\r
7074     /* if we are updating a board for which those exist (i.e. in boards[])    */\r
7075     if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)\r
7076     { int i;\r
7077 \r
7078       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
7079       oldEP = epStatus[p-1];\r
7080       epStatus[p] = EP_NONE;\r
7081 \r
7082       if( board[toY][toX] != EmptySquare ) \r
7083            epStatus[p] = EP_CAPTURE;  \r
7084 \r
7085       if( board[fromY][fromX] == WhitePawn ) {\r
7086            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7087                epStatus[p] = EP_PAWN_MOVE;\r
7088            if( toY-fromY==2) {\r
7089                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
7090                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7091                       epStatus[p] = toX | berolina;\r
7092                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
7093                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7094                       epStatus[p] = toX;\r
7095            }\r
7096       } else \r
7097       if( board[fromY][fromX] == BlackPawn ) {\r
7098            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7099                epStatus[p] = EP_PAWN_MOVE; \r
7100            if( toY-fromY== -2) {\r
7101                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
7102                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7103                       epStatus[p] = toX | berolina;\r
7104                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
7105                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7106                       epStatus[p] = toX;\r
7107            }\r
7108        }\r
7109 \r
7110        for(i=0; i<nrCastlingRights; i++) {\r
7111            castlingRights[p][i] = castlingRights[p-1][i];\r
7112            if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||\r
7113               castlingRights[p][i] == toX   && castlingRank[i] == toY   \r
7114              ) castlingRights[p][i] = -1; // revoke for moved or captured piece\r
7115        }\r
7116 \r
7117     }\r
7118 \r
7119   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
7120   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
7121        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
7122          \r
7123   if (fromX == toX && fromY == toY) return;\r
7124 \r
7125   if (fromY == DROP_RANK) {\r
7126         /* must be first */\r
7127         piece = board[toY][toX] = (ChessSquare) fromX;\r
7128   } else {\r
7129      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
7130      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
7131      if(gameInfo.variant == VariantKnightmate)\r
7132          king += (int) WhiteUnicorn - (int) WhiteKing;\r
7133 \r
7134     /* Code added by Tord: */\r
7135     /* FRC castling assumed when king captures friendly rook. */\r
7136     if (board[fromY][fromX] == WhiteKing &&\r
7137              board[toY][toX] == WhiteRook) {\r
7138       board[fromY][fromX] = EmptySquare;\r
7139       board[toY][toX] = EmptySquare;\r
7140       if(toX > fromX) {\r
7141         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
7142       } else {\r
7143         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
7144       }\r
7145     } else if (board[fromY][fromX] == BlackKing &&\r
7146                board[toY][toX] == BlackRook) {\r
7147       board[fromY][fromX] = EmptySquare;\r
7148       board[toY][toX] = EmptySquare;\r
7149       if(toX > fromX) {\r
7150         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
7151       } else {\r
7152         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
7153       }\r
7154     /* End of code added by Tord */\r
7155 \r
7156     } else if (board[fromY][fromX] == king\r
7157         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7158         && toY == fromY && toX > fromX+1) {\r
7159         board[fromY][fromX] = EmptySquare;\r
7160         board[toY][toX] = king;\r
7161         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7162         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7163     } else if (board[fromY][fromX] == king\r
7164         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7165                && toY == fromY && toX < fromX-1) {\r
7166         board[fromY][fromX] = EmptySquare;\r
7167         board[toY][toX] = king;\r
7168         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7169         board[fromY][BOARD_LEFT] = EmptySquare;\r
7170     } else if (board[fromY][fromX] == WhitePawn\r
7171                && toY == BOARD_HEIGHT-1\r
7172                && gameInfo.variant != VariantXiangqi\r
7173                ) {\r
7174         /* white pawn promotion */\r
7175         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
7176         if (board[toY][toX] == EmptySquare) {\r
7177             board[toY][toX] = WhiteQueen;\r
7178         }\r
7179         if(gameInfo.variant==VariantBughouse ||\r
7180            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7181             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7182         board[fromY][fromX] = EmptySquare;\r
7183     } else if ((fromY == BOARD_HEIGHT-4)\r
7184                && (toX != fromX)\r
7185                && gameInfo.variant != VariantXiangqi\r
7186                && gameInfo.variant != VariantBerolina\r
7187                && (board[fromY][fromX] == WhitePawn)\r
7188                && (board[toY][toX] == EmptySquare)) {\r
7189         board[fromY][fromX] = EmptySquare;\r
7190         board[toY][toX] = WhitePawn;\r
7191         captured = board[toY - 1][toX];\r
7192         board[toY - 1][toX] = EmptySquare;\r
7193     } else if ((fromY == BOARD_HEIGHT-4)\r
7194                && (toX == fromX)\r
7195                && gameInfo.variant == VariantBerolina\r
7196                && (board[fromY][fromX] == WhitePawn)\r
7197                && (board[toY][toX] == EmptySquare)) {\r
7198         board[fromY][fromX] = EmptySquare;\r
7199         board[toY][toX] = WhitePawn;\r
7200         if(oldEP & EP_BEROLIN_A) {\r
7201                 captured = board[fromY][fromX-1];\r
7202                 board[fromY][fromX-1] = EmptySquare;\r
7203         }else{  captured = board[fromY][fromX+1];\r
7204                 board[fromY][fromX+1] = EmptySquare;\r
7205         }\r
7206     } else if (board[fromY][fromX] == king\r
7207         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7208                && toY == fromY && toX > fromX+1) {\r
7209         board[fromY][fromX] = EmptySquare;\r
7210         board[toY][toX] = king;\r
7211         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7212         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7213     } else if (board[fromY][fromX] == king\r
7214         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7215                && toY == fromY && toX < fromX-1) {\r
7216         board[fromY][fromX] = EmptySquare;\r
7217         board[toY][toX] = king;\r
7218         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7219         board[fromY][BOARD_LEFT] = EmptySquare;\r
7220     } else if (fromY == 7 && fromX == 3\r
7221                && board[fromY][fromX] == BlackKing\r
7222                && toY == 7 && toX == 5) {\r
7223         board[fromY][fromX] = EmptySquare;\r
7224         board[toY][toX] = BlackKing;\r
7225         board[fromY][7] = EmptySquare;\r
7226         board[toY][4] = BlackRook;\r
7227     } else if (fromY == 7 && fromX == 3\r
7228                && board[fromY][fromX] == BlackKing\r
7229                && toY == 7 && toX == 1) {\r
7230         board[fromY][fromX] = EmptySquare;\r
7231         board[toY][toX] = BlackKing;\r
7232         board[fromY][0] = EmptySquare;\r
7233         board[toY][2] = BlackRook;\r
7234     } else if (board[fromY][fromX] == BlackPawn\r
7235                && toY == 0\r
7236                && gameInfo.variant != VariantXiangqi\r
7237                ) {\r
7238         /* black pawn promotion */\r
7239         board[0][toX] = CharToPiece(ToLower(promoChar));\r
7240         if (board[0][toX] == EmptySquare) {\r
7241             board[0][toX] = BlackQueen;\r
7242         }\r
7243         if(gameInfo.variant==VariantBughouse ||\r
7244            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7245             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7246         board[fromY][fromX] = EmptySquare;\r
7247     } else if ((fromY == 3)\r
7248                && (toX != fromX)\r
7249                && gameInfo.variant != VariantXiangqi\r
7250                && gameInfo.variant != VariantBerolina\r
7251                && (board[fromY][fromX] == BlackPawn)\r
7252                && (board[toY][toX] == EmptySquare)) {\r
7253         board[fromY][fromX] = EmptySquare;\r
7254         board[toY][toX] = BlackPawn;\r
7255         captured = board[toY + 1][toX];\r
7256         board[toY + 1][toX] = EmptySquare;\r
7257     } else if ((fromY == 3)\r
7258                && (toX == fromX)\r
7259                && gameInfo.variant == VariantBerolina\r
7260                && (board[fromY][fromX] == BlackPawn)\r
7261                && (board[toY][toX] == EmptySquare)) {\r
7262         board[fromY][fromX] = EmptySquare;\r
7263         board[toY][toX] = BlackPawn;\r
7264         if(oldEP & EP_BEROLIN_A) {\r
7265                 captured = board[fromY][fromX-1];\r
7266                 board[fromY][fromX-1] = EmptySquare;\r
7267         }else{  captured = board[fromY][fromX+1];\r
7268                 board[fromY][fromX+1] = EmptySquare;\r
7269         }\r
7270     } else {\r
7271         board[toY][toX] = board[fromY][fromX];\r
7272         board[fromY][fromX] = EmptySquare;\r
7273     }\r
7274 \r
7275     /* [HGM] now we promote for Shogi, if needed */\r
7276     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
7277         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7278   }\r
7279 \r
7280     if (gameInfo.holdingsWidth != 0) {\r
7281 \r
7282       /* !!A lot more code needs to be written to support holdings  */\r
7283       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
7284       /* penultimate board files, so they are automaticlly stored   */\r
7285       /* in the game history.                                       */\r
7286       if (fromY == DROP_RANK) {\r
7287         /* Delete from holdings, by decreasing count */\r
7288         /* and erasing image if necessary            */\r
7289         p = (int) fromX;\r
7290         if(p < (int) BlackPawn) { /* white drop */\r
7291              p -= (int)WhitePawn;\r
7292              if(p >= gameInfo.holdingsSize) p = 0;\r
7293              if(--board[p][BOARD_WIDTH-2] == 0)\r
7294                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
7295         } else {                  /* black drop */\r
7296              p -= (int)BlackPawn;\r
7297              if(p >= gameInfo.holdingsSize) p = 0;\r
7298              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
7299                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
7300         }\r
7301       }\r
7302       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
7303           && gameInfo.variant != VariantBughouse        ) {\r
7304         /* [HGM] holdings: Add to holdings, if holdings exist */\r
7305         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { \r
7306                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
7307                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
7308         }\r
7309         p = (int) captured;\r
7310         if (p >= (int) BlackPawn) {\r
7311           p -= (int)BlackPawn;\r
7312           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7313                   /* in Shogi restore piece to its original  first */\r
7314                   captured = (ChessSquare) (DEMOTED captured);\r
7315                   p = DEMOTED p;\r
7316           }\r
7317           p = PieceToNumber((ChessSquare)p);\r
7318           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
7319           board[p][BOARD_WIDTH-2]++;\r
7320           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;\r
7321         } else {\r
7322           p -= (int)WhitePawn;\r
7323           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7324                   captured = (ChessSquare) (DEMOTED captured);\r
7325                   p = DEMOTED p;\r
7326           }\r
7327           p = PieceToNumber((ChessSquare)p);\r
7328           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
7329           board[BOARD_HEIGHT-1-p][1]++;\r
7330           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;\r
7331         }\r
7332       }\r
7333 \r
7334     } else if (gameInfo.variant == VariantAtomic) {\r
7335       if (captured != EmptySquare) {\r
7336         int y, x;\r
7337         for (y = toY-1; y <= toY+1; y++) {\r
7338           for (x = toX-1; x <= toX+1; x++) {\r
7339             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
7340                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
7341               board[y][x] = EmptySquare;\r
7342             }\r
7343           }\r
7344         }\r
7345         board[toY][toX] = EmptySquare;\r
7346       }\r
7347     }\r
7348     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
7349         /* [HGM] Shogi promotions */\r
7350         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7351     }\r
7352 \r
7353 }\r
7354 \r
7355 /* Updates forwardMostMove */\r
7356 void\r
7357 MakeMove(fromX, fromY, toX, toY, promoChar)\r
7358      int fromX, fromY, toX, toY;\r
7359      int promoChar;\r
7360 {\r
7361 //    forwardMostMove++; // [HGM] bare: moved downstream\r
7362 \r
7363     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */\r
7364         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
7365         piece = boards[forwardMostMove][fromY][fromX];\r
7366         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
7367         if(gameInfo.variant == VariantKnightmate)\r
7368             king += (int) WhiteUnicorn - (int) WhiteKing;\r
7369         if(forwardMostMove == 0) {\r
7370             if(blackPlaysFirst) \r
7371                 fprintf(serverMoves, "%s;", second.tidy);\r
7372             fprintf(serverMoves, "%s;", first.tidy);\r
7373             if(!blackPlaysFirst) \r
7374                 fprintf(serverMoves, "%s;", second.tidy);\r
7375         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
7376         lastLoadFlag = loadFlag;\r
7377         // print base move\r
7378         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
7379         // print castling suffix\r
7380         if( toY == fromY && piece == king ) {\r
7381             if(toX-fromX > 1)\r
7382                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
7383             if(fromX-toX >1)\r
7384                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
7385         }\r
7386         // e.p. suffix\r
7387         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||\r
7388              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&\r
7389              boards[forwardMostMove][toY][toX] == EmptySquare\r
7390              && fromX != toX )\r
7391                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
7392         // promotion suffix\r
7393         if(promoChar != NULLCHAR)\r
7394                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
7395         if(!loadFlag) {\r
7396             fprintf(serverMoves, "/%d/%d",\r
7397                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);\r
7398             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;\r
7399             else                      timeLeft = blackTimeRemaining/1000;\r
7400             fprintf(serverMoves, "/%d", timeLeft);\r
7401         }\r
7402         fflush(serverMoves);\r
7403     }\r
7404 \r
7405     if (forwardMostMove+1 >= MAX_MOVES) {\r
7406       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
7407                         0, 1);\r
7408       return;\r
7409     }\r
7410     SwitchClocks();\r
7411     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;\r
7412     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;\r
7413     if (commentList[forwardMostMove+1] != NULL) {\r
7414         free(commentList[forwardMostMove+1]);\r
7415         commentList[forwardMostMove+1] = NULL;\r
7416     }\r
7417     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);\r
7418     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);\r
7419     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board\r
7420     gameInfo.result = GameUnfinished;\r
7421     if (gameInfo.resultDetails != NULL) {\r
7422         free(gameInfo.resultDetails);\r
7423         gameInfo.resultDetails = NULL;\r
7424     }\r
7425     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
7426                               moveList[forwardMostMove - 1]);\r
7427     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
7428                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
7429                              fromY, fromX, toY, toX, promoChar,\r
7430                              parseList[forwardMostMove - 1]);\r
7431     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
7432                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
7433                             castlingRights[forwardMostMove]) ) {\r
7434       case MT_NONE:\r
7435       case MT_STALEMATE:\r
7436       default:\r
7437         break;\r
7438       case MT_CHECK:\r
7439         if(gameInfo.variant != VariantShogi)\r
7440             strcat(parseList[forwardMostMove - 1], "+");\r
7441         break;\r
7442       case MT_CHECKMATE:\r
7443         strcat(parseList[forwardMostMove - 1], "#");\r
7444         break;\r
7445     }\r
7446     if (appData.debugMode) {\r
7447         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
7448     }\r
7449 \r
7450 }\r
7451 \r
7452 /* Updates currentMove if not pausing */\r
7453 void\r
7454 ShowMove(fromX, fromY, toX, toY)\r
7455 {\r
7456     int instant = (gameMode == PlayFromGameFile) ?\r
7457         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
7458     if(appData.noGUI) return;\r
7459     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
7460         if (!instant) {\r
7461             if (forwardMostMove == currentMove + 1) {\r
7462                 AnimateMove(boards[forwardMostMove - 1],\r
7463                             fromX, fromY, toX, toY);\r
7464             }\r
7465             if (appData.highlightLastMove) {\r
7466                 SetHighlights(fromX, fromY, toX, toY);\r
7467             }\r
7468         }\r
7469         currentMove = forwardMostMove;\r
7470     }\r
7471 \r
7472     if (instant) return;\r
7473 \r
7474     DisplayMove(currentMove - 1);\r
7475     DrawPosition(FALSE, boards[currentMove]);\r
7476     DisplayBothClocks();\r
7477     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
7478 }\r
7479 \r
7480 void SendEgtPath(ChessProgramState *cps)\r
7481 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */\r
7482         char buf[MSG_SIZ], name[MSG_SIZ], *p;\r
7483 \r
7484         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;\r
7485 \r
7486         while(*p) {\r
7487             char c, *q = name+1, *r, *s;\r
7488 \r
7489             name[0] = ','; // extract next format name from feature and copy with prefixed ','\r
7490             while(*p && *p != ',') *q++ = *p++;\r
7491             *q++ = ':'; *q = 0;\r
7492             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && \r
7493                 strcmp(name, ",nalimov:") == 0 ) {\r
7494                 // take nalimov path from the menu-changeable option first, if it is defined\r
7495                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);\r
7496                 SendToProgram(buf,cps);     // send egtbpath command for nalimov\r
7497             } else\r
7498             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||\r
7499                 (s = StrStr(appData.egtFormats, name)) != NULL) {\r
7500                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma\r
7501                 s = r = StrStr(s, ":") + 1; // beginning of path info\r
7502                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string\r
7503                 c = *r; *r = 0;             // temporarily null-terminate path info\r
7504                     *--q = 0;               // strip of trailig ':' from name\r
7505                     sprintf(buf, "egtbpath %s %s\n", name+1, s);\r
7506                 *r = c;\r
7507                 SendToProgram(buf,cps);     // send egtbpath command for this format\r
7508             }\r
7509             if(*p == ',') p++; // read away comma to position for next format name\r
7510         }\r
7511 }\r
7512 \r
7513 void\r
7514 InitChessProgram(cps, setup)\r
7515      ChessProgramState *cps;\r
7516      int setup; /* [HGM] needed to setup FRC opening position */\r
7517 {\r
7518     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
7519     if (appData.noChessProgram) return;\r
7520     hintRequested = FALSE;\r
7521     bookRequested = FALSE;\r
7522 \r
7523     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */\r
7524     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */\r
7525     if(cps->memSize) { /* [HGM] memory */\r
7526         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);\r
7527         SendToProgram(buf, cps);\r
7528     }\r
7529     SendEgtPath(cps); /* [HGM] EGT */\r
7530     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */\r
7531         sprintf(buf, "cores %d\n", appData.smpCores);\r
7532         SendToProgram(buf, cps);\r
7533     }\r
7534 \r
7535     SendToProgram(cps->initString, cps);\r
7536     if (gameInfo.variant != VariantNormal &&\r
7537         gameInfo.variant != VariantLoadable\r
7538         /* [HGM] also send variant if board size non-standard */\r
7539         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
7540                                             ) {\r
7541       char *v = VariantName(gameInfo.variant);\r
7542       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
7543         /* [HGM] in protocol 1 we have to assume all variants valid */\r
7544         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);\r
7545         DisplayFatalError(buf, 0, 1);\r
7546         return;\r
7547       }\r
7548 \r
7549       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
7550       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7551       if( gameInfo.variant == VariantXiangqi )\r
7552            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
7553       if( gameInfo.variant == VariantShogi )\r
7554            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
7555       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
7556            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
7557       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
7558                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
7559            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7560       if( gameInfo.variant == VariantCourier )\r
7561            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7562       if( gameInfo.variant == VariantSuper )\r
7563            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7564       if( gameInfo.variant == VariantGreat )\r
7565            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7566 \r
7567       if(overruled) {\r
7568            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
7569                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
7570            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
7571            if(StrStr(cps->variants, b) == NULL) { \r
7572                // specific sized variant not known, check if general sizing allowed\r
7573                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
7574                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
7575                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
7576                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
7577                        DisplayFatalError(buf, 0, 1);\r
7578                        return;\r
7579                    }\r
7580                    /* [HGM] here we really should compare with the maximum supported board size */\r
7581                }\r
7582            }\r
7583       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
7584       sprintf(buf, "variant %s\n", b);\r
7585       SendToProgram(buf, cps);\r
7586     }\r
7587     currentlyInitializedVariant = gameInfo.variant;\r
7588 \r
7589     /* [HGM] send opening position in FRC to first engine */\r
7590     if(setup) {\r
7591           SendToProgram("force\n", cps);\r
7592           SendBoard(cps, 0);\r
7593           /* engine is now in force mode! Set flag to wake it up after first move. */\r
7594           setboardSpoiledMachineBlack = 1;\r
7595     }\r
7596 \r
7597     if (cps->sendICS) {\r
7598       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
7599       SendToProgram(buf, cps);\r
7600     }\r
7601     cps->maybeThinking = FALSE;\r
7602     cps->offeredDraw = 0;\r
7603     if (!appData.icsActive) {\r
7604         SendTimeControl(cps, movesPerSession, timeControl,\r
7605                         timeIncrement, appData.searchDepth,\r
7606                         searchTime);\r
7607     }\r
7608     if (appData.showThinking \r
7609         // [HGM] thinking: four options require thinking output to be sent\r
7610         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
7611                                 ) {\r
7612         SendToProgram("post\n", cps);\r
7613     }\r
7614     SendToProgram("hard\n", cps);\r
7615     if (!appData.ponderNextMove) {\r
7616         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
7617            it without being sure what state we are in first.  "hard"\r
7618            is not a toggle, so that one is OK.\r
7619          */\r
7620         SendToProgram("easy\n", cps);\r
7621     }\r
7622     if (cps->usePing) {\r
7623       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
7624       SendToProgram(buf, cps);\r
7625     }\r
7626     cps->initDone = TRUE;\r
7627 }   \r
7628 \r
7629 \r
7630 void\r
7631 StartChessProgram(cps)\r
7632      ChessProgramState *cps;\r
7633 {\r
7634     char buf[MSG_SIZ];\r
7635     int err;\r
7636 \r
7637     if (appData.noChessProgram) return;\r
7638     cps->initDone = FALSE;\r
7639 \r
7640     if (strcmp(cps->host, "localhost") == 0) {\r
7641         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
7642     } else if (*appData.remoteShell == NULLCHAR) {\r
7643         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
7644     } else {\r
7645         if (*appData.remoteUser == NULLCHAR) {\r
7646             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
7647                     cps->program);\r
7648         } else {\r
7649             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
7650                     cps->host, appData.remoteUser, cps->program);\r
7651         }\r
7652         err = StartChildProcess(buf, "", &cps->pr);\r
7653     }\r
7654     \r
7655     if (err != 0) {\r
7656         sprintf(buf, _("Startup failure on '%s'"), cps->program);\r
7657         DisplayFatalError(buf, err, 1);\r
7658         cps->pr = NoProc;\r
7659         cps->isr = NULL;\r
7660         return;\r
7661     }\r
7662     \r
7663     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
7664     if (cps->protocolVersion > 1) {\r
7665       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
7666       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options\r
7667       cps->comboCnt = 0;  //                and values of combo boxes\r
7668       SendToProgram(buf, cps);\r
7669     } else {\r
7670       SendToProgram("xboard\n", cps);\r
7671     }\r
7672 }\r
7673 \r
7674 \r
7675 void\r
7676 TwoMachinesEventIfReady P((void))\r
7677 {\r
7678   if (first.lastPing != first.lastPong) {\r
7679     DisplayMessage("", _("Waiting for first chess program"));\r
7680     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7681     return;\r
7682   }\r
7683   if (second.lastPing != second.lastPong) {\r
7684     DisplayMessage("", _("Waiting for second chess program"));\r
7685     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7686     return;\r
7687   }\r
7688   ThawUI();\r
7689   TwoMachinesEvent();\r
7690 }\r
7691 \r
7692 void\r
7693 NextMatchGame P((void))\r
7694 {\r
7695     int index; /* [HGM] autoinc: step lod index during match */\r
7696     Reset(FALSE, TRUE);\r
7697     if (*appData.loadGameFile != NULLCHAR) {\r
7698         index = appData.loadGameIndex;\r
7699         if(index < 0) { // [HGM] autoinc\r
7700             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7701             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7702         } \r
7703         LoadGameFromFile(appData.loadGameFile,\r
7704                          index,\r
7705                          appData.loadGameFile, FALSE);\r
7706     } else if (*appData.loadPositionFile != NULLCHAR) {\r
7707         index = appData.loadPositionIndex;\r
7708         if(index < 0) { // [HGM] autoinc\r
7709             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7710             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7711         } \r
7712         LoadPositionFromFile(appData.loadPositionFile,\r
7713                              index,\r
7714                              appData.loadPositionFile);\r
7715     }\r
7716     TwoMachinesEventIfReady();\r
7717 }\r
7718 \r
7719 void UserAdjudicationEvent( int result )\r
7720 {\r
7721     ChessMove gameResult = GameIsDrawn;\r
7722 \r
7723     if( result > 0 ) {\r
7724         gameResult = WhiteWins;\r
7725     }\r
7726     else if( result < 0 ) {\r
7727         gameResult = BlackWins;\r
7728     }\r
7729 \r
7730     if( gameMode == TwoMachinesPlay ) {\r
7731         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
7732     }\r
7733 }\r
7734 \r
7735 \r
7736 void\r
7737 GameEnds(result, resultDetails, whosays)\r
7738      ChessMove result;\r
7739      char *resultDetails;\r
7740      int whosays;\r
7741 {\r
7742     GameMode nextGameMode;\r
7743     int isIcsGame;\r
7744     char buf[MSG_SIZ];\r
7745 \r
7746     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
7747     endingGame = 1;\r
7748 \r
7749     if (appData.debugMode) {\r
7750       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
7751               result, resultDetails ? resultDetails : "(null)", whosays);\r
7752     }\r
7753 \r
7754     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
7755         /* If we are playing on ICS, the server decides when the\r
7756            game is over, but the engine can offer to draw, claim \r
7757            a draw, or resign. \r
7758          */\r
7759 #if ZIPPY\r
7760         if (appData.zippyPlay && first.initDone) {\r
7761             if (result == GameIsDrawn) {\r
7762                 /* In case draw still needs to be claimed */\r
7763                 SendToICS(ics_prefix);\r
7764                 SendToICS("draw\n");\r
7765             } else if (StrCaseStr(resultDetails, "resign")) {\r
7766                 SendToICS(ics_prefix);\r
7767                 SendToICS("resign\n");\r
7768             }\r
7769         }\r
7770 #endif\r
7771         endingGame = 0; /* [HGM] crash */\r
7772         return;\r
7773     }\r
7774 \r
7775     /* If we're loading the game from a file, stop */\r
7776     if (whosays == GE_FILE) {\r
7777       (void) StopLoadGameTimer();\r
7778       gameFileFP = NULL;\r
7779     }\r
7780 \r
7781     /* Cancel draw offers */\r
7782     first.offeredDraw = second.offeredDraw = 0;\r
7783 \r
7784     /* If this is an ICS game, only ICS can really say it's done;\r
7785        if not, anyone can. */\r
7786     isIcsGame = (gameMode == IcsPlayingWhite || \r
7787                  gameMode == IcsPlayingBlack || \r
7788                  gameMode == IcsObserving    || \r
7789                  gameMode == IcsExamining);\r
7790 \r
7791     if (!isIcsGame || whosays == GE_ICS) {\r
7792         /* OK -- not an ICS game, or ICS said it was done */\r
7793         StopClocks();\r
7794         if (!isIcsGame && !appData.noChessProgram) \r
7795           SetUserThinkingEnables();\r
7796     \r
7797         /* [HGM] if a machine claims the game end we verify this claim */\r
7798         if(gameMode == TwoMachinesPlay && appData.testClaims) {\r
7799             if(appData.testLegality && whosays >= GE_ENGINE1 ) {\r
7800                 char claimer;\r
7801 \r
7802                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
7803                                             first.twoMachinesColor[0] :\r
7804                                             second.twoMachinesColor[0] ;\r
7805                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) &&\r
7806                     (result == WhiteWins && claimer == 'w' ||\r
7807                      result == BlackWins && claimer == 'b'   ) ) {\r
7808                 if (appData.debugMode) {\r
7809                      fprintf(debugFP, "result=%d sp=%d move=%d\n",\r
7810                         result, epStatus[forwardMostMove], forwardMostMove);\r
7811                 }\r
7812                       /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7813                       if(epStatus[forwardMostMove] != EP_CHECKMATE &&\r
7814                          result != (WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins)) {\r
7815                               sprintf(buf, "False win claim: '%s'", resultDetails);\r
7816                               result = claimer == 'w' ? BlackWins : WhiteWins;\r
7817                               resultDetails = buf;\r
7818                       }\r
7819                 } else\r
7820                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
7821                     && (forwardMostMove <= backwardMostMove ||\r
7822                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
7823                         (claimer=='b')==(forwardMostMove&1))\r
7824                                                                                   ) {\r
7825                       /* [HGM] verify: draws that were not flagged are false claims */\r
7826                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
7827                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7828                       resultDetails = buf;\r
7829                 }\r
7830                 /* (Claiming a loss is accepted no questions asked!) */\r
7831             }\r
7832             /* [HGM] bare: don't allow bare King to win */\r
7833             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)\r
7834                          && result != GameIsDrawn)\r
7835             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
7836                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
7837                         int p = (int)boards[forwardMostMove][i][j] - color;\r
7838                         if(p >= 0 && p <= (int)WhiteKing) k++;\r
7839                 }\r
7840                 if (appData.debugMode) {\r
7841                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",\r
7842                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);\r
7843                 }\r
7844                 if(k <= 1) {\r
7845                         result = GameIsDrawn;\r
7846                         sprintf(buf, "%s but bare king", resultDetails);\r
7847                         resultDetails = buf;\r
7848                 }\r
7849             }\r
7850         }\r
7851 \r
7852 \r
7853         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
7854             if(result==WhiteWins) c = '+';\r
7855             if(result==BlackWins) c = '-';\r
7856             if(resultDetails != NULL)\r
7857                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
7858         }\r
7859         if (resultDetails != NULL) {\r
7860             gameInfo.result = result;\r
7861             gameInfo.resultDetails = StrSave(resultDetails);\r
7862 \r
7863             /* display last move only if game was not loaded from file */\r
7864             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7865                 DisplayMove(currentMove - 1);\r
7866     \r
7867             if (forwardMostMove != 0) {\r
7868                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7869                     if (*appData.saveGameFile != NULLCHAR) {\r
7870                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7871                     } else if (appData.autoSaveGames) {\r
7872                         AutoSaveGame();\r
7873                     }\r
7874                     if (*appData.savePositionFile != NULLCHAR) {\r
7875                         SavePositionToFile(appData.savePositionFile);\r
7876                     }\r
7877                 }\r
7878             }\r
7879 \r
7880             /* Tell program how game ended in case it is learning */\r
7881             /* [HGM] Moved this to after saving the PGN, just in case */\r
7882             /* engine died and we got here through time loss. In that */\r
7883             /* case we will get a fatal error writing the pipe, which */\r
7884             /* would otherwise lose us the PGN.                       */\r
7885             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7886             /* output during GameEnds should never be fatal anymore   */\r
7887             if (gameMode == MachinePlaysWhite ||\r
7888                 gameMode == MachinePlaysBlack ||\r
7889                 gameMode == TwoMachinesPlay ||\r
7890                 gameMode == IcsPlayingWhite ||\r
7891                 gameMode == IcsPlayingBlack ||\r
7892                 gameMode == BeginningOfGame) {\r
7893                 char buf[MSG_SIZ];\r
7894                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7895                         resultDetails);\r
7896                 if (first.pr != NoProc) {\r
7897                     SendToProgram(buf, &first);\r
7898                 }\r
7899                 if (second.pr != NoProc &&\r
7900                     gameMode == TwoMachinesPlay) {\r
7901                     SendToProgram(buf, &second);\r
7902                 }\r
7903             }\r
7904         }\r
7905 \r
7906         if (appData.icsActive) {\r
7907             if (appData.quietPlay &&\r
7908                 (gameMode == IcsPlayingWhite ||\r
7909                  gameMode == IcsPlayingBlack)) {\r
7910                 SendToICS(ics_prefix);\r
7911                 SendToICS("set shout 1\n");\r
7912             }\r
7913             nextGameMode = IcsIdle;\r
7914             ics_user_moved = FALSE;\r
7915             /* clean up premove.  It's ugly when the game has ended and the\r
7916              * premove highlights are still on the board.\r
7917              */\r
7918             if (gotPremove) {\r
7919               gotPremove = FALSE;\r
7920               ClearPremoveHighlights();\r
7921               DrawPosition(FALSE, boards[currentMove]);\r
7922             }\r
7923             if (whosays == GE_ICS) {\r
7924                 switch (result) {\r
7925                 case WhiteWins:\r
7926                     if (gameMode == IcsPlayingWhite)\r
7927                         PlayIcsWinSound();\r
7928                     else if(gameMode == IcsPlayingBlack)\r
7929                         PlayIcsLossSound();\r
7930                     break;\r
7931                 case BlackWins:\r
7932                     if (gameMode == IcsPlayingBlack)\r
7933                         PlayIcsWinSound();\r
7934                     else if(gameMode == IcsPlayingWhite)\r
7935                         PlayIcsLossSound();\r
7936                     break;\r
7937                 case GameIsDrawn:\r
7938                     PlayIcsDrawSound();\r
7939                     break;\r
7940                 default:\r
7941                     PlayIcsUnfinishedSound();\r
7942                 }\r
7943             }\r
7944         } else if (gameMode == EditGame ||\r
7945                    gameMode == PlayFromGameFile || \r
7946                    gameMode == AnalyzeMode || \r
7947                    gameMode == AnalyzeFile) {\r
7948             nextGameMode = gameMode;\r
7949         } else {\r
7950             nextGameMode = EndOfGame;\r
7951         }\r
7952         pausing = FALSE;\r
7953         ModeHighlight();\r
7954     } else {\r
7955         nextGameMode = gameMode;\r
7956     }\r
7957 \r
7958     if (appData.noChessProgram) {\r
7959         gameMode = nextGameMode;\r
7960         ModeHighlight();\r
7961         endingGame = 0; /* [HGM] crash */\r
7962         return;\r
7963     }\r
7964 \r
7965     if (first.reuse) {\r
7966         /* Put first chess program into idle state */\r
7967         if (first.pr != NoProc &&\r
7968             (gameMode == MachinePlaysWhite ||\r
7969              gameMode == MachinePlaysBlack ||\r
7970              gameMode == TwoMachinesPlay ||\r
7971              gameMode == IcsPlayingWhite ||\r
7972              gameMode == IcsPlayingBlack ||\r
7973              gameMode == BeginningOfGame)) {\r
7974             SendToProgram("force\n", &first);\r
7975             if (first.usePing) {\r
7976               char buf[MSG_SIZ];\r
7977               sprintf(buf, "ping %d\n", ++first.lastPing);\r
7978               SendToProgram(buf, &first);\r
7979             }\r
7980         }\r
7981     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7982         /* Kill off first chess program */\r
7983         if (first.isr != NULL)\r
7984           RemoveInputSource(first.isr);\r
7985         first.isr = NULL;\r
7986     \r
7987         if (first.pr != NoProc) {\r
7988             ExitAnalyzeMode();\r
7989             DoSleep( appData.delayBeforeQuit );\r
7990             SendToProgram("quit\n", &first);\r
7991             DoSleep( appData.delayAfterQuit );\r
7992             DestroyChildProcess(first.pr, first.useSigterm);\r
7993         }\r
7994         first.pr = NoProc;\r
7995     }\r
7996     if (second.reuse) {\r
7997         /* Put second chess program into idle state */\r
7998         if (second.pr != NoProc &&\r
7999             gameMode == TwoMachinesPlay) {\r
8000             SendToProgram("force\n", &second);\r
8001             if (second.usePing) {\r
8002               char buf[MSG_SIZ];\r
8003               sprintf(buf, "ping %d\n", ++second.lastPing);\r
8004               SendToProgram(buf, &second);\r
8005             }\r
8006         }\r
8007     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
8008         /* Kill off second chess program */\r
8009         if (second.isr != NULL)\r
8010           RemoveInputSource(second.isr);\r
8011         second.isr = NULL;\r
8012     \r
8013         if (second.pr != NoProc) {\r
8014             DoSleep( appData.delayBeforeQuit );\r
8015             SendToProgram("quit\n", &second);\r
8016             DoSleep( appData.delayAfterQuit );\r
8017             DestroyChildProcess(second.pr, second.useSigterm);\r
8018         }\r
8019         second.pr = NoProc;\r
8020     }\r
8021 \r
8022     if (matchMode && gameMode == TwoMachinesPlay) {\r
8023         switch (result) {\r
8024         case WhiteWins:\r
8025           if (first.twoMachinesColor[0] == 'w') {\r
8026             first.matchWins++;\r
8027           } else {\r
8028             second.matchWins++;\r
8029           }\r
8030           break;\r
8031         case BlackWins:\r
8032           if (first.twoMachinesColor[0] == 'b') {\r
8033             first.matchWins++;\r
8034           } else {\r
8035             second.matchWins++;\r
8036           }\r
8037           break;\r
8038         default:\r
8039           break;\r
8040         }\r
8041         if (matchGame < appData.matchGames) {\r
8042             char *tmp;\r
8043             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */\r
8044                 tmp = first.twoMachinesColor;\r
8045                 first.twoMachinesColor = second.twoMachinesColor;\r
8046                 second.twoMachinesColor = tmp;\r
8047             }\r
8048             gameMode = nextGameMode;\r
8049             matchGame++;\r
8050             if(appData.matchPause>10000 || appData.matchPause<10)\r
8051                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
8052             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
8053             endingGame = 0; /* [HGM] crash */\r
8054             return;\r
8055         } else {\r
8056             char buf[MSG_SIZ];\r
8057             gameMode = nextGameMode;\r
8058             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),\r
8059                     first.tidy, second.tidy,\r
8060                     first.matchWins, second.matchWins,\r
8061                     appData.matchGames - (first.matchWins + second.matchWins));\r
8062             DisplayFatalError(buf, 0, 0);\r
8063         }\r
8064     }\r
8065     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
8066         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
8067       ExitAnalyzeMode();\r
8068     gameMode = nextGameMode;\r
8069     ModeHighlight();\r
8070     endingGame = 0;  /* [HGM] crash */\r
8071 }\r
8072 \r
8073 /* Assumes program was just initialized (initString sent).\r
8074    Leaves program in force mode. */\r
8075 void\r
8076 FeedMovesToProgram(cps, upto) \r
8077      ChessProgramState *cps;\r
8078      int upto;\r
8079 {\r
8080     int i;\r
8081     \r
8082     if (appData.debugMode)\r
8083       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
8084               startedFromSetupPosition ? "position and " : "",\r
8085               backwardMostMove, upto, cps->which);\r
8086     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
8087         // [HGM] variantswitch: make engine aware of new variant\r
8088         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
8089                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
8090         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
8091         SendToProgram(buf, cps);\r
8092         currentlyInitializedVariant = gameInfo.variant;\r
8093     }\r
8094     SendToProgram("force\n", cps);\r
8095     if (startedFromSetupPosition) {\r
8096         SendBoard(cps, backwardMostMove);\r
8097     if (appData.debugMode) {\r
8098         fprintf(debugFP, "feedMoves\n");\r
8099     }\r
8100     }\r
8101     for (i = backwardMostMove; i < upto; i++) {\r
8102         SendMoveToProgram(i, cps);\r
8103     }\r
8104 }\r
8105 \r
8106 \r
8107 void\r
8108 ResurrectChessProgram()\r
8109 {\r
8110      /* The chess program may have exited.\r
8111         If so, restart it and feed it all the moves made so far. */\r
8112 \r
8113     if (appData.noChessProgram || first.pr != NoProc) return;\r
8114     \r
8115     StartChessProgram(&first);\r
8116     InitChessProgram(&first, FALSE);\r
8117     FeedMovesToProgram(&first, currentMove);\r
8118 \r
8119     if (!first.sendTime) {\r
8120         /* can't tell gnuchess what its clock should read,\r
8121            so we bow to its notion. */\r
8122         ResetClocks();\r
8123         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
8124         timeRemaining[1][currentMove] = blackTimeRemaining;\r
8125     }\r
8126 \r
8127     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||\r
8128                 appData.icsEngineAnalyze) && first.analysisSupport) {\r
8129       SendToProgram("analyze\n", &first);\r
8130       first.analyzing = TRUE;\r
8131     }\r
8132 }\r
8133 \r
8134 /*\r
8135  * Button procedures\r
8136  */\r
8137 void\r
8138 Reset(redraw, init)\r
8139      int redraw, init;\r
8140 {\r
8141     int i;\r
8142 \r
8143     if (appData.debugMode) {\r
8144         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
8145                 redraw, init, gameMode);\r
8146     }\r
8147     pausing = pauseExamInvalid = FALSE;\r
8148     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
8149     firstMove = TRUE;\r
8150     whiteFlag = blackFlag = FALSE;\r
8151     userOfferedDraw = FALSE;\r
8152     hintRequested = bookRequested = FALSE;\r
8153     first.maybeThinking = FALSE;\r
8154     second.maybeThinking = FALSE;\r
8155     first.bookSuspend = FALSE; // [HGM] book\r
8156     second.bookSuspend = FALSE;\r
8157     thinkOutput[0] = NULLCHAR;\r
8158     lastHint[0] = NULLCHAR;\r
8159     ClearGameInfo(&gameInfo);\r
8160     gameInfo.variant = StringToVariant(appData.variant);\r
8161     ics_user_moved = ics_clock_paused = FALSE;\r
8162     ics_getting_history = H_FALSE;\r
8163     ics_gamenum = -1;\r
8164     white_holding[0] = black_holding[0] = NULLCHAR;\r
8165     ClearProgramStats();\r
8166     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode\r
8167     \r
8168     ResetFrontEnd();\r
8169     ClearHighlights();\r
8170     flipView = appData.flipView;\r
8171     ClearPremoveHighlights();\r
8172     gotPremove = FALSE;\r
8173     alarmSounded = FALSE;\r
8174 \r
8175     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8176     if(appData.serverMovesName != NULL) {\r
8177         /* [HGM] prepare to make moves file for broadcasting */\r
8178         clock_t t = clock();\r
8179         if(serverMoves != NULL) fclose(serverMoves);\r
8180         serverMoves = fopen(appData.serverMovesName, "r");\r
8181         if(serverMoves != NULL) {\r
8182             fclose(serverMoves);\r
8183             /* delay 15 sec before overwriting, so all clients can see end */\r
8184             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
8185         }\r
8186         serverMoves = fopen(appData.serverMovesName, "w");\r
8187     }\r
8188 \r
8189     ExitAnalyzeMode();\r
8190     gameMode = BeginningOfGame;\r
8191     ModeHighlight();\r
8192     if(appData.icsActive) gameInfo.variant = VariantNormal;\r
8193     InitPosition(redraw);\r
8194     for (i = 0; i < MAX_MOVES; i++) {\r
8195         if (commentList[i] != NULL) {\r
8196             free(commentList[i]);\r
8197             commentList[i] = NULL;\r
8198         }\r
8199     }\r
8200     ResetClocks();\r
8201     timeRemaining[0][0] = whiteTimeRemaining;\r
8202     timeRemaining[1][0] = blackTimeRemaining;\r
8203     if (first.pr == NULL) {\r
8204         StartChessProgram(&first);\r
8205     }\r
8206     if (init) {\r
8207             InitChessProgram(&first, startedFromSetupPosition);\r
8208     }\r
8209     DisplayTitle("");\r
8210     DisplayMessage("", "");\r
8211     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8212 }\r
8213 \r
8214 void\r
8215 AutoPlayGameLoop()\r
8216 {\r
8217     for (;;) {\r
8218         if (!AutoPlayOneMove())\r
8219           return;\r
8220         if (matchMode || appData.timeDelay == 0)\r
8221           continue;\r
8222         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
8223           return;\r
8224         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
8225         break;\r
8226     }\r
8227 }\r
8228 \r
8229 \r
8230 int\r
8231 AutoPlayOneMove()\r
8232 {\r
8233     int fromX, fromY, toX, toY;\r
8234 \r
8235     if (appData.debugMode) {\r
8236       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
8237     }\r
8238 \r
8239     if (gameMode != PlayFromGameFile)\r
8240       return FALSE;\r
8241 \r
8242     if (currentMove >= forwardMostMove) {\r
8243       gameMode = EditGame;\r
8244       ModeHighlight();\r
8245 \r
8246       /* [AS] Clear current move marker at the end of a game */\r
8247       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
8248 \r
8249       return FALSE;\r
8250     }\r
8251     \r
8252     toX = moveList[currentMove][2] - AAA;\r
8253     toY = moveList[currentMove][3] - ONE;\r
8254 \r
8255     if (moveList[currentMove][1] == '@') {\r
8256         if (appData.highlightLastMove) {\r
8257             SetHighlights(-1, -1, toX, toY);\r
8258         }\r
8259     } else {\r
8260         fromX = moveList[currentMove][0] - AAA;\r
8261         fromY = moveList[currentMove][1] - ONE;\r
8262 \r
8263         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
8264 \r
8265         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
8266 \r
8267         if (appData.highlightLastMove) {\r
8268             SetHighlights(fromX, fromY, toX, toY);\r
8269         }\r
8270     }\r
8271     DisplayMove(currentMove);\r
8272     SendMoveToProgram(currentMove++, &first);\r
8273     DisplayBothClocks();\r
8274     DrawPosition(FALSE, boards[currentMove]);\r
8275     // [HGM] PV info: always display, routine tests if empty\r
8276     DisplayComment(currentMove - 1, commentList[currentMove]);\r
8277     return TRUE;\r
8278 }\r
8279 \r
8280 \r
8281 int\r
8282 LoadGameOneMove(readAhead)\r
8283      ChessMove readAhead;\r
8284 {\r
8285     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
8286     char promoChar = NULLCHAR;\r
8287     ChessMove moveType;\r
8288     char move[MSG_SIZ];\r
8289     char *p, *q;\r
8290     \r
8291     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
8292         gameMode != AnalyzeMode && gameMode != Training) {\r
8293         gameFileFP = NULL;\r
8294         return FALSE;\r
8295     }\r
8296     \r
8297     yyboardindex = forwardMostMove;\r
8298     if (readAhead != (ChessMove)0) {\r
8299       moveType = readAhead;\r
8300     } else {\r
8301       if (gameFileFP == NULL)\r
8302           return FALSE;\r
8303       moveType = (ChessMove) yylex();\r
8304     }\r
8305     \r
8306     done = FALSE;\r
8307     switch (moveType) {\r
8308       case Comment:\r
8309         if (appData.debugMode) \r
8310           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8311         p = yy_text;\r
8312         if (*p == '{' || *p == '[' || *p == '(') {\r
8313             p[strlen(p) - 1] = NULLCHAR;\r
8314             p++;\r
8315         }\r
8316 \r
8317         /* append the comment but don't display it */\r
8318         while (*p == '\n') p++;\r
8319         AppendComment(currentMove, p);\r
8320         return TRUE;\r
8321 \r
8322       case WhiteCapturesEnPassant:\r
8323       case BlackCapturesEnPassant:\r
8324       case WhitePromotionChancellor:\r
8325       case BlackPromotionChancellor:\r
8326       case WhitePromotionArchbishop:\r
8327       case BlackPromotionArchbishop:\r
8328       case WhitePromotionCentaur:\r
8329       case BlackPromotionCentaur:\r
8330       case WhitePromotionQueen:\r
8331       case BlackPromotionQueen:\r
8332       case WhitePromotionRook:\r
8333       case BlackPromotionRook:\r
8334       case WhitePromotionBishop:\r
8335       case BlackPromotionBishop:\r
8336       case WhitePromotionKnight:\r
8337       case BlackPromotionKnight:\r
8338       case WhitePromotionKing:\r
8339       case BlackPromotionKing:\r
8340       case NormalMove:\r
8341       case WhiteKingSideCastle:\r
8342       case WhiteQueenSideCastle:\r
8343       case BlackKingSideCastle:\r
8344       case BlackQueenSideCastle:\r
8345       case WhiteKingSideCastleWild:\r
8346       case WhiteQueenSideCastleWild:\r
8347       case BlackKingSideCastleWild:\r
8348       case BlackQueenSideCastleWild:\r
8349       /* PUSH Fabien */\r
8350       case WhiteHSideCastleFR:\r
8351       case WhiteASideCastleFR:\r
8352       case BlackHSideCastleFR:\r
8353       case BlackASideCastleFR:\r
8354       /* POP Fabien */\r
8355         if (appData.debugMode)\r
8356           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8357         fromX = currentMoveString[0] - AAA;\r
8358         fromY = currentMoveString[1] - ONE;\r
8359         toX = currentMoveString[2] - AAA;\r
8360         toY = currentMoveString[3] - ONE;\r
8361         promoChar = currentMoveString[4];\r
8362         break;\r
8363 \r
8364       case WhiteDrop:\r
8365       case BlackDrop:\r
8366         if (appData.debugMode)\r
8367           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8368         fromX = moveType == WhiteDrop ?\r
8369           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
8370         (int) CharToPiece(ToLower(currentMoveString[0]));\r
8371         fromY = DROP_RANK;\r
8372         toX = currentMoveString[2] - AAA;\r
8373         toY = currentMoveString[3] - ONE;\r
8374         break;\r
8375 \r
8376       case WhiteWins:\r
8377       case BlackWins:\r
8378       case GameIsDrawn:\r
8379       case GameUnfinished:\r
8380         if (appData.debugMode)\r
8381           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
8382         p = strchr(yy_text, '{');\r
8383         if (p == NULL) p = strchr(yy_text, '(');\r
8384         if (p == NULL) {\r
8385             p = yy_text;\r
8386             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
8387         } else {\r
8388             q = strchr(p, *p == '{' ? '}' : ')');\r
8389             if (q != NULL) *q = NULLCHAR;\r
8390             p++;\r
8391         }\r
8392         GameEnds(moveType, p, GE_FILE);\r
8393         done = TRUE;\r
8394         if (cmailMsgLoaded) {\r
8395             ClearHighlights();\r
8396             flipView = WhiteOnMove(currentMove);\r
8397             if (moveType == GameUnfinished) flipView = !flipView;\r
8398             if (appData.debugMode)\r
8399               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
8400         }\r
8401         break;\r
8402 \r
8403       case (ChessMove) 0:       /* end of file */\r
8404         if (appData.debugMode)\r
8405           fprintf(debugFP, "Parser hit end of file\n");\r
8406         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8407                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8408           case MT_NONE:\r
8409           case MT_CHECK:\r
8410             break;\r
8411           case MT_CHECKMATE:\r
8412             if (WhiteOnMove(currentMove)) {\r
8413                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8414             } else {\r
8415                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8416             }\r
8417             break;\r
8418           case MT_STALEMATE:\r
8419             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8420             break;\r
8421         }\r
8422         done = TRUE;\r
8423         break;\r
8424 \r
8425       case MoveNumberOne:\r
8426         if (lastLoadGameStart == GNUChessGame) {\r
8427             /* GNUChessGames have numbers, but they aren't move numbers */\r
8428             if (appData.debugMode)\r
8429               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8430                       yy_text, (int) moveType);\r
8431             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8432         }\r
8433         /* else fall thru */\r
8434 \r
8435       case XBoardGame:\r
8436       case GNUChessGame:\r
8437       case PGNTag:\r
8438         /* Reached start of next game in file */\r
8439         if (appData.debugMode)\r
8440           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
8441         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8442                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8443           case MT_NONE:\r
8444           case MT_CHECK:\r
8445             break;\r
8446           case MT_CHECKMATE:\r
8447             if (WhiteOnMove(currentMove)) {\r
8448                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8449             } else {\r
8450                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8451             }\r
8452             break;\r
8453           case MT_STALEMATE:\r
8454             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8455             break;\r
8456         }\r
8457         done = TRUE;\r
8458         break;\r
8459 \r
8460       case PositionDiagram:     /* should not happen; ignore */\r
8461       case ElapsedTime:         /* ignore */\r
8462       case NAG:                 /* ignore */\r
8463         if (appData.debugMode)\r
8464           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8465                   yy_text, (int) moveType);\r
8466         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8467 \r
8468       case IllegalMove:\r
8469         if (appData.testLegality) {\r
8470             if (appData.debugMode)\r
8471               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
8472             sprintf(move, _("Illegal move: %d.%s%s"),\r
8473                     (forwardMostMove / 2) + 1,\r
8474                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8475             DisplayError(move, 0);\r
8476             done = TRUE;\r
8477         } else {\r
8478             if (appData.debugMode)\r
8479               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
8480                       yy_text, currentMoveString);\r
8481             fromX = currentMoveString[0] - AAA;\r
8482             fromY = currentMoveString[1] - ONE;\r
8483             toX = currentMoveString[2] - AAA;\r
8484             toY = currentMoveString[3] - ONE;\r
8485             promoChar = currentMoveString[4];\r
8486         }\r
8487         break;\r
8488 \r
8489       case AmbiguousMove:\r
8490         if (appData.debugMode)\r
8491           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
8492         sprintf(move, _("Ambiguous move: %d.%s%s"),\r
8493                 (forwardMostMove / 2) + 1,\r
8494                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8495         DisplayError(move, 0);\r
8496         done = TRUE;\r
8497         break;\r
8498 \r
8499       default:\r
8500       case ImpossibleMove:\r
8501         if (appData.debugMode)\r
8502           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);\r
8503         sprintf(move, _("Illegal move: %d.%s%s"),\r
8504                 (forwardMostMove / 2) + 1,\r
8505                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8506         DisplayError(move, 0);\r
8507         done = TRUE;\r
8508         break;\r
8509     }\r
8510 \r
8511     if (done) {\r
8512         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
8513             DrawPosition(FALSE, boards[currentMove]);\r
8514             DisplayBothClocks();\r
8515             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
8516               DisplayComment(currentMove - 1, commentList[currentMove]);\r
8517         }\r
8518         (void) StopLoadGameTimer();\r
8519         gameFileFP = NULL;\r
8520         cmailOldMove = forwardMostMove;\r
8521         return FALSE;\r
8522     } else {\r
8523         /* currentMoveString is set as a side-effect of yylex */\r
8524         strcat(currentMoveString, "\n");\r
8525         strcpy(moveList[forwardMostMove], currentMoveString);\r
8526         \r
8527         thinkOutput[0] = NULLCHAR;\r
8528         MakeMove(fromX, fromY, toX, toY, promoChar);\r
8529         currentMove = forwardMostMove;\r
8530         return TRUE;\r
8531     }\r
8532 }\r
8533 \r
8534 /* Load the nth game from the given file */\r
8535 int\r
8536 LoadGameFromFile(filename, n, title, useList)\r
8537      char *filename;\r
8538      int n;\r
8539      char *title;\r
8540      /*Boolean*/ int useList;\r
8541 {\r
8542     FILE *f;\r
8543     char buf[MSG_SIZ];\r
8544 \r
8545     if (strcmp(filename, "-") == 0) {\r
8546         f = stdin;\r
8547         title = "stdin";\r
8548     } else {\r
8549         f = fopen(filename, "rb");\r
8550         if (f == NULL) {\r
8551             sprintf(buf, _("Can't open \"%s\""), filename);\r
8552             DisplayError(buf, errno);\r
8553             return FALSE;\r
8554         }\r
8555     }\r
8556     if (fseek(f, 0, 0) == -1) {\r
8557         /* f is not seekable; probably a pipe */\r
8558         useList = FALSE;\r
8559     }\r
8560     if (useList && n == 0) {\r
8561         int error = GameListBuild(f);\r
8562         if (error) {\r
8563             DisplayError(_("Cannot build game list"), error);\r
8564         } else if (!ListEmpty(&gameList) &&\r
8565                    ((ListGame *) gameList.tailPred)->number > 1) {\r
8566             GameListPopUp(f, title);\r
8567             return TRUE;\r
8568         }\r
8569         GameListDestroy();\r
8570         n = 1;\r
8571     }\r
8572     if (n == 0) n = 1;\r
8573     return LoadGame(f, n, title, FALSE);\r
8574 }\r
8575 \r
8576 \r
8577 void\r
8578 MakeRegisteredMove()\r
8579 {\r
8580     int fromX, fromY, toX, toY;\r
8581     char promoChar;\r
8582     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8583         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
8584           case CMAIL_MOVE:\r
8585           case CMAIL_DRAW:\r
8586             if (appData.debugMode)\r
8587               fprintf(debugFP, "Restoring %s for game %d\n",\r
8588                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8589     \r
8590             thinkOutput[0] = NULLCHAR;\r
8591             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
8592             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
8593             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
8594             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
8595             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
8596             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
8597             MakeMove(fromX, fromY, toX, toY, promoChar);\r
8598             ShowMove(fromX, fromY, toX, toY);\r
8599               \r
8600             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8601                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8602               case MT_NONE:\r
8603               case MT_CHECK:\r
8604                 break;\r
8605                 \r
8606               case MT_CHECKMATE:\r
8607                 if (WhiteOnMove(currentMove)) {\r
8608                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
8609                 } else {\r
8610                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
8611                 }\r
8612                 break;\r
8613                 \r
8614               case MT_STALEMATE:\r
8615                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
8616                 break;\r
8617             }\r
8618 \r
8619             break;\r
8620             \r
8621           case CMAIL_RESIGN:\r
8622             if (WhiteOnMove(currentMove)) {\r
8623                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
8624             } else {\r
8625                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
8626             }\r
8627             break;\r
8628             \r
8629           case CMAIL_ACCEPT:\r
8630             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
8631             break;\r
8632               \r
8633           default:\r
8634             break;\r
8635         }\r
8636     }\r
8637 \r
8638     return;\r
8639 }\r
8640 \r
8641 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
8642 int\r
8643 CmailLoadGame(f, gameNumber, title, useList)\r
8644      FILE *f;\r
8645      int gameNumber;\r
8646      char *title;\r
8647      int useList;\r
8648 {\r
8649     int retVal;\r
8650 \r
8651     if (gameNumber > nCmailGames) {\r
8652         DisplayError(_("No more games in this message"), 0);\r
8653         return FALSE;\r
8654     }\r
8655     if (f == lastLoadGameFP) {\r
8656         int offset = gameNumber - lastLoadGameNumber;\r
8657         if (offset == 0) {\r
8658             cmailMsg[0] = NULLCHAR;\r
8659             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8660                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8661                 nCmailMovesRegistered--;\r
8662             }\r
8663             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
8664             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
8665                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
8666             }\r
8667         } else {\r
8668             if (! RegisterMove()) return FALSE;\r
8669         }\r
8670     }\r
8671 \r
8672     retVal = LoadGame(f, gameNumber, title, useList);\r
8673 \r
8674     /* Make move registered during previous look at this game, if any */\r
8675     MakeRegisteredMove();\r
8676 \r
8677     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
8678         commentList[currentMove]\r
8679           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
8680         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8681     }\r
8682 \r
8683     return retVal;\r
8684 }\r
8685 \r
8686 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
8687 int\r
8688 ReloadGame(offset)\r
8689      int offset;\r
8690 {\r
8691     int gameNumber = lastLoadGameNumber + offset;\r
8692     if (lastLoadGameFP == NULL) {\r
8693         DisplayError(_("No game has been loaded yet"), 0);\r
8694         return FALSE;\r
8695     }\r
8696     if (gameNumber <= 0) {\r
8697         DisplayError(_("Can't back up any further"), 0);\r
8698         return FALSE;\r
8699     }\r
8700     if (cmailMsgLoaded) {\r
8701         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
8702                              lastLoadGameTitle, lastLoadGameUseList);\r
8703     } else {\r
8704         return LoadGame(lastLoadGameFP, gameNumber,\r
8705                         lastLoadGameTitle, lastLoadGameUseList);\r
8706     }\r
8707 }\r
8708 \r
8709 \r
8710 \r
8711 /* Load the nth game from open file f */\r
8712 int\r
8713 LoadGame(f, gameNumber, title, useList)\r
8714      FILE *f;\r
8715      int gameNumber;\r
8716      char *title;\r
8717      int useList;\r
8718 {\r
8719     ChessMove cm;\r
8720     char buf[MSG_SIZ];\r
8721     int gn = gameNumber;\r
8722     ListGame *lg = NULL;\r
8723     int numPGNTags = 0;\r
8724     int err;\r
8725     GameMode oldGameMode;\r
8726     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
8727 \r
8728     if (appData.debugMode) \r
8729         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
8730 \r
8731     if (gameMode == Training )\r
8732         SetTrainingModeOff();\r
8733 \r
8734     oldGameMode = gameMode;\r
8735     if (gameMode != BeginningOfGame) {\r
8736       Reset(FALSE, TRUE);\r
8737     }\r
8738 \r
8739     gameFileFP = f;\r
8740     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
8741         fclose(lastLoadGameFP);\r
8742     }\r
8743 \r
8744     if (useList) {\r
8745         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
8746         \r
8747         if (lg) {\r
8748             fseek(f, lg->offset, 0);\r
8749             GameListHighlight(gameNumber);\r
8750             gn = 1;\r
8751         }\r
8752         else {\r
8753             DisplayError(_("Game number out of range"), 0);\r
8754             return FALSE;\r
8755         }\r
8756     } else {\r
8757         GameListDestroy();\r
8758         if (fseek(f, 0, 0) == -1) {\r
8759             if (f == lastLoadGameFP ?\r
8760                 gameNumber == lastLoadGameNumber + 1 :\r
8761                 gameNumber == 1) {\r
8762                 gn = 1;\r
8763             } else {\r
8764                 DisplayError(_("Can't seek on game file"), 0);\r
8765                 return FALSE;\r
8766             }\r
8767         }\r
8768     }\r
8769     lastLoadGameFP = f;\r
8770     lastLoadGameNumber = gameNumber;\r
8771     strcpy(lastLoadGameTitle, title);\r
8772     lastLoadGameUseList = useList;\r
8773 \r
8774     yynewfile(f);\r
8775 \r
8776     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
8777         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
8778                 lg->gameInfo.black);\r
8779             DisplayTitle(buf);\r
8780     } else if (*title != NULLCHAR) {\r
8781         if (gameNumber > 1) {\r
8782             sprintf(buf, "%s %d", title, gameNumber);\r
8783             DisplayTitle(buf);\r
8784         } else {\r
8785             DisplayTitle(title);\r
8786         }\r
8787     }\r
8788 \r
8789     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
8790         gameMode = PlayFromGameFile;\r
8791         ModeHighlight();\r
8792     }\r
8793 \r
8794     currentMove = forwardMostMove = backwardMostMove = 0;\r
8795     CopyBoard(boards[0], initialPosition);\r
8796     StopClocks();\r
8797 \r
8798     /*\r
8799      * Skip the first gn-1 games in the file.\r
8800      * Also skip over anything that precedes an identifiable \r
8801      * start of game marker, to avoid being confused by \r
8802      * garbage at the start of the file.  Currently \r
8803      * recognized start of game markers are the move number "1",\r
8804      * the pattern "gnuchess .* game", the pattern\r
8805      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
8806      * A game that starts with one of the latter two patterns\r
8807      * will also have a move number 1, possibly\r
8808      * following a position diagram.\r
8809      * 5-4-02: Let's try being more lenient and allowing a game to\r
8810      * start with an unnumbered move.  Does that break anything?\r
8811      */\r
8812     cm = lastLoadGameStart = (ChessMove) 0;\r
8813     while (gn > 0) {\r
8814         yyboardindex = forwardMostMove;\r
8815         cm = (ChessMove) yylex();\r
8816         switch (cm) {\r
8817           case (ChessMove) 0:\r
8818             if (cmailMsgLoaded) {\r
8819                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
8820             } else {\r
8821                 Reset(TRUE, TRUE);\r
8822                 DisplayError(_("Game not found in file"), 0);\r
8823             }\r
8824             return FALSE;\r
8825 \r
8826           case GNUChessGame:\r
8827           case XBoardGame:\r
8828             gn--;\r
8829             lastLoadGameStart = cm;\r
8830             break;\r
8831             \r
8832           case MoveNumberOne:\r
8833             switch (lastLoadGameStart) {\r
8834               case GNUChessGame:\r
8835               case XBoardGame:\r
8836               case PGNTag:\r
8837                 break;\r
8838               case MoveNumberOne:\r
8839               case (ChessMove) 0:\r
8840                 gn--;           /* count this game */\r
8841                 lastLoadGameStart = cm;\r
8842                 break;\r
8843               default:\r
8844                 /* impossible */\r
8845                 break;\r
8846             }\r
8847             break;\r
8848 \r
8849           case PGNTag:\r
8850             switch (lastLoadGameStart) {\r
8851               case GNUChessGame:\r
8852               case PGNTag:\r
8853               case MoveNumberOne:\r
8854               case (ChessMove) 0:\r
8855                 gn--;           /* count this game */\r
8856                 lastLoadGameStart = cm;\r
8857                 break;\r
8858               case XBoardGame:\r
8859                 lastLoadGameStart = cm; /* game counted already */\r
8860                 break;\r
8861               default:\r
8862                 /* impossible */\r
8863                 break;\r
8864             }\r
8865             if (gn > 0) {\r
8866                 do {\r
8867                     yyboardindex = forwardMostMove;\r
8868                     cm = (ChessMove) yylex();\r
8869                 } while (cm == PGNTag || cm == Comment);\r
8870             }\r
8871             break;\r
8872 \r
8873           case WhiteWins:\r
8874           case BlackWins:\r
8875           case GameIsDrawn:\r
8876             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8877                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8878                     != CMAIL_OLD_RESULT) {\r
8879                     nCmailResults ++ ;\r
8880                     cmailResult[  CMAIL_MAX_GAMES\r
8881                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8882                 }\r
8883             }\r
8884             break;\r
8885 \r
8886           case NormalMove:\r
8887             /* Only a NormalMove can be at the start of a game\r
8888              * without a position diagram. */\r
8889             if (lastLoadGameStart == (ChessMove) 0) {\r
8890               gn--;\r
8891               lastLoadGameStart = MoveNumberOne;\r
8892             }\r
8893             break;\r
8894 \r
8895           default:\r
8896             break;\r
8897         }\r
8898     }\r
8899     \r
8900     if (appData.debugMode)\r
8901       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8902 \r
8903     if (cm == XBoardGame) {\r
8904         /* Skip any header junk before position diagram and/or move 1 */\r
8905         for (;;) {\r
8906             yyboardindex = forwardMostMove;\r
8907             cm = (ChessMove) yylex();\r
8908 \r
8909             if (cm == (ChessMove) 0 ||\r
8910                 cm == GNUChessGame || cm == XBoardGame) {\r
8911                 /* Empty game; pretend end-of-file and handle later */\r
8912                 cm = (ChessMove) 0;\r
8913                 break;\r
8914             }\r
8915 \r
8916             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8917                 cm == PGNTag || cm == Comment)\r
8918               break;\r
8919         }\r
8920     } else if (cm == GNUChessGame) {\r
8921         if (gameInfo.event != NULL) {\r
8922             free(gameInfo.event);\r
8923         }\r
8924         gameInfo.event = StrSave(yy_text);\r
8925     }   \r
8926 \r
8927     startedFromSetupPosition = FALSE;\r
8928     while (cm == PGNTag) {\r
8929         if (appData.debugMode) \r
8930           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8931         err = ParsePGNTag(yy_text, &gameInfo);\r
8932         if (!err) numPGNTags++;\r
8933 \r
8934         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8935         if(gameInfo.variant != oldVariant) {\r
8936             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8937             InitPosition(TRUE);\r
8938             oldVariant = gameInfo.variant;\r
8939             if (appData.debugMode) \r
8940               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8941         }\r
8942 \r
8943 \r
8944         if (gameInfo.fen != NULL) {\r
8945           Board initial_position;\r
8946           startedFromSetupPosition = TRUE;\r
8947           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8948             Reset(TRUE, TRUE);\r
8949             DisplayError(_("Bad FEN position in file"), 0);\r
8950             return FALSE;\r
8951           }\r
8952           CopyBoard(boards[0], initial_position);\r
8953           if (blackPlaysFirst) {\r
8954             currentMove = forwardMostMove = backwardMostMove = 1;\r
8955             CopyBoard(boards[1], initial_position);\r
8956             strcpy(moveList[0], "");\r
8957             strcpy(parseList[0], "");\r
8958             timeRemaining[0][1] = whiteTimeRemaining;\r
8959             timeRemaining[1][1] = blackTimeRemaining;\r
8960             if (commentList[0] != NULL) {\r
8961               commentList[1] = commentList[0];\r
8962               commentList[0] = NULL;\r
8963             }\r
8964           } else {\r
8965             currentMove = forwardMostMove = backwardMostMove = 0;\r
8966           }\r
8967           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */\r
8968           {   int i;\r
8969               initialRulePlies = FENrulePlies;\r
8970               epStatus[forwardMostMove] = FENepStatus;\r
8971               for( i=0; i< nrCastlingRights; i++ )\r
8972                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
8973           }\r
8974           yyboardindex = forwardMostMove;\r
8975           free(gameInfo.fen);\r
8976           gameInfo.fen = NULL;\r
8977         }\r
8978 \r
8979         yyboardindex = forwardMostMove;\r
8980         cm = (ChessMove) yylex();\r
8981 \r
8982         /* Handle comments interspersed among the tags */\r
8983         while (cm == Comment) {\r
8984             char *p;\r
8985             if (appData.debugMode) \r
8986               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8987             p = yy_text;\r
8988             if (*p == '{' || *p == '[' || *p == '(') {\r
8989                 p[strlen(p) - 1] = NULLCHAR;\r
8990                 p++;\r
8991             }\r
8992             while (*p == '\n') p++;\r
8993             AppendComment(currentMove, p);\r
8994             yyboardindex = forwardMostMove;\r
8995             cm = (ChessMove) yylex();\r
8996         }\r
8997     }\r
8998 \r
8999     /* don't rely on existence of Event tag since if game was\r
9000      * pasted from clipboard the Event tag may not exist\r
9001      */\r
9002     if (numPGNTags > 0){\r
9003         char *tags;\r
9004         if (gameInfo.variant == VariantNormal) {\r
9005           gameInfo.variant = StringToVariant(gameInfo.event);\r
9006         }\r
9007         if (!matchMode) {\r
9008           if( appData.autoDisplayTags ) {\r
9009             tags = PGNTags(&gameInfo);\r
9010             TagsPopUp(tags, CmailMsg());\r
9011             free(tags);\r
9012           }\r
9013         }\r
9014     } else {\r
9015         /* Make something up, but don't display it now */\r
9016         SetGameInfo();\r
9017         TagsPopDown();\r
9018     }\r
9019 \r
9020     if (cm == PositionDiagram) {\r
9021         int i, j;\r
9022         char *p;\r
9023         Board initial_position;\r
9024 \r
9025         if (appData.debugMode)\r
9026           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
9027 \r
9028         if (!startedFromSetupPosition) {\r
9029             p = yy_text;\r
9030             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
9031               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
9032                 switch (*p) {\r
9033                   case '[':\r
9034                   case '-':\r
9035                   case ' ':\r
9036                   case '\t':\r
9037                   case '\n':\r
9038                   case '\r':\r
9039                     break;\r
9040                   default:\r
9041                     initial_position[i][j++] = CharToPiece(*p);\r
9042                     break;\r
9043                 }\r
9044             while (*p == ' ' || *p == '\t' ||\r
9045                    *p == '\n' || *p == '\r') p++;\r
9046         \r
9047             if (strncmp(p, "black", strlen("black"))==0)\r
9048               blackPlaysFirst = TRUE;\r
9049             else\r
9050               blackPlaysFirst = FALSE;\r
9051             startedFromSetupPosition = TRUE;\r
9052         \r
9053             CopyBoard(boards[0], initial_position);\r
9054             if (blackPlaysFirst) {\r
9055                 currentMove = forwardMostMove = backwardMostMove = 1;\r
9056                 CopyBoard(boards[1], initial_position);\r
9057                 strcpy(moveList[0], "");\r
9058                 strcpy(parseList[0], "");\r
9059                 timeRemaining[0][1] = whiteTimeRemaining;\r
9060                 timeRemaining[1][1] = blackTimeRemaining;\r
9061                 if (commentList[0] != NULL) {\r
9062                     commentList[1] = commentList[0];\r
9063                     commentList[0] = NULL;\r
9064                 }\r
9065             } else {\r
9066                 currentMove = forwardMostMove = backwardMostMove = 0;\r
9067             }\r
9068         }\r
9069         yyboardindex = forwardMostMove;\r
9070         cm = (ChessMove) yylex();\r
9071     }\r
9072 \r
9073     if (first.pr == NoProc) {\r
9074         StartChessProgram(&first);\r
9075     }\r
9076     InitChessProgram(&first, FALSE);\r
9077     SendToProgram("force\n", &first);\r
9078     if (startedFromSetupPosition) {\r
9079         SendBoard(&first, forwardMostMove);\r
9080     if (appData.debugMode) {\r
9081         fprintf(debugFP, "Load Game\n");\r
9082     }\r
9083         DisplayBothClocks();\r
9084     }      \r
9085 \r
9086     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
9087     loadFlag = appData.suppressLoadMoves;\r
9088 \r
9089     while (cm == Comment) {\r
9090         char *p;\r
9091         if (appData.debugMode) \r
9092           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
9093         p = yy_text;\r
9094         if (*p == '{' || *p == '[' || *p == '(') {\r
9095             p[strlen(p) - 1] = NULLCHAR;\r
9096             p++;\r
9097         }\r
9098         while (*p == '\n') p++;\r
9099         AppendComment(currentMove, p);\r
9100         yyboardindex = forwardMostMove;\r
9101         cm = (ChessMove) yylex();\r
9102     }\r
9103 \r
9104     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
9105         cm == WhiteWins || cm == BlackWins ||\r
9106         cm == GameIsDrawn || cm == GameUnfinished) {\r
9107         DisplayMessage("", _("No moves in game"));\r
9108         if (cmailMsgLoaded) {\r
9109             if (appData.debugMode)\r
9110               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
9111             ClearHighlights();\r
9112             flipView = FALSE;\r
9113         }\r
9114         DrawPosition(FALSE, boards[currentMove]);\r
9115         DisplayBothClocks();\r
9116         gameMode = EditGame;\r
9117         ModeHighlight();\r
9118         gameFileFP = NULL;\r
9119         cmailOldMove = 0;\r
9120         return TRUE;\r
9121     }\r
9122 \r
9123     // [HGM] PV info: routine tests if comment empty\r
9124     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
9125         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9126     }\r
9127     if (!matchMode && appData.timeDelay != 0) \r
9128       DrawPosition(FALSE, boards[currentMove]);\r
9129 \r
9130     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
9131       programStats.ok_to_send = 1;\r
9132     }\r
9133 \r
9134     /* if the first token after the PGN tags is a move\r
9135      * and not move number 1, retrieve it from the parser \r
9136      */\r
9137     if (cm != MoveNumberOne)\r
9138         LoadGameOneMove(cm);\r
9139 \r
9140     /* load the remaining moves from the file */\r
9141     while (LoadGameOneMove((ChessMove)0)) {\r
9142       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9143       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9144     }\r
9145 \r
9146     /* rewind to the start of the game */\r
9147     currentMove = backwardMostMove;\r
9148 \r
9149     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9150 \r
9151     if (oldGameMode == AnalyzeFile ||\r
9152         oldGameMode == AnalyzeMode) {\r
9153       AnalyzeFileEvent();\r
9154     }\r
9155 \r
9156     if (matchMode || appData.timeDelay == 0) {\r
9157       ToEndEvent();\r
9158       gameMode = EditGame;\r
9159       ModeHighlight();\r
9160     } else if (appData.timeDelay > 0) {\r
9161       AutoPlayGameLoop();\r
9162     }\r
9163 \r
9164     if (appData.debugMode) \r
9165         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
9166 \r
9167     loadFlag = 0; /* [HGM] true game starts */\r
9168     return TRUE;\r
9169 }\r
9170 \r
9171 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
9172 int\r
9173 ReloadPosition(offset)\r
9174      int offset;\r
9175 {\r
9176     int positionNumber = lastLoadPositionNumber + offset;\r
9177     if (lastLoadPositionFP == NULL) {\r
9178         DisplayError(_("No position has been loaded yet"), 0);\r
9179         return FALSE;\r
9180     }\r
9181     if (positionNumber <= 0) {\r
9182         DisplayError(_("Can't back up any further"), 0);\r
9183         return FALSE;\r
9184     }\r
9185     return LoadPosition(lastLoadPositionFP, positionNumber,\r
9186                         lastLoadPositionTitle);\r
9187 }\r
9188 \r
9189 /* Load the nth position from the given file */\r
9190 int\r
9191 LoadPositionFromFile(filename, n, title)\r
9192      char *filename;\r
9193      int n;\r
9194      char *title;\r
9195 {\r
9196     FILE *f;\r
9197     char buf[MSG_SIZ];\r
9198 \r
9199     if (strcmp(filename, "-") == 0) {\r
9200         return LoadPosition(stdin, n, "stdin");\r
9201     } else {\r
9202         f = fopen(filename, "rb");\r
9203         if (f == NULL) {\r
9204             sprintf(buf, _("Can't open \"%s\""), filename);\r
9205             DisplayError(buf, errno);\r
9206             return FALSE;\r
9207         } else {\r
9208             return LoadPosition(f, n, title);\r
9209         }\r
9210     }\r
9211 }\r
9212 \r
9213 /* Load the nth position from the given open file, and close it */\r
9214 int\r
9215 LoadPosition(f, positionNumber, title)\r
9216      FILE *f;\r
9217      int positionNumber;\r
9218      char *title;\r
9219 {\r
9220     char *p, line[MSG_SIZ];\r
9221     Board initial_position;\r
9222     int i, j, fenMode, pn;\r
9223     \r
9224     if (gameMode == Training )\r
9225         SetTrainingModeOff();\r
9226 \r
9227     if (gameMode != BeginningOfGame) {\r
9228         Reset(FALSE, TRUE);\r
9229     }\r
9230     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
9231         fclose(lastLoadPositionFP);\r
9232     }\r
9233     if (positionNumber == 0) positionNumber = 1;\r
9234     lastLoadPositionFP = f;\r
9235     lastLoadPositionNumber = positionNumber;\r
9236     strcpy(lastLoadPositionTitle, title);\r
9237     if (first.pr == NoProc) {\r
9238       StartChessProgram(&first);\r
9239       InitChessProgram(&first, FALSE);\r
9240     }    \r
9241     pn = positionNumber;\r
9242     if (positionNumber < 0) {\r
9243         /* Negative position number means to seek to that byte offset */\r
9244         if (fseek(f, -positionNumber, 0) == -1) {\r
9245             DisplayError(_("Can't seek on position file"), 0);\r
9246             return FALSE;\r
9247         };\r
9248         pn = 1;\r
9249     } else {\r
9250         if (fseek(f, 0, 0) == -1) {\r
9251             if (f == lastLoadPositionFP ?\r
9252                 positionNumber == lastLoadPositionNumber + 1 :\r
9253                 positionNumber == 1) {\r
9254                 pn = 1;\r
9255             } else {\r
9256                 DisplayError(_("Can't seek on position file"), 0);\r
9257                 return FALSE;\r
9258             }\r
9259         }\r
9260     }\r
9261     /* See if this file is FEN or old-style xboard */\r
9262     if (fgets(line, MSG_SIZ, f) == NULL) {\r
9263         DisplayError(_("Position not found in file"), 0);\r
9264         return FALSE;\r
9265     }\r
9266 #if 0\r
9267     switch (line[0]) {\r
9268       case '#':  case 'x':\r
9269       default:\r
9270         fenMode = FALSE;\r
9271         break;\r
9272       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
9273       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
9274       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
9275       case '7':  case '8':  case '9':\r
9276       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
9277       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
9278       case 'C':  case 'W':             case 'c':  case 'w': \r
9279         fenMode = TRUE;\r
9280         break;\r
9281     }\r
9282 #else\r
9283     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
9284     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
9285 #endif\r
9286 \r
9287     if (pn >= 2) {\r
9288         if (fenMode || line[0] == '#') pn--;\r
9289         while (pn > 0) {\r
9290             /* skip positions before number pn */\r
9291             if (fgets(line, MSG_SIZ, f) == NULL) {\r
9292                 Reset(TRUE, TRUE);\r
9293                 DisplayError(_("Position not found in file"), 0);\r
9294                 return FALSE;\r
9295             }\r
9296             if (fenMode || line[0] == '#') pn--;\r
9297         }\r
9298     }\r
9299 \r
9300     if (fenMode) {\r
9301         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
9302             DisplayError(_("Bad FEN position in file"), 0);\r
9303             return FALSE;\r
9304         }\r
9305     } else {\r
9306         (void) fgets(line, MSG_SIZ, f);\r
9307         (void) fgets(line, MSG_SIZ, f);\r
9308     \r
9309         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
9310             (void) fgets(line, MSG_SIZ, f);\r
9311             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
9312                 if (*p == ' ')\r
9313                   continue;\r
9314                 initial_position[i][j++] = CharToPiece(*p);\r
9315             }\r
9316         }\r
9317     \r
9318         blackPlaysFirst = FALSE;\r
9319         if (!feof(f)) {\r
9320             (void) fgets(line, MSG_SIZ, f);\r
9321             if (strncmp(line, "black", strlen("black"))==0)\r
9322               blackPlaysFirst = TRUE;\r
9323         }\r
9324     }\r
9325     startedFromSetupPosition = TRUE;\r
9326     \r
9327     SendToProgram("force\n", &first);\r
9328     CopyBoard(boards[0], initial_position);\r
9329     if (blackPlaysFirst) {\r
9330         currentMove = forwardMostMove = backwardMostMove = 1;\r
9331         strcpy(moveList[0], "");\r
9332         strcpy(parseList[0], "");\r
9333         CopyBoard(boards[1], initial_position);\r
9334         DisplayMessage("", _("Black to play"));\r
9335     } else {\r
9336         currentMove = forwardMostMove = backwardMostMove = 0;\r
9337         DisplayMessage("", _("White to play"));\r
9338     }\r
9339           /* [HGM] copy FEN attributes as well */\r
9340           {   int i;\r
9341               initialRulePlies = FENrulePlies;\r
9342               epStatus[forwardMostMove] = FENepStatus;\r
9343               for( i=0; i< nrCastlingRights; i++ )\r
9344                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
9345           }\r
9346     SendBoard(&first, forwardMostMove);\r
9347     if (appData.debugMode) {\r
9348 int i, j;\r
9349   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}\r
9350   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");\r
9351         fprintf(debugFP, "Load Position\n");\r
9352     }\r
9353 \r
9354     if (positionNumber > 1) {\r
9355         sprintf(line, "%s %d", title, positionNumber);\r
9356         DisplayTitle(line);\r
9357     } else {\r
9358         DisplayTitle(title);\r
9359     }\r
9360     gameMode = EditGame;\r
9361     ModeHighlight();\r
9362     ResetClocks();\r
9363     timeRemaining[0][1] = whiteTimeRemaining;\r
9364     timeRemaining[1][1] = blackTimeRemaining;\r
9365     DrawPosition(FALSE, boards[currentMove]);\r
9366    \r
9367     return TRUE;\r
9368 }\r
9369 \r
9370 \r
9371 void\r
9372 CopyPlayerNameIntoFileName(dest, src)\r
9373      char **dest, *src;\r
9374 {\r
9375     while (*src != NULLCHAR && *src != ',') {\r
9376         if (*src == ' ') {\r
9377             *(*dest)++ = '_';\r
9378             src++;\r
9379         } else {\r
9380             *(*dest)++ = *src++;\r
9381         }\r
9382     }\r
9383 }\r
9384 \r
9385 char *DefaultFileName(ext)\r
9386      char *ext;\r
9387 {\r
9388     static char def[MSG_SIZ];\r
9389     char *p;\r
9390 \r
9391     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
9392         p = def;\r
9393         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
9394         *p++ = '-';\r
9395         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
9396         *p++ = '.';\r
9397         strcpy(p, ext);\r
9398     } else {\r
9399         def[0] = NULLCHAR;\r
9400     }\r
9401     return def;\r
9402 }\r
9403 \r
9404 /* Save the current game to the given file */\r
9405 int\r
9406 SaveGameToFile(filename, append)\r
9407      char *filename;\r
9408      int append;\r
9409 {\r
9410     FILE *f;\r
9411     char buf[MSG_SIZ];\r
9412 \r
9413     if (strcmp(filename, "-") == 0) {\r
9414         return SaveGame(stdout, 0, NULL);\r
9415     } else {\r
9416         f = fopen(filename, append ? "a" : "w");\r
9417         if (f == NULL) {\r
9418             sprintf(buf, _("Can't open \"%s\""), filename);\r
9419             DisplayError(buf, errno);\r
9420             return FALSE;\r
9421         } else {\r
9422             return SaveGame(f, 0, NULL);\r
9423         }\r
9424     }\r
9425 }\r
9426 \r
9427 char *\r
9428 SavePart(str)\r
9429      char *str;\r
9430 {\r
9431     static char buf[MSG_SIZ];\r
9432     char *p;\r
9433     \r
9434     p = strchr(str, ' ');\r
9435     if (p == NULL) return str;\r
9436     strncpy(buf, str, p - str);\r
9437     buf[p - str] = NULLCHAR;\r
9438     return buf;\r
9439 }\r
9440 \r
9441 #define PGN_MAX_LINE 75\r
9442 \r
9443 #define PGN_SIDE_WHITE  0\r
9444 #define PGN_SIDE_BLACK  1\r
9445 \r
9446 /* [AS] */\r
9447 static int FindFirstMoveOutOfBook( int side )\r
9448 {\r
9449     int result = -1;\r
9450 \r
9451     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
9452         int index = backwardMostMove;\r
9453         int has_book_hit = 0;\r
9454 \r
9455         if( (index % 2) != side ) {\r
9456             index++;\r
9457         }\r
9458 \r
9459         while( index < forwardMostMove ) {\r
9460             /* Check to see if engine is in book */\r
9461             int depth = pvInfoList[index].depth;\r
9462             int score = pvInfoList[index].score;\r
9463             int in_book = 0;\r
9464 \r
9465             if( depth <= 2 ) {\r
9466                 in_book = 1;\r
9467             }\r
9468             else if( score == 0 && depth == 63 ) {\r
9469                 in_book = 1; /* Zappa */\r
9470             }\r
9471             else if( score == 2 && depth == 99 ) {\r
9472                 in_book = 1; /* Abrok */\r
9473             }\r
9474 \r
9475             has_book_hit += in_book;\r
9476 \r
9477             if( ! in_book ) {\r
9478                 result = index;\r
9479 \r
9480                 break;\r
9481             }\r
9482 \r
9483             index += 2;\r
9484         }\r
9485     }\r
9486 \r
9487     return result;\r
9488 }\r
9489 \r
9490 /* [AS] */\r
9491 void GetOutOfBookInfo( char * buf )\r
9492 {\r
9493     int oob[2];\r
9494     int i;\r
9495     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9496 \r
9497     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
9498     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
9499 \r
9500     *buf = '\0';\r
9501 \r
9502     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
9503         for( i=0; i<2; i++ ) {\r
9504             int idx = oob[i];\r
9505 \r
9506             if( idx >= 0 ) {\r
9507                 if( i > 0 && oob[0] >= 0 ) {\r
9508                     strcat( buf, "   " );\r
9509                 }\r
9510 \r
9511                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
9512                 sprintf( buf+strlen(buf), "%s%.2f", \r
9513                     pvInfoList[idx].score >= 0 ? "+" : "",\r
9514                     pvInfoList[idx].score / 100.0 );\r
9515             }\r
9516         }\r
9517     }\r
9518 }\r
9519 \r
9520 /* Save game in PGN style and close the file */\r
9521 int\r
9522 SaveGamePGN(f)\r
9523      FILE *f;\r
9524 {\r
9525     int i, offset, linelen, newblock;\r
9526     time_t tm;\r
9527 //    char *movetext;\r
9528     char numtext[32];\r
9529     int movelen, numlen, blank;\r
9530     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
9531 \r
9532     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9533     \r
9534     tm = time((time_t *) NULL);\r
9535     \r
9536     PrintPGNTags(f, &gameInfo);\r
9537     \r
9538     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9539         char *fen = PositionToFEN(backwardMostMove, 1);\r
9540         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
9541         fprintf(f, "\n{--------------\n");\r
9542         PrintPosition(f, backwardMostMove);\r
9543         fprintf(f, "--------------}\n");\r
9544         free(fen);\r
9545     }\r
9546     else {\r
9547         /* [AS] Out of book annotation */\r
9548         if( appData.saveOutOfBookInfo ) {\r
9549             char buf[64];\r
9550 \r
9551             GetOutOfBookInfo( buf );\r
9552 \r
9553             if( buf[0] != '\0' ) {\r
9554                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
9555             }\r
9556         }\r
9557 \r
9558         fprintf(f, "\n");\r
9559     }\r
9560 \r
9561     i = backwardMostMove;\r
9562     linelen = 0;\r
9563     newblock = TRUE;\r
9564 \r
9565     while (i < forwardMostMove) {\r
9566         /* Print comments preceding this move */\r
9567         if (commentList[i] != NULL) {\r
9568             if (linelen > 0) fprintf(f, "\n");\r
9569             fprintf(f, "{\n%s}\n", commentList[i]);\r
9570             linelen = 0;\r
9571             newblock = TRUE;\r
9572         }\r
9573 \r
9574         /* Format move number */\r
9575         if ((i % 2) == 0) {\r
9576             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
9577         } else {\r
9578             if (newblock) {\r
9579                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
9580             } else {\r
9581                 numtext[0] = NULLCHAR;\r
9582             }\r
9583         }\r
9584         numlen = strlen(numtext);\r
9585         newblock = FALSE;\r
9586 \r
9587         /* Print move number */\r
9588         blank = linelen > 0 && numlen > 0;\r
9589         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
9590             fprintf(f, "\n");\r
9591             linelen = 0;\r
9592             blank = 0;\r
9593         }\r
9594         if (blank) {\r
9595             fprintf(f, " ");\r
9596             linelen++;\r
9597         }\r
9598         fprintf(f, numtext);\r
9599         linelen += numlen;\r
9600 \r
9601         /* Get move */\r
9602         movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */\r
9603 \r
9604         /* Print move */\r
9605         blank = linelen > 0 && movelen > 0;\r
9606         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9607             fprintf(f, "\n");\r
9608             linelen = 0;\r
9609             blank = 0;\r
9610         }\r
9611         if (blank) {\r
9612             fprintf(f, " ");\r
9613             linelen++;\r
9614         }\r
9615         fprintf(f, parseList[i]);\r
9616         linelen += movelen;\r
9617 \r
9618         /* [AS] Add PV info if present */\r
9619         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9620             /* [HGM] add time */\r
9621             char buf[MSG_SIZ]; int seconds = 0;\r
9622 \r
9623 #if 0\r
9624             if(i >= backwardMostMove) {\r
9625                 if(WhiteOnMove(i))\r
9626                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]\r
9627                                   + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;\r
9628                 else\r
9629                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]\r
9630                                   + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;\r
9631             }\r
9632             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest\r
9633 #else\r
9634             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time\r
9635 #endif\r
9636 \r
9637             if( seconds <= 0) buf[0] = 0; else\r
9638             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {\r
9639                 seconds = (seconds + 4)/10; // round to full seconds\r
9640                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else\r
9641                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
9642             }\r
9643 \r
9644             sprintf( move_buffer, "{%s%.2f/%d%s}", \r
9645                 pvInfoList[i].score >= 0 ? "+" : "",\r
9646                 pvInfoList[i].score / 100.0,\r
9647                 pvInfoList[i].depth,\r
9648                 buf );\r
9649 \r
9650             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */\r
9651 \r
9652             /* Print score/depth */\r
9653             blank = linelen > 0 && movelen > 0;\r
9654             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9655                 fprintf(f, "\n");\r
9656                 linelen = 0;\r
9657                 blank = 0;\r
9658             }\r
9659             if (blank) {\r
9660                 fprintf(f, " ");\r
9661                 linelen++;\r
9662             }\r
9663             fprintf(f, move_buffer);\r
9664             linelen += movelen;\r
9665         }\r
9666 \r
9667         i++;\r
9668     }\r
9669     \r
9670     /* Start a new line */\r
9671     if (linelen > 0) fprintf(f, "\n");\r
9672 \r
9673     /* Print comments after last move */\r
9674     if (commentList[i] != NULL) {\r
9675         fprintf(f, "{\n%s}\n", commentList[i]);\r
9676     }\r
9677 \r
9678     /* Print result */\r
9679     if (gameInfo.resultDetails != NULL &&\r
9680         gameInfo.resultDetails[0] != NULLCHAR) {\r
9681         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
9682                 PGNResult(gameInfo.result));\r
9683     } else {\r
9684         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9685     }\r
9686 \r
9687     fclose(f);\r
9688     return TRUE;\r
9689 }\r
9690 \r
9691 /* Save game in old style and close the file */\r
9692 int\r
9693 SaveGameOldStyle(f)\r
9694      FILE *f;\r
9695 {\r
9696     int i, offset;\r
9697     time_t tm;\r
9698     \r
9699     tm = time((time_t *) NULL);\r
9700     \r
9701     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
9702     PrintOpponents(f);\r
9703     \r
9704     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9705         fprintf(f, "\n[--------------\n");\r
9706         PrintPosition(f, backwardMostMove);\r
9707         fprintf(f, "--------------]\n");\r
9708     } else {\r
9709         fprintf(f, "\n");\r
9710     }\r
9711 \r
9712     i = backwardMostMove;\r
9713     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9714 \r
9715     while (i < forwardMostMove) {\r
9716         if (commentList[i] != NULL) {\r
9717             fprintf(f, "[%s]\n", commentList[i]);\r
9718         }\r
9719 \r
9720         if ((i % 2) == 1) {\r
9721             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
9722             i++;\r
9723         } else {\r
9724             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
9725             i++;\r
9726             if (commentList[i] != NULL) {\r
9727                 fprintf(f, "\n");\r
9728                 continue;\r
9729             }\r
9730             if (i >= forwardMostMove) {\r
9731                 fprintf(f, "\n");\r
9732                 break;\r
9733             }\r
9734             fprintf(f, "%s\n", parseList[i]);\r
9735             i++;\r
9736         }\r
9737     }\r
9738     \r
9739     if (commentList[i] != NULL) {\r
9740         fprintf(f, "[%s]\n", commentList[i]);\r
9741     }\r
9742 \r
9743     /* This isn't really the old style, but it's close enough */\r
9744     if (gameInfo.resultDetails != NULL &&\r
9745         gameInfo.resultDetails[0] != NULLCHAR) {\r
9746         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
9747                 gameInfo.resultDetails);\r
9748     } else {\r
9749         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9750     }\r
9751 \r
9752     fclose(f);\r
9753     return TRUE;\r
9754 }\r
9755 \r
9756 /* Save the current game to open file f and close the file */\r
9757 int\r
9758 SaveGame(f, dummy, dummy2)\r
9759      FILE *f;\r
9760      int dummy;\r
9761      char *dummy2;\r
9762 {\r
9763     if (gameMode == EditPosition) EditPositionDone();\r
9764     if (appData.oldSaveStyle)\r
9765       return SaveGameOldStyle(f);\r
9766     else\r
9767       return SaveGamePGN(f);\r
9768 }\r
9769 \r
9770 /* Save the current position to the given file */\r
9771 int\r
9772 SavePositionToFile(filename)\r
9773      char *filename;\r
9774 {\r
9775     FILE *f;\r
9776     char buf[MSG_SIZ];\r
9777 \r
9778     if (strcmp(filename, "-") == 0) {\r
9779         return SavePosition(stdout, 0, NULL);\r
9780     } else {\r
9781         f = fopen(filename, "a");\r
9782         if (f == NULL) {\r
9783             sprintf(buf, _("Can't open \"%s\""), filename);\r
9784             DisplayError(buf, errno);\r
9785             return FALSE;\r
9786         } else {\r
9787             SavePosition(f, 0, NULL);\r
9788             return TRUE;\r
9789         }\r
9790     }\r
9791 }\r
9792 \r
9793 /* Save the current position to the given open file and close the file */\r
9794 int\r
9795 SavePosition(f, dummy, dummy2)\r
9796      FILE *f;\r
9797      int dummy;\r
9798      char *dummy2;\r
9799 {\r
9800     time_t tm;\r
9801     char *fen;\r
9802     \r
9803     if (appData.oldSaveStyle) {\r
9804         tm = time((time_t *) NULL);\r
9805     \r
9806         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
9807         PrintOpponents(f);\r
9808         fprintf(f, "[--------------\n");\r
9809         PrintPosition(f, currentMove);\r
9810         fprintf(f, "--------------]\n");\r
9811     } else {\r
9812         fen = PositionToFEN(currentMove, 1);\r
9813         fprintf(f, "%s\n", fen);\r
9814         free(fen);\r
9815     }\r
9816     fclose(f);\r
9817     return TRUE;\r
9818 }\r
9819 \r
9820 void\r
9821 ReloadCmailMsgEvent(unregister)\r
9822      int unregister;\r
9823 {\r
9824 #if !WIN32\r
9825     static char *inFilename = NULL;\r
9826     static char *outFilename;\r
9827     int i;\r
9828     struct stat inbuf, outbuf;\r
9829     int status;\r
9830     \r
9831     /* Any registered moves are unregistered if unregister is set, */\r
9832     /* i.e. invoked by the signal handler */\r
9833     if (unregister) {\r
9834         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9835             cmailMoveRegistered[i] = FALSE;\r
9836             if (cmailCommentList[i] != NULL) {\r
9837                 free(cmailCommentList[i]);\r
9838                 cmailCommentList[i] = NULL;\r
9839             }\r
9840         }\r
9841         nCmailMovesRegistered = 0;\r
9842     }\r
9843 \r
9844     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9845         cmailResult[i] = CMAIL_NOT_RESULT;\r
9846     }\r
9847     nCmailResults = 0;\r
9848 \r
9849     if (inFilename == NULL) {\r
9850         /* Because the filenames are static they only get malloced once  */\r
9851         /* and they never get freed                                      */\r
9852         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
9853         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
9854 \r
9855         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
9856         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
9857     }\r
9858     \r
9859     status = stat(outFilename, &outbuf);\r
9860     if (status < 0) {\r
9861         cmailMailedMove = FALSE;\r
9862     } else {\r
9863         status = stat(inFilename, &inbuf);\r
9864         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
9865     }\r
9866     \r
9867     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
9868        counts the games, notes how each one terminated, etc.\r
9869        \r
9870        It would be nice to remove this kludge and instead gather all\r
9871        the information while building the game list.  (And to keep it\r
9872        in the game list nodes instead of having a bunch of fixed-size\r
9873        parallel arrays.)  Note this will require getting each game's\r
9874        termination from the PGN tags, as the game list builder does\r
9875        not process the game moves.  --mann\r
9876        */\r
9877     cmailMsgLoaded = TRUE;\r
9878     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
9879     \r
9880     /* Load first game in the file or popup game menu */\r
9881     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
9882 \r
9883 #endif /* !WIN32 */\r
9884     return;\r
9885 }\r
9886 \r
9887 int\r
9888 RegisterMove()\r
9889 {\r
9890     FILE *f;\r
9891     char string[MSG_SIZ];\r
9892 \r
9893     if (   cmailMailedMove\r
9894         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9895         return TRUE;            /* Allow free viewing  */\r
9896     }\r
9897 \r
9898     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9899     /* with the move registered when the conditions for registering no   */\r
9900     /* longer hold                                                       */\r
9901     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9902         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9903         nCmailMovesRegistered --;\r
9904 \r
9905         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9906           {\r
9907               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9908               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9909           }\r
9910     }\r
9911 \r
9912     if (cmailOldMove == -1) {\r
9913         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);\r
9914         return FALSE;\r
9915     }\r
9916 \r
9917     if (currentMove > cmailOldMove + 1) {\r
9918         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);\r
9919         return FALSE;\r
9920     }\r
9921 \r
9922     if (currentMove < cmailOldMove) {\r
9923         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);\r
9924         return FALSE;\r
9925     }\r
9926 \r
9927     if (forwardMostMove > currentMove) {\r
9928         /* Silently truncate extra moves */\r
9929         TruncateGame();\r
9930     }\r
9931 \r
9932     if (   (currentMove == cmailOldMove + 1)\r
9933         || (   (currentMove == cmailOldMove)\r
9934             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9935                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9936         if (gameInfo.result != GameUnfinished) {\r
9937             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9938         }\r
9939 \r
9940         if (commentList[currentMove] != NULL) {\r
9941             cmailCommentList[lastLoadGameNumber - 1]\r
9942               = StrSave(commentList[currentMove]);\r
9943         }\r
9944         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9945 \r
9946         if (appData.debugMode)\r
9947           fprintf(debugFP, "Saving %s for game %d\n",\r
9948                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9949 \r
9950         sprintf(string,\r
9951                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
9952         \r
9953         f = fopen(string, "w");\r
9954         if (appData.oldSaveStyle) {\r
9955             SaveGameOldStyle(f); /* also closes the file */\r
9956             \r
9957             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
9958             f = fopen(string, "w");\r
9959             SavePosition(f, 0, NULL); /* also closes the file */\r
9960         } else {\r
9961             fprintf(f, "{--------------\n");\r
9962             PrintPosition(f, currentMove);\r
9963             fprintf(f, "--------------}\n\n");\r
9964             \r
9965             SaveGame(f, 0, NULL); /* also closes the file*/\r
9966         }\r
9967         \r
9968         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
9969         nCmailMovesRegistered ++;\r
9970     } else if (nCmailGames == 1) {\r
9971         DisplayError(_("You have not made a move yet"), 0);\r
9972         return FALSE;\r
9973     }\r
9974 \r
9975     return TRUE;\r
9976 }\r
9977 \r
9978 void\r
9979 MailMoveEvent()\r
9980 {\r
9981 #if !WIN32\r
9982     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
9983     FILE *commandOutput;\r
9984     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
9985     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
9986     int nBuffers;\r
9987     int i;\r
9988     int archived;\r
9989     char *arcDir;\r
9990 \r
9991     if (! cmailMsgLoaded) {\r
9992         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);\r
9993         return;\r
9994     }\r
9995 \r
9996     if (nCmailGames == nCmailResults) {\r
9997         DisplayError(_("No unfinished games"), 0);\r
9998         return;\r
9999     }\r
10000 \r
10001 #if CMAIL_PROHIBIT_REMAIL\r
10002     if (cmailMailedMove) {\r
10003         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
10004         DisplayError(msg, 0);\r
10005         return;\r
10006     }\r
10007 #endif\r
10008 \r
10009     if (! (cmailMailedMove || RegisterMove())) return;\r
10010     \r
10011     if (   cmailMailedMove\r
10012         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
10013         sprintf(string, partCommandString,\r
10014                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
10015         commandOutput = popen(string, "r");\r
10016 \r
10017         if (commandOutput == NULL) {\r
10018             DisplayError(_("Failed to invoke cmail"), 0);\r
10019         } else {\r
10020             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
10021                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
10022             }\r
10023             if (nBuffers > 1) {\r
10024                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
10025                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
10026                 nBytes = MSG_SIZ - 1;\r
10027             } else {\r
10028                 (void) memcpy(msg, buffer, nBytes);\r
10029             }\r
10030             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
10031 \r
10032             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
10033                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
10034 \r
10035                 archived = TRUE;\r
10036                 for (i = 0; i < nCmailGames; i ++) {\r
10037                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
10038                         archived = FALSE;\r
10039                     }\r
10040                 }\r
10041                 if (   archived\r
10042                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
10043                         != NULL)) {\r
10044                     sprintf(buffer, "%s/%s.%s.archive",\r
10045                             arcDir,\r
10046                             appData.cmailGameName,\r
10047                             gameInfo.date);\r
10048                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
10049                     cmailMsgLoaded = FALSE;\r
10050                 }\r
10051             }\r
10052 \r
10053             DisplayInformation(msg);\r
10054             pclose(commandOutput);\r
10055         }\r
10056     } else {\r
10057         if ((*cmailMsg) != '\0') {\r
10058             DisplayInformation(cmailMsg);\r
10059         }\r
10060     }\r
10061 \r
10062     return;\r
10063 #endif /* !WIN32 */\r
10064 }\r
10065 \r
10066 char *\r
10067 CmailMsg()\r
10068 {\r
10069 #if WIN32\r
10070     return NULL;\r
10071 #else\r
10072     int  prependComma = 0;\r
10073     char number[5];\r
10074     char string[MSG_SIZ];       /* Space for game-list */\r
10075     int  i;\r
10076     \r
10077     if (!cmailMsgLoaded) return "";\r
10078 \r
10079     if (cmailMailedMove) {\r
10080         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));\r
10081     } else {\r
10082         /* Create a list of games left */\r
10083         sprintf(string, "[");\r
10084         for (i = 0; i < nCmailGames; i ++) {\r
10085             if (! (   cmailMoveRegistered[i]\r
10086                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
10087                 if (prependComma) {\r
10088                     sprintf(number, ",%d", i + 1);\r
10089                 } else {\r
10090                     sprintf(number, "%d", i + 1);\r
10091                     prependComma = 1;\r
10092                 }\r
10093                 \r
10094                 strcat(string, number);\r
10095             }\r
10096         }\r
10097         strcat(string, "]");\r
10098 \r
10099         if (nCmailMovesRegistered + nCmailResults == 0) {\r
10100             switch (nCmailGames) {\r
10101               case 1:\r
10102                 sprintf(cmailMsg,\r
10103                         _("Still need to make move for game\n"));\r
10104                 break;\r
10105                 \r
10106               case 2:\r
10107                 sprintf(cmailMsg,\r
10108                         _("Still need to make moves for both games\n"));\r
10109                 break;\r
10110                 \r
10111               default:\r
10112                 sprintf(cmailMsg,\r
10113                         _("Still need to make moves for all %d games\n"),\r
10114                         nCmailGames);\r
10115                 break;\r
10116             }\r
10117         } else {\r
10118             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
10119               case 1:\r
10120                 sprintf(cmailMsg,\r
10121                         _("Still need to make a move for game %s\n"),\r
10122                         string);\r
10123                 break;\r
10124                 \r
10125               case 0:\r
10126                 if (nCmailResults == nCmailGames) {\r
10127                     sprintf(cmailMsg, _("No unfinished games\n"));\r
10128                 } else {\r
10129                     sprintf(cmailMsg, _("Ready to send mail\n"));\r
10130                 }\r
10131                 break;\r
10132                 \r
10133               default:\r
10134                 sprintf(cmailMsg,\r
10135                         _("Still need to make moves for games %s\n"),\r
10136                         string);\r
10137             }\r
10138         }\r
10139     }\r
10140     return cmailMsg;\r
10141 #endif /* WIN32 */\r
10142 }\r
10143 \r
10144 void\r
10145 ResetGameEvent()\r
10146 {\r
10147     if (gameMode == Training)\r
10148       SetTrainingModeOff();\r
10149 \r
10150     Reset(TRUE, TRUE);\r
10151     cmailMsgLoaded = FALSE;\r
10152     if (appData.icsActive) {\r
10153       SendToICS(ics_prefix);\r
10154       SendToICS("refresh\n");\r
10155     }\r
10156 }\r
10157 \r
10158 void\r
10159 ExitEvent(status)\r
10160      int status;\r
10161 {\r
10162     exiting++;\r
10163     if (exiting > 2) {\r
10164       /* Give up on clean exit */\r
10165       exit(status);\r
10166     }\r
10167     if (exiting > 1) {\r
10168       /* Keep trying for clean exit */\r
10169       return;\r
10170     }\r
10171 \r
10172     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
10173 \r
10174     if (telnetISR != NULL) {\r
10175       RemoveInputSource(telnetISR);\r
10176     }\r
10177     if (icsPR != NoProc) {\r
10178       DestroyChildProcess(icsPR, TRUE);\r
10179     }\r
10180 #if 0\r
10181     /* Save game if resource set and not already saved by GameEnds() */\r
10182     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
10183                              && forwardMostMove > 0) {\r
10184       if (*appData.saveGameFile != NULLCHAR) {\r
10185         SaveGameToFile(appData.saveGameFile, TRUE);\r
10186       } else if (appData.autoSaveGames) {\r
10187         AutoSaveGame();\r
10188       }\r
10189       if (*appData.savePositionFile != NULLCHAR) {\r
10190         SavePositionToFile(appData.savePositionFile);\r
10191       }\r
10192     }\r
10193     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10194 #else\r
10195     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
10196     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);\r
10197 #endif\r
10198     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
10199     /* make sure this other one finishes before killing it!                  */\r
10200     if(endingGame) { int count = 0;\r
10201         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
10202         while(endingGame && count++ < 10) DoSleep(1);\r
10203         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
10204     }\r
10205 \r
10206     /* Kill off chess programs */\r
10207     if (first.pr != NoProc) {\r
10208         ExitAnalyzeMode();\r
10209         \r
10210         DoSleep( appData.delayBeforeQuit );\r
10211         SendToProgram("quit\n", &first);\r
10212         DoSleep( appData.delayAfterQuit );\r
10213         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
10214     }\r
10215     if (second.pr != NoProc) {\r
10216         DoSleep( appData.delayBeforeQuit );\r
10217         SendToProgram("quit\n", &second);\r
10218         DoSleep( appData.delayAfterQuit );\r
10219         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
10220     }\r
10221     if (first.isr != NULL) {\r
10222         RemoveInputSource(first.isr);\r
10223     }\r
10224     if (second.isr != NULL) {\r
10225         RemoveInputSource(second.isr);\r
10226     }\r
10227 \r
10228     ShutDownFrontEnd();\r
10229     exit(status);\r
10230 }\r
10231 \r
10232 void\r
10233 PauseEvent()\r
10234 {\r
10235     if (appData.debugMode)\r
10236         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
10237     if (pausing) {\r
10238         pausing = FALSE;\r
10239         ModeHighlight();\r
10240         if (gameMode == MachinePlaysWhite ||\r
10241             gameMode == MachinePlaysBlack) {\r
10242             StartClocks();\r
10243         } else {\r
10244             DisplayBothClocks();\r
10245         }\r
10246         if (gameMode == PlayFromGameFile) {\r
10247             if (appData.timeDelay >= 0) \r
10248                 AutoPlayGameLoop();\r
10249         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
10250             Reset(FALSE, TRUE);\r
10251             SendToICS(ics_prefix);\r
10252             SendToICS("refresh\n");\r
10253         } else if (currentMove < forwardMostMove) {\r
10254             ForwardInner(forwardMostMove);\r
10255         }\r
10256         pauseExamInvalid = FALSE;\r
10257     } else {\r
10258         switch (gameMode) {\r
10259           default:\r
10260             return;\r
10261           case IcsExamining:\r
10262             pauseExamForwardMostMove = forwardMostMove;\r
10263             pauseExamInvalid = FALSE;\r
10264             /* fall through */\r
10265           case IcsObserving:\r
10266           case IcsPlayingWhite:\r
10267           case IcsPlayingBlack:\r
10268             pausing = TRUE;\r
10269             ModeHighlight();\r
10270             return;\r
10271           case PlayFromGameFile:\r
10272             (void) StopLoadGameTimer();\r
10273             pausing = TRUE;\r
10274             ModeHighlight();\r
10275             break;\r
10276           case BeginningOfGame:\r
10277             if (appData.icsActive) return;\r
10278             /* else fall through */\r
10279           case MachinePlaysWhite:\r
10280           case MachinePlaysBlack:\r
10281           case TwoMachinesPlay:\r
10282             if (forwardMostMove == 0)\r
10283               return;           /* don't pause if no one has moved */\r
10284             if ((gameMode == MachinePlaysWhite &&\r
10285                  !WhiteOnMove(forwardMostMove)) ||\r
10286                 (gameMode == MachinePlaysBlack &&\r
10287                  WhiteOnMove(forwardMostMove))) {\r
10288                 StopClocks();\r
10289             }\r
10290             pausing = TRUE;\r
10291             ModeHighlight();\r
10292             break;\r
10293         }\r
10294     }\r
10295 }\r
10296 \r
10297 void\r
10298 EditCommentEvent()\r
10299 {\r
10300     char title[MSG_SIZ];\r
10301 \r
10302     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
10303         strcpy(title, _("Edit comment"));\r
10304     } else {\r
10305         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,\r
10306                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
10307                 parseList[currentMove - 1]);\r
10308     }\r
10309 \r
10310     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
10311 }\r
10312 \r
10313 \r
10314 void\r
10315 EditTagsEvent()\r
10316 {\r
10317     char *tags = PGNTags(&gameInfo);\r
10318     EditTagsPopUp(tags);\r
10319     free(tags);\r
10320 }\r
10321 \r
10322 void\r
10323 AnalyzeModeEvent()\r
10324 {\r
10325     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
10326       return;\r
10327 \r
10328     if (gameMode != AnalyzeFile) {\r
10329         if (!appData.icsEngineAnalyze) {\r
10330                EditGameEvent();\r
10331                if (gameMode != EditGame) return;\r
10332         }\r
10333         ResurrectChessProgram();\r
10334         SendToProgram("analyze\n", &first);\r
10335         first.analyzing = TRUE;\r
10336         /*first.maybeThinking = TRUE;*/\r
10337         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10338         AnalysisPopUp(_("Analysis"),\r
10339                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10340     }\r
10341     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;\r
10342     pausing = FALSE;\r
10343     ModeHighlight();\r
10344     SetGameInfo();\r
10345 \r
10346     StartAnalysisClock();\r
10347     GetTimeMark(&lastNodeCountTime);\r
10348     lastNodeCount = 0;\r
10349 }\r
10350 \r
10351 void\r
10352 AnalyzeFileEvent()\r
10353 {\r
10354     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
10355       return;\r
10356 \r
10357     if (gameMode != AnalyzeMode) {\r
10358         EditGameEvent();\r
10359         if (gameMode != EditGame) return;\r
10360         ResurrectChessProgram();\r
10361         SendToProgram("analyze\n", &first);\r
10362         first.analyzing = TRUE;\r
10363         /*first.maybeThinking = TRUE;*/\r
10364         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10365         AnalysisPopUp(_("Analysis"),\r
10366                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10367     }\r
10368     gameMode = AnalyzeFile;\r
10369     pausing = FALSE;\r
10370     ModeHighlight();\r
10371     SetGameInfo();\r
10372 \r
10373     StartAnalysisClock();\r
10374     GetTimeMark(&lastNodeCountTime);\r
10375     lastNodeCount = 0;\r
10376 }\r
10377 \r
10378 void\r
10379 MachineWhiteEvent()\r
10380 {\r
10381     char buf[MSG_SIZ];\r
10382     char *bookHit = NULL;\r
10383 \r
10384     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
10385       return;\r
10386 \r
10387 \r
10388     if (gameMode == PlayFromGameFile || \r
10389         gameMode == TwoMachinesPlay  || \r
10390         gameMode == Training         || \r
10391         gameMode == AnalyzeMode      || \r
10392         gameMode == EndOfGame)\r
10393         EditGameEvent();\r
10394 \r
10395     if (gameMode == EditPosition) \r
10396         EditPositionDone();\r
10397 \r
10398     if (!WhiteOnMove(currentMove)) {\r
10399         DisplayError(_("It is not White's turn"), 0);\r
10400         return;\r
10401     }\r
10402   \r
10403     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10404       ExitAnalyzeMode();\r
10405 \r
10406     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10407         gameMode == AnalyzeFile)\r
10408         TruncateGame();\r
10409 \r
10410     ResurrectChessProgram();    /* in case it isn't running */\r
10411     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */\r
10412         gameMode = MachinePlaysWhite;\r
10413         ResetClocks();\r
10414     } else\r
10415     gameMode = MachinePlaysWhite;\r
10416     pausing = FALSE;\r
10417     ModeHighlight();\r
10418     SetGameInfo();\r
10419     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10420     DisplayTitle(buf);\r
10421     if (first.sendName) {\r
10422       sprintf(buf, "name %s\n", gameInfo.black);\r
10423       SendToProgram(buf, &first);\r
10424     }\r
10425     if (first.sendTime) {\r
10426       if (first.useColors) {\r
10427         SendToProgram("black\n", &first); /*gnu kludge*/\r
10428       }\r
10429       SendTimeRemaining(&first, TRUE);\r
10430     }\r
10431     if (first.useColors) {\r
10432       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately\r
10433     }\r
10434     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10435     SetMachineThinkingEnables();\r
10436     first.maybeThinking = TRUE;\r
10437     StartClocks();\r
10438 \r
10439     if (appData.autoFlipView && !flipView) {\r
10440       flipView = !flipView;\r
10441       DrawPosition(FALSE, NULL);\r
10442       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10443     }\r
10444 \r
10445     if(bookHit) { // [HGM] book: simulate book reply\r
10446         static char bookMove[MSG_SIZ]; // a bit generous?\r
10447 \r
10448         programStats.nodes = programStats.depth = programStats.time = \r
10449         programStats.score = programStats.got_only_move = 0;\r
10450         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10451 \r
10452         strcpy(bookMove, "move ");\r
10453         strcat(bookMove, bookHit);\r
10454         HandleMachineMove(bookMove, &first);\r
10455     }\r
10456 }\r
10457 \r
10458 void\r
10459 MachineBlackEvent()\r
10460 {\r
10461     char buf[MSG_SIZ];\r
10462    char *bookHit = NULL;\r
10463 \r
10464     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
10465         return;\r
10466 \r
10467 \r
10468     if (gameMode == PlayFromGameFile || \r
10469         gameMode == TwoMachinesPlay  || \r
10470         gameMode == Training         || \r
10471         gameMode == AnalyzeMode      || \r
10472         gameMode == EndOfGame)\r
10473         EditGameEvent();\r
10474 \r
10475     if (gameMode == EditPosition) \r
10476         EditPositionDone();\r
10477 \r
10478     if (WhiteOnMove(currentMove)) {\r
10479         DisplayError(_("It is not Black's turn"), 0);\r
10480         return;\r
10481     }\r
10482     \r
10483     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10484       ExitAnalyzeMode();\r
10485 \r
10486     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10487         gameMode == AnalyzeFile)\r
10488         TruncateGame();\r
10489 \r
10490     ResurrectChessProgram();    /* in case it isn't running */\r
10491     gameMode = MachinePlaysBlack;\r
10492     pausing = FALSE;\r
10493     ModeHighlight();\r
10494     SetGameInfo();\r
10495     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10496     DisplayTitle(buf);\r
10497     if (first.sendName) {\r
10498       sprintf(buf, "name %s\n", gameInfo.white);\r
10499       SendToProgram(buf, &first);\r
10500     }\r
10501     if (first.sendTime) {\r
10502       if (first.useColors) {\r
10503         SendToProgram("white\n", &first); /*gnu kludge*/\r
10504       }\r
10505       SendTimeRemaining(&first, FALSE);\r
10506     }\r
10507     if (first.useColors) {\r
10508       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately\r
10509     }\r
10510     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10511     SetMachineThinkingEnables();\r
10512     first.maybeThinking = TRUE;\r
10513     StartClocks();\r
10514 \r
10515     if (appData.autoFlipView && flipView) {\r
10516       flipView = !flipView;\r
10517       DrawPosition(FALSE, NULL);\r
10518       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10519     }\r
10520     if(bookHit) { // [HGM] book: simulate book reply\r
10521         static char bookMove[MSG_SIZ]; // a bit generous?\r
10522 \r
10523         programStats.nodes = programStats.depth = programStats.time = \r
10524         programStats.score = programStats.got_only_move = 0;\r
10525         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10526 \r
10527         strcpy(bookMove, "move ");\r
10528         strcat(bookMove, bookHit);\r
10529         HandleMachineMove(bookMove, &first);\r
10530     }\r
10531 }\r
10532 \r
10533 \r
10534 void\r
10535 DisplayTwoMachinesTitle()\r
10536 {\r
10537     char buf[MSG_SIZ];\r
10538     if (appData.matchGames > 0) {\r
10539         if (first.twoMachinesColor[0] == 'w') {\r
10540             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10541                     gameInfo.white, gameInfo.black,\r
10542                     first.matchWins, second.matchWins,\r
10543                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10544         } else {\r
10545             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10546                     gameInfo.white, gameInfo.black,\r
10547                     second.matchWins, first.matchWins,\r
10548                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10549         }\r
10550     } else {\r
10551         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10552     }\r
10553     DisplayTitle(buf);\r
10554 }\r
10555 \r
10556 void\r
10557 TwoMachinesEvent P((void))\r
10558 {\r
10559     int i;\r
10560     char buf[MSG_SIZ];\r
10561     ChessProgramState *onmove;\r
10562     char *bookHit = NULL;\r
10563     \r
10564     if (appData.noChessProgram) return;\r
10565 \r
10566     switch (gameMode) {\r
10567       case TwoMachinesPlay:\r
10568         return;\r
10569       case MachinePlaysWhite:\r
10570       case MachinePlaysBlack:\r
10571         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10572             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
10573             return;\r
10574         }\r
10575         /* fall through */\r
10576       case BeginningOfGame:\r
10577       case PlayFromGameFile:\r
10578       case EndOfGame:\r
10579         EditGameEvent();\r
10580         if (gameMode != EditGame) return;\r
10581         break;\r
10582       case EditPosition:\r
10583         EditPositionDone();\r
10584         break;\r
10585       case AnalyzeMode:\r
10586       case AnalyzeFile:\r
10587         ExitAnalyzeMode();\r
10588         break;\r
10589       case EditGame:\r
10590       default:\r
10591         break;\r
10592     }\r
10593 \r
10594     forwardMostMove = currentMove;\r
10595     ResurrectChessProgram();    /* in case first program isn't running */\r
10596 \r
10597     if (second.pr == NULL) {\r
10598         StartChessProgram(&second);\r
10599         if (second.protocolVersion == 1) {\r
10600           TwoMachinesEventIfReady();\r
10601         } else {\r
10602           /* kludge: allow timeout for initial "feature" command */\r
10603           FreezeUI();\r
10604           DisplayMessage("", _("Starting second chess program"));\r
10605           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
10606         }\r
10607         return;\r
10608     }\r
10609     DisplayMessage("", "");\r
10610     InitChessProgram(&second, FALSE);\r
10611     SendToProgram("force\n", &second);\r
10612     if (startedFromSetupPosition) {\r
10613         SendBoard(&second, backwardMostMove);\r
10614     if (appData.debugMode) {\r
10615         fprintf(debugFP, "Two Machines\n");\r
10616     }\r
10617     }\r
10618     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
10619         SendMoveToProgram(i, &second);\r
10620     }\r
10621 \r
10622     gameMode = TwoMachinesPlay;\r
10623     pausing = FALSE;\r
10624     ModeHighlight();\r
10625     SetGameInfo();\r
10626     DisplayTwoMachinesTitle();\r
10627     firstMove = TRUE;\r
10628     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
10629         onmove = &first;\r
10630     } else {\r
10631         onmove = &second;\r
10632     }\r
10633 \r
10634     SendToProgram(first.computerString, &first);\r
10635     if (first.sendName) {\r
10636       sprintf(buf, "name %s\n", second.tidy);\r
10637       SendToProgram(buf, &first);\r
10638     }\r
10639     SendToProgram(second.computerString, &second);\r
10640     if (second.sendName) {\r
10641       sprintf(buf, "name %s\n", first.tidy);\r
10642       SendToProgram(buf, &second);\r
10643     }\r
10644 \r
10645     ResetClocks();\r
10646     if (!first.sendTime || !second.sendTime) {\r
10647         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10648         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10649     }\r
10650     if (onmove->sendTime) {\r
10651       if (onmove->useColors) {\r
10652         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
10653       }\r
10654       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
10655     }\r
10656     if (onmove->useColors) {\r
10657       SendToProgram(onmove->twoMachinesColor, onmove);\r
10658     }\r
10659     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move\r
10660 //    SendToProgram("go\n", onmove);\r
10661     onmove->maybeThinking = TRUE;\r
10662     SetMachineThinkingEnables();\r
10663 \r
10664     StartClocks();\r
10665 \r
10666     if(bookHit) { // [HGM] book: simulate book reply\r
10667         static char bookMove[MSG_SIZ]; // a bit generous?\r
10668 \r
10669         programStats.nodes = programStats.depth = programStats.time = \r
10670         programStats.score = programStats.got_only_move = 0;\r
10671         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10672 \r
10673         strcpy(bookMove, "move ");\r
10674         strcat(bookMove, bookHit);\r
10675         HandleMachineMove(bookMove, &first);\r
10676     }\r
10677 }\r
10678 \r
10679 void\r
10680 TrainingEvent()\r
10681 {\r
10682     if (gameMode == Training) {\r
10683       SetTrainingModeOff();\r
10684       gameMode = PlayFromGameFile;\r
10685       DisplayMessage("", _("Training mode off"));\r
10686     } else {\r
10687       gameMode = Training;\r
10688       animateTraining = appData.animate;\r
10689 \r
10690       /* make sure we are not already at the end of the game */\r
10691       if (currentMove < forwardMostMove) {\r
10692         SetTrainingModeOn();\r
10693         DisplayMessage("", _("Training mode on"));\r
10694       } else {\r
10695         gameMode = PlayFromGameFile;\r
10696         DisplayError(_("Already at end of game"), 0);\r
10697       }\r
10698     }\r
10699     ModeHighlight();\r
10700 }\r
10701 \r
10702 void\r
10703 IcsClientEvent()\r
10704 {\r
10705     if (!appData.icsActive) return;\r
10706     switch (gameMode) {\r
10707       case IcsPlayingWhite:\r
10708       case IcsPlayingBlack:\r
10709       case IcsObserving:\r
10710       case IcsIdle:\r
10711       case BeginningOfGame:\r
10712       case IcsExamining:\r
10713         return;\r
10714 \r
10715       case EditGame:\r
10716         break;\r
10717 \r
10718       case EditPosition:\r
10719         EditPositionDone();\r
10720         break;\r
10721 \r
10722       case AnalyzeMode:\r
10723       case AnalyzeFile:\r
10724         ExitAnalyzeMode();\r
10725         break;\r
10726         \r
10727       default:\r
10728         EditGameEvent();\r
10729         break;\r
10730     }\r
10731 \r
10732     gameMode = IcsIdle;\r
10733     ModeHighlight();\r
10734     return;\r
10735 }\r
10736 \r
10737 \r
10738 void\r
10739 EditGameEvent()\r
10740 {\r
10741     int i;\r
10742 \r
10743     switch (gameMode) {\r
10744       case Training:\r
10745         SetTrainingModeOff();\r
10746         break;\r
10747       case MachinePlaysWhite:\r
10748       case MachinePlaysBlack:\r
10749       case BeginningOfGame:\r
10750         SendToProgram("force\n", &first);\r
10751         SetUserThinkingEnables();\r
10752         break;\r
10753       case PlayFromGameFile:\r
10754         (void) StopLoadGameTimer();\r
10755         if (gameFileFP != NULL) {\r
10756             gameFileFP = NULL;\r
10757         }\r
10758         break;\r
10759       case EditPosition:\r
10760         EditPositionDone();\r
10761         break;\r
10762       case AnalyzeMode:\r
10763       case AnalyzeFile:\r
10764         ExitAnalyzeMode();\r
10765         SendToProgram("force\n", &first);\r
10766         break;\r
10767       case TwoMachinesPlay:\r
10768         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10769         ResurrectChessProgram();\r
10770         SetUserThinkingEnables();\r
10771         break;\r
10772       case EndOfGame:\r
10773         ResurrectChessProgram();\r
10774         break;\r
10775       case IcsPlayingBlack:\r
10776       case IcsPlayingWhite:\r
10777         DisplayError(_("Warning: You are still playing a game"), 0);\r
10778         break;\r
10779       case IcsObserving:\r
10780         DisplayError(_("Warning: You are still observing a game"), 0);\r
10781         break;\r
10782       case IcsExamining:\r
10783         DisplayError(_("Warning: You are still examining a game"), 0);\r
10784         break;\r
10785       case IcsIdle:\r
10786         break;\r
10787       case EditGame:\r
10788       default:\r
10789         return;\r
10790     }\r
10791     \r
10792     pausing = FALSE;\r
10793     StopClocks();\r
10794     first.offeredDraw = second.offeredDraw = 0;\r
10795 \r
10796     if (gameMode == PlayFromGameFile) {\r
10797         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10798         blackTimeRemaining = timeRemaining[1][currentMove];\r
10799         DisplayTitle("");\r
10800     }\r
10801 \r
10802     if (gameMode == MachinePlaysWhite ||\r
10803         gameMode == MachinePlaysBlack ||\r
10804         gameMode == TwoMachinesPlay ||\r
10805         gameMode == EndOfGame) {\r
10806         i = forwardMostMove;\r
10807         while (i > currentMove) {\r
10808             SendToProgram("undo\n", &first);\r
10809             i--;\r
10810         }\r
10811         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10812         blackTimeRemaining = timeRemaining[1][currentMove];\r
10813         DisplayBothClocks();\r
10814         if (whiteFlag || blackFlag) {\r
10815             whiteFlag = blackFlag = 0;\r
10816         }\r
10817         DisplayTitle("");\r
10818     }           \r
10819     \r
10820     gameMode = EditGame;\r
10821     ModeHighlight();\r
10822     SetGameInfo();\r
10823 }\r
10824 \r
10825 \r
10826 void\r
10827 EditPositionEvent()\r
10828 {\r
10829     if (gameMode == EditPosition) {\r
10830         EditGameEvent();\r
10831         return;\r
10832     }\r
10833     \r
10834     EditGameEvent();\r
10835     if (gameMode != EditGame) return;\r
10836     \r
10837     gameMode = EditPosition;\r
10838     ModeHighlight();\r
10839     SetGameInfo();\r
10840     if (currentMove > 0)\r
10841       CopyBoard(boards[0], boards[currentMove]);\r
10842     \r
10843     blackPlaysFirst = !WhiteOnMove(currentMove);\r
10844     ResetClocks();\r
10845     currentMove = forwardMostMove = backwardMostMove = 0;\r
10846     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10847     DisplayMove(-1);\r
10848 }\r
10849 \r
10850 void\r
10851 ExitAnalyzeMode()\r
10852 {\r
10853     /* [DM] icsEngineAnalyze - possible call from other functions */\r
10854     if (appData.icsEngineAnalyze) {\r
10855         appData.icsEngineAnalyze = FALSE;\r
10856 \r
10857         DisplayMessage("",_("Close ICS engine analyze..."));\r
10858     }\r
10859     if (first.analysisSupport && first.analyzing) {\r
10860       SendToProgram("exit\n", &first);\r
10861       first.analyzing = FALSE;\r
10862     }\r
10863     AnalysisPopDown();\r
10864     thinkOutput[0] = NULLCHAR;\r
10865 }\r
10866 \r
10867 void\r
10868 EditPositionDone()\r
10869 {\r
10870     startedFromSetupPosition = TRUE;\r
10871     InitChessProgram(&first, FALSE);\r
10872     SendToProgram("force\n", &first);\r
10873     if (blackPlaysFirst) {\r
10874         strcpy(moveList[0], "");\r
10875         strcpy(parseList[0], "");\r
10876         currentMove = forwardMostMove = backwardMostMove = 1;\r
10877         CopyBoard(boards[1], boards[0]);\r
10878         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */\r
10879         { int i;\r
10880           epStatus[1] = epStatus[0];\r
10881           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];\r
10882         }\r
10883     } else {\r
10884         currentMove = forwardMostMove = backwardMostMove = 0;\r
10885     }\r
10886     SendBoard(&first, forwardMostMove);\r
10887     if (appData.debugMode) {\r
10888         fprintf(debugFP, "EditPosDone\n");\r
10889     }\r
10890     DisplayTitle("");\r
10891     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10892     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10893     gameMode = EditGame;\r
10894     ModeHighlight();\r
10895     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10896     ClearHighlights(); /* [AS] */\r
10897 }\r
10898 \r
10899 /* Pause for `ms' milliseconds */\r
10900 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10901 void\r
10902 TimeDelay(ms)\r
10903      long ms;\r
10904 {\r
10905     TimeMark m1, m2;\r
10906 \r
10907     GetTimeMark(&m1);\r
10908     do {\r
10909         GetTimeMark(&m2);\r
10910     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
10911 }\r
10912 \r
10913 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10914 void\r
10915 SendMultiLineToICS(buf)\r
10916      char *buf;\r
10917 {\r
10918     char temp[MSG_SIZ+1], *p;\r
10919     int len;\r
10920 \r
10921     len = strlen(buf);\r
10922     if (len > MSG_SIZ)\r
10923       len = MSG_SIZ;\r
10924   \r
10925     strncpy(temp, buf, len);\r
10926     temp[len] = 0;\r
10927 \r
10928     p = temp;\r
10929     while (*p) {\r
10930         if (*p == '\n' || *p == '\r')\r
10931           *p = ' ';\r
10932         ++p;\r
10933     }\r
10934 \r
10935     strcat(temp, "\n");\r
10936     SendToICS(temp);\r
10937     SendToPlayer(temp, strlen(temp));\r
10938 }\r
10939 \r
10940 void\r
10941 SetWhiteToPlayEvent()\r
10942 {\r
10943     if (gameMode == EditPosition) {\r
10944         blackPlaysFirst = FALSE;\r
10945         DisplayBothClocks();    /* works because currentMove is 0 */\r
10946     } else if (gameMode == IcsExamining) {\r
10947         SendToICS(ics_prefix);\r
10948         SendToICS("tomove white\n");\r
10949     }\r
10950 }\r
10951 \r
10952 void\r
10953 SetBlackToPlayEvent()\r
10954 {\r
10955     if (gameMode == EditPosition) {\r
10956         blackPlaysFirst = TRUE;\r
10957         currentMove = 1;        /* kludge */\r
10958         DisplayBothClocks();\r
10959         currentMove = 0;\r
10960     } else if (gameMode == IcsExamining) {\r
10961         SendToICS(ics_prefix);\r
10962         SendToICS("tomove black\n");\r
10963     }\r
10964 }\r
10965 \r
10966 void\r
10967 EditPositionMenuEvent(selection, x, y)\r
10968      ChessSquare selection;\r
10969      int x, y;\r
10970 {\r
10971     char buf[MSG_SIZ];\r
10972     ChessSquare piece = boards[0][y][x];\r
10973 \r
10974     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
10975 \r
10976     switch (selection) {\r
10977       case ClearBoard:\r
10978         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
10979             SendToICS(ics_prefix);\r
10980             SendToICS("bsetup clear\n");\r
10981         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
10982             SendToICS(ics_prefix);\r
10983             SendToICS("clearboard\n");\r
10984         } else {\r
10985             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;\r
10986                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */\r
10987                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
10988                     if (gameMode == IcsExamining) {\r
10989                         if (boards[currentMove][y][x] != EmptySquare) {\r
10990                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
10991                                     AAA + x, ONE + y);\r
10992                             SendToICS(buf);\r
10993                         }\r
10994                     } else {\r
10995                         boards[0][y][x] = p;\r
10996                     }\r
10997                 }\r
10998             }\r
10999         }\r
11000         if (gameMode == EditPosition) {\r
11001             DrawPosition(FALSE, boards[0]);\r
11002         }\r
11003         break;\r
11004 \r
11005       case WhitePlay:\r
11006         SetWhiteToPlayEvent();\r
11007         break;\r
11008 \r
11009       case BlackPlay:\r
11010         SetBlackToPlayEvent();\r
11011         break;\r
11012 \r
11013       case EmptySquare:\r
11014         if (gameMode == IcsExamining) {\r
11015             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
11016             SendToICS(buf);\r
11017         } else {\r
11018             boards[0][y][x] = EmptySquare;\r
11019             DrawPosition(FALSE, boards[0]);\r
11020         }\r
11021         break;\r
11022 \r
11023       case PromotePiece:\r
11024         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
11025            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
11026             selection = (ChessSquare) (PROMOTED piece);\r
11027         } else if(piece == EmptySquare) selection = WhiteSilver;\r
11028         else selection = (ChessSquare)((int)piece - 1);\r
11029         goto defaultlabel;\r
11030 \r
11031       case DemotePiece:\r
11032         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
11033            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
11034             selection = (ChessSquare) (DEMOTED piece);\r
11035         } else if(piece == EmptySquare) selection = BlackSilver;\r
11036         else selection = (ChessSquare)((int)piece + 1);       \r
11037         goto defaultlabel;\r
11038 \r
11039       case WhiteQueen:\r
11040       case BlackQueen:\r
11041         if(gameInfo.variant == VariantShatranj ||\r
11042            gameInfo.variant == VariantXiangqi  ||\r
11043            gameInfo.variant == VariantCourier    )\r
11044             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
11045         goto defaultlabel;\r
11046 \r
11047       case WhiteKing:\r
11048       case BlackKing:\r
11049         if(gameInfo.variant == VariantXiangqi)\r
11050             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
11051         if(gameInfo.variant == VariantKnightmate)\r
11052             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
11053       default:\r
11054         defaultlabel:\r
11055         if (gameMode == IcsExamining) {\r
11056             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
11057                     PieceToChar(selection), AAA + x, ONE + y);\r
11058             SendToICS(buf);\r
11059         } else {\r
11060             boards[0][y][x] = selection;\r
11061             DrawPosition(FALSE, boards[0]);\r
11062         }\r
11063         break;\r
11064     }\r
11065 }\r
11066 \r
11067 \r
11068 void\r
11069 DropMenuEvent(selection, x, y)\r
11070      ChessSquare selection;\r
11071      int x, y;\r
11072 {\r
11073     ChessMove moveType;\r
11074 \r
11075     switch (gameMode) {\r
11076       case IcsPlayingWhite:\r
11077       case MachinePlaysBlack:\r
11078         if (!WhiteOnMove(currentMove)) {\r
11079             DisplayMoveError(_("It is Black's turn"));\r
11080             return;\r
11081         }\r
11082         moveType = WhiteDrop;\r
11083         break;\r
11084       case IcsPlayingBlack:\r
11085       case MachinePlaysWhite:\r
11086         if (WhiteOnMove(currentMove)) {\r
11087             DisplayMoveError(_("It is White's turn"));\r
11088             return;\r
11089         }\r
11090         moveType = BlackDrop;\r
11091         break;\r
11092       case EditGame:\r
11093         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
11094         break;\r
11095       default:\r
11096         return;\r
11097     }\r
11098 \r
11099     if (moveType == BlackDrop && selection < BlackPawn) {\r
11100       selection = (ChessSquare) ((int) selection\r
11101                                  + (int) BlackPawn - (int) WhitePawn);\r
11102     }\r
11103     if (boards[currentMove][y][x] != EmptySquare) {\r
11104         DisplayMoveError(_("That square is occupied"));\r
11105         return;\r
11106     }\r
11107 \r
11108     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
11109 }\r
11110 \r
11111 void\r
11112 AcceptEvent()\r
11113 {\r
11114     /* Accept a pending offer of any kind from opponent */\r
11115     \r
11116     if (appData.icsActive) {\r
11117         SendToICS(ics_prefix);\r
11118         SendToICS("accept\n");\r
11119     } else if (cmailMsgLoaded) {\r
11120         if (currentMove == cmailOldMove &&\r
11121             commentList[cmailOldMove] != NULL &&\r
11122             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11123                    "Black offers a draw" : "White offers a draw")) {\r
11124             TruncateGame();\r
11125             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11126             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11127         } else {\r
11128             DisplayError(_("There is no pending offer on this move"), 0);\r
11129             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11130         }\r
11131     } else {\r
11132         /* Not used for offers from chess program */\r
11133     }\r
11134 }\r
11135 \r
11136 void\r
11137 DeclineEvent()\r
11138 {\r
11139     /* Decline a pending offer of any kind from opponent */\r
11140     \r
11141     if (appData.icsActive) {\r
11142         SendToICS(ics_prefix);\r
11143         SendToICS("decline\n");\r
11144     } else if (cmailMsgLoaded) {\r
11145         if (currentMove == cmailOldMove &&\r
11146             commentList[cmailOldMove] != NULL &&\r
11147             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11148                    "Black offers a draw" : "White offers a draw")) {\r
11149 #ifdef NOTDEF\r
11150             AppendComment(cmailOldMove, "Draw declined");\r
11151             DisplayComment(cmailOldMove - 1, "Draw declined");\r
11152 #endif /*NOTDEF*/\r
11153         } else {\r
11154             DisplayError(_("There is no pending offer on this move"), 0);\r
11155         }\r
11156     } else {\r
11157         /* Not used for offers from chess program */\r
11158     }\r
11159 }\r
11160 \r
11161 void\r
11162 RematchEvent()\r
11163 {\r
11164     /* Issue ICS rematch command */\r
11165     if (appData.icsActive) {\r
11166         SendToICS(ics_prefix);\r
11167         SendToICS("rematch\n");\r
11168     }\r
11169 }\r
11170 \r
11171 void\r
11172 CallFlagEvent()\r
11173 {\r
11174     /* Call your opponent's flag (claim a win on time) */\r
11175     if (appData.icsActive) {\r
11176         SendToICS(ics_prefix);\r
11177         SendToICS("flag\n");\r
11178     } else {\r
11179         switch (gameMode) {\r
11180           default:\r
11181             return;\r
11182           case MachinePlaysWhite:\r
11183             if (whiteFlag) {\r
11184                 if (blackFlag)\r
11185                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11186                            GE_PLAYER);\r
11187                 else\r
11188                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
11189             } else {\r
11190                 DisplayError(_("Your opponent is not out of time"), 0);\r
11191             }\r
11192             break;\r
11193           case MachinePlaysBlack:\r
11194             if (blackFlag) {\r
11195                 if (whiteFlag)\r
11196                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11197                            GE_PLAYER);\r
11198                 else\r
11199                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
11200             } else {\r
11201                 DisplayError(_("Your opponent is not out of time"), 0);\r
11202             }\r
11203             break;\r
11204         }\r
11205     }\r
11206 }\r
11207 \r
11208 void\r
11209 DrawEvent()\r
11210 {\r
11211     /* Offer draw or accept pending draw offer from opponent */\r
11212     \r
11213     if (appData.icsActive) {\r
11214         /* Note: tournament rules require draw offers to be\r
11215            made after you make your move but before you punch\r
11216            your clock.  Currently ICS doesn't let you do that;\r
11217            instead, you immediately punch your clock after making\r
11218            a move, but you can offer a draw at any time. */\r
11219         \r
11220         SendToICS(ics_prefix);\r
11221         SendToICS("draw\n");\r
11222     } else if (cmailMsgLoaded) {\r
11223         if (currentMove == cmailOldMove &&\r
11224             commentList[cmailOldMove] != NULL &&\r
11225             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11226                    "Black offers a draw" : "White offers a draw")) {\r
11227             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11228             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11229         } else if (currentMove == cmailOldMove + 1) {\r
11230             char *offer = WhiteOnMove(cmailOldMove) ?\r
11231               "White offers a draw" : "Black offers a draw";\r
11232             AppendComment(currentMove, offer);\r
11233             DisplayComment(currentMove - 1, offer);\r
11234             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
11235         } else {\r
11236             DisplayError(_("You must make your move before offering a draw"), 0);\r
11237             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11238         }\r
11239     } else if (first.offeredDraw) {\r
11240         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
11241     } else {\r
11242         if (first.sendDrawOffers) {\r
11243             SendToProgram("draw\n", &first);\r
11244             userOfferedDraw = TRUE;\r
11245         }\r
11246     }\r
11247 }\r
11248 \r
11249 void\r
11250 AdjournEvent()\r
11251 {\r
11252     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
11253     \r
11254     if (appData.icsActive) {\r
11255         SendToICS(ics_prefix);\r
11256         SendToICS("adjourn\n");\r
11257     } else {\r
11258         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
11259     }\r
11260 }\r
11261 \r
11262 \r
11263 void\r
11264 AbortEvent()\r
11265 {\r
11266     /* Offer Abort or accept pending Abort offer from opponent */\r
11267     \r
11268     if (appData.icsActive) {\r
11269         SendToICS(ics_prefix);\r
11270         SendToICS("abort\n");\r
11271     } else {\r
11272         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
11273     }\r
11274 }\r
11275 \r
11276 void\r
11277 ResignEvent()\r
11278 {\r
11279     /* Resign.  You can do this even if it's not your turn. */\r
11280     \r
11281     if (appData.icsActive) {\r
11282         SendToICS(ics_prefix);\r
11283         SendToICS("resign\n");\r
11284     } else {\r
11285         switch (gameMode) {\r
11286           case MachinePlaysWhite:\r
11287             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11288             break;\r
11289           case MachinePlaysBlack:\r
11290             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11291             break;\r
11292           case EditGame:\r
11293             if (cmailMsgLoaded) {\r
11294                 TruncateGame();\r
11295                 if (WhiteOnMove(cmailOldMove)) {\r
11296                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11297                 } else {\r
11298                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11299                 }\r
11300                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
11301             }\r
11302             break;\r
11303           default:\r
11304             break;\r
11305         }\r
11306     }\r
11307 }\r
11308 \r
11309 \r
11310 void\r
11311 StopObservingEvent()\r
11312 {\r
11313     /* Stop observing current games */\r
11314     SendToICS(ics_prefix);\r
11315     SendToICS("unobserve\n");\r
11316 }\r
11317 \r
11318 void\r
11319 StopExaminingEvent()\r
11320 {\r
11321     /* Stop observing current game */\r
11322     SendToICS(ics_prefix);\r
11323     SendToICS("unexamine\n");\r
11324 }\r
11325 \r
11326 void\r
11327 ForwardInner(target)\r
11328      int target;\r
11329 {\r
11330     int limit;\r
11331 \r
11332     if (appData.debugMode)\r
11333         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
11334                 target, currentMove, forwardMostMove);\r
11335 \r
11336     if (gameMode == EditPosition)\r
11337       return;\r
11338 \r
11339     if (gameMode == PlayFromGameFile && !pausing)\r
11340       PauseEvent();\r
11341     \r
11342     if (gameMode == IcsExamining && pausing)\r
11343       limit = pauseExamForwardMostMove;\r
11344     else\r
11345       limit = forwardMostMove;\r
11346     \r
11347     if (target > limit) target = limit;\r
11348 \r
11349     if (target > 0 && moveList[target - 1][0]) {\r
11350         int fromX, fromY, toX, toY;\r
11351         toX = moveList[target - 1][2] - AAA;\r
11352         toY = moveList[target - 1][3] - ONE;\r
11353         if (moveList[target - 1][1] == '@') {\r
11354             if (appData.highlightLastMove) {\r
11355                 SetHighlights(-1, -1, toX, toY);\r
11356             }\r
11357         } else {\r
11358             fromX = moveList[target - 1][0] - AAA;\r
11359             fromY = moveList[target - 1][1] - ONE;\r
11360             if (target == currentMove + 1) {\r
11361                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
11362             }\r
11363             if (appData.highlightLastMove) {\r
11364                 SetHighlights(fromX, fromY, toX, toY);\r
11365             }\r
11366         }\r
11367     }\r
11368     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
11369         gameMode == Training || gameMode == PlayFromGameFile || \r
11370         gameMode == AnalyzeFile) {\r
11371         while (currentMove < target) {\r
11372             SendMoveToProgram(currentMove++, &first);\r
11373         }\r
11374     } else {\r
11375         currentMove = target;\r
11376     }\r
11377     \r
11378     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11379         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11380         blackTimeRemaining = timeRemaining[1][currentMove];\r
11381     }\r
11382     DisplayBothClocks();\r
11383     DisplayMove(currentMove - 1);\r
11384     DrawPosition(FALSE, boards[currentMove]);\r
11385     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11386     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
11387         DisplayComment(currentMove - 1, commentList[currentMove]);\r
11388     }\r
11389 }\r
11390 \r
11391 \r
11392 void\r
11393 ForwardEvent()\r
11394 {\r
11395     if (gameMode == IcsExamining && !pausing) {\r
11396         SendToICS(ics_prefix);\r
11397         SendToICS("forward\n");\r
11398     } else {\r
11399         ForwardInner(currentMove + 1);\r
11400     }\r
11401 }\r
11402 \r
11403 void\r
11404 ToEndEvent()\r
11405 {\r
11406     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11407         /* to optimze, we temporarily turn off analysis mode while we feed\r
11408          * the remaining moves to the engine. Otherwise we get analysis output\r
11409          * after each move.\r
11410          */ \r
11411         if (first.analysisSupport) {\r
11412           SendToProgram("exit\nforce\n", &first);\r
11413           first.analyzing = FALSE;\r
11414         }\r
11415     }\r
11416         \r
11417     if (gameMode == IcsExamining && !pausing) {\r
11418         SendToICS(ics_prefix);\r
11419         SendToICS("forward 999999\n");\r
11420     } else {\r
11421         ForwardInner(forwardMostMove);\r
11422     }\r
11423 \r
11424     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11425         /* we have fed all the moves, so reactivate analysis mode */\r
11426         SendToProgram("analyze\n", &first);\r
11427         first.analyzing = TRUE;\r
11428         /*first.maybeThinking = TRUE;*/\r
11429         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11430     }\r
11431 }\r
11432 \r
11433 void\r
11434 BackwardInner(target)\r
11435      int target;\r
11436 {\r
11437     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
11438 \r
11439     if (appData.debugMode)\r
11440         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
11441                 target, currentMove, forwardMostMove);\r
11442 \r
11443     if (gameMode == EditPosition) return;\r
11444     if (currentMove <= backwardMostMove) {\r
11445         ClearHighlights();\r
11446         DrawPosition(full_redraw, boards[currentMove]);\r
11447         return;\r
11448     }\r
11449     if (gameMode == PlayFromGameFile && !pausing)\r
11450       PauseEvent();\r
11451     \r
11452     if (moveList[target][0]) {\r
11453         int fromX, fromY, toX, toY;\r
11454         toX = moveList[target][2] - AAA;\r
11455         toY = moveList[target][3] - ONE;\r
11456         if (moveList[target][1] == '@') {\r
11457             if (appData.highlightLastMove) {\r
11458                 SetHighlights(-1, -1, toX, toY);\r
11459             }\r
11460         } else {\r
11461             fromX = moveList[target][0] - AAA;\r
11462             fromY = moveList[target][1] - ONE;\r
11463             if (target == currentMove - 1) {\r
11464                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
11465             }\r
11466             if (appData.highlightLastMove) {\r
11467                 SetHighlights(fromX, fromY, toX, toY);\r
11468             }\r
11469         }\r
11470     }\r
11471     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
11472         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
11473         while (currentMove > target) {\r
11474             SendToProgram("undo\n", &first);\r
11475             currentMove--;\r
11476         }\r
11477     } else {\r
11478         currentMove = target;\r
11479     }\r
11480     \r
11481     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11482         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11483         blackTimeRemaining = timeRemaining[1][currentMove];\r
11484     }\r
11485     DisplayBothClocks();\r
11486     DisplayMove(currentMove - 1);\r
11487     DrawPosition(full_redraw, boards[currentMove]);\r
11488     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11489     // [HGM] PV info: routine tests if comment empty\r
11490     DisplayComment(currentMove - 1, commentList[currentMove]);\r
11491 }\r
11492 \r
11493 void\r
11494 BackwardEvent()\r
11495 {\r
11496     if (gameMode == IcsExamining && !pausing) {\r
11497         SendToICS(ics_prefix);\r
11498         SendToICS("backward\n");\r
11499     } else {\r
11500         BackwardInner(currentMove - 1);\r
11501     }\r
11502 }\r
11503 \r
11504 void\r
11505 ToStartEvent()\r
11506 {\r
11507     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11508         /* to optimze, we temporarily turn off analysis mode while we undo\r
11509          * all the moves. Otherwise we get analysis output after each undo.\r
11510          */ \r
11511         if (first.analysisSupport) {\r
11512           SendToProgram("exit\nforce\n", &first);\r
11513           first.analyzing = FALSE;\r
11514         }\r
11515     }\r
11516 \r
11517     if (gameMode == IcsExamining && !pausing) {\r
11518         SendToICS(ics_prefix);\r
11519         SendToICS("backward 999999\n");\r
11520     } else {\r
11521         BackwardInner(backwardMostMove);\r
11522     }\r
11523 \r
11524     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11525         /* we have fed all the moves, so reactivate analysis mode */\r
11526         SendToProgram("analyze\n", &first);\r
11527         first.analyzing = TRUE;\r
11528         /*first.maybeThinking = TRUE;*/\r
11529         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11530     }\r
11531 }\r
11532 \r
11533 void\r
11534 ToNrEvent(int to)\r
11535 {\r
11536   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
11537   if (to >= forwardMostMove) to = forwardMostMove;\r
11538   if (to <= backwardMostMove) to = backwardMostMove;\r
11539   if (to < currentMove) {\r
11540     BackwardInner(to);\r
11541   } else {\r
11542     ForwardInner(to);\r
11543   }\r
11544 }\r
11545 \r
11546 void\r
11547 RevertEvent()\r
11548 {\r
11549     if (gameMode != IcsExamining) {\r
11550         DisplayError(_("You are not examining a game"), 0);\r
11551         return;\r
11552     }\r
11553     if (pausing) {\r
11554         DisplayError(_("You can't revert while pausing"), 0);\r
11555         return;\r
11556     }\r
11557     SendToICS(ics_prefix);\r
11558     SendToICS("revert\n");\r
11559 }\r
11560 \r
11561 void\r
11562 RetractMoveEvent()\r
11563 {\r
11564     switch (gameMode) {\r
11565       case MachinePlaysWhite:\r
11566       case MachinePlaysBlack:\r
11567         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
11568             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
11569             return;\r
11570         }\r
11571         if (forwardMostMove < 2) return;\r
11572         currentMove = forwardMostMove = forwardMostMove - 2;\r
11573         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11574         blackTimeRemaining = timeRemaining[1][currentMove];\r
11575         DisplayBothClocks();\r
11576         DisplayMove(currentMove - 1);\r
11577         ClearHighlights();/*!! could figure this out*/\r
11578         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
11579         SendToProgram("remove\n", &first);\r
11580         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
11581         break;\r
11582 \r
11583       case BeginningOfGame:\r
11584       default:\r
11585         break;\r
11586 \r
11587       case IcsPlayingWhite:\r
11588       case IcsPlayingBlack:\r
11589         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
11590             SendToICS(ics_prefix);\r
11591             SendToICS("takeback 2\n");\r
11592         } else {\r
11593             SendToICS(ics_prefix);\r
11594             SendToICS("takeback 1\n");\r
11595         }\r
11596         break;\r
11597     }\r
11598 }\r
11599 \r
11600 void\r
11601 MoveNowEvent()\r
11602 {\r
11603     ChessProgramState *cps;\r
11604 \r
11605     switch (gameMode) {\r
11606       case MachinePlaysWhite:\r
11607         if (!WhiteOnMove(forwardMostMove)) {\r
11608             DisplayError(_("It is your turn"), 0);\r
11609             return;\r
11610         }\r
11611         cps = &first;\r
11612         break;\r
11613       case MachinePlaysBlack:\r
11614         if (WhiteOnMove(forwardMostMove)) {\r
11615             DisplayError(_("It is your turn"), 0);\r
11616             return;\r
11617         }\r
11618         cps = &first;\r
11619         break;\r
11620       case TwoMachinesPlay:\r
11621         if (WhiteOnMove(forwardMostMove) ==\r
11622             (first.twoMachinesColor[0] == 'w')) {\r
11623             cps = &first;\r
11624         } else {\r
11625             cps = &second;\r
11626         }\r
11627         break;\r
11628       case BeginningOfGame:\r
11629       default:\r
11630         return;\r
11631     }\r
11632     SendToProgram("?\n", cps);\r
11633 }\r
11634 \r
11635 void\r
11636 TruncateGameEvent()\r
11637 {\r
11638     EditGameEvent();\r
11639     if (gameMode != EditGame) return;\r
11640     TruncateGame();\r
11641 }\r
11642 \r
11643 void\r
11644 TruncateGame()\r
11645 {\r
11646     if (forwardMostMove > currentMove) {\r
11647         if (gameInfo.resultDetails != NULL) {\r
11648             free(gameInfo.resultDetails);\r
11649             gameInfo.resultDetails = NULL;\r
11650             gameInfo.result = GameUnfinished;\r
11651         }\r
11652         forwardMostMove = currentMove;\r
11653         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
11654                    currentMove-1);\r
11655     }\r
11656 }\r
11657 \r
11658 void\r
11659 HintEvent()\r
11660 {\r
11661     if (appData.noChessProgram) return;\r
11662     switch (gameMode) {\r
11663       case MachinePlaysWhite:\r
11664         if (WhiteOnMove(forwardMostMove)) {\r
11665             DisplayError(_("Wait until your turn"), 0);\r
11666             return;\r
11667         }\r
11668         break;\r
11669       case BeginningOfGame:\r
11670       case MachinePlaysBlack:\r
11671         if (!WhiteOnMove(forwardMostMove)) {\r
11672             DisplayError(_("Wait until your turn"), 0);\r
11673             return;\r
11674         }\r
11675         break;\r
11676       default:\r
11677         DisplayError(_("No hint available"), 0);\r
11678         return;\r
11679     }\r
11680     SendToProgram("hint\n", &first);\r
11681     hintRequested = TRUE;\r
11682 }\r
11683 \r
11684 void\r
11685 BookEvent()\r
11686 {\r
11687     if (appData.noChessProgram) return;\r
11688     switch (gameMode) {\r
11689       case MachinePlaysWhite:\r
11690         if (WhiteOnMove(forwardMostMove)) {\r
11691             DisplayError(_("Wait until your turn"), 0);\r
11692             return;\r
11693         }\r
11694         break;\r
11695       case BeginningOfGame:\r
11696       case MachinePlaysBlack:\r
11697         if (!WhiteOnMove(forwardMostMove)) {\r
11698             DisplayError(_("Wait until your turn"), 0);\r
11699             return;\r
11700         }\r
11701         break;\r
11702       case EditPosition:\r
11703         EditPositionDone();\r
11704         break;\r
11705       case TwoMachinesPlay:\r
11706         return;\r
11707       default:\r
11708         break;\r
11709     }\r
11710     SendToProgram("bk\n", &first);\r
11711     bookOutput[0] = NULLCHAR;\r
11712     bookRequested = TRUE;\r
11713 }\r
11714 \r
11715 void\r
11716 AboutGameEvent()\r
11717 {\r
11718     char *tags = PGNTags(&gameInfo);\r
11719     TagsPopUp(tags, CmailMsg());\r
11720     free(tags);\r
11721 }\r
11722 \r
11723 /* end button procedures */\r
11724 \r
11725 void\r
11726 PrintPosition(fp, move)\r
11727      FILE *fp;\r
11728      int move;\r
11729 {\r
11730     int i, j;\r
11731     \r
11732     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11733         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11734             char c = PieceToChar(boards[move][i][j]);\r
11735             fputc(c == 'x' ? '.' : c, fp);\r
11736             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
11737         }\r
11738     }\r
11739     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
11740       fprintf(fp, "white to play\n");\r
11741     else\r
11742       fprintf(fp, "black to play\n");\r
11743 }\r
11744 \r
11745 void\r
11746 PrintOpponents(fp)\r
11747      FILE *fp;\r
11748 {\r
11749     if (gameInfo.white != NULL) {\r
11750         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
11751     } else {\r
11752         fprintf(fp, "\n");\r
11753     }\r
11754 }\r
11755 \r
11756 /* Find last component of program's own name, using some heuristics */\r
11757 void\r
11758 TidyProgramName(prog, host, buf)\r
11759      char *prog, *host, buf[MSG_SIZ];\r
11760 {\r
11761     char *p, *q;\r
11762     int local = (strcmp(host, "localhost") == 0);\r
11763     while (!local && (p = strchr(prog, ';')) != NULL) {\r
11764         p++;\r
11765         while (*p == ' ') p++;\r
11766         prog = p;\r
11767     }\r
11768     if (*prog == '"' || *prog == '\'') {\r
11769         q = strchr(prog + 1, *prog);\r
11770     } else {\r
11771         q = strchr(prog, ' ');\r
11772     }\r
11773     if (q == NULL) q = prog + strlen(prog);\r
11774     p = q;\r
11775     while (p >= prog && *p != '/' && *p != '\\') p--;\r
11776     p++;\r
11777     if(p == prog && *p == '"') p++;\r
11778     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
11779     memcpy(buf, p, q - p);\r
11780     buf[q - p] = NULLCHAR;\r
11781     if (!local) {\r
11782         strcat(buf, "@");\r
11783         strcat(buf, host);\r
11784     }\r
11785 }\r
11786 \r
11787 char *\r
11788 TimeControlTagValue()\r
11789 {\r
11790     char buf[MSG_SIZ];\r
11791     if (!appData.clockMode) {\r
11792         strcpy(buf, "-");\r
11793     } else if (movesPerSession > 0) {\r
11794         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
11795     } else if (timeIncrement == 0) {\r
11796         sprintf(buf, "%ld", timeControl/1000);\r
11797     } else {\r
11798         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
11799     }\r
11800     return StrSave(buf);\r
11801 }\r
11802 \r
11803 void\r
11804 SetGameInfo()\r
11805 {\r
11806     /* This routine is used only for certain modes */\r
11807     VariantClass v = gameInfo.variant;\r
11808     ClearGameInfo(&gameInfo);\r
11809     gameInfo.variant = v;\r
11810 \r
11811     switch (gameMode) {\r
11812       case MachinePlaysWhite:\r
11813         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11814         gameInfo.site = StrSave(HostName());\r
11815         gameInfo.date = PGNDate();\r
11816         gameInfo.round = StrSave("-");\r
11817         gameInfo.white = StrSave(first.tidy);\r
11818         gameInfo.black = StrSave(UserName());\r
11819         gameInfo.timeControl = TimeControlTagValue();\r
11820         break;\r
11821 \r
11822       case MachinePlaysBlack:\r
11823         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11824         gameInfo.site = StrSave(HostName());\r
11825         gameInfo.date = PGNDate();\r
11826         gameInfo.round = StrSave("-");\r
11827         gameInfo.white = StrSave(UserName());\r
11828         gameInfo.black = StrSave(first.tidy);\r
11829         gameInfo.timeControl = TimeControlTagValue();\r
11830         break;\r
11831 \r
11832       case TwoMachinesPlay:\r
11833         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11834         gameInfo.site = StrSave(HostName());\r
11835         gameInfo.date = PGNDate();\r
11836         if (matchGame > 0) {\r
11837             char buf[MSG_SIZ];\r
11838             sprintf(buf, "%d", matchGame);\r
11839             gameInfo.round = StrSave(buf);\r
11840         } else {\r
11841             gameInfo.round = StrSave("-");\r
11842         }\r
11843         if (first.twoMachinesColor[0] == 'w') {\r
11844             gameInfo.white = StrSave(first.tidy);\r
11845             gameInfo.black = StrSave(second.tidy);\r
11846         } else {\r
11847             gameInfo.white = StrSave(second.tidy);\r
11848             gameInfo.black = StrSave(first.tidy);\r
11849         }\r
11850         gameInfo.timeControl = TimeControlTagValue();\r
11851         break;\r
11852 \r
11853       case EditGame:\r
11854         gameInfo.event = StrSave("Edited game");\r
11855         gameInfo.site = StrSave(HostName());\r
11856         gameInfo.date = PGNDate();\r
11857         gameInfo.round = StrSave("-");\r
11858         gameInfo.white = StrSave("-");\r
11859         gameInfo.black = StrSave("-");\r
11860         break;\r
11861 \r
11862       case EditPosition:\r
11863         gameInfo.event = StrSave("Edited position");\r
11864         gameInfo.site = StrSave(HostName());\r
11865         gameInfo.date = PGNDate();\r
11866         gameInfo.round = StrSave("-");\r
11867         gameInfo.white = StrSave("-");\r
11868         gameInfo.black = StrSave("-");\r
11869         break;\r
11870 \r
11871       case IcsPlayingWhite:\r
11872       case IcsPlayingBlack:\r
11873       case IcsObserving:\r
11874       case IcsExamining:\r
11875         break;\r
11876 \r
11877       case PlayFromGameFile:\r
11878         gameInfo.event = StrSave("Game from non-PGN file");\r
11879         gameInfo.site = StrSave(HostName());\r
11880         gameInfo.date = PGNDate();\r
11881         gameInfo.round = StrSave("-");\r
11882         gameInfo.white = StrSave("?");\r
11883         gameInfo.black = StrSave("?");\r
11884         break;\r
11885 \r
11886       default:\r
11887         break;\r
11888     }\r
11889 }\r
11890 \r
11891 void\r
11892 ReplaceComment(index, text)\r
11893      int index;\r
11894      char *text;\r
11895 {\r
11896     int len;\r
11897 \r
11898     while (*text == '\n') text++;\r
11899     len = strlen(text);\r
11900     while (len > 0 && text[len - 1] == '\n') len--;\r
11901 \r
11902     if (commentList[index] != NULL)\r
11903       free(commentList[index]);\r
11904 \r
11905     if (len == 0) {\r
11906         commentList[index] = NULL;\r
11907         return;\r
11908     }\r
11909     commentList[index] = (char *) malloc(len + 2);\r
11910     strncpy(commentList[index], text, len);\r
11911     commentList[index][len] = '\n';\r
11912     commentList[index][len + 1] = NULLCHAR;\r
11913 }\r
11914 \r
11915 void\r
11916 CrushCRs(text)\r
11917      char *text;\r
11918 {\r
11919   char *p = text;\r
11920   char *q = text;\r
11921   char ch;\r
11922 \r
11923   do {\r
11924     ch = *p++;\r
11925     if (ch == '\r') continue;\r
11926     *q++ = ch;\r
11927   } while (ch != '\0');\r
11928 }\r
11929 \r
11930 void\r
11931 AppendComment(index, text)\r
11932      int index;\r
11933      char *text;\r
11934 {\r
11935     int oldlen, len;\r
11936     char *old;\r
11937 \r
11938     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11939 \r
11940     CrushCRs(text);\r
11941     while (*text == '\n') text++;\r
11942     len = strlen(text);\r
11943     while (len > 0 && text[len - 1] == '\n') len--;\r
11944 \r
11945     if (len == 0) return;\r
11946 \r
11947     if (commentList[index] != NULL) {\r
11948         old = commentList[index];\r
11949         oldlen = strlen(old);\r
11950         commentList[index] = (char *) malloc(oldlen + len + 2);\r
11951         strcpy(commentList[index], old);\r
11952         free(old);\r
11953         strncpy(&commentList[index][oldlen], text, len);\r
11954         commentList[index][oldlen + len] = '\n';\r
11955         commentList[index][oldlen + len + 1] = NULLCHAR;\r
11956     } else {\r
11957         commentList[index] = (char *) malloc(len + 2);\r
11958         strncpy(commentList[index], text, len);\r
11959         commentList[index][len] = '\n';\r
11960         commentList[index][len + 1] = NULLCHAR;\r
11961     }\r
11962 }\r
11963 \r
11964 static char * FindStr( char * text, char * sub_text )\r
11965 {\r
11966     char * result = strstr( text, sub_text );\r
11967 \r
11968     if( result != NULL ) {\r
11969         result += strlen( sub_text );\r
11970     }\r
11971 \r
11972     return result;\r
11973 }\r
11974 \r
11975 /* [AS] Try to extract PV info from PGN comment */\r
11976 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
11977 char *GetInfoFromComment( int index, char * text )\r
11978 {\r
11979     char * sep = text;\r
11980 \r
11981     if( text != NULL && index > 0 ) {\r
11982         int score = 0;\r
11983         int depth = 0;\r
11984         int time = -1, sec = 0, deci;\r
11985         char * s_eval = FindStr( text, "[%eval " );\r
11986         char * s_emt = FindStr( text, "[%emt " );\r
11987 \r
11988         if( s_eval != NULL || s_emt != NULL ) {\r
11989             /* New style */\r
11990             char delim;\r
11991 \r
11992             if( s_eval != NULL ) {\r
11993                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
11994                     return text;\r
11995                 }\r
11996 \r
11997                 if( delim != ']' ) {\r
11998                     return text;\r
11999                 }\r
12000             }\r
12001 \r
12002             if( s_emt != NULL ) {\r
12003             }\r
12004         }\r
12005         else {\r
12006             /* We expect something like: [+|-]nnn.nn/dd */\r
12007             int score_lo = 0;\r
12008 \r
12009             sep = strchr( text, '/' );\r
12010             if( sep == NULL || sep < (text+4) ) {\r
12011                 return text;\r
12012             }\r
12013 \r
12014             time = -1; sec = -1; deci = -1;\r
12015             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
12016                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&\r
12017                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
12018                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
12019                 return text;\r
12020             }\r
12021 \r
12022             if( score_lo < 0 || score_lo >= 100 ) {\r
12023                 return text;\r
12024             }\r
12025 \r
12026             if(sec >= 0) time = 600*time + 10*sec; else\r
12027             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec\r
12028 \r
12029             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
12030 \r
12031             /* [HGM] PV time: now locate end of PV info */\r
12032             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
12033             if(time >= 0)\r
12034             while( *++sep >= '0' && *sep <= '9'); // strip time\r
12035             if(sec >= 0)\r
12036             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
12037             if(deci >= 0)\r
12038             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds\r
12039             while(*sep == ' ') sep++;\r
12040         }\r
12041 \r
12042         if( depth <= 0 ) {\r
12043             return text;\r
12044         }\r
12045 \r
12046         if( time < 0 ) {\r
12047             time = -1;\r
12048         }\r
12049 \r
12050         pvInfoList[index-1].depth = depth;\r
12051         pvInfoList[index-1].score = score;\r
12052         pvInfoList[index-1].time  = 10*time; // centi-sec\r
12053     }\r
12054     return sep;\r
12055 }\r
12056 \r
12057 void\r
12058 SendToProgram(message, cps)\r
12059      char *message;\r
12060      ChessProgramState *cps;\r
12061 {\r
12062     int count, outCount, error;\r
12063     char buf[MSG_SIZ];\r
12064 \r
12065     if (cps->pr == NULL) return;\r
12066     Attention(cps);\r
12067     \r
12068     if (appData.debugMode) {\r
12069         TimeMark now;\r
12070         GetTimeMark(&now);\r
12071         fprintf(debugFP, "%ld >%-6s: %s", \r
12072                 SubtractTimeMarks(&now, &programStartTime),\r
12073                 cps->which, message);\r
12074     }\r
12075     \r
12076     count = strlen(message);\r
12077     outCount = OutputToProcess(cps->pr, message, count, &error);\r
12078     if (outCount < count && !exiting \r
12079                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
12080         sprintf(buf, _("Error writing to %s chess program"), cps->which);\r
12081         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12082             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12083                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12084                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
12085             } else {\r
12086                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12087             }\r
12088             gameInfo.resultDetails = buf;\r
12089         }\r
12090         DisplayFatalError(buf, error, 1);\r
12091     }\r
12092 }\r
12093 \r
12094 void\r
12095 ReceiveFromProgram(isr, closure, message, count, error)\r
12096      InputSourceRef isr;\r
12097      VOIDSTAR closure;\r
12098      char *message;\r
12099      int count;\r
12100      int error;\r
12101 {\r
12102     char *end_str;\r
12103     char buf[MSG_SIZ];\r
12104     ChessProgramState *cps = (ChessProgramState *)closure;\r
12105 \r
12106     if (isr != cps->isr) return; /* Killed intentionally */\r
12107     if (count <= 0) {\r
12108         if (count == 0) {\r
12109             sprintf(buf,\r
12110                     _("Error: %s chess program (%s) exited unexpectedly"),\r
12111                     cps->which, cps->program);\r
12112         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12113                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12114                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12115                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);\r
12116                 } else {\r
12117                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12118                 }\r
12119                 gameInfo.resultDetails = buf;\r
12120             }\r
12121             RemoveInputSource(cps->isr);\r
12122             DisplayFatalError(buf, 0, 1);\r
12123         } else {\r
12124             sprintf(buf,\r
12125                     _("Error reading from %s chess program (%s)"),\r
12126                     cps->which, cps->program);\r
12127             RemoveInputSource(cps->isr);\r
12128 \r
12129             /* [AS] Program is misbehaving badly... kill it */\r
12130             if( count == -2 ) {\r
12131                 DestroyChildProcess( cps->pr, 9 );\r
12132                 cps->pr = NoProc;\r
12133             }\r
12134 \r
12135             DisplayFatalError(buf, error, 1);\r
12136         }\r
12137         return;\r
12138     }\r
12139     \r
12140     if ((end_str = strchr(message, '\r')) != NULL)\r
12141       *end_str = NULLCHAR;\r
12142     if ((end_str = strchr(message, '\n')) != NULL)\r
12143       *end_str = NULLCHAR;\r
12144     \r
12145     if (appData.debugMode) {\r
12146         TimeMark now; int print = 1;\r
12147         char *quote = ""; char c; int i;\r
12148 \r
12149         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
12150                 char start = message[0];\r
12151                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
12152                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
12153                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&\r
12154                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
12155                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
12156                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&\r
12157                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
12158                         { quote = "# "; print = (appData.engineComments == 2); }\r
12159                 message[0] = start; // restore original message\r
12160         }\r
12161         if(print) {\r
12162                 GetTimeMark(&now);\r
12163                 fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
12164                         SubtractTimeMarks(&now, &programStartTime), cps->which, \r
12165                         quote,\r
12166                         message);\r
12167         }\r
12168     }\r
12169 \r
12170     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */\r
12171     if (appData.icsEngineAnalyze) {\r
12172         if (strstr(message, "whisper") != NULL ||\r
12173              strstr(message, "kibitz") != NULL || \r
12174             strstr(message, "tellics") != NULL) return;\r
12175     }\r
12176 \r
12177     HandleMachineMove(message, cps);\r
12178 }\r
12179 \r
12180 \r
12181 void\r
12182 SendTimeControl(cps, mps, tc, inc, sd, st)\r
12183      ChessProgramState *cps;\r
12184      int mps, inc, sd, st;\r
12185      long tc;\r
12186 {\r
12187     char buf[MSG_SIZ];\r
12188     int seconds;\r
12189 \r
12190     if( timeControl_2 > 0 ) {\r
12191         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
12192             tc = timeControl_2;\r
12193         }\r
12194     }\r
12195     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
12196     inc /= cps->timeOdds;\r
12197     st  /= cps->timeOdds;\r
12198 \r
12199     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
12200 \r
12201     if (st > 0) {\r
12202       /* Set exact time per move, normally using st command */\r
12203       if (cps->stKludge) {\r
12204         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
12205         seconds = st % 60;\r
12206         if (seconds == 0) {\r
12207           sprintf(buf, "level 1 %d\n", st/60);\r
12208         } else {\r
12209           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
12210         }\r
12211       } else {\r
12212         sprintf(buf, "st %d\n", st);\r
12213       }\r
12214     } else {\r
12215       /* Set conventional or incremental time control, using level command */\r
12216       if (seconds == 0) {\r
12217         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
12218            Fixed in later versions, but still avoid :seconds\r
12219            when seconds is 0. */\r
12220         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
12221       } else {\r
12222         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
12223                 seconds, inc/1000);\r
12224       }\r
12225     }\r
12226     SendToProgram(buf, cps);\r
12227 \r
12228     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
12229     /* Orthogonally, limit search to given depth */\r
12230     if (sd > 0) {\r
12231       if (cps->sdKludge) {\r
12232         sprintf(buf, "depth\n%d\n", sd);\r
12233       } else {\r
12234         sprintf(buf, "sd %d\n", sd);\r
12235       }\r
12236       SendToProgram(buf, cps);\r
12237     }\r
12238 \r
12239     if(cps->nps > 0) { /* [HGM] nps */\r
12240         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
12241         else {\r
12242                 sprintf(buf, "nps %d\n", cps->nps);\r
12243               SendToProgram(buf, cps);\r
12244         }\r
12245     }\r
12246 }\r
12247 \r
12248 ChessProgramState *WhitePlayer()\r
12249 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
12250 {\r
12251     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || \r
12252        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)\r
12253         return &second;\r
12254     return &first;\r
12255 }\r
12256 \r
12257 void\r
12258 SendTimeRemaining(cps, machineWhite)\r
12259      ChessProgramState *cps;\r
12260      int /*boolean*/ machineWhite;\r
12261 {\r
12262     char message[MSG_SIZ];\r
12263     long time, otime;\r
12264 \r
12265     /* Note: this routine must be called when the clocks are stopped\r
12266        or when they have *just* been set or switched; otherwise\r
12267        it will be off by the time since the current tick started.\r
12268     */\r
12269     if (machineWhite) {\r
12270         time = whiteTimeRemaining / 10;\r
12271         otime = blackTimeRemaining / 10;\r
12272     } else {\r
12273         time = blackTimeRemaining / 10;\r
12274         otime = whiteTimeRemaining / 10;\r
12275     }\r
12276     /* [HGM] translate opponent's time by time-odds factor */\r
12277     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
12278     if (appData.debugMode) {\r
12279         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
12280     }\r
12281 \r
12282     if (time <= 0) time = 1;\r
12283     if (otime <= 0) otime = 1;\r
12284     \r
12285     sprintf(message, "time %ld\n", time);\r
12286     SendToProgram(message, cps);\r
12287 \r
12288     sprintf(message, "otim %ld\n", otime);\r
12289     SendToProgram(message, cps);\r
12290 }\r
12291 \r
12292 int\r
12293 BoolFeature(p, name, loc, cps)\r
12294      char **p;\r
12295      char *name;\r
12296      int *loc;\r
12297      ChessProgramState *cps;\r
12298 {\r
12299   char buf[MSG_SIZ];\r
12300   int len = strlen(name);\r
12301   int val;\r
12302   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12303     (*p) += len + 1;\r
12304     sscanf(*p, "%d", &val);\r
12305     *loc = (val != 0);\r
12306     while (**p && **p != ' ') (*p)++;\r
12307     sprintf(buf, "accepted %s\n", name);\r
12308     SendToProgram(buf, cps);\r
12309     return TRUE;\r
12310   }\r
12311   return FALSE;\r
12312 }\r
12313 \r
12314 int\r
12315 IntFeature(p, name, loc, cps)\r
12316      char **p;\r
12317      char *name;\r
12318      int *loc;\r
12319      ChessProgramState *cps;\r
12320 {\r
12321   char buf[MSG_SIZ];\r
12322   int len = strlen(name);\r
12323   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12324     (*p) += len + 1;\r
12325     sscanf(*p, "%d", loc);\r
12326     while (**p && **p != ' ') (*p)++;\r
12327     sprintf(buf, "accepted %s\n", name);\r
12328     SendToProgram(buf, cps);\r
12329     return TRUE;\r
12330   }\r
12331   return FALSE;\r
12332 }\r
12333 \r
12334 int\r
12335 StringFeature(p, name, loc, cps)\r
12336      char **p;\r
12337      char *name;\r
12338      char loc[];\r
12339      ChessProgramState *cps;\r
12340 {\r
12341   char buf[MSG_SIZ];\r
12342   int len = strlen(name);\r
12343   if (strncmp((*p), name, len) == 0\r
12344       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
12345     (*p) += len + 2;\r
12346     sscanf(*p, "%[^\"]", loc);\r
12347     while (**p && **p != '\"') (*p)++;\r
12348     if (**p == '\"') (*p)++;\r
12349     sprintf(buf, "accepted %s\n", name);\r
12350     SendToProgram(buf, cps);\r
12351     return TRUE;\r
12352   }\r
12353   return FALSE;\r
12354 }\r
12355 \r
12356 int \r
12357 ParseOption(Option *opt, ChessProgramState *cps)\r
12358 // [HGM] options: process the string that defines an engine option, and determine\r
12359 // name, type, default value, and allowed value range\r
12360 {\r
12361         char *p, *q, buf[MSG_SIZ];\r
12362         int n, min = (-1)<<31, max = 1<<31, def;\r
12363 \r
12364         if(p = strstr(opt->name, " -spin ")) {\r
12365             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;\r
12366             if(max < min) max = min; // enforce consistency\r
12367             if(def < min) def = min;\r
12368             if(def > max) def = max;\r
12369             opt->value = def;\r
12370             opt->min = min;\r
12371             opt->max = max;\r
12372             opt->type = Spin;\r
12373         } else if(p = strstr(opt->name, " -string ")) {\r
12374             opt->textValue = p+9;\r
12375             opt->type = TextBox;\r
12376         } else if(p = strstr(opt->name, " -check ")) {\r
12377             if(sscanf(p, " -check %d", &def) < 1) return FALSE;\r
12378             opt->value = (def != 0);\r
12379             opt->type = CheckBox;\r
12380         } else if(p = strstr(opt->name, " -combo ")) {\r
12381             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type\r
12382             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices\r
12383             opt->value = n = 0;\r
12384             while(q = StrStr(q, " /// ")) {\r
12385                 n++; *q = 0;    // count choices, and null-terminate each of them\r
12386                 q += 5;\r
12387                 if(*q == '*') { // remember default, which is marked with * prefix\r
12388                     q++;\r
12389                     opt->value = n;\r
12390                 }\r
12391                 cps->comboList[cps->comboCnt++] = q;\r
12392             }\r
12393             cps->comboList[cps->comboCnt++] = NULL;\r
12394             opt->max = n + 1;\r
12395             opt->type = ComboBox;\r
12396         } else if(p = strstr(opt->name, " -button")) {\r
12397             opt->type = Button;\r
12398         } else if(p = strstr(opt->name, " -save")) {\r
12399             opt->type = SaveButton;\r
12400         } else return FALSE;\r
12401         *p = 0; // terminate option name\r
12402         // now look if the command-line options define a setting for this engine option.\r
12403         p = strstr(cps->optionSettings, opt->name);\r
12404         if(p == cps->optionSettings || p[-1] == ',') {\r
12405                 sprintf(buf, "option %s", p);\r
12406                 if(p = strstr(buf, ",")) *p = 0;\r
12407                 strcat(buf, "\n");\r
12408                 SendToProgram(buf, cps);\r
12409         }\r
12410         return TRUE;\r
12411 }\r
12412 \r
12413 void\r
12414 FeatureDone(cps, val)\r
12415      ChessProgramState* cps;\r
12416      int val;\r
12417 {\r
12418   DelayedEventCallback cb = GetDelayedEvent();\r
12419   if ((cb == InitBackEnd3 && cps == &first) ||\r
12420       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
12421     CancelDelayedEvent();\r
12422     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
12423   }\r
12424   cps->initDone = val;\r
12425 }\r
12426 \r
12427 /* Parse feature command from engine */\r
12428 void\r
12429 ParseFeatures(args, cps)\r
12430      char* args;\r
12431      ChessProgramState *cps;  \r
12432 {\r
12433   char *p = args;\r
12434   char *q;\r
12435   int val;\r
12436   char buf[MSG_SIZ];\r
12437 \r
12438   for (;;) {\r
12439     while (*p == ' ') p++;\r
12440     if (*p == NULLCHAR) return;\r
12441 \r
12442     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
12443     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
12444     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
12445     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
12446     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
12447     if (BoolFeature(&p, "reuse", &val, cps)) {\r
12448       /* Engine can disable reuse, but can't enable it if user said no */\r
12449       if (!val) cps->reuse = FALSE;\r
12450       continue;\r
12451     }\r
12452     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
12453     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
12454       if (gameMode == TwoMachinesPlay) {\r
12455         DisplayTwoMachinesTitle();\r
12456       } else {\r
12457         DisplayTitle("");\r
12458       }\r
12459       continue;\r
12460     }\r
12461     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
12462     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
12463     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
12464     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
12465     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
12466     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
12467     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
12468     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
12469     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
12470     if (IntFeature(&p, "done", &val, cps)) {\r
12471       FeatureDone(cps, val);\r
12472       continue;\r
12473     }\r
12474     /* Added by Tord: */\r
12475     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
12476     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
12477     /* End of additions by Tord */\r
12478 \r
12479     /* [HGM] added features: */\r
12480     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
12481     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
12482     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
12483     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
12484     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12485     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
12486     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {\r
12487         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature\r
12488         if(cps->nrOptions >= MAX_OPTIONS) {\r
12489             cps->nrOptions--;\r
12490             sprintf(buf, "%s engine has too many options\n", cps->which);\r
12491             DisplayError(buf, 0);\r
12492         }\r
12493         continue;\r
12494     }\r
12495     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12496     /* End of additions by HGM */\r
12497 \r
12498     /* unknown feature: complain and skip */\r
12499     q = p;\r
12500     while (*q && *q != '=') q++;\r
12501     sprintf(buf, "rejected %.*s\n", q-p, p);\r
12502     SendToProgram(buf, cps);\r
12503     p = q;\r
12504     if (*p == '=') {\r
12505       p++;\r
12506       if (*p == '\"') {\r
12507         p++;\r
12508         while (*p && *p != '\"') p++;\r
12509         if (*p == '\"') p++;\r
12510       } else {\r
12511         while (*p && *p != ' ') p++;\r
12512       }\r
12513     }\r
12514   }\r
12515 \r
12516 }\r
12517 \r
12518 void\r
12519 PeriodicUpdatesEvent(newState)\r
12520      int newState;\r
12521 {\r
12522     if (newState == appData.periodicUpdates)\r
12523       return;\r
12524 \r
12525     appData.periodicUpdates=newState;\r
12526 \r
12527     /* Display type changes, so update it now */\r
12528     DisplayAnalysis();\r
12529 \r
12530     /* Get the ball rolling again... */\r
12531     if (newState) {\r
12532         AnalysisPeriodicEvent(1);\r
12533         StartAnalysisClock();\r
12534     }\r
12535 }\r
12536 \r
12537 void\r
12538 PonderNextMoveEvent(newState)\r
12539      int newState;\r
12540 {\r
12541     if (newState == appData.ponderNextMove) return;\r
12542     if (gameMode == EditPosition) EditPositionDone();\r
12543     if (newState) {\r
12544         SendToProgram("hard\n", &first);\r
12545         if (gameMode == TwoMachinesPlay) {\r
12546             SendToProgram("hard\n", &second);\r
12547         }\r
12548     } else {\r
12549         SendToProgram("easy\n", &first);\r
12550         thinkOutput[0] = NULLCHAR;\r
12551         if (gameMode == TwoMachinesPlay) {\r
12552             SendToProgram("easy\n", &second);\r
12553         }\r
12554     }\r
12555     appData.ponderNextMove = newState;\r
12556 }\r
12557 \r
12558 void\r
12559 NewSettingEvent(option, command, value)\r
12560      char *command;\r
12561      int option, value;\r
12562 {\r
12563     char buf[MSG_SIZ];\r
12564 \r
12565     if (gameMode == EditPosition) EditPositionDone();\r
12566     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);\r
12567     SendToProgram(buf, &first);\r
12568     if (gameMode == TwoMachinesPlay) {\r
12569         SendToProgram(buf, &second);\r
12570     }\r
12571 }\r
12572 \r
12573 void\r
12574 ShowThinkingEvent()\r
12575 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup\r
12576 {\r
12577     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated\r
12578     int newState = appData.showThinking\r
12579         // [HGM] thinking: other features now need thinking output as well\r
12580         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();\r
12581     \r
12582     if (oldState == newState) return;\r
12583     oldState = newState;\r
12584     if (gameMode == EditPosition) EditPositionDone();\r
12585     if (oldState) {\r
12586         SendToProgram("post\n", &first);\r
12587         if (gameMode == TwoMachinesPlay) {\r
12588             SendToProgram("post\n", &second);\r
12589         }\r
12590     } else {\r
12591         SendToProgram("nopost\n", &first);\r
12592         thinkOutput[0] = NULLCHAR;\r
12593         if (gameMode == TwoMachinesPlay) {\r
12594             SendToProgram("nopost\n", &second);\r
12595         }\r
12596     }\r
12597 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!\r
12598 }\r
12599 \r
12600 void\r
12601 AskQuestionEvent(title, question, replyPrefix, which)\r
12602      char *title; char *question; char *replyPrefix; char *which;\r
12603 {\r
12604   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
12605   if (pr == NoProc) return;\r
12606   AskQuestion(title, question, replyPrefix, pr);\r
12607 }\r
12608 \r
12609 void\r
12610 DisplayMove(moveNumber)\r
12611      int moveNumber;\r
12612 {\r
12613     char message[MSG_SIZ];\r
12614     char res[MSG_SIZ];\r
12615     char cpThinkOutput[MSG_SIZ];\r
12616 \r
12617     if(appData.noGUI) return; // [HGM] fast: suppress display of moves\r
12618     \r
12619     if (moveNumber == forwardMostMove - 1 || \r
12620         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12621 \r
12622         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
12623 \r
12624         if (strchr(cpThinkOutput, '\n')) {\r
12625             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
12626         }\r
12627     } else {\r
12628         *cpThinkOutput = NULLCHAR;\r
12629     }\r
12630 \r
12631     /* [AS] Hide thinking from human user */\r
12632     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
12633         *cpThinkOutput = NULLCHAR;\r
12634         if( thinkOutput[0] != NULLCHAR ) {\r
12635             int i;\r
12636 \r
12637             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
12638                 cpThinkOutput[i] = '.';\r
12639             }\r
12640             cpThinkOutput[i] = NULLCHAR;\r
12641             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
12642         }\r
12643     }\r
12644 \r
12645     if (moveNumber == forwardMostMove - 1 &&\r
12646         gameInfo.resultDetails != NULL) {\r
12647         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
12648             sprintf(res, " %s", PGNResult(gameInfo.result));\r
12649         } else {\r
12650             sprintf(res, " {%s} %s",\r
12651                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
12652         }\r
12653     } else {\r
12654         res[0] = NULLCHAR;\r
12655     }\r
12656 \r
12657     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12658         DisplayMessage(res, cpThinkOutput);\r
12659     } else {\r
12660         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
12661                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
12662                 parseList[moveNumber], res);\r
12663         DisplayMessage(message, cpThinkOutput);\r
12664     }\r
12665 }\r
12666 \r
12667 void\r
12668 DisplayAnalysisText(text)\r
12669      char *text;\r
12670 {\r
12671     char buf[MSG_SIZ];\r
12672 \r
12673     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile \r
12674                || appData.icsEngineAnalyze) {\r
12675         sprintf(buf, "Analysis (%s)", first.tidy);\r
12676         AnalysisPopUp(buf, text);\r
12677     }\r
12678 }\r
12679 \r
12680 static int\r
12681 only_one_move(str)\r
12682      char *str;\r
12683 {\r
12684     while (*str && isspace(*str)) ++str;\r
12685     while (*str && !isspace(*str)) ++str;\r
12686     if (!*str) return 1;\r
12687     while (*str && isspace(*str)) ++str;\r
12688     if (!*str) return 1;\r
12689     return 0;\r
12690 }\r
12691 \r
12692 void\r
12693 DisplayAnalysis()\r
12694 {\r
12695     char buf[MSG_SIZ];\r
12696     char lst[MSG_SIZ / 2];\r
12697     double nps;\r
12698     static char *xtra[] = { "", " (--)", " (++)" };\r
12699     int h, m, s, cs;\r
12700   \r
12701     if (programStats.time == 0) {\r
12702         programStats.time = 1;\r
12703     }\r
12704   \r
12705     if (programStats.got_only_move) {\r
12706         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
12707     } else {\r
12708         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
12709 \r
12710         nps = (u64ToDouble(programStats.nodes) /\r
12711              ((double)programStats.time /100.0));\r
12712 \r
12713         cs = programStats.time % 100;\r
12714         s = programStats.time / 100;\r
12715         h = (s / (60*60));\r
12716         s = s - h*60*60;\r
12717         m = (s/60);\r
12718         s = s - m*60;\r
12719 \r
12720         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
12721           if (programStats.move_name[0] != NULLCHAR) {\r
12722             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12723                     programStats.depth,\r
12724                     programStats.nr_moves-programStats.moves_left,\r
12725                     programStats.nr_moves, programStats.move_name,\r
12726                     ((float)programStats.score)/100.0, lst,\r
12727                     only_one_move(lst)?\r
12728                     xtra[programStats.got_fail] : "",\r
12729                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12730           } else {\r
12731             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12732                     programStats.depth,\r
12733                     programStats.nr_moves-programStats.moves_left,\r
12734                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
12735                     lst,\r
12736                     only_one_move(lst)?\r
12737                     xtra[programStats.got_fail] : "",\r
12738                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12739           }\r
12740         } else {\r
12741             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12742                     programStats.depth,\r
12743                     ((float)programStats.score)/100.0,\r
12744                     lst,\r
12745                     only_one_move(lst)?\r
12746                     xtra[programStats.got_fail] : "",\r
12747                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12748         }\r
12749     }\r
12750     DisplayAnalysisText(buf);\r
12751 }\r
12752 \r
12753 void\r
12754 DisplayComment(moveNumber, text)\r
12755      int moveNumber;\r
12756      char *text;\r
12757 {\r
12758     char title[MSG_SIZ];\r
12759     char buf[8000]; // comment can be long!\r
12760     int score, depth;\r
12761 \r
12762     if( appData.autoDisplayComment ) {\r
12763         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12764             strcpy(title, "Comment");\r
12765         } else {\r
12766             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
12767                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
12768                     parseList[moveNumber]);\r
12769         }\r
12770     } else title[0] = 0;\r
12771 \r
12772     // [HGM] PV info: display PV info together with (or as) comment\r
12773     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
12774         if(text == NULL) text = "";                                           \r
12775         score = pvInfoList[moveNumber].score;\r
12776         sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
12777                               depth, (pvInfoList[moveNumber].time+50)/100, text);\r
12778         CommentPopUp(title, buf);\r
12779     } else\r
12780     if (text != NULL)\r
12781         CommentPopUp(title, text);\r
12782 }\r
12783 \r
12784 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
12785  * might be busy thinking or pondering.  It can be omitted if your\r
12786  * gnuchess is configured to stop thinking immediately on any user\r
12787  * input.  However, that gnuchess feature depends on the FIONREAD\r
12788  * ioctl, which does not work properly on some flavors of Unix.\r
12789  */\r
12790 void\r
12791 Attention(cps)\r
12792      ChessProgramState *cps;\r
12793 {\r
12794 #if ATTENTION\r
12795     if (!cps->useSigint) return;\r
12796     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
12797     switch (gameMode) {\r
12798       case MachinePlaysWhite:\r
12799       case MachinePlaysBlack:\r
12800       case TwoMachinesPlay:\r
12801       case IcsPlayingWhite:\r
12802       case IcsPlayingBlack:\r
12803       case AnalyzeMode:\r
12804       case AnalyzeFile:\r
12805         /* Skip if we know it isn't thinking */\r
12806         if (!cps->maybeThinking) return;\r
12807         if (appData.debugMode)\r
12808           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
12809         InterruptChildProcess(cps->pr);\r
12810         cps->maybeThinking = FALSE;\r
12811         break;\r
12812       default:\r
12813         break;\r
12814     }\r
12815 #endif /*ATTENTION*/\r
12816 }\r
12817 \r
12818 int\r
12819 CheckFlags()\r
12820 {\r
12821     if (whiteTimeRemaining <= 0) {\r
12822         if (!whiteFlag) {\r
12823             whiteFlag = TRUE;\r
12824             if (appData.icsActive) {\r
12825                 if (appData.autoCallFlag &&\r
12826                     gameMode == IcsPlayingBlack && !blackFlag) {\r
12827                   SendToICS(ics_prefix);\r
12828                   SendToICS("flag\n");\r
12829                 }\r
12830             } else {\r
12831                 if (blackFlag) {\r
12832                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12833                 } else {\r
12834                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));\r
12835                     if (appData.autoCallFlag) {\r
12836                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
12837                         return TRUE;\r
12838                     }\r
12839                 }\r
12840             }\r
12841         }\r
12842     }\r
12843     if (blackTimeRemaining <= 0) {\r
12844         if (!blackFlag) {\r
12845             blackFlag = TRUE;\r
12846             if (appData.icsActive) {\r
12847                 if (appData.autoCallFlag &&\r
12848                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
12849                   SendToICS(ics_prefix);\r
12850                   SendToICS("flag\n");\r
12851                 }\r
12852             } else {\r
12853                 if (whiteFlag) {\r
12854                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12855                 } else {\r
12856                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));\r
12857                     if (appData.autoCallFlag) {\r
12858                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
12859                         return TRUE;\r
12860                     }\r
12861                 }\r
12862             }\r
12863         }\r
12864     }\r
12865     return FALSE;\r
12866 }\r
12867 \r
12868 void\r
12869 CheckTimeControl()\r
12870 {\r
12871     if (!appData.clockMode || appData.icsActive ||\r
12872         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
12873 \r
12874     /*\r
12875      * add time to clocks when time control is achieved ([HGM] now also used for increment)\r
12876      */\r
12877     if ( !WhiteOnMove(forwardMostMove) )\r
12878         /* White made time control */\r
12879         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12880         /* [HGM] time odds: correct new time quota for time odds! */\r
12881                                             / WhitePlayer()->timeOdds;\r
12882       else\r
12883         /* Black made time control */\r
12884         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12885                                             / WhitePlayer()->other->timeOdds;\r
12886 }\r
12887 \r
12888 void\r
12889 DisplayBothClocks()\r
12890 {\r
12891     int wom = gameMode == EditPosition ?\r
12892       !blackPlaysFirst : WhiteOnMove(currentMove);\r
12893     DisplayWhiteClock(whiteTimeRemaining, wom);\r
12894     DisplayBlackClock(blackTimeRemaining, !wom);\r
12895 }\r
12896 \r
12897 \r
12898 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
12899    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
12900    to use other calls if you don't.  Clocks will be less accurate if\r
12901    you have neither ftime nor gettimeofday.\r
12902 */\r
12903 \r
12904 /* VS 2008 requires the #include outside of the function */\r
12905 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME\r
12906 #include <sys/timeb.h>\r
12907 #endif\r
12908 \r
12909 /* Get the current time as a TimeMark */\r
12910 void\r
12911 GetTimeMark(tm)\r
12912      TimeMark *tm;\r
12913 {\r
12914 #if HAVE_GETTIMEOFDAY\r
12915 \r
12916     struct timeval timeVal;\r
12917     struct timezone timeZone;\r
12918 \r
12919     gettimeofday(&timeVal, &timeZone);\r
12920     tm->sec = (long) timeVal.tv_sec; \r
12921     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
12922 \r
12923 #else /*!HAVE_GETTIMEOFDAY*/\r
12924 #if HAVE_FTIME\r
12925 \r
12926 // include <sys/timeb.h> / moved to just above start of function\r
12927     struct timeb timeB;\r
12928 \r
12929     ftime(&timeB);\r
12930     tm->sec = (long) timeB.time;\r
12931     tm->ms = (int) timeB.millitm;\r
12932 \r
12933 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
12934     tm->sec = (long) time(NULL);\r
12935     tm->ms = 0;\r
12936 #endif\r
12937 #endif\r
12938 }\r
12939 \r
12940 /* Return the difference in milliseconds between two\r
12941    time marks.  We assume the difference will fit in a long!\r
12942 */\r
12943 long\r
12944 SubtractTimeMarks(tm2, tm1)\r
12945      TimeMark *tm2, *tm1;\r
12946 {\r
12947     return 1000L*(tm2->sec - tm1->sec) +\r
12948            (long) (tm2->ms - tm1->ms);\r
12949 }\r
12950 \r
12951 \r
12952 /*\r
12953  * Code to manage the game clocks.\r
12954  *\r
12955  * In tournament play, black starts the clock and then white makes a move.\r
12956  * We give the human user a slight advantage if he is playing white---the\r
12957  * clocks don't run until he makes his first move, so it takes zero time.\r
12958  * Also, we don't account for network lag, so we could get out of sync\r
12959  * with GNU Chess's clock -- but then, referees are always right.  \r
12960  */\r
12961 \r
12962 static TimeMark tickStartTM;\r
12963 static long intendedTickLength;\r
12964 \r
12965 long\r
12966 NextTickLength(timeRemaining)\r
12967      long timeRemaining;\r
12968 {\r
12969     long nominalTickLength, nextTickLength;\r
12970 \r
12971     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
12972       nominalTickLength = 100L;\r
12973     else\r
12974       nominalTickLength = 1000L;\r
12975     nextTickLength = timeRemaining % nominalTickLength;\r
12976     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
12977 \r
12978     return nextTickLength;\r
12979 }\r
12980 \r
12981 /* Adjust clock one minute up or down */\r
12982 void\r
12983 AdjustClock(Boolean which, int dir)\r
12984 {\r
12985     if(which) blackTimeRemaining += 60000*dir;\r
12986     else      whiteTimeRemaining += 60000*dir;\r
12987     DisplayBothClocks();\r
12988 }\r
12989 \r
12990 /* Stop clocks and reset to a fresh time control */\r
12991 void\r
12992 ResetClocks() \r
12993 {\r
12994     (void) StopClockTimer();\r
12995     if (appData.icsActive) {\r
12996         whiteTimeRemaining = blackTimeRemaining = 0;\r
12997     } else { /* [HGM] correct new time quote for time odds */\r
12998         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
12999         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
13000     }\r
13001     if (whiteFlag || blackFlag) {\r
13002         DisplayTitle("");\r
13003         whiteFlag = blackFlag = FALSE;\r
13004     }\r
13005     DisplayBothClocks();\r
13006 }\r
13007 \r
13008 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
13009 \r
13010 /* Decrement running clock by amount of time that has passed */\r
13011 void\r
13012 DecrementClocks()\r
13013 {\r
13014     long timeRemaining;\r
13015     long lastTickLength, fudge;\r
13016     TimeMark now;\r
13017 \r
13018     if (!appData.clockMode) return;\r
13019     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
13020         \r
13021     GetTimeMark(&now);\r
13022 \r
13023     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13024 \r
13025     /* Fudge if we woke up a little too soon */\r
13026     fudge = intendedTickLength - lastTickLength;\r
13027     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
13028 \r
13029     if (WhiteOnMove(forwardMostMove)) {\r
13030         if(whiteNPS >= 0) lastTickLength = 0;\r
13031         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
13032         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
13033                           WhiteOnMove(currentMove));\r
13034     } else {\r
13035         if(blackNPS >= 0) lastTickLength = 0;\r
13036         timeRemaining = blackTimeRemaining -= lastTickLength;\r
13037         DisplayBlackClock(blackTimeRemaining - fudge,\r
13038                           !WhiteOnMove(currentMove));\r
13039     }\r
13040 \r
13041     if (CheckFlags()) return;\r
13042         \r
13043     tickStartTM = now;\r
13044     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
13045     StartClockTimer(intendedTickLength);\r
13046 \r
13047     /* if the time remaining has fallen below the alarm threshold, sound the\r
13048      * alarm. if the alarm has sounded and (due to a takeback or time control\r
13049      * with increment) the time remaining has increased to a level above the\r
13050      * threshold, reset the alarm so it can sound again. \r
13051      */\r
13052     \r
13053     if (appData.icsActive && appData.icsAlarm) {\r
13054 \r
13055         /* make sure we are dealing with the user's clock */\r
13056         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
13057                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
13058            )) return;\r
13059 \r
13060         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
13061             alarmSounded = FALSE;\r
13062         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
13063             PlayAlarmSound();\r
13064             alarmSounded = TRUE;\r
13065         }\r
13066     }\r
13067 }\r
13068 \r
13069 \r
13070 /* A player has just moved, so stop the previously running\r
13071    clock and (if in clock mode) start the other one.\r
13072    We redisplay both clocks in case we're in ICS mode, because\r
13073    ICS gives us an update to both clocks after every move.\r
13074    Note that this routine is called *after* forwardMostMove\r
13075    is updated, so the last fractional tick must be subtracted\r
13076    from the color that is *not* on move now.\r
13077 */\r
13078 void\r
13079 SwitchClocks()\r
13080 {\r
13081     long lastTickLength;\r
13082     TimeMark now;\r
13083     int flagged = FALSE;\r
13084 \r
13085     GetTimeMark(&now);\r
13086 \r
13087     if (StopClockTimer() && appData.clockMode) {\r
13088         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13089         if (WhiteOnMove(forwardMostMove)) {\r
13090             if(blackNPS >= 0) lastTickLength = 0;\r
13091             blackTimeRemaining -= lastTickLength;\r
13092            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13093 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13094                  pvInfoList[forwardMostMove-1].time =               // use GUI time\r
13095                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
13096         } else {\r
13097            if(whiteNPS >= 0) lastTickLength = 0;\r
13098            whiteTimeRemaining -= lastTickLength;\r
13099            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13100 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13101                  pvInfoList[forwardMostMove-1].time = \r
13102                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
13103         }\r
13104         flagged = CheckFlags();\r
13105     }\r
13106     CheckTimeControl();\r
13107 \r
13108     if (flagged || !appData.clockMode) return;\r
13109 \r
13110     switch (gameMode) {\r
13111       case MachinePlaysBlack:\r
13112       case MachinePlaysWhite:\r
13113       case BeginningOfGame:\r
13114         if (pausing) return;\r
13115         break;\r
13116 \r
13117       case EditGame:\r
13118       case PlayFromGameFile:\r
13119       case IcsExamining:\r
13120         return;\r
13121 \r
13122       default:\r
13123         break;\r
13124     }\r
13125 \r
13126     tickStartTM = now;\r
13127     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13128       whiteTimeRemaining : blackTimeRemaining);\r
13129     StartClockTimer(intendedTickLength);\r
13130 }\r
13131         \r
13132 \r
13133 /* Stop both clocks */\r
13134 void\r
13135 StopClocks()\r
13136 {       \r
13137     long lastTickLength;\r
13138     TimeMark now;\r
13139 \r
13140     if (!StopClockTimer()) return;\r
13141     if (!appData.clockMode) return;\r
13142 \r
13143     GetTimeMark(&now);\r
13144 \r
13145     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13146     if (WhiteOnMove(forwardMostMove)) {\r
13147         if(whiteNPS >= 0) lastTickLength = 0;\r
13148         whiteTimeRemaining -= lastTickLength;\r
13149         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
13150     } else {\r
13151         if(blackNPS >= 0) lastTickLength = 0;\r
13152         blackTimeRemaining -= lastTickLength;\r
13153         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
13154     }\r
13155     CheckFlags();\r
13156 }\r
13157         \r
13158 /* Start clock of player on move.  Time may have been reset, so\r
13159    if clock is already running, stop and restart it. */\r
13160 void\r
13161 StartClocks()\r
13162 {\r
13163     (void) StopClockTimer(); /* in case it was running already */\r
13164     DisplayBothClocks();\r
13165     if (CheckFlags()) return;\r
13166 \r
13167     if (!appData.clockMode) return;\r
13168     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
13169 \r
13170     GetTimeMark(&tickStartTM);\r
13171     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13172       whiteTimeRemaining : blackTimeRemaining);\r
13173 \r
13174    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
13175     whiteNPS = blackNPS = -1; \r
13176     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
13177        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
13178         whiteNPS = first.nps;\r
13179     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
13180        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
13181         blackNPS = first.nps;\r
13182     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
13183         whiteNPS = second.nps;\r
13184     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
13185         blackNPS = second.nps;\r
13186     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
13187 \r
13188     StartClockTimer(intendedTickLength);\r
13189 }\r
13190 \r
13191 char *\r
13192 TimeString(ms)\r
13193      long ms;\r
13194 {\r
13195     long second, minute, hour, day;\r
13196     char *sign = "";\r
13197     static char buf[32];\r
13198     \r
13199     if (ms > 0 && ms <= 9900) {\r
13200       /* convert milliseconds to tenths, rounding up */\r
13201       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
13202 \r
13203       sprintf(buf, " %03.1f ", tenths/10.0);\r
13204       return buf;\r
13205     }\r
13206 \r
13207     /* convert milliseconds to seconds, rounding up */\r
13208     /* use floating point to avoid strangeness of integer division\r
13209        with negative dividends on many machines */\r
13210     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
13211 \r
13212     if (second < 0) {\r
13213         sign = "-";\r
13214         second = -second;\r
13215     }\r
13216     \r
13217     day = second / (60 * 60 * 24);\r
13218     second = second % (60 * 60 * 24);\r
13219     hour = second / (60 * 60);\r
13220     second = second % (60 * 60);\r
13221     minute = second / 60;\r
13222     second = second % 60;\r
13223     \r
13224     if (day > 0)\r
13225       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
13226               sign, day, hour, minute, second);\r
13227     else if (hour > 0)\r
13228       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
13229     else\r
13230       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
13231     \r
13232     return buf;\r
13233 }\r
13234 \r
13235 \r
13236 /*\r
13237  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
13238  */\r
13239 char *\r
13240 StrStr(string, match)\r
13241      char *string, *match;\r
13242 {\r
13243     int i, length;\r
13244     \r
13245     length = strlen(match);\r
13246     \r
13247     for (i = strlen(string) - length; i >= 0; i--, string++)\r
13248       if (!strncmp(match, string, length))\r
13249         return string;\r
13250     \r
13251     return NULL;\r
13252 }\r
13253 \r
13254 char *\r
13255 StrCaseStr(string, match)\r
13256      char *string, *match;\r
13257 {\r
13258     int i, j, length;\r
13259     \r
13260     length = strlen(match);\r
13261     \r
13262     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
13263         for (j = 0; j < length; j++) {\r
13264             if (ToLower(match[j]) != ToLower(string[j]))\r
13265               break;\r
13266         }\r
13267         if (j == length) return string;\r
13268     }\r
13269 \r
13270     return NULL;\r
13271 }\r
13272 \r
13273 #ifndef _amigados\r
13274 int\r
13275 StrCaseCmp(s1, s2)\r
13276      char *s1, *s2;\r
13277 {\r
13278     char c1, c2;\r
13279     \r
13280     for (;;) {\r
13281         c1 = ToLower(*s1++);\r
13282         c2 = ToLower(*s2++);\r
13283         if (c1 > c2) return 1;\r
13284         if (c1 < c2) return -1;\r
13285         if (c1 == NULLCHAR) return 0;\r
13286     }\r
13287 }\r
13288 \r
13289 \r
13290 int\r
13291 ToLower(c)\r
13292      int c;\r
13293 {\r
13294     return isupper(c) ? tolower(c) : c;\r
13295 }\r
13296 \r
13297 \r
13298 int\r
13299 ToUpper(c)\r
13300      int c;\r
13301 {\r
13302     return islower(c) ? toupper(c) : c;\r
13303 }\r
13304 #endif /* !_amigados    */\r
13305 \r
13306 char *\r
13307 StrSave(s)\r
13308      char *s;\r
13309 {\r
13310     char *ret;\r
13311 \r
13312     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
13313         strcpy(ret, s);\r
13314     }\r
13315     return ret;\r
13316 }\r
13317 \r
13318 char *\r
13319 StrSavePtr(s, savePtr)\r
13320      char *s, **savePtr;\r
13321 {\r
13322     if (*savePtr) {\r
13323         free(*savePtr);\r
13324     }\r
13325     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
13326         strcpy(*savePtr, s);\r
13327     }\r
13328     return(*savePtr);\r
13329 }\r
13330 \r
13331 char *\r
13332 PGNDate()\r
13333 {\r
13334     time_t clock;\r
13335     struct tm *tm;\r
13336     char buf[MSG_SIZ];\r
13337 \r
13338     clock = time((time_t *)NULL);\r
13339     tm = localtime(&clock);\r
13340     sprintf(buf, "%04d.%02d.%02d",\r
13341             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
13342     return StrSave(buf);\r
13343 }\r
13344 \r
13345 \r
13346 char *\r
13347 PositionToFEN(move, useFEN960)\r
13348      int move;\r
13349      int useFEN960;\r
13350 {\r
13351     int i, j, fromX, fromY, toX, toY;\r
13352     int whiteToPlay;\r
13353     char buf[128];\r
13354     char *p, *q;\r
13355     int emptycount;\r
13356     ChessSquare piece;\r
13357 \r
13358     whiteToPlay = (gameMode == EditPosition) ?\r
13359       !blackPlaysFirst : (move % 2 == 0);\r
13360     p = buf;\r
13361 \r
13362     /* Piece placement data */\r
13363     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13364         emptycount = 0;\r
13365         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
13366             if (boards[move][i][j] == EmptySquare) {\r
13367                 emptycount++;\r
13368             } else { ChessSquare piece = boards[move][i][j];\r
13369                 if (emptycount > 0) {\r
13370                     if(emptycount<10) /* [HGM] can be >= 10 */\r
13371                         *p++ = '0' + emptycount;\r
13372                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13373                     emptycount = 0;\r
13374                 }\r
13375                 if(PieceToChar(piece) == '+') {\r
13376                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
13377                     *p++ = '+';\r
13378                     piece = (ChessSquare)(DEMOTED piece);\r
13379                 } \r
13380                 *p++ = PieceToChar(piece);\r
13381                 if(p[-1] == '~') {\r
13382                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
13383                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
13384                     *p++ = '~';\r
13385                 }\r
13386             }\r
13387         }\r
13388         if (emptycount > 0) {\r
13389             if(emptycount<10) /* [HGM] can be >= 10 */\r
13390                 *p++ = '0' + emptycount;\r
13391             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13392             emptycount = 0;\r
13393         }\r
13394         *p++ = '/';\r
13395     }\r
13396     *(p - 1) = ' ';\r
13397 \r
13398     /* [HGM] print Crazyhouse or Shogi holdings */\r
13399     if( gameInfo.holdingsWidth ) {\r
13400         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
13401         q = p;\r
13402         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
13403             piece = boards[move][i][BOARD_WIDTH-1];\r
13404             if( piece != EmptySquare )\r
13405               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
13406                   *p++ = PieceToChar(piece);\r
13407         }\r
13408         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
13409             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
13410             if( piece != EmptySquare )\r
13411               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
13412                   *p++ = PieceToChar(piece);\r
13413         }\r
13414 \r
13415         if( q == p ) *p++ = '-';\r
13416         *p++ = ']';\r
13417         *p++ = ' ';\r
13418     }\r
13419 \r
13420     /* Active color */\r
13421     *p++ = whiteToPlay ? 'w' : 'b';\r
13422     *p++ = ' ';\r
13423 \r
13424   if(nrCastlingRights) {\r
13425      q = p;\r
13426      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
13427        /* [HGM] write directly from rights */\r
13428            if(castlingRights[move][2] >= 0 &&\r
13429               castlingRights[move][0] >= 0   )\r
13430                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
13431            if(castlingRights[move][2] >= 0 &&\r
13432               castlingRights[move][1] >= 0   )\r
13433                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
13434            if(castlingRights[move][5] >= 0 &&\r
13435               castlingRights[move][3] >= 0   )\r
13436                 *p++ = castlingRights[move][3] + AAA;\r
13437            if(castlingRights[move][5] >= 0 &&\r
13438               castlingRights[move][4] >= 0   )\r
13439                 *p++ = castlingRights[move][4] + AAA;\r
13440      } else {\r
13441 \r
13442         /* [HGM] write true castling rights */\r
13443         if( nrCastlingRights == 6 ) {\r
13444             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
13445                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
13446             if(castlingRights[move][1] == BOARD_LEFT &&\r
13447                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
13448             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
13449                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
13450             if(castlingRights[move][4] == BOARD_LEFT &&\r
13451                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
13452         }\r
13453      }\r
13454      if (q == p) *p++ = '-'; /* No castling rights */\r
13455      *p++ = ' ';\r
13456   }\r
13457 \r
13458   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13459      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13460     /* En passant target square */\r
13461     if (move > backwardMostMove) {\r
13462         fromX = moveList[move - 1][0] - AAA;\r
13463         fromY = moveList[move - 1][1] - ONE;\r
13464         toX = moveList[move - 1][2] - AAA;\r
13465         toY = moveList[move - 1][3] - ONE;\r
13466         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
13467             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
13468             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
13469             fromX == toX) {\r
13470             /* 2-square pawn move just happened */\r
13471             *p++ = toX + AAA;\r
13472             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
13473         } else {\r
13474             *p++ = '-';\r
13475         }\r
13476     } else {\r
13477         *p++ = '-';\r
13478     }\r
13479     *p++ = ' ';\r
13480   }\r
13481 \r
13482     /* [HGM] find reversible plies */\r
13483     {   int i = 0, j=move;\r
13484 \r
13485         if (appData.debugMode) { int k;\r
13486             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
13487             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
13488                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
13489 \r
13490         }\r
13491 \r
13492         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
13493         if( j == backwardMostMove ) i += initialRulePlies;\r
13494         sprintf(p, "%d ", i);\r
13495         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
13496     }\r
13497     /* Fullmove number */\r
13498     sprintf(p, "%d", (move / 2) + 1);\r
13499     \r
13500     return StrSave(buf);\r
13501 }\r
13502 \r
13503 Boolean\r
13504 ParseFEN(board, blackPlaysFirst, fen)\r
13505     Board board;\r
13506      int *blackPlaysFirst;\r
13507      char *fen;\r
13508 {\r
13509     int i, j;\r
13510     char *p;\r
13511     int emptycount;\r
13512     ChessSquare piece;\r
13513 \r
13514     p = fen;\r
13515 \r
13516     /* [HGM] by default clear Crazyhouse holdings, if present */\r
13517     if(gameInfo.holdingsWidth) {\r
13518        for(i=0; i<BOARD_HEIGHT; i++) {\r
13519            board[i][0]             = EmptySquare; /* black holdings */\r
13520            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
13521            board[i][1]             = (ChessSquare) 0; /* black counts */\r
13522            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
13523        }\r
13524     }\r
13525 \r
13526     /* Piece placement data */\r
13527     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13528         j = 0;\r
13529         for (;;) {\r
13530             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
13531                 if (*p == '/') p++;\r
13532                 emptycount = gameInfo.boardWidth - j;\r
13533                 while (emptycount--)\r
13534                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13535                 break;\r
13536 #if(BOARD_SIZE >= 10)\r
13537             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
13538                 p++; emptycount=10;\r
13539                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13540                 while (emptycount--)\r
13541                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13542 #endif\r
13543             } else if (isdigit(*p)) {\r
13544                 emptycount = *p++ - '0';\r
13545                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
13546                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13547                 while (emptycount--)\r
13548                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13549             } else if (*p == '+' || isalpha(*p)) {\r
13550                 if (j >= gameInfo.boardWidth) return FALSE;\r
13551                 if(*p=='+') {\r
13552                     piece = CharToPiece(*++p);\r
13553                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
13554                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
13555                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
13556                 } else piece = CharToPiece(*p++);\r
13557 \r
13558                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
13559                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
13560                     piece = (ChessSquare) (PROMOTED piece);\r
13561                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
13562                     p++;\r
13563                 }\r
13564                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
13565             } else {\r
13566                 return FALSE;\r
13567             }\r
13568         }\r
13569     }\r
13570     while (*p == '/' || *p == ' ') p++;\r
13571 \r
13572     /* [HGM] look for Crazyhouse holdings here */\r
13573     while(*p==' ') p++;\r
13574     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
13575         if(*p == '[') p++;\r
13576         if(*p == '-' ) *p++; /* empty holdings */ else {\r
13577             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
13578             /* if we would allow FEN reading to set board size, we would   */\r
13579             /* have to add holdings and shift the board read so far here   */\r
13580             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
13581                 *p++;\r
13582                 if((int) piece >= (int) BlackPawn ) {\r
13583                     i = (int)piece - (int)BlackPawn;\r
13584                     i = PieceToNumber((ChessSquare)i);\r
13585                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13586                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
13587                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
13588                 } else {\r
13589                     i = (int)piece - (int)WhitePawn;\r
13590                     i = PieceToNumber((ChessSquare)i);\r
13591                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13592                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
13593                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
13594                 }\r
13595             }\r
13596         }\r
13597         if(*p == ']') *p++;\r
13598     }\r
13599 \r
13600     while(*p == ' ') p++;\r
13601 \r
13602     /* Active color */\r
13603     switch (*p++) {\r
13604       case 'w':\r
13605         *blackPlaysFirst = FALSE;\r
13606         break;\r
13607       case 'b': \r
13608         *blackPlaysFirst = TRUE;\r
13609         break;\r
13610       default:\r
13611         return FALSE;\r
13612     }\r
13613 \r
13614     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
13615     /* return the extra info in global variiables             */\r
13616 \r
13617     /* set defaults in case FEN is incomplete */\r
13618     FENepStatus = EP_UNKNOWN;\r
13619     for(i=0; i<nrCastlingRights; i++ ) {\r
13620         FENcastlingRights[i] =\r
13621             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
13622     }   /* assume possible unless obviously impossible */\r
13623     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
13624     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
13625     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
13626     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
13627     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
13628     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
13629     FENrulePlies = 0;\r
13630 \r
13631     while(*p==' ') p++;\r
13632     if(nrCastlingRights) {\r
13633       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
13634           /* castling indicator present, so default becomes no castlings */\r
13635           for(i=0; i<nrCastlingRights; i++ ) {\r
13636                  FENcastlingRights[i] = -1;\r
13637           }\r
13638       }\r
13639       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
13640              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
13641              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
13642              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
13643         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
13644 \r
13645         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
13646             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
13647             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
13648         }\r
13649         switch(c) {\r
13650           case'K':\r
13651               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
13652               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
13653               FENcastlingRights[2] = whiteKingFile;\r
13654               break;\r
13655           case'Q':\r
13656               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
13657               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
13658               FENcastlingRights[2] = whiteKingFile;\r
13659               break;\r
13660           case'k':\r
13661               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
13662               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
13663               FENcastlingRights[5] = blackKingFile;\r
13664               break;\r
13665           case'q':\r
13666               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
13667               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
13668               FENcastlingRights[5] = blackKingFile;\r
13669           case '-':\r
13670               break;\r
13671           default: /* FRC castlings */\r
13672               if(c >= 'a') { /* black rights */\r
13673                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13674                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
13675                   if(i == BOARD_RGHT) break;\r
13676                   FENcastlingRights[5] = i;\r
13677                   c -= AAA;\r
13678                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
13679                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
13680                   if(c > i)\r
13681                       FENcastlingRights[3] = c;\r
13682                   else\r
13683                       FENcastlingRights[4] = c;\r
13684               } else { /* white rights */\r
13685                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13686                     if(board[0][i] == WhiteKing) break;\r
13687                   if(i == BOARD_RGHT) break;\r
13688                   FENcastlingRights[2] = i;\r
13689                   c -= AAA - 'a' + 'A';\r
13690                   if(board[0][c] >= WhiteKing) break;\r
13691                   if(c > i)\r
13692                       FENcastlingRights[0] = c;\r
13693                   else\r
13694                       FENcastlingRights[1] = c;\r
13695               }\r
13696         }\r
13697       }\r
13698     if (appData.debugMode) {\r
13699         fprintf(debugFP, "FEN castling rights:");\r
13700         for(i=0; i<nrCastlingRights; i++)\r
13701         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
13702         fprintf(debugFP, "\n");\r
13703     }\r
13704 \r
13705       while(*p==' ') p++;\r
13706     }\r
13707 \r
13708     /* read e.p. field in games that know e.p. capture */\r
13709     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13710        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13711       if(*p=='-') {\r
13712         p++; FENepStatus = EP_NONE;\r
13713       } else {\r
13714          char c = *p++ - AAA;\r
13715 \r
13716          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
13717          if(*p >= '0' && *p <='9') *p++;\r
13718          FENepStatus = c;\r
13719       }\r
13720     }\r
13721 \r
13722 \r
13723     if(sscanf(p, "%d", &i) == 1) {\r
13724         FENrulePlies = i; /* 50-move ply counter */\r
13725         /* (The move number is still ignored)    */\r
13726     }\r
13727 \r
13728     return TRUE;\r
13729 }\r
13730       \r
13731 void\r
13732 EditPositionPasteFEN(char *fen)\r
13733 {\r
13734   if (fen != NULL) {\r
13735     Board initial_position;\r
13736 \r
13737     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
13738       DisplayError(_("Bad FEN position in clipboard"), 0);\r
13739       return ;\r
13740     } else {\r
13741       int savedBlackPlaysFirst = blackPlaysFirst;\r
13742       EditPositionEvent();\r
13743       blackPlaysFirst = savedBlackPlaysFirst;\r
13744       CopyBoard(boards[0], initial_position);\r
13745           /* [HGM] copy FEN attributes as well */\r
13746           {   int i;\r
13747               initialRulePlies = FENrulePlies;\r
13748               epStatus[0] = FENepStatus;\r
13749               for( i=0; i<nrCastlingRights; i++ )\r
13750                   castlingRights[0][i] = FENcastlingRights[i];\r
13751           }\r
13752       EditPositionDone();\r
13753       DisplayBothClocks();\r
13754       DrawPosition(FALSE, boards[currentMove]);\r
13755     }\r
13756   }\r
13757 }\r