Updated all files to GPL version 3.
[xboard.git] / backend.c
1 /*\r
2  * backend.c -- Common back end for X and Windows NT versions of\r
3  * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $\r
4  *\r
5  * Copyright 1991 by Digital Equipment Corporation, Maynard,\r
6  * Massachusetts.  Enhancements Copyright\r
7  * 1992-2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software\r
8  * Foundation, Inc.\r
9  *\r
10  * The following terms apply to Digital Equipment Corporation's copyright\r
11  * interest in XBoard:\r
12  * ------------------------------------------------------------------------\r
13  * All Rights Reserved\r
14  *\r
15  * Permission to use, copy, modify, and distribute this software and its\r
16  * documentation for any purpose and without fee is hereby granted,\r
17  * provided that the above copyright notice appear in all copies and that\r
18  * both that copyright notice and this permission notice appear in\r
19  * supporting documentation, and that the name of Digital not be\r
20  * used in advertising or publicity pertaining to distribution of the\r
21  * software without specific, written prior permission.\r
22  *\r
23  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING\r
24  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL\r
25  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR\r
26  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,\r
27  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\r
28  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS\r
29  * SOFTWARE.\r
30  * ------------------------------------------------------------------------\r
31  *\r
32  * The following terms apply to the enhanced version of XBoard\r
33  * distributed by the Free Software Foundation:\r
34  * ------------------------------------------------------------------------\r
35  *\r
36  * GNU XBoard is free software: you can redistribute it and/or modify\r
37  * it under the terms of the GNU General Public License as published by\r
38  * the Free Software Foundation, either version 3 of the License, or (at\r
39  * your option) any later version.\r
40  *\r
41  * GNU XBoard is distributed in the hope that it will be useful, but\r
42  * WITHOUT ANY WARRANTY; without even the implied warranty of\r
43  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
44  * General Public License for more details.\r
45  *\r
46  * You should have received a copy of the GNU General Public License\r
47  * along with this program. If not, see http://www.gnu.org/licenses/.  *\r
48  *\r
49  *------------------------------------------------------------------------\r
50  ** See the file ChangeLog for a revision history.  */\r
51 \r
52 /* [AS] Also useful here for debugging */\r
53 #ifdef WIN32\r
54 #include <windows.h>\r
55 \r
56 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );\r
57 \r
58 #else\r
59 \r
60 #define DoSleep( n ) if( (n) >= 0) sleep(n)\r
61 \r
62 #endif\r
63 \r
64 #include "config.h"\r
65 \r
66 #include <assert.h>\r
67 #include <stdio.h>\r
68 #include <ctype.h>\r
69 #include <errno.h>\r
70 #include <sys/types.h>\r
71 #include <sys/stat.h>\r
72 #include <math.h>\r
73 \r
74 #if STDC_HEADERS\r
75 # include <stdlib.h>\r
76 # include <string.h>\r
77 #else /* not STDC_HEADERS */\r
78 # if HAVE_STRING_H\r
79 #  include <string.h>\r
80 # else /* not HAVE_STRING_H */\r
81 #  include <strings.h>\r
82 # endif /* not HAVE_STRING_H */\r
83 #endif /* not STDC_HEADERS */\r
84 \r
85 #if HAVE_SYS_FCNTL_H\r
86 # include <sys/fcntl.h>\r
87 #else /* not HAVE_SYS_FCNTL_H */\r
88 # if HAVE_FCNTL_H\r
89 #  include <fcntl.h>\r
90 # endif /* HAVE_FCNTL_H */\r
91 #endif /* not HAVE_SYS_FCNTL_H */\r
92 \r
93 #if TIME_WITH_SYS_TIME\r
94 # include <sys/time.h>\r
95 # include <time.h>\r
96 #else\r
97 # if HAVE_SYS_TIME_H\r
98 #  include <sys/time.h>\r
99 # else\r
100 #  include <time.h>\r
101 # endif\r
102 #endif\r
103 \r
104 #if defined(_amigados) && !defined(__GNUC__)\r
105 struct timezone {\r
106     int tz_minuteswest;\r
107     int tz_dsttime;\r
108 };\r
109 extern int gettimeofday(struct timeval *, struct timezone *);\r
110 #endif\r
111 \r
112 #if HAVE_UNISTD_H\r
113 # include <unistd.h>\r
114 #endif\r
115 \r
116 #include "common.h"\r
117 #include "frontend.h"\r
118 #include "backend.h"\r
119 #include "parser.h"\r
120 #include "moves.h"\r
121 #if ZIPPY\r
122 # include "zippy.h"\r
123 #endif\r
124 #include "backendz.h"\r
125 #include "gettext.h" \r
126  \r
127 #ifdef ENABLE_NLS \r
128 # define _(s) gettext (s) \r
129 # define N_(s) gettext_noop (s) \r
130 #else \r
131 # define _(s) (s) \r
132 # define N_(s) s \r
133 #endif \r
134 \r
135 \r
136 /* A point in time */\r
137 typedef struct {\r
138     long sec;  /* Assuming this is >= 32 bits */\r
139     int ms;    /* Assuming this is >= 16 bits */\r
140 } TimeMark;\r
141 \r
142 int establish P((void));\r
143 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,\r
144                          char *buf, int count, int error));\r
145 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,\r
146                       char *buf, int count, int error));\r
147 void SendToICS P((char *s));\r
148 void SendToICSDelayed P((char *s, long msdelay));\r
149 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,\r
150                       int toX, int toY));\r
151 void InitPosition P((int redraw));\r
152 void HandleMachineMove P((char *message, ChessProgramState *cps));\r
153 int AutoPlayOneMove P((void));\r
154 int LoadGameOneMove P((ChessMove readAhead));\r
155 int LoadGameFromFile P((char *filename, int n, char *title, int useList));\r
156 int LoadPositionFromFile P((char *filename, int n, char *title));\r
157 int SavePositionToFile P((char *filename));\r
158 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,\r
159                   Board board));\r
160 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));\r
161 void ShowMove P((int fromX, int fromY, int toX, int toY));\r
162 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,\r
163                    /*char*/int promoChar));\r
164 void BackwardInner P((int target));\r
165 void ForwardInner P((int target));\r
166 void GameEnds P((ChessMove result, char *resultDetails, int whosays));\r
167 void EditPositionDone P((void));\r
168 void PrintOpponents P((FILE *fp));\r
169 void PrintPosition P((FILE *fp, int move));\r
170 void StartChessProgram P((ChessProgramState *cps));\r
171 void SendToProgram P((char *message, ChessProgramState *cps));\r
172 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));\r
173 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,\r
174                            char *buf, int count, int error));\r
175 void SendTimeControl P((ChessProgramState *cps,\r
176                         int mps, long tc, int inc, int sd, int st));\r
177 char *TimeControlTagValue P((void));\r
178 void Attention P((ChessProgramState *cps));\r
179 void FeedMovesToProgram P((ChessProgramState *cps, int upto));\r
180 void ResurrectChessProgram P((void));\r
181 void DisplayComment P((int moveNumber, char *text));\r
182 void DisplayMove P((int moveNumber));\r
183 void DisplayAnalysis P((void));\r
184 \r
185 void ParseGameHistory P((char *game));\r
186 void ParseBoard12 P((char *string));\r
187 void StartClocks P((void));\r
188 void SwitchClocks P((void));\r
189 void StopClocks P((void));\r
190 void ResetClocks P((void));\r
191 char *PGNDate P((void));\r
192 void SetGameInfo P((void));\r
193 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));\r
194 int RegisterMove P((void));\r
195 void MakeRegisteredMove P((void));\r
196 void TruncateGame P((void));\r
197 int looking_at P((char *, int *, char *));\r
198 void CopyPlayerNameIntoFileName P((char **, char *));\r
199 char *SavePart P((char *));\r
200 int SaveGameOldStyle P((FILE *));\r
201 int SaveGamePGN P((FILE *));\r
202 void GetTimeMark P((TimeMark *));\r
203 long SubtractTimeMarks P((TimeMark *, TimeMark *));\r
204 int CheckFlags P((void));\r
205 long NextTickLength P((long));\r
206 void CheckTimeControl P((void));\r
207 void show_bytes P((FILE *, char *, int));\r
208 int string_to_rating P((char *str));\r
209 void ParseFeatures P((char* args, ChessProgramState *cps));\r
210 void InitBackEnd3 P((void));\r
211 void FeatureDone P((ChessProgramState* cps, int val));\r
212 void InitChessProgram P((ChessProgramState *cps, int setup));\r
213 \r
214 #ifdef WIN32\r
215        extern void ConsoleCreate();\r
216 #endif\r
217 \r
218 ChessProgramState *WhitePlayer();\r
219 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c\r
220 int VerifyDisplayMode P(());\r
221 \r
222 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment\r
223 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c\r
224 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move\r
225 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book\r
226 extern char installDir[MSG_SIZ];\r
227 \r
228 extern int tinyLayout, smallLayout;\r
229 ChessProgramStats programStats;\r
230 static int exiting = 0; /* [HGM] moved to top */\r
231 static int setboardSpoiledMachineBlack = 0, errorExitFlag = 0;\r
232 extern int startedFromPositionFile;\r
233 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */\r
234 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */\r
235 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */\r
236 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */\r
237 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */\r
238 int opponentKibitzes;\r
239 \r
240 /* States for ics_getting_history */\r
241 #define H_FALSE 0\r
242 #define H_REQUESTED 1\r
243 #define H_GOT_REQ_HEADER 2\r
244 #define H_GOT_UNREQ_HEADER 3\r
245 #define H_GETTING_MOVES 4\r
246 #define H_GOT_UNWANTED_HEADER 5\r
247 \r
248 /* whosays values for GameEnds */\r
249 #define GE_ICS 0\r
250 #define GE_ENGINE 1\r
251 #define GE_PLAYER 2\r
252 #define GE_FILE 3\r
253 #define GE_XBOARD 4\r
254 #define GE_ENGINE1 5\r
255 #define GE_ENGINE2 6\r
256 \r
257 /* Maximum number of games in a cmail message */\r
258 #define CMAIL_MAX_GAMES 20\r
259 \r
260 /* Different types of move when calling RegisterMove */\r
261 #define CMAIL_MOVE   0\r
262 #define CMAIL_RESIGN 1\r
263 #define CMAIL_DRAW   2\r
264 #define CMAIL_ACCEPT 3\r
265 \r
266 /* Different types of result to remember for each game */\r
267 #define CMAIL_NOT_RESULT 0\r
268 #define CMAIL_OLD_RESULT 1\r
269 #define CMAIL_NEW_RESULT 2\r
270 \r
271 /* Telnet protocol constants */\r
272 #define TN_WILL 0373\r
273 #define TN_WONT 0374\r
274 #define TN_DO   0375\r
275 #define TN_DONT 0376\r
276 #define TN_IAC  0377\r
277 #define TN_ECHO 0001\r
278 #define TN_SGA  0003\r
279 #define TN_PORT 23\r
280 \r
281 /* [AS] */\r
282 static char * safeStrCpy( char * dst, const char * src, size_t count )\r
283 {\r
284     assert( dst != NULL );\r
285     assert( src != NULL );\r
286     assert( count > 0 );\r
287 \r
288     strncpy( dst, src, count );\r
289     dst[ count-1 ] = '\0';\r
290     return dst;\r
291 }\r
292 \r
293 static char * safeStrCat( char * dst, const char * src, size_t count )\r
294 {\r
295     size_t  dst_len;\r
296 \r
297     assert( dst != NULL );\r
298     assert( src != NULL );\r
299     assert( count > 0 );\r
300 \r
301     dst_len = strlen(dst);\r
302 \r
303     assert( count > dst_len ); /* Buffer size must be greater than current length */\r
304 \r
305     safeStrCpy( dst + dst_len, src, count - dst_len );\r
306 \r
307     return dst;\r
308 }\r
309 \r
310 /* Some compiler can't cast u64 to double\r
311  * This function do the job for us:\r
312 \r
313  * We use the highest bit for cast, this only\r
314  * works if the highest bit is not\r
315  * in use (This should not happen)\r
316  *\r
317  * We used this for all compiler\r
318  */\r
319 double\r
320 u64ToDouble(u64 value)\r
321 {\r
322   double r;\r
323   u64 tmp = value & u64Const(0x7fffffffffffffff);\r
324   r = (double)(s64)tmp;\r
325   if (value & u64Const(0x8000000000000000))\r
326        r +=  9.2233720368547758080e18; /* 2^63 */\r
327  return r;\r
328 }\r
329 \r
330 /* Fake up flags for now, as we aren't keeping track of castling\r
331    availability yet. [HGM] Change of logic: the flag now only\r
332    indicates the type of castlings allowed by the rule of the game.\r
333    The actual rights themselves are maintained in the array\r
334    castlingRights, as part of the game history, and are not probed\r
335    by this function.\r
336  */\r
337 int\r
338 PosFlags(index)\r
339 {\r
340   int flags = F_ALL_CASTLE_OK;\r
341   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;\r
342   switch (gameInfo.variant) {\r
343   case VariantSuicide:\r
344     flags &= ~F_ALL_CASTLE_OK;\r
345   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!\r
346     flags |= F_IGNORE_CHECK;\r
347     break;\r
348   case VariantAtomic:\r
349     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;\r
350     break;\r
351   case VariantKriegspiel:\r
352     flags |= F_KRIEGSPIEL_CAPTURE;\r
353     break;\r
354   case VariantCapaRandom: \r
355   case VariantFischeRandom:\r
356     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */\r
357   case VariantNoCastle:\r
358   case VariantShatranj:\r
359   case VariantCourier:\r
360     flags &= ~F_ALL_CASTLE_OK;\r
361     break;\r
362   default:\r
363     break;\r
364   }\r
365   return flags;\r
366 }\r
367 \r
368 FILE *gameFileFP, *debugFP;\r
369 \r
370 /* \r
371     [AS] Note: sometimes, the sscanf() function is used to parse the input\r
372     into a fixed-size buffer. Because of this, we must be prepared to\r
373     receive strings as long as the size of the input buffer, which is currently\r
374     set to 4K for Windows and 8K for the rest.\r
375     So, we must either allocate sufficiently large buffers here, or\r
376     reduce the size of the input buffer in the input reading part.\r
377 */\r
378 \r
379 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];\r
380 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];\r
381 char thinkOutput1[MSG_SIZ*10];\r
382 \r
383 ChessProgramState first, second;\r
384 \r
385 /* premove variables */\r
386 int premoveToX = 0;\r
387 int premoveToY = 0;\r
388 int premoveFromX = 0;\r
389 int premoveFromY = 0;\r
390 int premovePromoChar = 0;\r
391 int gotPremove = 0;\r
392 Boolean alarmSounded;\r
393 /* end premove variables */\r
394 \r
395 char *ics_prefix = "$";\r
396 int ics_type = ICS_GENERIC;\r
397 \r
398 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;\r
399 int pauseExamForwardMostMove = 0;\r
400 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;\r
401 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];\r
402 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;\r
403 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;\r
404 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;\r
405 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;\r
406 int whiteFlag = FALSE, blackFlag = FALSE;\r
407 int userOfferedDraw = FALSE;\r
408 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;\r
409 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;\r
410 int cmailMoveType[CMAIL_MAX_GAMES];\r
411 long ics_clock_paused = 0;\r
412 ProcRef icsPR = NoProc, cmailPR = NoProc;\r
413 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;\r
414 GameMode gameMode = BeginningOfGame;\r
415 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];\r
416 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];\r
417 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */\r
418 int hiddenThinkOutputState = 0; /* [AS] */\r
419 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */\r
420 int adjudicateLossPlies = 6;\r
421 char white_holding[64], black_holding[64];\r
422 TimeMark lastNodeCountTime;\r
423 long lastNodeCount=0;\r
424 int have_sent_ICS_logon = 0;\r
425 int movesPerSession;\r
426 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;\r
427 long timeControl_2; /* [AS] Allow separate time controls */\r
428 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */\r
429 long timeRemaining[2][MAX_MOVES];\r
430 int matchGame = 0;\r
431 TimeMark programStartTime;\r
432 char ics_handle[MSG_SIZ];\r
433 int have_set_title = 0;\r
434 \r
435 /* animateTraining preserves the state of appData.animate\r
436  * when Training mode is activated. This allows the\r
437  * response to be animated when appData.animate == TRUE and\r
438  * appData.animateDragging == TRUE.\r
439  */\r
440 Boolean animateTraining;\r
441 \r
442 GameInfo gameInfo;\r
443 \r
444 AppData appData;\r
445 \r
446 Board boards[MAX_MOVES];\r
447 /* [HGM] Following 7 needed for accurate legality tests: */\r
448 char  epStatus[MAX_MOVES];\r
449 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1\r
450 char  castlingRank[BOARD_SIZE]; // and corresponding ranks\r
451 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];\r
452 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status\r
453 int   initialRulePlies, FENrulePlies;\r
454 char  FENepStatus;\r
455 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)\r
456 int loadFlag = 0; \r
457 int shuffleOpenings;\r
458 \r
459 ChessSquare  FIDEArray[2][BOARD_SIZE] = {\r
460     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
461         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
462     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
463         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
464 };\r
465 \r
466 ChessSquare twoKingsArray[2][BOARD_SIZE] = {\r
467     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
468         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },\r
469     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
470         BlackKing, BlackKing, BlackKnight, BlackRook }\r
471 };\r
472 \r
473 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {\r
474     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,\r
475         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },\r
476     { BlackRook, BlackMan, BlackBishop, BlackQueen,\r
477         BlackUnicorn, BlackBishop, BlackMan, BlackRook }\r
478 };\r
479 \r
480 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */\r
481     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,\r
482         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
483     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,\r
484         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
485 };\r
486 \r
487 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */\r
488     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,\r
489         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
490     { BlackRook, BlackKnight, BlackAlfil, BlackKing,\r
491         BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
492 };\r
493 \r
494 \r
495 #if (BOARD_SIZE>=10)\r
496 ChessSquare ShogiArray[2][BOARD_SIZE] = {\r
497     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,\r
498         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },\r
499     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,\r
500         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }\r
501 };\r
502 \r
503 ChessSquare XiangqiArray[2][BOARD_SIZE] = {\r
504     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,\r
505         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
506     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,\r
507         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
508 };\r
509 \r
510 ChessSquare CapablancaArray[2][BOARD_SIZE] = {\r
511     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, \r
512         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },\r
513     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, \r
514         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
515 };\r
516 \r
517 ChessSquare GreatArray[2][BOARD_SIZE] = {\r
518     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, \r
519         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },\r
520     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, \r
521         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },\r
522 };\r
523 \r
524 ChessSquare JanusArray[2][BOARD_SIZE] = {\r
525     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, \r
526         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },\r
527     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, \r
528         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }\r
529 };\r
530 \r
531 #ifdef GOTHIC\r
532 ChessSquare GothicArray[2][BOARD_SIZE] = {\r
533     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, \r
534         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },\r
535     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, \r
536         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }\r
537 };\r
538 #else // !GOTHIC\r
539 #define GothicArray CapablancaArray\r
540 #endif // !GOTHIC\r
541 \r
542 #ifdef FALCON\r
543 ChessSquare FalconArray[2][BOARD_SIZE] = {\r
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, \r
545         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },\r
546     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, \r
547         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }\r
548 };\r
549 #else // !FALCON\r
550 #define FalconArray CapablancaArray\r
551 #endif // !FALCON\r
552 \r
553 #else // !(BOARD_SIZE>=10)\r
554 #define XiangqiPosition FIDEArray\r
555 #define CapablancaArray FIDEArray\r
556 #define GothicArray FIDEArray\r
557 #define GreatArray FIDEArray\r
558 #endif // !(BOARD_SIZE>=10)\r
559 \r
560 #if (BOARD_SIZE>=12)\r
561 ChessSquare CourierArray[2][BOARD_SIZE] = {\r
562     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,\r
563         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },\r
564     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,\r
565         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }\r
566 };\r
567 #else // !(BOARD_SIZE>=12)\r
568 #define CourierArray CapablancaArray\r
569 #endif // !(BOARD_SIZE>=12)\r
570 \r
571 \r
572 Board initialPosition;\r
573 \r
574 \r
575 /* Convert str to a rating. Checks for special cases of "----",\r
576 \r
577    "++++", etc. Also strips ()'s */\r
578 int\r
579 string_to_rating(str)\r
580   char *str;\r
581 {\r
582   while(*str && !isdigit(*str)) ++str;\r
583   if (!*str)\r
584     return 0;   /* One of the special "no rating" cases */\r
585   else\r
586     return atoi(str);\r
587 }\r
588 \r
589 void\r
590 ClearProgramStats()\r
591 {\r
592     /* Init programStats */\r
593     programStats.movelist[0] = 0;\r
594     programStats.depth = 0;\r
595     programStats.nr_moves = 0;\r
596     programStats.moves_left = 0;\r
597     programStats.nodes = 0;\r
598     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output\r
599     programStats.score = 0;\r
600     programStats.got_only_move = 0;\r
601     programStats.got_fail = 0;\r
602     programStats.line_is_book = 0;\r
603 }\r
604 \r
605 void\r
606 InitBackEnd1()\r
607 {\r
608     int matched, min, sec;\r
609 \r
610     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options\r
611 \r
612     GetTimeMark(&programStartTime);\r
613 \r
614     ClearProgramStats();\r
615     programStats.ok_to_send = 1;\r
616     programStats.seen_stat = 0;\r
617 \r
618     /*\r
619      * Initialize game list\r
620      */\r
621     ListNew(&gameList);\r
622 \r
623 \r
624     /*\r
625      * Internet chess server status\r
626      */\r
627     if (appData.icsActive) {\r
628         appData.matchMode = FALSE;\r
629         appData.matchGames = 0;\r
630 #if ZIPPY       \r
631         appData.noChessProgram = !appData.zippyPlay;\r
632 #else\r
633         appData.zippyPlay = FALSE;\r
634         appData.zippyTalk = FALSE;\r
635         appData.noChessProgram = TRUE;\r
636 #endif\r
637         if (*appData.icsHelper != NULLCHAR) {\r
638             appData.useTelnet = TRUE;\r
639             appData.telnetProgram = appData.icsHelper;\r
640         }\r
641     } else {\r
642         appData.zippyTalk = appData.zippyPlay = FALSE;\r
643     }\r
644 \r
645     /* [AS] Initialize pv info list [HGM] and game state */\r
646     {\r
647         int i, j;\r
648 \r
649         for( i=0; i<MAX_MOVES; i++ ) {\r
650             pvInfoList[i].depth = -1;\r
651             epStatus[i]=EP_NONE;\r
652             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
653         }\r
654     }\r
655 \r
656     /*\r
657      * Parse timeControl resource\r
658      */\r
659     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,\r
660                           appData.movesPerSession)) {\r
661         char buf[MSG_SIZ];\r
662         sprintf(buf, _("bad timeControl option %s"), appData.timeControl);\r
663         DisplayFatalError(buf, 0, 2);\r
664     }\r
665 \r
666     /*\r
667      * Parse searchTime resource\r
668      */\r
669     if (*appData.searchTime != NULLCHAR) {\r
670         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);\r
671         if (matched == 1) {\r
672             searchTime = min * 60;\r
673         } else if (matched == 2) {\r
674             searchTime = min * 60 + sec;\r
675         } else {\r
676             char buf[MSG_SIZ];\r
677             sprintf(buf, _("bad searchTime option %s"), appData.searchTime);\r
678             DisplayFatalError(buf, 0, 2);\r
679         }\r
680     }\r
681 \r
682     /* [AS] Adjudication threshold */\r
683     adjudicateLossThreshold = appData.adjudicateLossThreshold;\r
684     \r
685     first.which = "first";\r
686     second.which = "second";\r
687     first.maybeThinking = second.maybeThinking = FALSE;\r
688     first.pr = second.pr = NoProc;\r
689     first.isr = second.isr = NULL;\r
690     first.sendTime = second.sendTime = 2;\r
691     first.sendDrawOffers = 1;\r
692     if (appData.firstPlaysBlack) {\r
693         first.twoMachinesColor = "black\n";\r
694         second.twoMachinesColor = "white\n";\r
695     } else {\r
696         first.twoMachinesColor = "white\n";\r
697         second.twoMachinesColor = "black\n";\r
698     }\r
699     first.program = appData.firstChessProgram;\r
700     second.program = appData.secondChessProgram;\r
701     first.host = appData.firstHost;\r
702     second.host = appData.secondHost;\r
703     first.dir = appData.firstDirectory;\r
704     second.dir = appData.secondDirectory;\r
705     first.other = &second;\r
706     second.other = &first;\r
707     first.initString = appData.initString;\r
708     second.initString = appData.secondInitString;\r
709     first.computerString = appData.firstComputerString;\r
710     second.computerString = appData.secondComputerString;\r
711     first.useSigint = second.useSigint = TRUE;\r
712     first.useSigterm = second.useSigterm = TRUE;\r
713     first.reuse = appData.reuseFirst;\r
714     second.reuse = appData.reuseSecond;\r
715     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second\r
716     second.nps = appData.secondNPS;\r
717     first.useSetboard = second.useSetboard = FALSE;\r
718     first.useSAN = second.useSAN = FALSE;\r
719     first.usePing = second.usePing = FALSE;\r
720     first.lastPing = second.lastPing = 0;\r
721     first.lastPong = second.lastPong = 0;\r
722     first.usePlayother = second.usePlayother = FALSE;\r
723     first.useColors = second.useColors = TRUE;\r
724     first.useUsermove = second.useUsermove = FALSE;\r
725     first.sendICS = second.sendICS = FALSE;\r
726     first.sendName = second.sendName = appData.icsActive;\r
727     first.sdKludge = second.sdKludge = FALSE;\r
728     first.stKludge = second.stKludge = FALSE;\r
729     TidyProgramName(first.program, first.host, first.tidy);\r
730     TidyProgramName(second.program, second.host, second.tidy);\r
731     first.matchWins = second.matchWins = 0;\r
732     strcpy(first.variants, appData.variant);\r
733     strcpy(second.variants, appData.variant);\r
734     first.analysisSupport = second.analysisSupport = 2; /* detect */\r
735     first.analyzing = second.analyzing = FALSE;\r
736     first.initDone = second.initDone = FALSE;\r
737 \r
738     /* New features added by Tord: */\r
739     first.useFEN960 = FALSE; second.useFEN960 = FALSE;\r
740     first.useOOCastle = TRUE; second.useOOCastle = TRUE;\r
741     /* End of new features added by Tord. */\r
742 \r
743     /* [HGM] time odds: set factor for each machine */\r
744     first.timeOdds  = appData.firstTimeOdds;\r
745     second.timeOdds = appData.secondTimeOdds;\r
746     { int norm = 1;\r
747         if(appData.timeOddsMode) {\r
748             norm = first.timeOdds;\r
749             if(norm > second.timeOdds) norm = second.timeOdds;\r
750         }\r
751         first.timeOdds /= norm;\r
752         second.timeOdds /= norm;\r
753     }\r
754 \r
755     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/\r
756     first.accumulateTC = appData.firstAccumulateTC;\r
757     second.accumulateTC = appData.secondAccumulateTC;\r
758     first.maxNrOfSessions = second.maxNrOfSessions = 1;\r
759 \r
760     /* [HGM] debug */\r
761     first.debug = second.debug = FALSE;\r
762     first.supportsNPS = second.supportsNPS = UNKNOWN;\r
763 \r
764     /* [HGM] options */\r
765     first.optionSettings  = appData.firstOptions;\r
766     second.optionSettings = appData.secondOptions;\r
767 \r
768     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
769     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
770     first.isUCI = appData.firstIsUCI; /* [AS] */\r
771     second.isUCI = appData.secondIsUCI; /* [AS] */\r
772     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
773     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
774 \r
775     if (appData.firstProtocolVersion > PROTOVER ||\r
776         appData.firstProtocolVersion < 1) {\r
777       char buf[MSG_SIZ];\r
778       sprintf(buf, _("protocol version %d not supported"),\r
779               appData.firstProtocolVersion);\r
780       DisplayFatalError(buf, 0, 2);\r
781     } else {\r
782       first.protocolVersion = appData.firstProtocolVersion;\r
783     }\r
784 \r
785     if (appData.secondProtocolVersion > PROTOVER ||\r
786         appData.secondProtocolVersion < 1) {\r
787       char buf[MSG_SIZ];\r
788       sprintf(buf, _("protocol version %d not supported"),\r
789               appData.secondProtocolVersion);\r
790       DisplayFatalError(buf, 0, 2);\r
791     } else {\r
792       second.protocolVersion = appData.secondProtocolVersion;\r
793     }\r
794 \r
795     if (appData.icsActive) {\r
796         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */\r
797     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
798         appData.clockMode = FALSE;\r
799         first.sendTime = second.sendTime = 0;\r
800     }\r
801     \r
802 #if ZIPPY\r
803     /* Override some settings from environment variables, for backward\r
804        compatibility.  Unfortunately it's not feasible to have the env\r
805        vars just set defaults, at least in xboard.  Ugh.\r
806     */\r
807     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
808       ZippyInit();\r
809     }\r
810 #endif\r
811     \r
812     if (appData.noChessProgram) {\r
813         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
814                                         + strlen(PATCHLEVEL));\r
815         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
816     } else {\r
817 #if 0\r
818         char *p, *q;\r
819         q = first.program;\r
820         while (*q != ' ' && *q != NULLCHAR) q++;\r
821         p = q;\r
822         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */\r
823         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
824                                         + strlen(PATCHLEVEL) + (q - p));\r
825         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
826         strncat(programVersion, p, q - p);\r
827 #else\r
828         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */\r
829         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
830                                         + strlen(PATCHLEVEL) + strlen(first.tidy));\r
831         sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);\r
832 #endif\r
833     }\r
834 \r
835     if (!appData.icsActive) {\r
836       char buf[MSG_SIZ];\r
837       /* Check for variants that are supported only in ICS mode,\r
838          or not at all.  Some that are accepted here nevertheless\r
839          have bugs; see comments below.\r
840       */\r
841       VariantClass variant = StringToVariant(appData.variant);\r
842       switch (variant) {\r
843       case VariantBughouse:     /* need four players and two boards */\r
844       case VariantKriegspiel:   /* need to hide pieces and move details */\r
845       /* case VariantFischeRandom: (Fabien: moved below) */\r
846         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);\r
847         DisplayFatalError(buf, 0, 2);\r
848         return;\r
849 \r
850       case VariantUnknown:\r
851       case VariantLoadable:\r
852       case Variant29:\r
853       case Variant30:\r
854       case Variant31:\r
855       case Variant32:\r
856       case Variant33:\r
857       case Variant34:\r
858       case Variant35:\r
859       case Variant36:\r
860       default:\r
861         sprintf(buf, _("Unknown variant name %s"), appData.variant);\r
862         DisplayFatalError(buf, 0, 2);\r
863         return;\r
864 \r
865       case VariantXiangqi:    /* [HGM] repetition rules not implemented */\r
866       case VariantFairy:      /* [HGM] TestLegality definitely off! */\r
867       case VariantGothic:     /* [HGM] should work */\r
868       case VariantCapablanca: /* [HGM] should work */\r
869       case VariantCourier:    /* [HGM] initial forced moves not implemented */\r
870       case VariantShogi:      /* [HGM] drops not tested for legality */\r
871       case VariantKnightmate: /* [HGM] should work */\r
872       case VariantCylinder:   /* [HGM] untested */\r
873       case VariantFalcon:     /* [HGM] untested */\r
874       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)\r
875                                  offboard interposition not understood */\r
876       case VariantNormal:     /* definitely works! */\r
877       case VariantWildCastle: /* pieces not automatically shuffled */\r
878       case VariantNoCastle:   /* pieces not automatically shuffled */\r
879       case VariantFischeRandom: /* [HGM] works and shuffles pieces */\r
880       case VariantLosers:     /* should work except for win condition,\r
881                                  and doesn't know captures are mandatory */\r
882       case VariantSuicide:    /* should work except for win condition,\r
883                                  and doesn't know captures are mandatory */\r
884       case VariantGiveaway:   /* should work except for win condition,\r
885                                  and doesn't know captures are mandatory */\r
886       case VariantTwoKings:   /* should work */\r
887       case VariantAtomic:     /* should work except for win condition */\r
888       case Variant3Check:     /* should work except for win condition */\r
889       case VariantShatranj:   /* should work except for all win conditions */\r
890       case VariantBerolina:   /* might work if TestLegality is off */\r
891       case VariantCapaRandom: /* should work */\r
892       case VariantJanus:      /* should work */\r
893       case VariantSuper:      /* experimental */\r
894       case VariantGreat:      /* experimental, requires legality testing to be off */\r
895         break;\r
896       }\r
897     }\r
898 \r
899     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard\r
900     InitEngineUCI( installDir, &second );\r
901 }\r
902 \r
903 int NextIntegerFromString( char ** str, long * value )\r
904 {\r
905     int result = -1;\r
906     char * s = *str;\r
907 \r
908     while( *s == ' ' || *s == '\t' ) {\r
909         s++;\r
910     }\r
911 \r
912     *value = 0;\r
913 \r
914     if( *s >= '0' && *s <= '9' ) {\r
915         while( *s >= '0' && *s <= '9' ) {\r
916             *value = *value * 10 + (*s - '0');\r
917             s++;\r
918         }\r
919 \r
920         result = 0;\r
921     }\r
922 \r
923     *str = s;\r
924 \r
925     return result;\r
926 }\r
927 \r
928 int NextTimeControlFromString( char ** str, long * value )\r
929 {\r
930     long temp;\r
931     int result = NextIntegerFromString( str, &temp );\r
932 \r
933     if( result == 0 ) {\r
934         *value = temp * 60; /* Minutes */\r
935         if( **str == ':' ) {\r
936             (*str)++;\r
937             result = NextIntegerFromString( str, &temp );\r
938             *value += temp; /* Seconds */\r
939         }\r
940     }\r
941 \r
942     return result;\r
943 }\r
944 \r
945 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)\r
946 {   /* [HGM] routine added to read '+moves/time' for secondary time control */\r
947     int result = -1; long temp, temp2;\r
948 \r
949     if(**str != '+') return -1; // old params remain in force!\r
950     (*str)++;\r
951     if( NextTimeControlFromString( str, &temp ) ) return -1;\r
952 \r
953     if(**str != '/') {\r
954         /* time only: incremental or sudden-death time control */\r
955         if(**str == '+') { /* increment follows; read it */\r
956             (*str)++;\r
957             if(result = NextIntegerFromString( str, &temp2)) return -1;\r
958             *inc = temp2 * 1000;\r
959         } else *inc = 0;\r
960         *moves = 0; *tc = temp * 1000; \r
961         return 0;\r
962     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */\r
963 \r
964     (*str)++; /* classical time control */\r
965     result = NextTimeControlFromString( str, &temp2);\r
966     if(result == 0) {\r
967         *moves = temp/60;\r
968         *tc    = temp2 * 1000;\r
969         *inc   = 0;\r
970     }\r
971     return result;\r
972 }\r
973 \r
974 int GetTimeQuota(int movenr)\r
975 {   /* [HGM] get time to add from the multi-session time-control string */\r
976     int moves=1; /* kludge to force reading of first session */\r
977     long time, increment;\r
978     char *s = fullTimeControlString;\r
979 \r
980     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);\r
981     do {\r
982         if(moves) NextSessionFromString(&s, &moves, &time, &increment);\r
983         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);\r
984         if(movenr == -1) return time;    /* last move before new session     */\r
985         if(!moves) return increment;     /* current session is incremental   */\r
986         if(movenr >= 0) movenr -= moves; /* we already finished this session */\r
987     } while(movenr >= -1);               /* try again for next session       */\r
988 \r
989     return 0; // no new time quota on this move\r
990 }\r
991 \r
992 int\r
993 ParseTimeControl(tc, ti, mps)\r
994      char *tc;\r
995      int ti;\r
996      int mps;\r
997 {\r
998 #if 0\r
999     int matched, min, sec;\r
1000 \r
1001     matched = sscanf(tc, "%d:%d", &min, &sec);\r
1002     if (matched == 1) {\r
1003         timeControl = min * 60 * 1000;\r
1004     } else if (matched == 2) {\r
1005         timeControl = (min * 60 + sec) * 1000;\r
1006     } else {\r
1007         return FALSE;\r
1008     }\r
1009 #else\r
1010     long tc1;\r
1011     long tc2;\r
1012     char buf[MSG_SIZ];\r
1013 \r
1014     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;\r
1015     if(ti > 0) {\r
1016         if(mps)\r
1017              sprintf(buf, "+%d/%s+%d", mps, tc, ti);\r
1018         else sprintf(buf, "+%s+%d", tc, ti);\r
1019     } else {\r
1020         if(mps)\r
1021              sprintf(buf, "+%d/%s", mps, tc);\r
1022         else sprintf(buf, "+%s", tc);\r
1023     }\r
1024     fullTimeControlString = StrSave(buf);\r
1025 \r
1026     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
1027         return FALSE;\r
1028     }\r
1029 \r
1030     if( *tc == '/' ) {\r
1031         /* Parse second time control */\r
1032         tc++;\r
1033 \r
1034         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
1035             return FALSE;\r
1036         }\r
1037 \r
1038         if( tc2 == 0 ) {\r
1039             return FALSE;\r
1040         }\r
1041 \r
1042         timeControl_2 = tc2 * 1000;\r
1043     }\r
1044     else {\r
1045         timeControl_2 = 0;\r
1046     }\r
1047 \r
1048     if( tc1 == 0 ) {\r
1049         return FALSE;\r
1050     }\r
1051 \r
1052     timeControl = tc1 * 1000;\r
1053 #endif\r
1054 \r
1055     if (ti >= 0) {\r
1056         timeIncrement = ti * 1000;  /* convert to ms */\r
1057         movesPerSession = 0;\r
1058     } else {\r
1059         timeIncrement = 0;\r
1060         movesPerSession = mps;\r
1061     }\r
1062     return TRUE;\r
1063 }\r
1064 \r
1065 void\r
1066 InitBackEnd2()\r
1067 {\r
1068     if (appData.debugMode) {\r
1069         fprintf(debugFP, "%s\n", programVersion);\r
1070     }\r
1071 \r
1072     if (appData.matchGames > 0) {\r
1073         appData.matchMode = TRUE;\r
1074     } else if (appData.matchMode) {\r
1075         appData.matchGames = 1;\r
1076     }\r
1077     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */\r
1078         appData.matchGames = appData.sameColorGames;\r
1079     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */\r
1080         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;\r
1081         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;\r
1082     }\r
1083     Reset(TRUE, FALSE);\r
1084     if (appData.noChessProgram || first.protocolVersion == 1) {\r
1085       InitBackEnd3();\r
1086     } else {\r
1087       /* kludge: allow timeout for initial "feature" commands */\r
1088       FreezeUI();\r
1089       DisplayMessage("", _("Starting chess program"));\r
1090       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
1091     }\r
1092 }\r
1093 \r
1094 void\r
1095 InitBackEnd3 P((void))\r
1096 {\r
1097     GameMode initialMode;\r
1098     char buf[MSG_SIZ];\r
1099     int err;\r
1100 \r
1101     InitChessProgram(&first, startedFromSetupPosition);\r
1102 \r
1103 \r
1104     if (appData.icsActive) {\r
1105 #ifdef WIN32\r
1106         /* [DM] Make a console window if needed [HGM] merged ifs */\r
1107         ConsoleCreate(); \r
1108 #endif\r
1109         err = establish();\r
1110         if (err != 0) {\r
1111             if (*appData.icsCommPort != NULLCHAR) {\r
1112                 sprintf(buf, _("Could not open comm port %s"),  \r
1113                         appData.icsCommPort);\r
1114             } else {\r
1115                 sprintf(buf, _("Could not connect to host %s, port %s"),  \r
1116                         appData.icsHost, appData.icsPort);\r
1117             }\r
1118             DisplayFatalError(buf, err, 1);\r
1119             return;\r
1120         }\r
1121         SetICSMode();\r
1122         telnetISR =\r
1123           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
1124         fromUserISR =\r
1125           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
1126     } else if (appData.noChessProgram) {\r
1127         SetNCPMode();\r
1128     } else {\r
1129         SetGNUMode();\r
1130     }\r
1131 \r
1132     if (*appData.cmailGameName != NULLCHAR) {\r
1133         SetCmailMode();\r
1134         OpenLoopback(&cmailPR);\r
1135         cmailISR =\r
1136           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
1137     }\r
1138     \r
1139     ThawUI();\r
1140     DisplayMessage("", "");\r
1141     if (StrCaseCmp(appData.initialMode, "") == 0) {\r
1142       initialMode = BeginningOfGame;\r
1143     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
1144       initialMode = TwoMachinesPlay;\r
1145     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
1146       initialMode = AnalyzeFile; \r
1147     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
1148       initialMode = AnalyzeMode;\r
1149     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
1150       initialMode = MachinePlaysWhite;\r
1151     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
1152       initialMode = MachinePlaysBlack;\r
1153     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
1154       initialMode = EditGame;\r
1155     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
1156       initialMode = EditPosition;\r
1157     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
1158       initialMode = Training;\r
1159     } else {\r
1160       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);\r
1161       DisplayFatalError(buf, 0, 2);\r
1162       return;\r
1163     }\r
1164 \r
1165     if (appData.matchMode) {\r
1166         /* Set up machine vs. machine match */\r
1167         if (appData.noChessProgram) {\r
1168             DisplayFatalError(_("Can't have a match with no chess programs"),\r
1169                               0, 2);\r
1170             return;\r
1171         }\r
1172         matchMode = TRUE;\r
1173         matchGame = 1;\r
1174         if (*appData.loadGameFile != NULLCHAR) {\r
1175             int index = appData.loadGameIndex; // [HGM] autoinc\r
1176             if(index<0) lastIndex = index = 1;\r
1177             if (!LoadGameFromFile(appData.loadGameFile,\r
1178                                   index,\r
1179                                   appData.loadGameFile, FALSE)) {\r
1180                 DisplayFatalError(_("Bad game file"), 0, 1);\r
1181                 return;\r
1182             }\r
1183         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1184             int index = appData.loadPositionIndex; // [HGM] autoinc\r
1185             if(index<0) lastIndex = index = 1;\r
1186             if (!LoadPositionFromFile(appData.loadPositionFile,\r
1187                                       index,\r
1188                                       appData.loadPositionFile)) {\r
1189                 DisplayFatalError(_("Bad position file"), 0, 1);\r
1190                 return;\r
1191             }\r
1192         }\r
1193         TwoMachinesEvent();\r
1194     } else if (*appData.cmailGameName != NULLCHAR) {\r
1195         /* Set up cmail mode */\r
1196         ReloadCmailMsgEvent(TRUE);\r
1197     } else {\r
1198         /* Set up other modes */\r
1199         if (initialMode == AnalyzeFile) {\r
1200           if (*appData.loadGameFile == NULLCHAR) {\r
1201             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);\r
1202             return;\r
1203           }\r
1204         }\r
1205         if (*appData.loadGameFile != NULLCHAR) {\r
1206             (void) LoadGameFromFile(appData.loadGameFile,\r
1207                                     appData.loadGameIndex,\r
1208                                     appData.loadGameFile, TRUE);\r
1209         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1210             (void) LoadPositionFromFile(appData.loadPositionFile,\r
1211                                         appData.loadPositionIndex,\r
1212                                         appData.loadPositionFile);\r
1213             /* [HGM] try to make self-starting even after FEN load */\r
1214             /* to allow automatic setup of fairy variants with wtm */\r
1215             if(initialMode == BeginningOfGame && !blackPlaysFirst) {\r
1216                 gameMode = BeginningOfGame;\r
1217                 setboardSpoiledMachineBlack = 1;\r
1218             }\r
1219             /* [HGM] loadPos: make that every new game uses the setup */\r
1220             /* from file as long as we do not switch variant          */\r
1221             if(!blackPlaysFirst) { int i;\r
1222                 startedFromPositionFile = TRUE;\r
1223                 CopyBoard(filePosition, boards[0]);\r
1224                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];\r
1225             }\r
1226         }\r
1227         if (initialMode == AnalyzeMode) {\r
1228           if (appData.noChessProgram) {\r
1229             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);\r
1230             return;\r
1231           }\r
1232           if (appData.icsActive) {\r
1233             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);\r
1234             return;\r
1235           }\r
1236           AnalyzeModeEvent();\r
1237         } else if (initialMode == AnalyzeFile) {\r
1238           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent\r
1239           ShowThinkingEvent();\r
1240           AnalyzeFileEvent();\r
1241           AnalysisPeriodicEvent(1);\r
1242         } else if (initialMode == MachinePlaysWhite) {\r
1243           if (appData.noChessProgram) {\r
1244             DisplayFatalError(_("MachineWhite mode requires a chess engine"),\r
1245                               0, 2);\r
1246             return;\r
1247           }\r
1248           if (appData.icsActive) {\r
1249             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),\r
1250                               0, 2);\r
1251             return;\r
1252           }\r
1253           MachineWhiteEvent();\r
1254         } else if (initialMode == MachinePlaysBlack) {\r
1255           if (appData.noChessProgram) {\r
1256             DisplayFatalError(_("MachineBlack mode requires a chess engine"),\r
1257                               0, 2);\r
1258             return;\r
1259           }\r
1260           if (appData.icsActive) {\r
1261             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),\r
1262                               0, 2);\r
1263             return;\r
1264           }\r
1265           MachineBlackEvent();\r
1266         } else if (initialMode == TwoMachinesPlay) {\r
1267           if (appData.noChessProgram) {\r
1268             DisplayFatalError(_("TwoMachines mode requires a chess engine"),\r
1269                               0, 2);\r
1270             return;\r
1271           }\r
1272           if (appData.icsActive) {\r
1273             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),\r
1274                               0, 2);\r
1275             return;\r
1276           }\r
1277           TwoMachinesEvent();\r
1278         } else if (initialMode == EditGame) {\r
1279           EditGameEvent();\r
1280         } else if (initialMode == EditPosition) {\r
1281           EditPositionEvent();\r
1282         } else if (initialMode == Training) {\r
1283           if (*appData.loadGameFile == NULLCHAR) {\r
1284             DisplayFatalError(_("Training mode requires a game file"), 0, 2);\r
1285             return;\r
1286           }\r
1287           TrainingEvent();\r
1288         }\r
1289     }\r
1290 }\r
1291 \r
1292 /*\r
1293  * Establish will establish a contact to a remote host.port.\r
1294  * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
1295  *  used to talk to the host.\r
1296  * Returns 0 if okay, error code if not.\r
1297  */\r
1298 int\r
1299 establish()\r
1300 {\r
1301     char buf[MSG_SIZ];\r
1302 \r
1303     if (*appData.icsCommPort != NULLCHAR) {\r
1304         /* Talk to the host through a serial comm port */\r
1305         return OpenCommPort(appData.icsCommPort, &icsPR);\r
1306 \r
1307     } else if (*appData.gateway != NULLCHAR) {\r
1308         if (*appData.remoteShell == NULLCHAR) {\r
1309             /* Use the rcmd protocol to run telnet program on a gateway host */\r
1310             sprintf(buf, "%s %s %s",\r
1311                     appData.telnetProgram, appData.icsHost, appData.icsPort);\r
1312             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
1313 \r
1314         } else {\r
1315             /* Use the rsh program to run telnet program on a gateway host */\r
1316             if (*appData.remoteUser == NULLCHAR) {\r
1317                 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,\r
1318                         appData.gateway, appData.telnetProgram,\r
1319                         appData.icsHost, appData.icsPort);\r
1320             } else {\r
1321                 sprintf(buf, "%s %s -l %s %s %s %s",\r
1322                         appData.remoteShell, appData.gateway, \r
1323                         appData.remoteUser, appData.telnetProgram,\r
1324                         appData.icsHost, appData.icsPort);\r
1325             }\r
1326             return StartChildProcess(buf, "", &icsPR);\r
1327 \r
1328         }\r
1329     } else if (appData.useTelnet) {\r
1330         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
1331 \r
1332     } else {\r
1333         /* TCP socket interface differs somewhat between\r
1334            Unix and NT; handle details in the front end.\r
1335            */\r
1336         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
1337     }\r
1338 }\r
1339 \r
1340 void\r
1341 show_bytes(fp, buf, count)\r
1342      FILE *fp;\r
1343      char *buf;\r
1344      int count;\r
1345 {\r
1346     while (count--) {\r
1347         if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
1348             fprintf(fp, "\\%03o", *buf & 0xff);\r
1349         } else {\r
1350             putc(*buf, fp);\r
1351         }\r
1352         buf++;\r
1353     }\r
1354     fflush(fp);\r
1355 }\r
1356 \r
1357 /* Returns an errno value */\r
1358 int\r
1359 OutputMaybeTelnet(pr, message, count, outError)\r
1360      ProcRef pr;\r
1361      char *message;\r
1362      int count;\r
1363      int *outError;\r
1364 {\r
1365     char buf[8192], *p, *q, *buflim;\r
1366     int left, newcount, outcount;\r
1367 \r
1368     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
1369         *appData.gateway != NULLCHAR) {\r
1370         if (appData.debugMode) {\r
1371             fprintf(debugFP, ">ICS: ");\r
1372             show_bytes(debugFP, message, count);\r
1373             fprintf(debugFP, "\n");\r
1374         }\r
1375         return OutputToProcess(pr, message, count, outError);\r
1376     }\r
1377 \r
1378     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
1379     p = message;\r
1380     q = buf;\r
1381     left = count;\r
1382     newcount = 0;\r
1383     while (left) {\r
1384         if (q >= buflim) {\r
1385             if (appData.debugMode) {\r
1386                 fprintf(debugFP, ">ICS: ");\r
1387                 show_bytes(debugFP, buf, newcount);\r
1388                 fprintf(debugFP, "\n");\r
1389             }\r
1390             outcount = OutputToProcess(pr, buf, newcount, outError);\r
1391             if (outcount < newcount) return -1; /* to be sure */\r
1392             q = buf;\r
1393             newcount = 0;\r
1394         }\r
1395         if (*p == '\n') {\r
1396             *q++ = '\r';\r
1397             newcount++;\r
1398         } else if (((unsigned char) *p) == TN_IAC) {\r
1399             *q++ = (char) TN_IAC;\r
1400             newcount ++;\r
1401         }\r
1402         *q++ = *p++;\r
1403         newcount++;\r
1404         left--;\r
1405     }\r
1406     if (appData.debugMode) {\r
1407         fprintf(debugFP, ">ICS: ");\r
1408         show_bytes(debugFP, buf, newcount);\r
1409         fprintf(debugFP, "\n");\r
1410     }\r
1411     outcount = OutputToProcess(pr, buf, newcount, outError);\r
1412     if (outcount < newcount) return -1; /* to be sure */\r
1413     return count;\r
1414 }\r
1415 \r
1416 void\r
1417 read_from_player(isr, closure, message, count, error)\r
1418      InputSourceRef isr;\r
1419      VOIDSTAR closure;\r
1420      char *message;\r
1421      int count;\r
1422      int error;\r
1423 {\r
1424     int outError, outCount;\r
1425     static int gotEof = 0;\r
1426 \r
1427     /* Pass data read from player on to ICS */\r
1428     if (count > 0) {\r
1429         gotEof = 0;\r
1430         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
1431         if (outCount < count) {\r
1432             DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1433         }\r
1434     } else if (count < 0) {\r
1435         RemoveInputSource(isr);\r
1436         DisplayFatalError(_("Error reading from keyboard"), error, 1);\r
1437     } else if (gotEof++ > 0) {\r
1438         RemoveInputSource(isr);\r
1439         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);\r
1440     }\r
1441 }\r
1442 \r
1443 void\r
1444 SendToICS(s)\r
1445      char *s;\r
1446 {\r
1447     int count, outCount, outError;\r
1448 \r
1449     if (icsPR == NULL) return;\r
1450 \r
1451     count = strlen(s);\r
1452     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
1453     if (outCount < count) {\r
1454         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1455     }\r
1456 }\r
1457 \r
1458 /* This is used for sending logon scripts to the ICS. Sending\r
1459    without a delay causes problems when using timestamp on ICC\r
1460    (at least on my machine). */\r
1461 void\r
1462 SendToICSDelayed(s,msdelay)\r
1463      char *s;\r
1464      long msdelay;\r
1465 {\r
1466     int count, outCount, outError;\r
1467 \r
1468     if (icsPR == NULL) return;\r
1469 \r
1470     count = strlen(s);\r
1471     if (appData.debugMode) {\r
1472         fprintf(debugFP, ">ICS: ");\r
1473         show_bytes(debugFP, s, count);\r
1474         fprintf(debugFP, "\n");\r
1475     }\r
1476     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
1477                                       msdelay);\r
1478     if (outCount < count) {\r
1479         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1480     }\r
1481 }\r
1482 \r
1483 \r
1484 /* Remove all highlighting escape sequences in s\r
1485    Also deletes any suffix starting with '(' \r
1486    */\r
1487 char *\r
1488 StripHighlightAndTitle(s)\r
1489      char *s;\r
1490 {\r
1491     static char retbuf[MSG_SIZ];\r
1492     char *p = retbuf;\r
1493 \r
1494     while (*s != NULLCHAR) {\r
1495         while (*s == '\033') {\r
1496             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1497             if (*s != NULLCHAR) s++;\r
1498         }\r
1499         while (*s != NULLCHAR && *s != '\033') {\r
1500             if (*s == '(' || *s == '[') {\r
1501                 *p = NULLCHAR;\r
1502                 return retbuf;\r
1503             }\r
1504             *p++ = *s++;\r
1505         }\r
1506     }\r
1507     *p = NULLCHAR;\r
1508     return retbuf;\r
1509 }\r
1510 \r
1511 /* Remove all highlighting escape sequences in s */\r
1512 char *\r
1513 StripHighlight(s)\r
1514      char *s;\r
1515 {\r
1516     static char retbuf[MSG_SIZ];\r
1517     char *p = retbuf;\r
1518 \r
1519     while (*s != NULLCHAR) {\r
1520         while (*s == '\033') {\r
1521             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1522             if (*s != NULLCHAR) s++;\r
1523         }\r
1524         while (*s != NULLCHAR && *s != '\033') {\r
1525             *p++ = *s++;\r
1526         }\r
1527     }\r
1528     *p = NULLCHAR;\r
1529     return retbuf;\r
1530 }\r
1531 \r
1532 char *variantNames[] = VARIANT_NAMES;\r
1533 char *\r
1534 VariantName(v)\r
1535      VariantClass v;\r
1536 {\r
1537     return variantNames[v];\r
1538 }\r
1539 \r
1540 \r
1541 /* Identify a variant from the strings the chess servers use or the\r
1542    PGN Variant tag names we use. */\r
1543 VariantClass\r
1544 StringToVariant(e)\r
1545      char *e;\r
1546 {\r
1547     char *p;\r
1548     int wnum = -1;\r
1549     VariantClass v = VariantNormal;\r
1550     int i, found = FALSE;\r
1551     char buf[MSG_SIZ];\r
1552 \r
1553     if (!e) return v;\r
1554 \r
1555     /* [HGM] skip over optional board-size prefixes */\r
1556     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||\r
1557         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
1558         while( *e++ != '_');\r
1559     }\r
1560 \r
1561     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1562       if (StrCaseStr(e, variantNames[i])) {\r
1563         v = (VariantClass) i;\r
1564         found = TRUE;\r
1565         break;\r
1566       }\r
1567     }\r
1568 \r
1569     if (!found) {\r
1570       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1571           || StrCaseStr(e, "wild/fr") \r
1572           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {\r
1573         v = VariantFischeRandom;\r
1574       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1575                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1576         p += i;\r
1577         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1578         if (isdigit(*p)) {\r
1579           wnum = atoi(p);\r
1580         } else {\r
1581           wnum = -1;\r
1582         }\r
1583         switch (wnum) {\r
1584         case 0: /* FICS only, actually */\r
1585         case 1:\r
1586           /* Castling legal even if K starts on d-file */\r
1587           v = VariantWildCastle;\r
1588           break;\r
1589         case 2:\r
1590         case 3:\r
1591         case 4:\r
1592           /* Castling illegal even if K & R happen to start in\r
1593              normal positions. */\r
1594           v = VariantNoCastle;\r
1595           break;\r
1596         case 5:\r
1597         case 7:\r
1598         case 8:\r
1599         case 10:\r
1600         case 11:\r
1601         case 12:\r
1602         case 13:\r
1603         case 14:\r
1604         case 15:\r
1605         case 18:\r
1606         case 19:\r
1607           /* Castling legal iff K & R start in normal positions */\r
1608           v = VariantNormal;\r
1609           break;\r
1610         case 6:\r
1611         case 20:\r
1612         case 21:\r
1613           /* Special wilds for position setup; unclear what to do here */\r
1614           v = VariantLoadable;\r
1615           break;\r
1616         case 9:\r
1617           /* Bizarre ICC game */\r
1618           v = VariantTwoKings;\r
1619           break;\r
1620         case 16:\r
1621           v = VariantKriegspiel;\r
1622           break;\r
1623         case 17:\r
1624           v = VariantLosers;\r
1625           break;\r
1626         case 22:\r
1627           v = VariantFischeRandom;\r
1628           break;\r
1629         case 23:\r
1630           v = VariantCrazyhouse;\r
1631           break;\r
1632         case 24:\r
1633           v = VariantBughouse;\r
1634           break;\r
1635         case 25:\r
1636           v = Variant3Check;\r
1637           break;\r
1638         case 26:\r
1639           /* Not quite the same as FICS suicide! */\r
1640           v = VariantGiveaway;\r
1641           break;\r
1642         case 27:\r
1643           v = VariantAtomic;\r
1644           break;\r
1645         case 28:\r
1646           v = VariantShatranj;\r
1647           break;\r
1648 \r
1649         /* Temporary names for future ICC types.  The name *will* change in \r
1650            the next xboard/WinBoard release after ICC defines it. */\r
1651         case 29:\r
1652           v = Variant29;\r
1653           break;\r
1654         case 30:\r
1655           v = Variant30;\r
1656           break;\r
1657         case 31:\r
1658           v = Variant31;\r
1659           break;\r
1660         case 32:\r
1661           v = Variant32;\r
1662           break;\r
1663         case 33:\r
1664           v = Variant33;\r
1665           break;\r
1666         case 34:\r
1667           v = Variant34;\r
1668           break;\r
1669         case 35:\r
1670           v = Variant35;\r
1671           break;\r
1672         case 36:\r
1673           v = Variant36;\r
1674           break;\r
1675         case 37:\r
1676           v = VariantShogi;\r
1677           break;\r
1678         case 38:\r
1679           v = VariantXiangqi;\r
1680           break;\r
1681         case 39:\r
1682           v = VariantCourier;\r
1683           break;\r
1684         case 40:\r
1685           v = VariantGothic;\r
1686           break;\r
1687         case 41:\r
1688           v = VariantCapablanca;\r
1689           break;\r
1690         case 42:\r
1691           v = VariantKnightmate;\r
1692           break;\r
1693         case 43:\r
1694           v = VariantFairy;\r
1695           break;\r
1696         case 44:\r
1697           v = VariantCylinder;\r
1698           break;\r
1699         case 45:\r
1700           v = VariantFalcon;\r
1701           break;\r
1702         case 46:\r
1703           v = VariantCapaRandom;\r
1704           break;\r
1705         case 47:\r
1706           v = VariantBerolina;\r
1707           break;\r
1708         case 48:\r
1709           v = VariantJanus;\r
1710           break;\r
1711         case 49:\r
1712           v = VariantSuper;\r
1713           break;\r
1714         case 50:\r
1715           v = VariantGreat;\r
1716           break;\r
1717         case -1:\r
1718           /* Found "wild" or "w" in the string but no number;\r
1719              must assume it's normal chess. */\r
1720           v = VariantNormal;\r
1721           break;\r
1722         default:\r
1723           sprintf(buf, _("Unknown wild type %d"), wnum);\r
1724           DisplayError(buf, 0);\r
1725           v = VariantUnknown;\r
1726           break;\r
1727         }\r
1728       }\r
1729     }\r
1730     if (appData.debugMode) {\r
1731       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),\r
1732               e, wnum, VariantName(v));\r
1733     }\r
1734     return v;\r
1735 }\r
1736 \r
1737 static int leftover_start = 0, leftover_len = 0;\r
1738 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1739 \r
1740 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1741    advance *index beyond it, and set leftover_start to the new value of\r
1742    *index; else return FALSE.  If pattern contains the character '*', it\r
1743    matches any sequence of characters not containing '\r', '\n', or the\r
1744    character following the '*' (if any), and the matched sequence(s) are\r
1745    copied into star_match.\r
1746    */\r
1747 int\r
1748 looking_at(buf, index, pattern)\r
1749      char *buf;\r
1750      int *index;\r
1751      char *pattern;\r
1752 {\r
1753     char *bufp = &buf[*index], *patternp = pattern;\r
1754     int star_count = 0;\r
1755     char *matchp = star_match[0];\r
1756     \r
1757     for (;;) {\r
1758         if (*patternp == NULLCHAR) {\r
1759             *index = leftover_start = bufp - buf;\r
1760             *matchp = NULLCHAR;\r
1761             return TRUE;\r
1762         }\r
1763         if (*bufp == NULLCHAR) return FALSE;\r
1764         if (*patternp == '*') {\r
1765             if (*bufp == *(patternp + 1)) {\r
1766                 *matchp = NULLCHAR;\r
1767                 matchp = star_match[++star_count];\r
1768                 patternp += 2;\r
1769                 bufp++;\r
1770                 continue;\r
1771             } else if (*bufp == '\n' || *bufp == '\r') {\r
1772                 patternp++;\r
1773                 if (*patternp == NULLCHAR)\r
1774                   continue;\r
1775                 else\r
1776                   return FALSE;\r
1777             } else {\r
1778                 *matchp++ = *bufp++;\r
1779                 continue;\r
1780             }\r
1781         }\r
1782         if (*patternp != *bufp) return FALSE;\r
1783         patternp++;\r
1784         bufp++;\r
1785     }\r
1786 }\r
1787 \r
1788 void\r
1789 SendToPlayer(data, length)\r
1790      char *data;\r
1791      int length;\r
1792 {\r
1793     int error, outCount;\r
1794     outCount = OutputToProcess(NoProc, data, length, &error);\r
1795     if (outCount < length) {\r
1796         DisplayFatalError(_("Error writing to display"), error, 1);\r
1797     }\r
1798 }\r
1799 \r
1800 void\r
1801 PackHolding(packed, holding)\r
1802      char packed[];\r
1803      char *holding;\r
1804 {\r
1805     char *p = holding;\r
1806     char *q = packed;\r
1807     int runlength = 0;\r
1808     int curr = 9999;\r
1809     do {\r
1810         if (*p == curr) {\r
1811             runlength++;\r
1812         } else {\r
1813             switch (runlength) {\r
1814               case 0:\r
1815                 break;\r
1816               case 1:\r
1817                 *q++ = curr;\r
1818                 break;\r
1819               case 2:\r
1820                 *q++ = curr;\r
1821                 *q++ = curr;\r
1822                 break;\r
1823               default:\r
1824                 sprintf(q, "%d", runlength);\r
1825                 while (*q) q++;\r
1826                 *q++ = curr;\r
1827                 break;\r
1828             }\r
1829             runlength = 1;\r
1830             curr = *p;\r
1831         }\r
1832     } while (*p++);\r
1833     *q = NULLCHAR;\r
1834 }\r
1835 \r
1836 /* Telnet protocol requests from the front end */\r
1837 void\r
1838 TelnetRequest(ddww, option)\r
1839      unsigned char ddww, option;\r
1840 {\r
1841     unsigned char msg[3];\r
1842     int outCount, outError;\r
1843 \r
1844     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1845 \r
1846     if (appData.debugMode) {\r
1847         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1848         switch (ddww) {\r
1849           case TN_DO:\r
1850             ddwwStr = "DO";\r
1851             break;\r
1852           case TN_DONT:\r
1853             ddwwStr = "DONT";\r
1854             break;\r
1855           case TN_WILL:\r
1856             ddwwStr = "WILL";\r
1857             break;\r
1858           case TN_WONT:\r
1859             ddwwStr = "WONT";\r
1860             break;\r
1861           default:\r
1862             ddwwStr = buf1;\r
1863             sprintf(buf1, "%d", ddww);\r
1864             break;\r
1865         }\r
1866         switch (option) {\r
1867           case TN_ECHO:\r
1868             optionStr = "ECHO";\r
1869             break;\r
1870           default:\r
1871             optionStr = buf2;\r
1872             sprintf(buf2, "%d", option);\r
1873             break;\r
1874         }\r
1875         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1876     }\r
1877     msg[0] = TN_IAC;\r
1878     msg[1] = ddww;\r
1879     msg[2] = option;\r
1880     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1881     if (outCount < 3) {\r
1882         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1883     }\r
1884 }\r
1885 \r
1886 void\r
1887 DoEcho()\r
1888 {\r
1889     if (!appData.icsActive) return;\r
1890     TelnetRequest(TN_DO, TN_ECHO);\r
1891 }\r
1892 \r
1893 void\r
1894 DontEcho()\r
1895 {\r
1896     if (!appData.icsActive) return;\r
1897     TelnetRequest(TN_DONT, TN_ECHO);\r
1898 }\r
1899 \r
1900 void\r
1901 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
1902 {\r
1903     /* put the holdings sent to us by the server on the board holdings area */\r
1904     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
1905     char p;\r
1906     ChessSquare piece;\r
1907 \r
1908     if(gameInfo.holdingsWidth < 2)  return;\r
1909 \r
1910     if( (int)lowestPiece >= BlackPawn ) {\r
1911         holdingsColumn = 0;\r
1912         countsColumn = 1;\r
1913         holdingsStartRow = BOARD_HEIGHT-1;\r
1914         direction = -1;\r
1915     } else {\r
1916         holdingsColumn = BOARD_WIDTH-1;\r
1917         countsColumn = BOARD_WIDTH-2;\r
1918         holdingsStartRow = 0;\r
1919         direction = 1;\r
1920     }\r
1921 \r
1922     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
1923         board[i][holdingsColumn] = EmptySquare;\r
1924         board[i][countsColumn]   = (ChessSquare) 0;\r
1925     }\r
1926     while( (p=*holdings++) != NULLCHAR ) {\r
1927         piece = CharToPiece( ToUpper(p) );\r
1928         if(piece == EmptySquare) continue;\r
1929         /*j = (int) piece - (int) WhitePawn;*/\r
1930         j = PieceToNumber(piece);\r
1931         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1932         if(j < 0) continue;               /* should not happen */\r
1933         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );\r
1934         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
1935         board[holdingsStartRow+j*direction][countsColumn]++;\r
1936     }\r
1937 \r
1938 }\r
1939 \r
1940 \r
1941 void\r
1942 VariantSwitch(Board board, VariantClass newVariant)\r
1943 {\r
1944    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;\r
1945    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;\r
1946    Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;\r
1947 \r
1948    startedFromPositionFile = FALSE;\r
1949    if(gameInfo.variant == newVariant) return;\r
1950 \r
1951    /* [HGM] This routine is called each time an assignment is made to\r
1952     * gameInfo.variant during a game, to make sure the board sizes\r
1953     * are set to match the new variant. If that means adding or deleting\r
1954     * holdings, we shift the playing board accordingly\r
1955     * This kludge is needed because in ICS observe mode, we get boards\r
1956     * of an ongoing game without knowing the variant, and learn about the\r
1957     * latter only later. This can be because of the move list we requested,\r
1958     * in which case the game history is refilled from the beginning anyway,\r
1959     * but also when receiving holdings of a crazyhouse game. In the latter\r
1960     * case we want to add those holdings to the already received position.\r
1961     */\r
1962 \r
1963 \r
1964   if (appData.debugMode) {\r
1965     fprintf(debugFP, "Switch board from %s to %s\n",\r
1966                VariantName(gameInfo.variant), VariantName(newVariant));\r
1967     setbuf(debugFP, NULL);\r
1968   }\r
1969     shuffleOpenings = 0;       /* [HGM] shuffle */\r
1970     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
1971     switch(newVariant) {\r
1972             case VariantShogi:\r
1973               newWidth = 9;  newHeight = 9;\r
1974               gameInfo.holdingsSize = 7;\r
1975             case VariantBughouse:\r
1976             case VariantCrazyhouse:\r
1977               newHoldingsWidth = 2; break;\r
1978             default:\r
1979               newHoldingsWidth = gameInfo.holdingsSize = 0;\r
1980     }\r
1981 \r
1982     if(newWidth  != gameInfo.boardWidth  ||\r
1983        newHeight != gameInfo.boardHeight ||\r
1984        newHoldingsWidth != gameInfo.holdingsWidth ) {\r
1985 \r
1986         /* shift position to new playing area, if needed */\r
1987         if(newHoldingsWidth > gameInfo.holdingsWidth) {\r
1988            for(i=0; i<BOARD_HEIGHT; i++) \r
1989                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)\r
1990                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
1991                                                      board[i][j];\r
1992            for(i=0; i<newHeight; i++) {\r
1993                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;\r
1994                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;\r
1995            }\r
1996         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {\r
1997            for(i=0; i<BOARD_HEIGHT; i++)\r
1998                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
1999                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
2000                                                  board[i][j];\r
2001         }\r
2002 \r
2003         gameInfo.boardWidth  = newWidth;\r
2004         gameInfo.boardHeight = newHeight;\r
2005         gameInfo.holdingsWidth = newHoldingsWidth;\r
2006         gameInfo.variant = newVariant;\r
2007         InitDrawingSizes(-2, 0);\r
2008 \r
2009         /* [HGM] The following should definitely be solved in a better way */\r
2010 #if 0\r
2011         CopyBoard(board, tempBoard); /* save position in case it is board[0] */\r
2012         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];\r
2013         saveEP = epStatus[0];\r
2014 #endif\r
2015         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */\r
2016 #if 0\r
2017         epStatus[0] = saveEP;\r
2018         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];\r
2019         CopyBoard(tempBoard, board); /* restore position received from ICS   */\r
2020 #endif\r
2021     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }\r
2022 \r
2023     forwardMostMove = oldForwardMostMove;\r
2024     backwardMostMove = oldBackwardMostMove;\r
2025     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */\r
2026 }\r
2027 \r
2028 static int loggedOn = FALSE;\r
2029 \r
2030 /*-- Game start info cache: --*/\r
2031 int gs_gamenum;\r
2032 char gs_kind[MSG_SIZ];\r
2033 static char player1Name[128] = "";\r
2034 static char player2Name[128] = "";\r
2035 static int player1Rating = -1;\r
2036 static int player2Rating = -1;\r
2037 /*----------------------------*/\r
2038 \r
2039 ColorClass curColor = ColorNormal;\r
2040 int suppressKibitz = 0;\r
2041 \r
2042 void\r
2043 read_from_ics(isr, closure, data, count, error)\r
2044      InputSourceRef isr;\r
2045      VOIDSTAR closure;\r
2046      char *data;\r
2047      int count;\r
2048      int error;\r
2049 {\r
2050 #define BUF_SIZE 8192\r
2051 #define STARTED_NONE 0\r
2052 #define STARTED_MOVES 1\r
2053 #define STARTED_BOARD 2\r
2054 #define STARTED_OBSERVE 3\r
2055 #define STARTED_HOLDINGS 4\r
2056 #define STARTED_CHATTER 5\r
2057 #define STARTED_COMMENT 6\r
2058 #define STARTED_MOVES_NOHIDE 7\r
2059     \r
2060     static int started = STARTED_NONE;\r
2061     static char parse[20000];\r
2062     static int parse_pos = 0;\r
2063     static char buf[BUF_SIZE + 1];\r
2064     static int firstTime = TRUE, intfSet = FALSE;\r
2065     static ColorClass prevColor = ColorNormal;\r
2066     static int savingComment = FALSE;\r
2067     char str[500];\r
2068     int i, oldi;\r
2069     int buf_len;\r
2070     int next_out;\r
2071     int tkind;\r
2072     int backup;    /* [DM] For zippy color lines */\r
2073     char *p;\r
2074 \r
2075     if (appData.debugMode) {\r
2076       if (!error) {\r
2077         fprintf(debugFP, "<ICS: ");\r
2078         show_bytes(debugFP, data, count);\r
2079         fprintf(debugFP, "\n");\r
2080       }\r
2081     }\r
2082 \r
2083     if (appData.debugMode) { int f = forwardMostMove;\r
2084         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,\r
2085                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
2086     }\r
2087     if (count > 0) {\r
2088         /* If last read ended with a partial line that we couldn't parse,\r
2089            prepend it to the new read and try again. */\r
2090         if (leftover_len > 0) {\r
2091             for (i=0; i<leftover_len; i++)\r
2092               buf[i] = buf[leftover_start + i];\r
2093         }\r
2094 \r
2095         /* Copy in new characters, removing nulls and \r's */\r
2096         buf_len = leftover_len;\r
2097         for (i = 0; i < count; i++) {\r
2098             if (data[i] != NULLCHAR && data[i] != '\r')\r
2099               buf[buf_len++] = data[i];\r
2100             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && \r
2101                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') \r
2102                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous\r
2103         }\r
2104 \r
2105         buf[buf_len] = NULLCHAR;\r
2106         next_out = leftover_len;\r
2107         leftover_start = 0;\r
2108         \r
2109         i = 0;\r
2110         while (i < buf_len) {\r
2111             /* Deal with part of the TELNET option negotiation\r
2112                protocol.  We refuse to do anything beyond the\r
2113                defaults, except that we allow the WILL ECHO option,\r
2114                which ICS uses to turn off password echoing when we are\r
2115                directly connected to it.  We reject this option\r
2116                if localLineEditing mode is on (always on in xboard)\r
2117                and we are talking to port 23, which might be a real\r
2118                telnet server that will try to keep WILL ECHO on permanently.\r
2119              */\r
2120             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
2121                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
2122                 unsigned char option;\r
2123                 oldi = i;\r
2124                 switch ((unsigned char) buf[++i]) {\r
2125                   case TN_WILL:\r
2126                     if (appData.debugMode)\r
2127                       fprintf(debugFP, "\n<WILL ");\r
2128                     switch (option = (unsigned char) buf[++i]) {\r
2129                       case TN_ECHO:\r
2130                         if (appData.debugMode)\r
2131                           fprintf(debugFP, "ECHO ");\r
2132                         /* Reply only if this is a change, according\r
2133                            to the protocol rules. */\r
2134                         if (remoteEchoOption) break;\r
2135                         if (appData.localLineEditing &&\r
2136                             atoi(appData.icsPort) == TN_PORT) {\r
2137                             TelnetRequest(TN_DONT, TN_ECHO);\r
2138                         } else {\r
2139                             EchoOff();\r
2140                             TelnetRequest(TN_DO, TN_ECHO);\r
2141                             remoteEchoOption = TRUE;\r
2142                         }\r
2143                         break;\r
2144                       default:\r
2145                         if (appData.debugMode)\r
2146                           fprintf(debugFP, "%d ", option);\r
2147                         /* Whatever this is, we don't want it. */\r
2148                         TelnetRequest(TN_DONT, option);\r
2149                         break;\r
2150                     }\r
2151                     break;\r
2152                   case TN_WONT:\r
2153                     if (appData.debugMode)\r
2154                       fprintf(debugFP, "\n<WONT ");\r
2155                     switch (option = (unsigned char) buf[++i]) {\r
2156                       case TN_ECHO:\r
2157                         if (appData.debugMode)\r
2158                           fprintf(debugFP, "ECHO ");\r
2159                         /* Reply only if this is a change, according\r
2160                            to the protocol rules. */\r
2161                         if (!remoteEchoOption) break;\r
2162                         EchoOn();\r
2163                         TelnetRequest(TN_DONT, TN_ECHO);\r
2164                         remoteEchoOption = FALSE;\r
2165                         break;\r
2166                       default:\r
2167                         if (appData.debugMode)\r
2168                           fprintf(debugFP, "%d ", (unsigned char) option);\r
2169                         /* Whatever this is, it must already be turned\r
2170                            off, because we never agree to turn on\r
2171                            anything non-default, so according to the\r
2172                            protocol rules, we don't reply. */\r
2173                         break;\r
2174                     }\r
2175                     break;\r
2176                   case TN_DO:\r
2177                     if (appData.debugMode)\r
2178                       fprintf(debugFP, "\n<DO ");\r
2179                     switch (option = (unsigned char) buf[++i]) {\r
2180                       default:\r
2181                         /* Whatever this is, we refuse to do it. */\r
2182                         if (appData.debugMode)\r
2183                           fprintf(debugFP, "%d ", option);\r
2184                         TelnetRequest(TN_WONT, option);\r
2185                         break;\r
2186                     }\r
2187                     break;\r
2188                   case TN_DONT:\r
2189                     if (appData.debugMode)\r
2190                       fprintf(debugFP, "\n<DONT ");\r
2191                     switch (option = (unsigned char) buf[++i]) {\r
2192                       default:\r
2193                         if (appData.debugMode)\r
2194                           fprintf(debugFP, "%d ", option);\r
2195                         /* Whatever this is, we are already not doing\r
2196                            it, because we never agree to do anything\r
2197                            non-default, so according to the protocol\r
2198                            rules, we don't reply. */\r
2199                         break;\r
2200                     }\r
2201                     break;\r
2202                   case TN_IAC:\r
2203                     if (appData.debugMode)\r
2204                       fprintf(debugFP, "\n<IAC ");\r
2205                     /* Doubled IAC; pass it through */\r
2206                     i--;\r
2207                     break;\r
2208                   default:\r
2209                     if (appData.debugMode)\r
2210                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
2211                     /* Drop all other telnet commands on the floor */\r
2212                     break;\r
2213                 }\r
2214                 if (oldi > next_out)\r
2215                   SendToPlayer(&buf[next_out], oldi - next_out);\r
2216                 if (++i > next_out)\r
2217                   next_out = i;\r
2218                 continue;\r
2219             }\r
2220                 \r
2221             /* OK, this at least will *usually* work */\r
2222             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
2223                 loggedOn = TRUE;\r
2224             }\r
2225             \r
2226             if (loggedOn && !intfSet) {\r
2227                 if (ics_type == ICS_ICC) {\r
2228                   sprintf(str,\r
2229                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
2230                           programVersion);\r
2231 \r
2232                 } else if (ics_type == ICS_CHESSNET) {\r
2233                   sprintf(str, "/style 12\n");\r
2234                 } else {\r
2235                   strcpy(str, "alias $ @\n$set interface ");\r
2236                   strcat(str, programVersion);\r
2237                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
2238 #ifdef WIN32\r
2239                   strcat(str, "$iset nohighlight 1\n");\r
2240 #endif\r
2241                   strcat(str, "$iset lock 1\n$style 12\n");\r
2242                 }\r
2243                 SendToICS(str);\r
2244                 intfSet = TRUE;\r
2245             }\r
2246 \r
2247             if (started == STARTED_COMMENT) {\r
2248                 /* Accumulate characters in comment */\r
2249                 parse[parse_pos++] = buf[i];\r
2250                 if (buf[i] == '\n') {\r
2251                     parse[parse_pos] = NULLCHAR;\r
2252                     if(!suppressKibitz) // [HGM] kibitz\r
2253                         AppendComment(forwardMostMove, StripHighlight(parse));\r
2254                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window\r
2255                         int nrDigit = 0, nrAlph = 0, i;\r
2256                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input\r
2257                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }\r
2258                         parse[parse_pos] = NULLCHAR;\r
2259                         // try to be smart: if it does not look like search info, it should go to\r
2260                         // ICS interaction window after all, not to engine-output window.\r
2261                         for(i=0; i<parse_pos; i++) { // count letters and digits\r
2262                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');\r
2263                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');\r
2264                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');\r
2265                         }\r
2266                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info\r
2267                             OutputKibitz(suppressKibitz, parse);\r
2268                         } else {\r
2269                             char tmp[MSG_SIZ];\r
2270                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);\r
2271                             SendToPlayer(tmp, strlen(tmp));\r
2272                         }\r
2273                     }\r
2274                     started = STARTED_NONE;\r
2275                 } else {\r
2276                     /* Don't match patterns against characters in chatter */\r
2277                     i++;\r
2278                     continue;\r
2279                 }\r
2280             }\r
2281             if (started == STARTED_CHATTER) {\r
2282                 if (buf[i] != '\n') {\r
2283                     /* Don't match patterns against characters in chatter */\r
2284                     i++;\r
2285                     continue;\r
2286                 }\r
2287                 started = STARTED_NONE;\r
2288             }\r
2289 \r
2290             /* Kludge to deal with rcmd protocol */\r
2291             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2292                 DisplayFatalError(&buf[1], 0, 1);\r
2293                 continue;\r
2294             } else {\r
2295                 firstTime = FALSE;\r
2296             }\r
2297 \r
2298             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2299                 ics_type = ICS_ICC;\r
2300                 ics_prefix = "/";\r
2301                 if (appData.debugMode)\r
2302                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2303                 continue;\r
2304             }\r
2305             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2306                 ics_type = ICS_FICS;\r
2307                 ics_prefix = "$";\r
2308                 if (appData.debugMode)\r
2309                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2310                 continue;\r
2311             }\r
2312             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2313                 ics_type = ICS_CHESSNET;\r
2314                 ics_prefix = "/";\r
2315                 if (appData.debugMode)\r
2316                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2317                 continue;\r
2318             }\r
2319 \r
2320             if (!loggedOn &&\r
2321                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2322                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2323                  looking_at(buf, &i, "will be \"*\""))) {\r
2324               strcpy(ics_handle, star_match[0]);\r
2325               continue;\r
2326             }\r
2327 \r
2328             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2329               char buf[MSG_SIZ];\r
2330               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2331               DisplayIcsInteractionTitle(buf);\r
2332               have_set_title = TRUE;\r
2333             }\r
2334 \r
2335             /* skip finger notes */\r
2336             if (started == STARTED_NONE &&\r
2337                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2338                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2339                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2340               started = STARTED_CHATTER;\r
2341               i += 3;\r
2342               continue;\r
2343             }\r
2344 \r
2345             /* skip formula vars */\r
2346             if (started == STARTED_NONE &&\r
2347                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2348               started = STARTED_CHATTER;\r
2349               i += 3;\r
2350               continue;\r
2351             }\r
2352 \r
2353             oldi = i;\r
2354             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window\r
2355             if (appData.autoKibitz && started == STARTED_NONE && \r
2356                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze\r
2357                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {\r
2358                 if(looking_at(buf, &i, "* kibitzes: ") &&\r
2359                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || \r
2360                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent\r
2361                         suppressKibitz = TRUE;\r
2362                         if((StrStr(star_match[0], gameInfo.white) == star_match[0])\r
2363                                 && (gameMode == IcsPlayingWhite) ||\r
2364                            (StrStr(star_match[0], gameInfo.black) == star_match[0])\r
2365                                 && (gameMode == IcsPlayingBlack)   ) // opponent kibitz\r
2366                             started = STARTED_CHATTER; // own kibitz we simply discard\r
2367                         else {\r
2368                             started = STARTED_COMMENT; // make sure it will be collected in parse[]\r
2369                             parse_pos = 0; parse[0] = NULLCHAR;\r
2370                             savingComment = TRUE;\r
2371                             suppressKibitz = gameMode != IcsObserving ? 2 :\r
2372                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;\r
2373                         } \r
2374                         continue;\r
2375                 } else\r
2376                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz\r
2377                     started = STARTED_CHATTER;\r
2378                     suppressKibitz = TRUE;\r
2379                 }\r
2380             } // [HGM] kibitz: end of patch\r
2381 \r
2382             if (appData.zippyTalk || appData.zippyPlay) {\r
2383                 /* [DM] Backup address for color zippy lines */\r
2384                 backup = i;\r
2385 #if ZIPPY\r
2386        #ifdef WIN32\r
2387                if (loggedOn == TRUE)\r
2388                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||\r
2389                           (appData.zippyPlay && ZippyMatch(buf, &backup)));\r
2390        #else\r
2391                 if (ZippyControl(buf, &i) ||\r
2392                     ZippyConverse(buf, &i) ||\r
2393                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2394                       loggedOn = TRUE;\r
2395                       if (!appData.colorize) continue;\r
2396                 }\r
2397        #endif\r
2398 #endif\r
2399             } // [DM] 'else { ' deleted\r
2400                 if (/* Don't color "message" or "messages" output */\r
2401                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2402                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2403                     looking_at(buf, &i, "--* (*:*): ") ||\r
2404                     /* Regular tells and says */\r
2405                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2406                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2407                     looking_at(buf, &i, "* says: ") ||\r
2408                     /* Message notifications (same color as tells) */\r
2409                     looking_at(buf, &i, "* has left a message ") ||\r
2410                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2411                     /* Whispers and kibitzes */\r
2412                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2413                     looking_at(buf, &i, "* kibitzes: ") ||\r
2414                     /* Channel tells */\r
2415                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2416 \r
2417                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2418                       /* Avoid "tells you:" spoofs in channels */\r
2419                      tkind = 3;\r
2420                   }\r
2421                   if (star_match[0][0] == NULLCHAR ||\r
2422                       strchr(star_match[0], ' ') ||\r
2423                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2424                     /* Reject bogus matches */\r
2425                     i = oldi;\r
2426                   } else {\r
2427                     if (appData.colorize) {\r
2428                       if (oldi > next_out) {\r
2429                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2430                         next_out = oldi;\r
2431                       }\r
2432                       switch (tkind) {\r
2433                       case 1:\r
2434                         Colorize(ColorTell, FALSE);\r
2435                         curColor = ColorTell;\r
2436                         break;\r
2437                       case 2:\r
2438                         Colorize(ColorKibitz, FALSE);\r
2439                         curColor = ColorKibitz;\r
2440                         break;\r
2441                       case 3:\r
2442                         p = strrchr(star_match[1], '(');\r
2443                         if (p == NULL) {\r
2444                           p = star_match[1];\r
2445                         } else {\r
2446                           p++;\r
2447                         }\r
2448                         if (atoi(p) == 1) {\r
2449                           Colorize(ColorChannel1, FALSE);\r
2450                           curColor = ColorChannel1;\r
2451                         } else {\r
2452                           Colorize(ColorChannel, FALSE);\r
2453                           curColor = ColorChannel;\r
2454                         }\r
2455                         break;\r
2456                       case 5:\r
2457                         curColor = ColorNormal;\r
2458                         break;\r
2459                       }\r
2460                     }\r
2461                     if (started == STARTED_NONE && appData.autoComment &&\r
2462                         (gameMode == IcsObserving ||\r
2463                          gameMode == IcsPlayingWhite ||\r
2464                          gameMode == IcsPlayingBlack)) {\r
2465                       parse_pos = i - oldi;\r
2466                       memcpy(parse, &buf[oldi], parse_pos);\r
2467                       parse[parse_pos] = NULLCHAR;\r
2468                       started = STARTED_COMMENT;\r
2469                       savingComment = TRUE;\r
2470                     } else {\r
2471                       started = STARTED_CHATTER;\r
2472                       savingComment = FALSE;\r
2473                     }\r
2474                     loggedOn = TRUE;\r
2475                     continue;\r
2476                   }\r
2477                 }\r
2478 \r
2479                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2480                     looking_at(buf, &i, "* c-shouts: ")) {\r
2481                     if (appData.colorize) {\r
2482                         if (oldi > next_out) {\r
2483                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2484                             next_out = oldi;\r
2485                         }\r
2486                         Colorize(ColorSShout, FALSE);\r
2487                         curColor = ColorSShout;\r
2488                     }\r
2489                     loggedOn = TRUE;\r
2490                     started = STARTED_CHATTER;\r
2491                     continue;\r
2492                 }\r
2493 \r
2494                 if (looking_at(buf, &i, "--->")) {\r
2495                     loggedOn = TRUE;\r
2496                     continue;\r
2497                 }\r
2498 \r
2499                 if (looking_at(buf, &i, "* shouts: ") ||\r
2500                     looking_at(buf, &i, "--> ")) {\r
2501                     if (appData.colorize) {\r
2502                         if (oldi > next_out) {\r
2503                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2504                             next_out = oldi;\r
2505                         }\r
2506                         Colorize(ColorShout, FALSE);\r
2507                         curColor = ColorShout;\r
2508                     }\r
2509                     loggedOn = TRUE;\r
2510                     started = STARTED_CHATTER;\r
2511                     continue;\r
2512                 }\r
2513 \r
2514                 if (looking_at( buf, &i, "Challenge:")) {\r
2515                     if (appData.colorize) {\r
2516                         if (oldi > next_out) {\r
2517                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2518                             next_out = oldi;\r
2519                         }\r
2520                         Colorize(ColorChallenge, FALSE);\r
2521                         curColor = ColorChallenge;\r
2522                     }\r
2523                     loggedOn = TRUE;\r
2524                     continue;\r
2525                 }\r
2526 \r
2527                 if (looking_at(buf, &i, "* offers you") ||\r
2528                     looking_at(buf, &i, "* offers to be") ||\r
2529                     looking_at(buf, &i, "* would like to") ||\r
2530                     looking_at(buf, &i, "* requests to") ||\r
2531                     looking_at(buf, &i, "Your opponent offers") ||\r
2532                     looking_at(buf, &i, "Your opponent requests")) {\r
2533 \r
2534                     if (appData.colorize) {\r
2535                         if (oldi > next_out) {\r
2536                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2537                             next_out = oldi;\r
2538                         }\r
2539                         Colorize(ColorRequest, FALSE);\r
2540                         curColor = ColorRequest;\r
2541                     }\r
2542                     continue;\r
2543                 }\r
2544 \r
2545                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2546                     if (appData.colorize) {\r
2547                         if (oldi > next_out) {\r
2548                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2549                             next_out = oldi;\r
2550                         }\r
2551                         Colorize(ColorSeek, FALSE);\r
2552                         curColor = ColorSeek;\r
2553                     }\r
2554                     continue;\r
2555             }\r
2556 \r
2557             if (looking_at(buf, &i, "\\   ")) {\r
2558                 if (prevColor != ColorNormal) {\r
2559                     if (oldi > next_out) {\r
2560                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2561                         next_out = oldi;\r
2562                     }\r
2563                     Colorize(prevColor, TRUE);\r
2564                     curColor = prevColor;\r
2565                 }\r
2566                 if (savingComment) {\r
2567                     parse_pos = i - oldi;\r
2568                     memcpy(parse, &buf[oldi], parse_pos);\r
2569                     parse[parse_pos] = NULLCHAR;\r
2570                     started = STARTED_COMMENT;\r
2571                 } else {\r
2572                     started = STARTED_CHATTER;\r
2573                 }\r
2574                 continue;\r
2575             }\r
2576 \r
2577             if (looking_at(buf, &i, "Black Strength :") ||\r
2578                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2579                 looking_at(buf, &i, "<10>") ||\r
2580                 looking_at(buf, &i, "#@#")) {\r
2581                 /* Wrong board style */\r
2582                 loggedOn = TRUE;\r
2583                 SendToICS(ics_prefix);\r
2584                 SendToICS("set style 12\n");\r
2585                 SendToICS(ics_prefix);\r
2586                 SendToICS("refresh\n");\r
2587                 continue;\r
2588             }\r
2589             \r
2590             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2591                 ICSInitScript();\r
2592                 have_sent_ICS_logon = 1;\r
2593                 continue;\r
2594             }\r
2595               \r
2596             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2597                 (looking_at(buf, &i, "\n<12> ") ||\r
2598                  looking_at(buf, &i, "<12> "))) {\r
2599                 loggedOn = TRUE;\r
2600                 if (oldi > next_out) {\r
2601                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2602                 }\r
2603                 next_out = i;\r
2604                 started = STARTED_BOARD;\r
2605                 parse_pos = 0;\r
2606                 continue;\r
2607             }\r
2608 \r
2609             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2610                 looking_at(buf, &i, "<b1> ")) {\r
2611                 if (oldi > next_out) {\r
2612                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2613                 }\r
2614                 next_out = i;\r
2615                 started = STARTED_HOLDINGS;\r
2616                 parse_pos = 0;\r
2617                 continue;\r
2618             }\r
2619 \r
2620             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2621                 loggedOn = TRUE;\r
2622                 /* Header for a move list -- first line */\r
2623 \r
2624                 switch (ics_getting_history) {\r
2625                   case H_FALSE:\r
2626                     switch (gameMode) {\r
2627                       case IcsIdle:\r
2628                       case BeginningOfGame:\r
2629                         /* User typed "moves" or "oldmoves" while we\r
2630                            were idle.  Pretend we asked for these\r
2631                            moves and soak them up so user can step\r
2632                            through them and/or save them.\r
2633                            */\r
2634                         Reset(FALSE, TRUE);\r
2635                         gameMode = IcsObserving;\r
2636                         ModeHighlight();\r
2637                         ics_gamenum = -1;\r
2638                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2639                         break;\r
2640                       case EditGame: /*?*/\r
2641                       case EditPosition: /*?*/\r
2642                         /* Should above feature work in these modes too? */\r
2643                         /* For now it doesn't */\r
2644                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2645                         break;\r
2646                       default:\r
2647                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2648                         break;\r
2649                     }\r
2650                     break;\r
2651                   case H_REQUESTED:\r
2652                     /* Is this the right one? */\r
2653                     if (gameInfo.white && gameInfo.black &&\r
2654                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2655                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2656                         /* All is well */\r
2657                         ics_getting_history = H_GOT_REQ_HEADER;\r
2658                     }\r
2659                     break;\r
2660                   case H_GOT_REQ_HEADER:\r
2661                   case H_GOT_UNREQ_HEADER:\r
2662                   case H_GOT_UNWANTED_HEADER:\r
2663                   case H_GETTING_MOVES:\r
2664                     /* Should not happen */\r
2665                     DisplayError(_("Error gathering move list: two headers"), 0);\r
2666                     ics_getting_history = H_FALSE;\r
2667                     break;\r
2668                 }\r
2669 \r
2670                 /* Save player ratings into gameInfo if needed */\r
2671                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2672                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2673                     (gameInfo.whiteRating == -1 ||\r
2674                      gameInfo.blackRating == -1)) {\r
2675 \r
2676                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2677                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2678                     if (appData.debugMode)\r
2679                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), \r
2680                               gameInfo.whiteRating, gameInfo.blackRating);\r
2681                 }\r
2682                 continue;\r
2683             }\r
2684 \r
2685             if (looking_at(buf, &i,\r
2686               "* * match, initial time: * minute*, increment: * second")) {\r
2687                 /* Header for a move list -- second line */\r
2688                 /* Initial board will follow if this is a wild game */\r
2689                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2690                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2691                 gameInfo.event = StrSave(str);\r
2692                 /* [HGM] we switched variant. Translate boards if needed. */\r
2693                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2694                 continue;\r
2695             }\r
2696 \r
2697             if (looking_at(buf, &i, "Move  ")) {\r
2698                 /* Beginning of a move list */\r
2699                 switch (ics_getting_history) {\r
2700                   case H_FALSE:\r
2701                     /* Normally should not happen */\r
2702                     /* Maybe user hit reset while we were parsing */\r
2703                     break;\r
2704                   case H_REQUESTED:\r
2705                     /* Happens if we are ignoring a move list that is not\r
2706                      * the one we just requested.  Common if the user\r
2707                      * tries to observe two games without turning off\r
2708                      * getMoveList */\r
2709                     break;\r
2710                   case H_GETTING_MOVES:\r
2711                     /* Should not happen */\r
2712                     DisplayError(_("Error gathering move list: nested"), 0);\r
2713                     ics_getting_history = H_FALSE;\r
2714                     break;\r
2715                   case H_GOT_REQ_HEADER:\r
2716                     ics_getting_history = H_GETTING_MOVES;\r
2717                     started = STARTED_MOVES;\r
2718                     parse_pos = 0;\r
2719                     if (oldi > next_out) {\r
2720                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2721                     }\r
2722                     break;\r
2723                   case H_GOT_UNREQ_HEADER:\r
2724                     ics_getting_history = H_GETTING_MOVES;\r
2725                     started = STARTED_MOVES_NOHIDE;\r
2726                     parse_pos = 0;\r
2727                     break;\r
2728                   case H_GOT_UNWANTED_HEADER:\r
2729                     ics_getting_history = H_FALSE;\r
2730                     break;\r
2731                 }\r
2732                 continue;\r
2733             }                           \r
2734             \r
2735             if (looking_at(buf, &i, "% ") ||\r
2736                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2737                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book\r
2738                 savingComment = FALSE;\r
2739                 switch (started) {\r
2740                   case STARTED_MOVES:\r
2741                   case STARTED_MOVES_NOHIDE:\r
2742                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2743                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2744                     ParseGameHistory(parse);\r
2745 #if ZIPPY\r
2746                     if (appData.zippyPlay && first.initDone) {\r
2747                         FeedMovesToProgram(&first, forwardMostMove);\r
2748                         if (gameMode == IcsPlayingWhite) {\r
2749                             if (WhiteOnMove(forwardMostMove)) {\r
2750                                 if (first.sendTime) {\r
2751                                   if (first.useColors) {\r
2752                                     SendToProgram("black\n", &first); \r
2753                                   }\r
2754                                   SendTimeRemaining(&first, TRUE);\r
2755                                 }\r
2756 #if 0\r
2757                                 if (first.useColors) {\r
2758                                   SendToProgram("white\ngo\n", &first);\r
2759                                 } else {\r
2760                                   SendToProgram("go\n", &first);\r
2761                                 }\r
2762 #else\r
2763                                 if (first.useColors) {\r
2764                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent\r
2765                                 }\r
2766                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos\r
2767 #endif\r
2768                                 first.maybeThinking = TRUE;\r
2769                             } else {\r
2770                                 if (first.usePlayother) {\r
2771                                   if (first.sendTime) {\r
2772                                     SendTimeRemaining(&first, TRUE);\r
2773                                   }\r
2774                                   SendToProgram("playother\n", &first);\r
2775                                   firstMove = FALSE;\r
2776                                 } else {\r
2777                                   firstMove = TRUE;\r
2778                                 }\r
2779                             }\r
2780                         } else if (gameMode == IcsPlayingBlack) {\r
2781                             if (!WhiteOnMove(forwardMostMove)) {\r
2782                                 if (first.sendTime) {\r
2783                                   if (first.useColors) {\r
2784                                     SendToProgram("white\n", &first);\r
2785                                   }\r
2786                                   SendTimeRemaining(&first, FALSE);\r
2787                                 }\r
2788 #if 0\r
2789                                 if (first.useColors) {\r
2790                                   SendToProgram("black\ngo\n", &first);\r
2791                                 } else {\r
2792                                   SendToProgram("go\n", &first);\r
2793                                 }\r
2794 #else\r
2795                                 if (first.useColors) {\r
2796                                   SendToProgram("black\n", &first);\r
2797                                 }\r
2798                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);\r
2799 #endif\r
2800                                 first.maybeThinking = TRUE;\r
2801                             } else {\r
2802                                 if (first.usePlayother) {\r
2803                                   if (first.sendTime) {\r
2804                                     SendTimeRemaining(&first, FALSE);\r
2805                                   }\r
2806                                   SendToProgram("playother\n", &first);\r
2807                                   firstMove = FALSE;\r
2808                                 } else {\r
2809                                   firstMove = TRUE;\r
2810                                 }\r
2811                             }\r
2812                         }                       \r
2813                     }\r
2814 #endif\r
2815                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2816                         /* Moves came from oldmoves or moves command\r
2817                            while we weren't doing anything else.\r
2818                            */\r
2819                         currentMove = forwardMostMove;\r
2820                         ClearHighlights();/*!!could figure this out*/\r
2821                         flipView = appData.flipView;\r
2822                         DrawPosition(FALSE, boards[currentMove]);\r
2823                         DisplayBothClocks();\r
2824                         sprintf(str, "%s vs. %s",\r
2825                                 gameInfo.white, gameInfo.black);\r
2826                         DisplayTitle(str);\r
2827                         gameMode = IcsIdle;\r
2828                     } else {\r
2829                         /* Moves were history of an active game */\r
2830                         if (gameInfo.resultDetails != NULL) {\r
2831                             free(gameInfo.resultDetails);\r
2832                             gameInfo.resultDetails = NULL;\r
2833                         }\r
2834                     }\r
2835                     HistorySet(parseList, backwardMostMove,\r
2836                                forwardMostMove, currentMove-1);\r
2837                     DisplayMove(currentMove - 1);\r
2838                     if (started == STARTED_MOVES) next_out = i;\r
2839                     started = STARTED_NONE;\r
2840                     ics_getting_history = H_FALSE;\r
2841                     break;\r
2842 \r
2843                   case STARTED_OBSERVE:\r
2844                     started = STARTED_NONE;\r
2845                     SendToICS(ics_prefix);\r
2846                     SendToICS("refresh\n");\r
2847                     break;\r
2848 \r
2849                   default:\r
2850                     break;\r
2851                 }\r
2852                 if(bookHit) { // [HGM] book: simulate book reply\r
2853                     static char bookMove[MSG_SIZ]; // a bit generous?\r
2854 \r
2855                     programStats.depth = programStats.nodes = programStats.time = \r
2856                     programStats.score = programStats.got_only_move = 0;\r
2857                     sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
2858 \r
2859                     strcpy(bookMove, "move ");\r
2860                     strcat(bookMove, bookHit);\r
2861                     HandleMachineMove(bookMove, &first);\r
2862                 }\r
2863                 continue;\r
2864             }\r
2865             \r
2866             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2867                  started == STARTED_HOLDINGS ||\r
2868                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2869                 /* Accumulate characters in move list or board */\r
2870                 parse[parse_pos++] = buf[i];\r
2871             }\r
2872             \r
2873             /* Start of game messages.  Mostly we detect start of game\r
2874                when the first board image arrives.  On some versions\r
2875                of the ICS, though, we need to do a "refresh" after starting\r
2876                to observe in order to get the current board right away. */\r
2877             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2878                 started = STARTED_OBSERVE;\r
2879                 continue;\r
2880             }\r
2881 \r
2882             /* Handle auto-observe */\r
2883             if (appData.autoObserve &&\r
2884                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2885                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2886                 char *player;\r
2887                 /* Choose the player that was highlighted, if any. */\r
2888                 if (star_match[0][0] == '\033' ||\r
2889                     star_match[1][0] != '\033') {\r
2890                     player = star_match[0];\r
2891                 } else {\r
2892                     player = star_match[2];\r
2893                 }\r
2894                 sprintf(str, "%sobserve %s\n",\r
2895                         ics_prefix, StripHighlightAndTitle(player));\r
2896                 SendToICS(str);\r
2897 \r
2898                 /* Save ratings from notify string */\r
2899                 strcpy(player1Name, star_match[0]);\r
2900                 player1Rating = string_to_rating(star_match[1]);\r
2901                 strcpy(player2Name, star_match[2]);\r
2902                 player2Rating = string_to_rating(star_match[3]);\r
2903 \r
2904                 if (appData.debugMode)\r
2905                   fprintf(debugFP, \r
2906                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2907                           player1Name, player1Rating,\r
2908                           player2Name, player2Rating);\r
2909 \r
2910                 continue;\r
2911             }\r
2912 \r
2913             /* Deal with automatic examine mode after a game,\r
2914                and with IcsObserving -> IcsExamining transition */\r
2915             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2916                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2917 \r
2918                 int gamenum = atoi(star_match[0]);\r
2919                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2920                     gamenum == ics_gamenum) {\r
2921                     /* We were already playing or observing this game;\r
2922                        no need to refetch history */\r
2923                     gameMode = IcsExamining;\r
2924                     if (pausing) {\r
2925                         pauseExamForwardMostMove = forwardMostMove;\r
2926                     } else if (currentMove < forwardMostMove) {\r
2927                         ForwardInner(forwardMostMove);\r
2928                     }\r
2929                 } else {\r
2930                     /* I don't think this case really can happen */\r
2931                     SendToICS(ics_prefix);\r
2932                     SendToICS("refresh\n");\r
2933                 }\r
2934                 continue;\r
2935             }    \r
2936             \r
2937             /* Error messages */\r
2938             if (ics_user_moved) {\r
2939                 if (looking_at(buf, &i, "Illegal move") ||\r
2940                     looking_at(buf, &i, "Not a legal move") ||\r
2941                     looking_at(buf, &i, "Your king is in check") ||\r
2942                     looking_at(buf, &i, "It isn't your turn") ||\r
2943                     looking_at(buf, &i, "It is not your move")) {\r
2944                     /* Illegal move */\r
2945                     ics_user_moved = 0;\r
2946                     if (forwardMostMove > backwardMostMove) {\r
2947                         currentMove = --forwardMostMove;\r
2948                         DisplayMove(currentMove - 1); /* before DMError */\r
2949                         DisplayMoveError(_("Illegal move (rejected by ICS)"));\r
2950                         DrawPosition(FALSE, boards[currentMove]);\r
2951                         SwitchClocks();\r
2952                         DisplayBothClocks();\r
2953                     }\r
2954                     continue;\r
2955                 }\r
2956             }\r
2957 \r
2958             if (looking_at(buf, &i, "still have time") ||\r
2959                 looking_at(buf, &i, "not out of time") ||\r
2960                 looking_at(buf, &i, "either player is out of time") ||\r
2961                 looking_at(buf, &i, "has timeseal; checking")) {\r
2962                 /* We must have called his flag a little too soon */\r
2963                 whiteFlag = blackFlag = FALSE;\r
2964                 continue;\r
2965             }\r
2966 \r
2967             if (looking_at(buf, &i, "added * seconds to") ||\r
2968                 looking_at(buf, &i, "seconds were added to")) {\r
2969                 /* Update the clocks */\r
2970                 SendToICS(ics_prefix);\r
2971                 SendToICS("refresh\n");\r
2972                 continue;\r
2973             }\r
2974 \r
2975             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2976                 ics_clock_paused = TRUE;\r
2977                 StopClocks();\r
2978                 continue;\r
2979             }\r
2980 \r
2981             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2982                 ics_clock_paused = FALSE;\r
2983                 StartClocks();\r
2984                 continue;\r
2985             }\r
2986 \r
2987             /* Grab player ratings from the Creating: message.\r
2988                Note we have to check for the special case when\r
2989                the ICS inserts things like [white] or [black]. */\r
2990             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
2991                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
2992                 /* star_matches:\r
2993                    0    player 1 name (not necessarily white)\r
2994                    1    player 1 rating\r
2995                    2    empty, white, or black (IGNORED)\r
2996                    3    player 2 name (not necessarily black)\r
2997                    4    player 2 rating\r
2998                    \r
2999                    The names/ratings are sorted out when the game\r
3000                    actually starts (below).\r
3001                 */\r
3002                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
3003                 player1Rating = string_to_rating(star_match[1]);\r
3004                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
3005                 player2Rating = string_to_rating(star_match[4]);\r
3006 \r
3007                 if (appData.debugMode)\r
3008                   fprintf(debugFP, \r
3009                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
3010                           player1Name, player1Rating,\r
3011                           player2Name, player2Rating);\r
3012 \r
3013                 continue;\r
3014             }\r
3015             \r
3016             /* Improved generic start/end-of-game messages */\r
3017             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
3018                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
3019                 /* If tkind == 0: */\r
3020                 /* star_match[0] is the game number */\r
3021                 /*           [1] is the white player's name */\r
3022                 /*           [2] is the black player's name */\r
3023                 /* For end-of-game: */\r
3024                 /*           [3] is the reason for the game end */\r
3025                 /*           [4] is a PGN end game-token, preceded by " " */\r
3026                 /* For start-of-game: */\r
3027                 /*           [3] begins with "Creating" or "Continuing" */\r
3028                 /*           [4] is " *" or empty (don't care). */\r
3029                 int gamenum = atoi(star_match[0]);\r
3030                 char *whitename, *blackname, *why, *endtoken;\r
3031                 ChessMove endtype = (ChessMove) 0;\r
3032 \r
3033                 if (tkind == 0) {\r
3034                   whitename = star_match[1];\r
3035                   blackname = star_match[2];\r
3036                   why = star_match[3];\r
3037                   endtoken = star_match[4];\r
3038                 } else {\r
3039                   whitename = star_match[1];\r
3040                   blackname = star_match[3];\r
3041                   why = star_match[5];\r
3042                   endtoken = star_match[6];\r
3043                 }\r
3044 \r
3045                 /* Game start messages */\r
3046                 if (strncmp(why, "Creating ", 9) == 0 ||\r
3047                     strncmp(why, "Continuing ", 11) == 0) {\r
3048                     gs_gamenum = gamenum;\r
3049                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
3050 #if ZIPPY\r
3051                     if (appData.zippyPlay) {\r
3052                         ZippyGameStart(whitename, blackname);\r
3053                     }\r
3054 #endif /*ZIPPY*/\r
3055                     continue;\r
3056                 }\r
3057 \r
3058                 /* Game end messages */\r
3059                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
3060                     ics_gamenum != gamenum) {\r
3061                     continue;\r
3062                 }\r
3063                 while (endtoken[0] == ' ') endtoken++;\r
3064                 switch (endtoken[0]) {\r
3065                   case '*':\r
3066                   default:\r
3067                     endtype = GameUnfinished;\r
3068                     break;\r
3069                   case '0':\r
3070                     endtype = BlackWins;\r
3071                     break;\r
3072                   case '1':\r
3073                     if (endtoken[1] == '/')\r
3074                       endtype = GameIsDrawn;\r
3075                     else\r
3076                       endtype = WhiteWins;\r
3077                     break;\r
3078                 }\r
3079                 GameEnds(endtype, why, GE_ICS);\r
3080 #if ZIPPY\r
3081                 if (appData.zippyPlay && first.initDone) {\r
3082                     ZippyGameEnd(endtype, why);\r
3083                     if (first.pr == NULL) {\r
3084                       /* Start the next process early so that we'll\r
3085                          be ready for the next challenge */\r
3086                       StartChessProgram(&first);\r
3087                     }\r
3088                     /* Send "new" early, in case this command takes\r
3089                        a long time to finish, so that we'll be ready\r
3090                        for the next challenge. */\r
3091                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'\r
3092                     Reset(TRUE, TRUE);\r
3093                 }\r
3094 #endif /*ZIPPY*/\r
3095                 continue;\r
3096             }\r
3097 \r
3098             if (looking_at(buf, &i, "Removing game * from observation") ||\r
3099                 looking_at(buf, &i, "no longer observing game *") ||\r
3100                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
3101                 if (gameMode == IcsObserving &&\r
3102                     atoi(star_match[0]) == ics_gamenum)\r
3103                   {\r
3104                       /* icsEngineAnalyze */\r
3105                       if (appData.icsEngineAnalyze) {\r
3106                             ExitAnalyzeMode();\r
3107                             ModeHighlight();\r
3108                       }\r
3109                       StopClocks();\r
3110                       gameMode = IcsIdle;\r
3111                       ics_gamenum = -1;\r
3112                       ics_user_moved = FALSE;\r
3113                   }\r
3114                 continue;\r
3115             }\r
3116 \r
3117             if (looking_at(buf, &i, "no longer examining game *")) {\r
3118                 if (gameMode == IcsExamining &&\r
3119                     atoi(star_match[0]) == ics_gamenum)\r
3120                   {\r
3121                       gameMode = IcsIdle;\r
3122                       ics_gamenum = -1;\r
3123                       ics_user_moved = FALSE;\r
3124                   }\r
3125                 continue;\r
3126             }\r
3127 \r
3128             /* Advance leftover_start past any newlines we find,\r
3129                so only partial lines can get reparsed */\r
3130             if (looking_at(buf, &i, "\n")) {\r
3131                 prevColor = curColor;\r
3132                 if (curColor != ColorNormal) {\r
3133                     if (oldi > next_out) {\r
3134                         SendToPlayer(&buf[next_out], oldi - next_out);\r
3135                         next_out = oldi;\r
3136                     }\r
3137                     Colorize(ColorNormal, FALSE);\r
3138                     curColor = ColorNormal;\r
3139                 }\r
3140                 if (started == STARTED_BOARD) {\r
3141                     started = STARTED_NONE;\r
3142                     parse[parse_pos] = NULLCHAR;\r
3143                     ParseBoard12(parse);\r
3144                     ics_user_moved = 0;\r
3145 \r
3146                     /* Send premove here */\r
3147                     if (appData.premove) {\r
3148                       char str[MSG_SIZ];\r
3149                       if (currentMove == 0 &&\r
3150                           gameMode == IcsPlayingWhite &&\r
3151                           appData.premoveWhite) {\r
3152                         sprintf(str, "%s%s\n", ics_prefix,\r
3153                                 appData.premoveWhiteText);\r
3154                         if (appData.debugMode)\r
3155                           fprintf(debugFP, "Sending premove:\n");\r
3156                         SendToICS(str);\r
3157                       } else if (currentMove == 1 &&\r
3158                                  gameMode == IcsPlayingBlack &&\r
3159                                  appData.premoveBlack) {\r
3160                         sprintf(str, "%s%s\n", ics_prefix,\r
3161                                 appData.premoveBlackText);\r
3162                         if (appData.debugMode)\r
3163                           fprintf(debugFP, "Sending premove:\n");\r
3164                         SendToICS(str);\r
3165                       } else if (gotPremove) {\r
3166                         gotPremove = 0;\r
3167                         ClearPremoveHighlights();\r
3168                         if (appData.debugMode)\r
3169                           fprintf(debugFP, "Sending premove:\n");\r
3170                           UserMoveEvent(premoveFromX, premoveFromY, \r
3171                                         premoveToX, premoveToY, \r
3172                                         premovePromoChar);\r
3173                       }\r
3174                     }\r
3175 \r
3176                     /* Usually suppress following prompt */\r
3177                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
3178                         if (looking_at(buf, &i, "*% ")) {\r
3179                             savingComment = FALSE;\r
3180                         }\r
3181                     }\r
3182                     next_out = i;\r
3183                 } else if (started == STARTED_HOLDINGS) {\r
3184                     int gamenum;\r
3185                     char new_piece[MSG_SIZ];\r
3186                     started = STARTED_NONE;\r
3187                     parse[parse_pos] = NULLCHAR;\r
3188                     if (appData.debugMode)\r
3189                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
3190                                                         parse, currentMove);\r
3191                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
3192                         gamenum == ics_gamenum) {\r
3193                         if (gameInfo.variant == VariantNormal) {\r
3194                           /* [HGM] We seem to switch variant during a game!\r
3195                            * Presumably no holdings were displayed, so we have\r
3196                            * to move the position two files to the right to\r
3197                            * create room for them!\r
3198                            */\r
3199                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
3200                           /* Get a move list just to see the header, which\r
3201                              will tell us whether this is really bug or zh */\r
3202                           if (ics_getting_history == H_FALSE) {\r
3203                             ics_getting_history = H_REQUESTED;\r
3204                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3205                             SendToICS(str);\r
3206                           }\r
3207                         }\r
3208                         new_piece[0] = NULLCHAR;\r
3209                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
3210                                &gamenum, white_holding, black_holding,\r
3211                                new_piece);\r
3212                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
3213                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
3214                         /* [HGM] copy holdings to board holdings area */\r
3215                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
3216                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
3217 #if ZIPPY\r
3218                         if (appData.zippyPlay && first.initDone) {\r
3219                             ZippyHoldings(white_holding, black_holding,\r
3220                                           new_piece);\r
3221                         }\r
3222 #endif /*ZIPPY*/\r
3223                         if (tinyLayout || smallLayout) {\r
3224                             char wh[16], bh[16];\r
3225                             PackHolding(wh, white_holding);\r
3226                             PackHolding(bh, black_holding);\r
3227                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
3228                                     gameInfo.white, gameInfo.black);\r
3229                         } else {\r
3230                             sprintf(str, "%s [%s] vs. %s [%s]",\r
3231                                     gameInfo.white, white_holding,\r
3232                                     gameInfo.black, black_holding);\r
3233                         }\r
3234 \r
3235                         DrawPosition(FALSE, boards[currentMove]);\r
3236                         DisplayTitle(str);\r
3237                     }\r
3238                     /* Suppress following prompt */\r
3239                     if (looking_at(buf, &i, "*% ")) {\r
3240                         savingComment = FALSE;\r
3241                     }\r
3242                     next_out = i;\r
3243                 }\r
3244                 continue;\r
3245             }\r
3246 \r
3247             i++;                /* skip unparsed character and loop back */\r
3248         }\r
3249         \r
3250         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window\r
3251             started != STARTED_HOLDINGS && i > next_out) {\r
3252             SendToPlayer(&buf[next_out], i - next_out);\r
3253             next_out = i;\r
3254         }\r
3255         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above\r
3256         \r
3257         leftover_len = buf_len - leftover_start;\r
3258         /* if buffer ends with something we couldn't parse,\r
3259            reparse it after appending the next read */\r
3260         \r
3261     } else if (count == 0) {\r
3262         RemoveInputSource(isr);\r
3263         DisplayFatalError(_("Connection closed by ICS"), 0, 0);\r
3264     } else {\r
3265         DisplayFatalError(_("Error reading from ICS"), error, 1);\r
3266     }\r
3267 }\r
3268 \r
3269 \r
3270 /* Board style 12 looks like this:\r
3271    \r
3272    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0\r
3273    \r
3274  * The "<12> " is stripped before it gets to this routine.  The two\r
3275  * trailing 0's (flip state and clock ticking) are later addition, and\r
3276  * some chess servers may not have them, or may have only the first.\r
3277  * Additional trailing fields may be added in the future.  \r
3278  */\r
3279 \r
3280 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"\r
3281 \r
3282 #define RELATION_OBSERVING_PLAYED    0\r
3283 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
3284 #define RELATION_PLAYING_MYMOVE      1\r
3285 #define RELATION_PLAYING_NOTMYMOVE  -1\r
3286 #define RELATION_EXAMINING           2\r
3287 #define RELATION_ISOLATED_BOARD     -3\r
3288 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
3289 \r
3290 void\r
3291 ParseBoard12(string)\r
3292      char *string;\r
3293\r
3294     GameMode newGameMode;\r
3295     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;\r
3296     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;\r
3297     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
3298     char to_play, board_chars[200];\r
3299     char move_str[500], str[500], elapsed_time[500];\r
3300     char black[32], white[32];\r
3301     Board board;\r
3302     int prevMove = currentMove;\r
3303     int ticking = 2;\r
3304     ChessMove moveType;\r
3305     int fromX, fromY, toX, toY;\r
3306     char promoChar;\r
3307     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */\r
3308     char *bookHit = NULL; // [HGM] book\r
3309 \r
3310     fromX = fromY = toX = toY = -1;\r
3311     \r
3312     newGame = FALSE;\r
3313 \r
3314     if (appData.debugMode)\r
3315       fprintf(debugFP, _("Parsing board: %s\n"), string);\r
3316 \r
3317     move_str[0] = NULLCHAR;\r
3318     elapsed_time[0] = NULLCHAR;\r
3319     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */\r
3320         int  i = 0, j;\r
3321         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {\r
3322             if(string[i] == ' ') { ranks++; files = 0; }\r
3323             else files++;\r
3324             i++;\r
3325         }\r
3326         for(j = 0; j <i; j++) board_chars[j] = string[j];\r
3327         board_chars[i] = '\0';\r
3328         string += i + 1;\r
3329     }\r
3330     n = sscanf(string, PATTERN, &to_play, &double_push,\r
3331                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
3332                &gamenum, white, black, &relation, &basetime, &increment,\r
3333                &white_stren, &black_stren, &white_time, &black_time,\r
3334                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
3335                &ticking);\r
3336 \r
3337     if (n < 21) {\r
3338         sprintf(str, _("Failed to parse board string:\n\"%s\""), string);\r
3339         DisplayError(str, 0);\r
3340         return;\r
3341     }\r
3342 \r
3343     /* Convert the move number to internal form */\r
3344     moveNum = (moveNum - 1) * 2;\r
3345     if (to_play == 'B') moveNum++;\r
3346     if (moveNum >= MAX_MOVES) {\r
3347       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
3348                         0, 1);\r
3349       return;\r
3350     }\r
3351     \r
3352     switch (relation) {\r
3353       case RELATION_OBSERVING_PLAYED:\r
3354       case RELATION_OBSERVING_STATIC:\r
3355         if (gamenum == -1) {\r
3356             /* Old ICC buglet */\r
3357             relation = RELATION_OBSERVING_STATIC;\r
3358         }\r
3359         newGameMode = IcsObserving;\r
3360         break;\r
3361       case RELATION_PLAYING_MYMOVE:\r
3362       case RELATION_PLAYING_NOTMYMOVE:\r
3363         newGameMode =\r
3364           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3365             IcsPlayingWhite : IcsPlayingBlack;\r
3366         break;\r
3367       case RELATION_EXAMINING:\r
3368         newGameMode = IcsExamining;\r
3369         break;\r
3370       case RELATION_ISOLATED_BOARD:\r
3371       default:\r
3372         /* Just display this board.  If user was doing something else,\r
3373            we will forget about it until the next board comes. */ \r
3374         newGameMode = IcsIdle;\r
3375         break;\r
3376       case RELATION_STARTING_POSITION:\r
3377         newGameMode = gameMode;\r
3378         break;\r
3379     }\r
3380     \r
3381     /* Modify behavior for initial board display on move listing\r
3382        of wild games.\r
3383        */\r
3384     switch (ics_getting_history) {\r
3385       case H_FALSE:\r
3386       case H_REQUESTED:\r
3387         break;\r
3388       case H_GOT_REQ_HEADER:\r
3389       case H_GOT_UNREQ_HEADER:\r
3390         /* This is the initial position of the current game */\r
3391         gamenum = ics_gamenum;\r
3392         moveNum = 0;            /* old ICS bug workaround */\r
3393         if (to_play == 'B') {\r
3394           startedFromSetupPosition = TRUE;\r
3395           blackPlaysFirst = TRUE;\r
3396           moveNum = 1;\r
3397           if (forwardMostMove == 0) forwardMostMove = 1;\r
3398           if (backwardMostMove == 0) backwardMostMove = 1;\r
3399           if (currentMove == 0) currentMove = 1;\r
3400         }\r
3401         newGameMode = gameMode;\r
3402         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3403         break;\r
3404       case H_GOT_UNWANTED_HEADER:\r
3405         /* This is an initial board that we don't want */\r
3406         return;\r
3407       case H_GETTING_MOVES:\r
3408         /* Should not happen */\r
3409         DisplayError(_("Error gathering move list: extra board"), 0);\r
3410         ics_getting_history = H_FALSE;\r
3411         return;\r
3412     }\r
3413     \r
3414     /* Take action if this is the first board of a new game, or of a\r
3415        different game than is currently being displayed.  */\r
3416     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3417         relation == RELATION_ISOLATED_BOARD) {\r
3418         \r
3419         /* Forget the old game and get the history (if any) of the new one */\r
3420         if (gameMode != BeginningOfGame) {\r
3421           Reset(FALSE, TRUE);\r
3422         }\r
3423         newGame = TRUE;\r
3424         if (appData.autoRaiseBoard) BoardToTop();\r
3425         prevMove = -3;\r
3426         if (gamenum == -1) {\r
3427             newGameMode = IcsIdle;\r
3428         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3429                    appData.getMoveList) {\r
3430             /* Need to get game history */\r
3431             ics_getting_history = H_REQUESTED;\r
3432             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3433             SendToICS(str);\r
3434         }\r
3435         \r
3436         /* Initially flip the board to have black on the bottom if playing\r
3437            black or if the ICS flip flag is set, but let the user change\r
3438            it with the Flip View button. */\r
3439         flipView = appData.autoFlipView ? \r
3440           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3441           appData.flipView;\r
3442         \r
3443         /* Done with values from previous mode; copy in new ones */\r
3444         gameMode = newGameMode;\r
3445         ModeHighlight();\r
3446         ics_gamenum = gamenum;\r
3447         if (gamenum == gs_gamenum) {\r
3448             int klen = strlen(gs_kind);\r
3449             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3450             sprintf(str, "ICS %s", gs_kind);\r
3451             gameInfo.event = StrSave(str);\r
3452         } else {\r
3453             gameInfo.event = StrSave("ICS game");\r
3454         }\r
3455         gameInfo.site = StrSave(appData.icsHost);\r
3456         gameInfo.date = PGNDate();\r
3457         gameInfo.round = StrSave("-");\r
3458         gameInfo.white = StrSave(white);\r
3459         gameInfo.black = StrSave(black);\r
3460         timeControl = basetime * 60 * 1000;\r
3461         timeControl_2 = 0;\r
3462         timeIncrement = increment * 1000;\r
3463         movesPerSession = 0;\r
3464         gameInfo.timeControl = TimeControlTagValue();\r
3465         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3466   if (appData.debugMode) {\r
3467     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3468     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3469     setbuf(debugFP, NULL);\r
3470   }\r
3471 \r
3472         gameInfo.outOfBook = NULL;\r
3473         \r
3474         /* Do we have the ratings? */\r
3475         if (strcmp(player1Name, white) == 0 &&\r
3476             strcmp(player2Name, black) == 0) {\r
3477             if (appData.debugMode)\r
3478               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3479                       player1Rating, player2Rating);\r
3480             gameInfo.whiteRating = player1Rating;\r
3481             gameInfo.blackRating = player2Rating;\r
3482         } else if (strcmp(player2Name, white) == 0 &&\r
3483                    strcmp(player1Name, black) == 0) {\r
3484             if (appData.debugMode)\r
3485               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3486                       player2Rating, player1Rating);\r
3487             gameInfo.whiteRating = player2Rating;\r
3488             gameInfo.blackRating = player1Rating;\r
3489         }\r
3490         player1Name[0] = player2Name[0] = NULLCHAR;\r
3491 \r
3492         /* Silence shouts if requested */\r
3493         if (appData.quietPlay &&\r
3494             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3495             SendToICS(ics_prefix);\r
3496             SendToICS("set shout 0\n");\r
3497         }\r
3498     }\r
3499     \r
3500     /* Deal with midgame name changes */\r
3501     if (!newGame) {\r
3502         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3503             if (gameInfo.white) free(gameInfo.white);\r
3504             gameInfo.white = StrSave(white);\r
3505         }\r
3506         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3507             if (gameInfo.black) free(gameInfo.black);\r
3508             gameInfo.black = StrSave(black);\r
3509         }\r
3510     }\r
3511     \r
3512     /* Throw away game result if anything actually changes in examine mode */\r
3513     if (gameMode == IcsExamining && !newGame) {\r
3514         gameInfo.result = GameUnfinished;\r
3515         if (gameInfo.resultDetails != NULL) {\r
3516             free(gameInfo.resultDetails);\r
3517             gameInfo.resultDetails = NULL;\r
3518         }\r
3519     }\r
3520     \r
3521     /* In pausing && IcsExamining mode, we ignore boards coming\r
3522        in if they are in a different variation than we are. */\r
3523     if (pauseExamInvalid) return;\r
3524     if (pausing && gameMode == IcsExamining) {\r
3525         if (moveNum <= pauseExamForwardMostMove) {\r
3526             pauseExamInvalid = TRUE;\r
3527             forwardMostMove = pauseExamForwardMostMove;\r
3528             return;\r
3529         }\r
3530     }\r
3531     \r
3532   if (appData.debugMode) {\r
3533     fprintf(debugFP, "load %dx%d board\n", files, ranks);\r
3534   }\r
3535     /* Parse the board */\r
3536     for (k = 0; k < ranks; k++) {\r
3537       for (j = 0; j < files; j++)\r
3538         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);\r
3539       if(gameInfo.holdingsWidth > 1) {\r
3540            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3541            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3542       }\r
3543     }\r
3544     CopyBoard(boards[moveNum], board);\r
3545     if (moveNum == 0) {\r
3546         startedFromSetupPosition =\r
3547           !CompareBoards(board, initialPosition);\r
3548         if(startedFromSetupPosition)\r
3549             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */\r
3550     }\r
3551 \r
3552     /* [HGM] Set castling rights. Take the outermost Rooks,\r
3553        to make it also work for FRC opening positions. Note that board12\r
3554        is really defective for later FRC positions, as it has no way to\r
3555        indicate which Rook can castle if they are on the same side of King.\r
3556        For the initial position we grant rights to the outermost Rooks,\r
3557        and remember thos rights, and we then copy them on positions\r
3558        later in an FRC game. This means WB might not recognize castlings with\r
3559        Rooks that have moved back to their original position as illegal,\r
3560        but in ICS mode that is not its job anyway.\r
3561     */\r
3562     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)\r
3563     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;\r
3564 \r
3565         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3566             if(board[0][i] == WhiteRook) j = i;\r
3567         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3568         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3569             if(board[0][i] == WhiteRook) j = i;\r
3570         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3571         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3572             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3573         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3574         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3575             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3576         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3577 \r
3578         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }\r
3579         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3580             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;\r
3581         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3582             if(board[BOARD_HEIGHT-1][k] == bKing)\r
3583                 initialRights[5] = castlingRights[moveNum][5] = k;\r
3584     } else { int r;\r
3585         r = castlingRights[moveNum][0] = initialRights[0];\r
3586         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;\r
3587         r = castlingRights[moveNum][1] = initialRights[1];\r
3588         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;\r
3589         r = castlingRights[moveNum][3] = initialRights[3];\r
3590         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;\r
3591         r = castlingRights[moveNum][4] = initialRights[4];\r
3592         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;\r
3593         /* wildcastle kludge: always assume King has rights */\r
3594         r = castlingRights[moveNum][2] = initialRights[2];\r
3595         r = castlingRights[moveNum][5] = initialRights[5];\r
3596     }\r
3597     /* [HGM] e.p. rights. Assume that ICS sends file number here? */\r
3598     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;\r
3599 \r
3600     \r
3601     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3602         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3603         /* This was an initial position from a move list, not\r
3604            the current position */\r
3605         return;\r
3606     }\r
3607     \r
3608     /* Update currentMove and known move number limits */\r
3609     newMove = newGame || moveNum > forwardMostMove;\r
3610 \r
3611     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */\r
3612     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {\r
3613         takeback = forwardMostMove - moveNum;\r
3614         for (i = 0; i < takeback; i++) {\r
3615              if (appData.debugMode) fprintf(debugFP, "take back move\n");\r
3616              SendToProgram("undo\n", &first);\r
3617         }\r
3618     }\r
3619 \r
3620     if (newGame) {\r
3621         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3622         if (gameMode == IcsExamining && moveNum == 0) {\r
3623           /* Workaround for ICS limitation: we are not told the wild\r
3624              type when starting to examine a game.  But if we ask for\r
3625              the move list, the move list header will tell us */\r
3626             ics_getting_history = H_REQUESTED;\r
3627             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3628             SendToICS(str);\r
3629         }\r
3630     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3631                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3632         forwardMostMove = moveNum;\r
3633         if (!pausing || currentMove > forwardMostMove)\r
3634           currentMove = forwardMostMove;\r
3635     } else {\r
3636         /* New part of history that is not contiguous with old part */ \r
3637         if (pausing && gameMode == IcsExamining) {\r
3638             pauseExamInvalid = TRUE;\r
3639             forwardMostMove = pauseExamForwardMostMove;\r
3640             return;\r
3641         }\r
3642         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3643         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3644             ics_getting_history = H_REQUESTED;\r
3645             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3646             SendToICS(str);\r
3647         }\r
3648     }\r
3649     \r
3650     /* Update the clocks */\r
3651     if (strchr(elapsed_time, '.')) {\r
3652       /* Time is in ms */\r
3653       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3654       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3655     } else {\r
3656       /* Time is in seconds */\r
3657       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3658       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3659     }\r
3660       \r
3661 \r
3662 #if ZIPPY\r
3663     if (appData.zippyPlay && newGame &&\r
3664         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3665         gameMode != IcsExamining)\r
3666       ZippyFirstBoard(moveNum, basetime, increment);\r
3667 #endif\r
3668     \r
3669     /* Put the move on the move list, first converting\r
3670        to canonical algebraic form. */\r
3671     if (moveNum > 0) {\r
3672   if (appData.debugMode) {\r
3673     if (appData.debugMode) { int f = forwardMostMove;\r
3674         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,\r
3675                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
3676     }\r
3677     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3678     fprintf(debugFP, "moveNum = %d\n", moveNum);\r
3679     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3680     setbuf(debugFP, NULL);\r
3681   }\r
3682         if (moveNum <= backwardMostMove) {\r
3683             /* We don't know what the board looked like before\r
3684                this move.  Punt. */\r
3685             strcpy(parseList[moveNum - 1], move_str);\r
3686             strcat(parseList[moveNum - 1], " ");\r
3687             strcat(parseList[moveNum - 1], elapsed_time);\r
3688             moveList[moveNum - 1][0] = NULLCHAR;\r
3689         } else if (strcmp(move_str, "none") == 0) {\r
3690             // [HGM] long SAN: swapped order; test for 'none' before parsing move\r
3691             /* Again, we don't know what the board looked like;\r
3692                this is really the start of the game. */\r
3693             parseList[moveNum - 1][0] = NULLCHAR;\r
3694             moveList[moveNum - 1][0] = NULLCHAR;\r
3695             backwardMostMove = moveNum;\r
3696             startedFromSetupPosition = TRUE;\r
3697             fromX = fromY = toX = toY = -1;\r
3698         } else {\r
3699           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. \r
3700           //                 So we parse the long-algebraic move string in stead of the SAN move\r
3701           int valid; char buf[MSG_SIZ], *prom;\r
3702 \r
3703           // str looks something like "Q/a1-a2"; kill the slash\r
3704           if(str[1] == '/') \r
3705                 sprintf(buf, "%c%s", str[0], str+2);\r
3706           else  strcpy(buf, str); // might be castling\r
3707           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) \r
3708                 strcat(buf, prom); // long move lacks promo specification!\r
3709           if(!appData.testLegality) {\r
3710                 if(appData.debugMode) \r
3711                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);\r
3712                 strcpy(move_str, buf);\r
3713           }\r
3714           valid = ParseOneMove(move_str, moveNum - 1, &moveType,\r
3715                                 &fromX, &fromY, &toX, &toY, &promoChar)\r
3716                || ParseOneMove(buf, moveNum - 1, &moveType,\r
3717                                 &fromX, &fromY, &toX, &toY, &promoChar);\r
3718           // end of long SAN patch\r
3719           if (valid) {\r
3720             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3721                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3722                                      fromY, fromX, toY, toX, promoChar,\r
3723                                      parseList[moveNum-1]);\r
3724             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3725                              castlingRights[moveNum]) ) {\r
3726               case MT_NONE:\r
3727               case MT_STALEMATE:\r
3728               default:\r
3729                 break;\r
3730               case MT_CHECK:\r
3731                 if(gameInfo.variant != VariantShogi)\r
3732                     strcat(parseList[moveNum - 1], "+");\r
3733                 break;\r
3734               case MT_CHECKMATE:\r
3735                 strcat(parseList[moveNum - 1], "#");\r
3736                 break;\r
3737             }\r
3738             strcat(parseList[moveNum - 1], " ");\r
3739             strcat(parseList[moveNum - 1], elapsed_time);\r
3740             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3741             strcpy(moveList[moveNum - 1], currentMoveString);\r
3742             strcat(moveList[moveNum - 1], "\n");\r
3743           } else {\r
3744             /* Move from ICS was illegal!?  Punt. */\r
3745   if (appData.debugMode) {\r
3746     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3747     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3748   }\r
3749 #if 0\r
3750             if (appData.testLegality && appData.debugMode) {\r
3751                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3752                 DisplayError(str, 0);\r
3753             }\r
3754 #endif\r
3755             strcpy(parseList[moveNum - 1], move_str);\r
3756             strcat(parseList[moveNum - 1], " ");\r
3757             strcat(parseList[moveNum - 1], elapsed_time);\r
3758             moveList[moveNum - 1][0] = NULLCHAR;\r
3759             fromX = fromY = toX = toY = -1;\r
3760           }\r
3761         }\r
3762   if (appData.debugMode) {\r
3763     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3764     setbuf(debugFP, NULL);\r
3765   }\r
3766 \r
3767 #if ZIPPY\r
3768         /* Send move to chess program (BEFORE animating it). */\r
3769         if (appData.zippyPlay && !newGame && newMove && \r
3770            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3771 \r
3772             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3773                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3774                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3775                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),\r
3776                             move_str);\r
3777                     DisplayError(str, 0);\r
3778                 } else {\r
3779                     if (first.sendTime) {\r
3780                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3781                     }\r
3782                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book\r
3783                     if (firstMove && !bookHit) {\r
3784                         firstMove = FALSE;\r
3785                         if (first.useColors) {\r
3786                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3787                                         "white\ngo\n" :\r
3788                                         "black\ngo\n", &first);\r
3789                         } else {\r
3790                           SendToProgram("go\n", &first);\r
3791                         }\r
3792                         first.maybeThinking = TRUE;\r
3793                     }\r
3794                 }\r
3795             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3796               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3797                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);\r
3798                 DisplayError(str, 0);\r
3799               } else {\r
3800                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!\r
3801                 SendMoveToProgram(moveNum - 1, &first);\r
3802               }\r
3803             }\r
3804         }\r
3805 #endif\r
3806     }\r
3807 \r
3808     if (moveNum > 0 && !gotPremove) {\r
3809         /* If move comes from a remote source, animate it.  If it\r
3810            isn't remote, it will have already been animated. */\r
3811         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3812             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3813         }\r
3814         if (!pausing && appData.highlightLastMove) {\r
3815             SetHighlights(fromX, fromY, toX, toY);\r
3816         }\r
3817     }\r
3818     \r
3819     /* Start the clocks */\r
3820     whiteFlag = blackFlag = FALSE;\r
3821     appData.clockMode = !(basetime == 0 && increment == 0);\r
3822     if (ticking == 0) {\r
3823       ics_clock_paused = TRUE;\r
3824       StopClocks();\r
3825     } else if (ticking == 1) {\r
3826       ics_clock_paused = FALSE;\r
3827     }\r
3828     if (gameMode == IcsIdle ||\r
3829         relation == RELATION_OBSERVING_STATIC ||\r
3830         relation == RELATION_EXAMINING ||\r
3831         ics_clock_paused)\r
3832       DisplayBothClocks();\r
3833     else\r
3834       StartClocks();\r
3835     \r
3836     /* Display opponents and material strengths */\r
3837     if (gameInfo.variant != VariantBughouse &&\r
3838         gameInfo.variant != VariantCrazyhouse) {\r
3839         if (tinyLayout || smallLayout) {\r
3840             if(gameInfo.variant == VariantNormal)\r
3841                 sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3842                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3843                     basetime, increment);\r
3844             else\r
3845                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", \r
3846                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3847                     basetime, increment, (int) gameInfo.variant);\r
3848         } else {\r
3849             if(gameInfo.variant == VariantNormal)\r
3850                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3851                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3852                     basetime, increment);\r
3853             else\r
3854                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", \r
3855                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3856                     basetime, increment, VariantName(gameInfo.variant));\r
3857         }\r
3858         DisplayTitle(str);\r
3859   if (appData.debugMode) {\r
3860     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);\r
3861   }\r
3862     }\r
3863 \r
3864    \r
3865     /* Display the board */\r
3866     if (!pausing) {\r
3867       \r
3868       if (appData.premove)\r
3869           if (!gotPremove || \r
3870              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3871              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3872               ClearPremoveHighlights();\r
3873 \r
3874       DrawPosition(FALSE, boards[currentMove]);\r
3875       DisplayMove(moveNum - 1);\r
3876       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3877         RingBell();\r
3878     }\r
3879 \r
3880     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3881 #if ZIPPY\r
3882     if(bookHit) { // [HGM] book: simulate book reply\r
3883         static char bookMove[MSG_SIZ]; // a bit generous?\r
3884 \r
3885         programStats.depth = programStats.nodes = programStats.time = \r
3886         programStats.score = programStats.got_only_move = 0;\r
3887         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
3888 \r
3889         strcpy(bookMove, "move ");\r
3890         strcat(bookMove, bookHit);\r
3891         HandleMachineMove(bookMove, &first);\r
3892     }\r
3893 #endif\r
3894 }\r
3895 \r
3896 void\r
3897 GetMoveListEvent()\r
3898 {\r
3899     char buf[MSG_SIZ];\r
3900     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3901         ics_getting_history = H_REQUESTED;\r
3902         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3903         SendToICS(buf);\r
3904     }\r
3905 }\r
3906 \r
3907 void\r
3908 AnalysisPeriodicEvent(force)\r
3909      int force;\r
3910 {\r
3911     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3912          && !force) || !appData.periodicUpdates)\r
3913       return;\r
3914 \r
3915     /* Send . command to Crafty to collect stats */\r
3916     SendToProgram(".\n", &first);\r
3917 \r
3918     /* Don't send another until we get a response (this makes\r
3919        us stop sending to old Crafty's which don't understand\r
3920        the "." command (sending illegal cmds resets node count & time,\r
3921        which looks bad)) */\r
3922     programStats.ok_to_send = 0;\r
3923 }\r
3924 \r
3925 void\r
3926 SendMoveToProgram(moveNum, cps)\r
3927      int moveNum;\r
3928      ChessProgramState *cps;\r
3929 {\r
3930     char buf[MSG_SIZ];\r
3931 \r
3932     if (cps->useUsermove) {\r
3933       SendToProgram("usermove ", cps);\r
3934     }\r
3935     if (cps->useSAN) {\r
3936       char *space;\r
3937       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3938         int len = space - parseList[moveNum];\r
3939         memcpy(buf, parseList[moveNum], len);\r
3940         buf[len++] = '\n';\r
3941         buf[len] = NULLCHAR;\r
3942       } else {\r
3943         sprintf(buf, "%s\n", parseList[moveNum]);\r
3944       }\r
3945       SendToProgram(buf, cps);\r
3946     } else {\r
3947       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */\r
3948         AlphaRank(moveList[moveNum], 4);\r
3949         SendToProgram(moveList[moveNum], cps);\r
3950         AlphaRank(moveList[moveNum], 4); // and back\r
3951       } else\r
3952       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3953        * the engine. It would be nice to have a better way to identify castle \r
3954        * moves here. */\r
3955       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)\r
3956                                                                          && cps->useOOCastle) {\r
3957         int fromX = moveList[moveNum][0] - AAA; \r
3958         int fromY = moveList[moveNum][1] - ONE;\r
3959         int toX = moveList[moveNum][2] - AAA; \r
3960         int toY = moveList[moveNum][3] - ONE;\r
3961         if((boards[moveNum][fromY][fromX] == WhiteKing \r
3962             && boards[moveNum][toY][toX] == WhiteRook)\r
3963            || (boards[moveNum][fromY][fromX] == BlackKing \r
3964                && boards[moveNum][toY][toX] == BlackRook)) {\r
3965           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3966           else SendToProgram("O-O-O\n", cps);\r
3967         }\r
3968         else SendToProgram(moveList[moveNum], cps);\r
3969       }\r
3970       else SendToProgram(moveList[moveNum], cps);\r
3971       /* End of additions by Tord */\r
3972     }\r
3973 \r
3974     /* [HGM] setting up the opening has brought engine in force mode! */\r
3975     /*       Send 'go' if we are in a mode where machine should play. */\r
3976     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
3977         (gameMode == TwoMachinesPlay   ||\r
3978 #ifdef ZIPPY\r
3979          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||\r
3980 #endif\r
3981          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
3982         SendToProgram("go\n", cps);\r
3983   if (appData.debugMode) {\r
3984     fprintf(debugFP, "(extra)\n");\r
3985   }\r
3986     }\r
3987     setboardSpoiledMachineBlack = 0;\r
3988 }\r
3989 \r
3990 void\r
3991 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
3992      ChessMove moveType;\r
3993      int fromX, fromY, toX, toY;\r
3994 {\r
3995     char user_move[MSG_SIZ];\r
3996 \r
3997     switch (moveType) {\r
3998       default:\r
3999         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),\r
4000                 (int)moveType, fromX, fromY, toX, toY);\r
4001         DisplayError(user_move + strlen("say "), 0);\r
4002         break;\r
4003       case WhiteKingSideCastle:\r
4004       case BlackKingSideCastle:\r
4005       case WhiteQueenSideCastleWild:\r
4006       case BlackQueenSideCastleWild:\r
4007       /* PUSH Fabien */\r
4008       case WhiteHSideCastleFR:\r
4009       case BlackHSideCastleFR:\r
4010       /* POP Fabien */\r
4011         sprintf(user_move, "o-o\n");\r
4012         break;\r
4013       case WhiteQueenSideCastle:\r
4014       case BlackQueenSideCastle:\r
4015       case WhiteKingSideCastleWild:\r
4016       case BlackKingSideCastleWild:\r
4017       /* PUSH Fabien */\r
4018       case WhiteASideCastleFR:\r
4019       case BlackASideCastleFR:\r
4020       /* POP Fabien */\r
4021         sprintf(user_move, "o-o-o\n");\r
4022         break;\r
4023       case WhitePromotionQueen:\r
4024       case BlackPromotionQueen:\r
4025       case WhitePromotionRook:\r
4026       case BlackPromotionRook:\r
4027       case WhitePromotionBishop:\r
4028       case BlackPromotionBishop:\r
4029       case WhitePromotionKnight:\r
4030       case BlackPromotionKnight:\r
4031       case WhitePromotionKing:\r
4032       case BlackPromotionKing:\r
4033       case WhitePromotionChancellor:\r
4034       case BlackPromotionChancellor:\r
4035       case WhitePromotionArchbishop:\r
4036       case BlackPromotionArchbishop:\r
4037         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)\r
4038             sprintf(user_move, "%c%c%c%c=%c\n",\r
4039                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4040                 PieceToChar(WhiteFerz));\r
4041         else if(gameInfo.variant == VariantGreat)\r
4042             sprintf(user_move, "%c%c%c%c=%c\n",\r
4043                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4044                 PieceToChar(WhiteMan));\r
4045         else\r
4046             sprintf(user_move, "%c%c%c%c=%c\n",\r
4047                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4048                 PieceToChar(PromoPiece(moveType)));\r
4049         break;\r
4050       case WhiteDrop:\r
4051       case BlackDrop:\r
4052         sprintf(user_move, "%c@%c%c\n",\r
4053                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
4054                 AAA + toX, ONE + toY);\r
4055         break;\r
4056       case NormalMove:\r
4057       case WhiteCapturesEnPassant:\r
4058       case BlackCapturesEnPassant:\r
4059       case IllegalMove:  /* could be a variant we don't quite understand */\r
4060         sprintf(user_move, "%c%c%c%c\n",\r
4061                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
4062         break;\r
4063     }\r
4064     SendToICS(user_move);\r
4065 }\r
4066 \r
4067 void\r
4068 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
4069      int rf, ff, rt, ft;\r
4070      char promoChar;\r
4071      char move[7];\r
4072 {\r
4073     if (rf == DROP_RANK) {\r
4074         sprintf(move, "%c@%c%c\n",\r
4075                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
4076     } else {\r
4077         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
4078             sprintf(move, "%c%c%c%c\n",\r
4079                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
4080         } else {\r
4081             sprintf(move, "%c%c%c%c%c\n",\r
4082                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
4083         }\r
4084     }\r
4085 }\r
4086 \r
4087 void\r
4088 ProcessICSInitScript(f)\r
4089      FILE *f;\r
4090 {\r
4091     char buf[MSG_SIZ];\r
4092 \r
4093     while (fgets(buf, MSG_SIZ, f)) {\r
4094         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
4095     }\r
4096 \r
4097     fclose(f);\r
4098 }\r
4099 \r
4100 \r
4101 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
4102 void\r
4103 AlphaRank(char *move, int n)\r
4104 {\r
4105     char *p = move, c; int x, y;\r
4106 \r
4107     if (appData.debugMode) {\r
4108         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
4109     }\r
4110 \r
4111     if(move[1]=='*' && \r
4112        move[2]>='0' && move[2]<='9' &&\r
4113        move[3]>='a' && move[3]<='x'    ) {\r
4114         move[1] = '@';\r
4115         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4116         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4117     } else\r
4118     if(move[0]>='0' && move[0]<='9' &&\r
4119        move[1]>='a' && move[1]<='x' &&\r
4120        move[2]>='0' && move[2]<='9' &&\r
4121        move[3]>='a' && move[3]<='x'    ) {\r
4122         /* input move, Shogi -> normal */\r
4123         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;\r
4124         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\r
4125         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4126         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4127     } else\r
4128     if(move[1]=='@' &&\r
4129        move[3]>='0' && move[3]<='9' &&\r
4130        move[2]>='a' && move[2]<='x'    ) {\r
4131         move[1] = '*';\r
4132         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4133         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4134     } else\r
4135     if(\r
4136        move[0]>='a' && move[0]<='x' &&\r
4137        move[3]>='0' && move[3]<='9' &&\r
4138        move[2]>='a' && move[2]<='x'    ) {\r
4139          /* output move, normal -> Shogi */\r
4140         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
4141         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
4142         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4143         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4144         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
4145     }\r
4146     if (appData.debugMode) {\r
4147         fprintf(debugFP, "   out = '%s'\n", move);\r
4148     }\r
4149 }\r
4150 \r
4151 /* Parser for moves from gnuchess, ICS, or user typein box */\r
4152 Boolean\r
4153 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
4154      char *move;\r
4155      int moveNum;\r
4156      ChessMove *moveType;\r
4157      int *fromX, *fromY, *toX, *toY;\r
4158      char *promoChar;\r
4159 {       \r
4160     if (appData.debugMode) {\r
4161         fprintf(debugFP, "move to parse: %s\n", move);\r
4162     }\r
4163     *moveType = yylexstr(moveNum, move);\r
4164 \r
4165     switch (*moveType) {\r
4166       case WhitePromotionChancellor:\r
4167       case BlackPromotionChancellor:\r
4168       case WhitePromotionArchbishop:\r
4169       case BlackPromotionArchbishop:\r
4170       case WhitePromotionQueen:\r
4171       case BlackPromotionQueen:\r
4172       case WhitePromotionRook:\r
4173       case BlackPromotionRook:\r
4174       case WhitePromotionBishop:\r
4175       case BlackPromotionBishop:\r
4176       case WhitePromotionKnight:\r
4177       case BlackPromotionKnight:\r
4178       case WhitePromotionKing:\r
4179       case BlackPromotionKing:\r
4180       case NormalMove:\r
4181       case WhiteCapturesEnPassant:\r
4182       case BlackCapturesEnPassant:\r
4183       case WhiteKingSideCastle:\r
4184       case WhiteQueenSideCastle:\r
4185       case BlackKingSideCastle:\r
4186       case BlackQueenSideCastle:\r
4187       case WhiteKingSideCastleWild:\r
4188       case WhiteQueenSideCastleWild:\r
4189       case BlackKingSideCastleWild:\r
4190       case BlackQueenSideCastleWild:\r
4191       /* Code added by Tord: */\r
4192       case WhiteHSideCastleFR:\r
4193       case WhiteASideCastleFR:\r
4194       case BlackHSideCastleFR:\r
4195       case BlackASideCastleFR:\r
4196       /* End of code added by Tord */\r
4197       case IllegalMove:         /* bug or odd chess variant */\r
4198         *fromX = currentMoveString[0] - AAA;\r
4199         *fromY = currentMoveString[1] - ONE;\r
4200         *toX = currentMoveString[2] - AAA;\r
4201         *toY = currentMoveString[3] - ONE;\r
4202         *promoChar = currentMoveString[4];\r
4203         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
4204             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
4205     if (appData.debugMode) {\r
4206         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
4207     }\r
4208             *fromX = *fromY = *toX = *toY = 0;\r
4209             return FALSE;\r
4210         }\r
4211         if (appData.testLegality) {\r
4212           return (*moveType != IllegalMove);\r
4213         } else {\r
4214           return !(fromX == fromY && toX == toY);\r
4215         }\r
4216 \r
4217       case WhiteDrop:\r
4218       case BlackDrop:\r
4219         *fromX = *moveType == WhiteDrop ?\r
4220           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
4221           (int) CharToPiece(ToLower(currentMoveString[0]));\r
4222         *fromY = DROP_RANK;\r
4223         *toX = currentMoveString[2] - AAA;\r
4224         *toY = currentMoveString[3] - ONE;\r
4225         *promoChar = NULLCHAR;\r
4226         return TRUE;\r
4227 \r
4228       case AmbiguousMove:\r
4229       case ImpossibleMove:\r
4230       case (ChessMove) 0:       /* end of file */\r
4231       case ElapsedTime:\r
4232       case Comment:\r
4233       case PGNTag:\r
4234       case NAG:\r
4235       case WhiteWins:\r
4236       case BlackWins:\r
4237       case GameIsDrawn:\r
4238       default:\r
4239     if (appData.debugMode) {\r
4240         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
4241     }\r
4242         /* bug? */\r
4243         *fromX = *fromY = *toX = *toY = 0;\r
4244         *promoChar = NULLCHAR;\r
4245         return FALSE;\r
4246     }\r
4247 }\r
4248 \r
4249 /* [AS] FRC game initialization */\r
4250 static int FindEmptySquare( Board board, int n )\r
4251 {\r
4252     int i = 0;\r
4253 \r
4254     while( 1 ) {\r
4255         while( board[0][i] != EmptySquare ) i++;\r
4256         if( n == 0 )\r
4257             break;\r
4258         n--;\r
4259         i++;\r
4260     }\r
4261 \r
4262     return i;\r
4263 }\r
4264 \r
4265 #if 0\r
4266 static void ShuffleFRC( Board board )\r
4267 {\r
4268     int i;\r
4269 \r
4270     srand( time(0) );\r
4271     \r
4272     for( i=0; i<8; i++ ) {\r
4273         board[0][i] = EmptySquare;\r
4274     }\r
4275 \r
4276     board[0][(rand() % 4)*2  ] = WhiteBishop; /* On dark square */\r
4277     board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */\r
4278     board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;\r
4279     board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;\r
4280     board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;\r
4281     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4282     initialRights[1]  = initialRights[4]  =\r
4283     castlingRights[0][1] = castlingRights[0][4] = i;\r
4284     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4285     initialRights[2]  = initialRights[5]  =\r
4286     castlingRights[0][2] = castlingRights[0][5] = i;\r
4287     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4288     initialRights[0]  = initialRights[3]  =\r
4289     castlingRights[0][0] = castlingRights[0][3] = i;\r
4290 \r
4291     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4292         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4293     }\r
4294 }\r
4295 \r
4296 static unsigned char FRC_KnightTable[10] = {\r
4297     0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33\r
4298 };\r
4299 \r
4300 static void SetupFRC( Board board, int pos_index )\r
4301 {\r
4302     int i;\r
4303     unsigned char knights;\r
4304 \r
4305     /* Bring the position index into a safe range (just in case...) */\r
4306     if( pos_index < 0 ) pos_index = 0;\r
4307 \r
4308     pos_index %= 960;\r
4309 \r
4310     /* Clear the board */\r
4311     for( i=0; i<8; i++ ) {\r
4312         board[0][i] = EmptySquare;\r
4313     }\r
4314 \r
4315     /* Place bishops and queen */\r
4316     board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */\r
4317     pos_index /= 4;\r
4318     \r
4319     board[0][ (pos_index % 4)*2     ] = WhiteBishop; /* On dark square */\r
4320     pos_index /= 4;\r
4321 \r
4322     board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;\r
4323     pos_index /= 6;\r
4324 \r
4325     /* Place knigths */\r
4326     knights = FRC_KnightTable[ pos_index ];\r
4327 \r
4328     board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;\r
4329     board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;\r
4330 \r
4331     /* Place rooks and king */\r
4332     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4333     initialRights[1]  = initialRights[4]  =\r
4334     castlingRights[0][1] = castlingRights[0][4] = i;\r
4335     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4336     initialRights[2]  = initialRights[5]  =\r
4337     castlingRights[0][2] = castlingRights[0][5] = i;\r
4338     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4339     initialRights[0]  = initialRights[3]  =\r
4340     castlingRights[0][0] = castlingRights[0][3] = i;\r
4341 \r
4342     /* Mirror piece placement for black */\r
4343     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4344         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4345     }\r
4346 }\r
4347 #else\r
4348 // [HGM] shuffle: a more general way to suffle opening setups, applicable to arbitrry variants.\r
4349 // All positions will have equal probability, but the current method will not provide a unique\r
4350 // numbering scheme for arrays that contain 3 or more pieces of the same kind.\r
4351 #define DARK 1\r
4352 #define LITE 2\r
4353 #define ANY 3\r
4354 \r
4355 int squaresLeft[4];\r
4356 int piecesLeft[(int)BlackPawn];\r
4357 long long int seed, nrOfShuffles;\r
4358 \r
4359 void GetPositionNumber()\r
4360 {       // sets global variable seed\r
4361         int i;\r
4362 \r
4363         seed = appData.defaultFrcPosition;\r
4364         if(seed < 0) { // randomize based on time for negative FRC position numbers\r
4365                 srandom(time(0)); \r
4366                 for(i=0; i<50; i++) seed += random();\r
4367                 seed = random() ^ random() >> 8 ^ random() << 8;\r
4368                 if(seed<0) seed = -seed;\r
4369         }\r
4370 }\r
4371 \r
4372 int put(Board board, int pieceType, int rank, int n, int shade)\r
4373 // put the piece on the (n-1)-th empty squares of the given shade\r
4374 {\r
4375         int i;\r
4376 \r
4377         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
4378                 if( ((i-BOARD_LEFT)&1)+1 & shade && board[rank][i] == EmptySquare && n-- == 0) {\r
4379                         board[rank][i] = (ChessSquare) pieceType;\r
4380                         squaresLeft[(i-BOARD_LEFT&1) + 1]--;\r
4381                         squaresLeft[ANY]--;\r
4382                         piecesLeft[pieceType]--; \r
4383                         return i;\r
4384                 }\r
4385         }\r
4386         return -1;\r
4387 }\r
4388 \r
4389 \r
4390 void AddOnePiece(Board board, int pieceType, int rank, int shade)\r
4391 // calculate where the next piece goes, (any empty square), and put it there\r
4392 {\r
4393         int i;\r
4394 \r
4395         i = seed % squaresLeft[shade];\r
4396         nrOfShuffles *= squaresLeft[shade];\r
4397         seed /= squaresLeft[shade];\r
4398         put(board, pieceType, rank, i, shade);\r
4399 }\r
4400 \r
4401 void AddTwoPieces(Board board, int pieceType, int rank)\r
4402 // calculate where the next 2 identical pieces go, (any empty square), and put it there\r
4403 {\r
4404         int i, n=squaresLeft[ANY], j=n-1, k;\r
4405 \r
4406         k = n*(n-1)/2; // nr of possibilities, not counting permutations\r
4407         i = seed % k;  // pick one\r
4408         nrOfShuffles *= k;\r
4409         seed /= k;\r
4410         while(i >= j) i -= j--;\r
4411         j = n - 1 - j; i += j;\r
4412         put(board, pieceType, rank, j, ANY);\r
4413         put(board, pieceType, rank, i, ANY);\r
4414 }\r
4415 \r
4416 void SetUpShuffle(Board board, int number)\r
4417 {\r
4418         int i, p, first=1;\r
4419 \r
4420         GetPositionNumber(); nrOfShuffles = 1;\r
4421 \r
4422         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;\r
4423         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;\r
4424         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];\r
4425 \r
4426         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;\r
4427 \r
4428         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board\r
4429             p = (int) board[0][i];\r
4430             if(p < (int) BlackPawn) piecesLeft[p] ++;\r
4431             board[0][i] = EmptySquare;\r
4432         }\r
4433 \r
4434         if(PosFlags(0) & F_ALL_CASTLE_OK) {\r
4435             // shuffles restricted to allow normal castling put KRR first\r
4436             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle\r
4437                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4438             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles\r
4439                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4440             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling\r
4441                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);\r
4442             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling\r
4443                 put(board, WhiteRook, 0, 0, ANY);\r
4444             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle\r
4445         }\r
4446 \r
4447         if((BOARD_RGHT-BOARD_LEFT & 1) == 0)\r
4448             // only for even boards make effort to put pairs of colorbound pieces on opposite colors\r
4449             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {\r
4450                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;\r
4451                 while(piecesLeft[p] >= 2) {\r
4452                     AddOnePiece(board, p, 0, LITE);\r
4453                     AddOnePiece(board, p, 0, DARK);\r
4454                 }\r
4455                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)\r
4456             }\r
4457 \r
4458         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {\r
4459             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere\r
4460             // but we leave King and Rooks for last, to possibly obey FRC restriction\r
4461             if(p == (int)WhiteRook) continue;\r
4462             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations\r
4463             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece\r
4464         }\r
4465 \r
4466         // now everything is placed, except perhaps King (Unicorn) and Rooks\r
4467 \r
4468         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {\r
4469             // Last King gets castling rights\r
4470             while(piecesLeft[(int)WhiteUnicorn]) {\r
4471                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4472                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4473             }\r
4474 \r
4475             while(piecesLeft[(int)WhiteKing]) {\r
4476                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4477                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4478             }\r
4479 \r
4480 \r
4481         } else {\r
4482             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);\r
4483             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);\r
4484         }\r
4485 \r
4486         // Only Rooks can be left; simply place them all\r
4487         while(piecesLeft[(int)WhiteRook]) {\r
4488                 i = put(board, WhiteRook, 0, 0, ANY);\r
4489                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights\r
4490                         if(first) {\r
4491                                 first=0;\r
4492                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;\r
4493                         }\r
4494                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;\r
4495                 }\r
4496         }\r
4497         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white\r
4498             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;\r
4499         }\r
4500 \r
4501         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize\r
4502 }\r
4503 \r
4504 #endif\r
4505 \r
4506 int SetCharTable( char *table, const char * map )\r
4507 /* [HGM] moved here from winboard.c because of its general usefulness */\r
4508 /*       Basically a safe strcpy that uses the last character as King */\r
4509 {\r
4510     int result = FALSE; int NrPieces;\r
4511 \r
4512     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
4513                     && NrPieces >= 12 && !(NrPieces&1)) {\r
4514         int i; /* [HGM] Accept even length from 12 to 34 */\r
4515 \r
4516         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
4517         for( i=0; i<NrPieces/2-1; i++ ) {\r
4518             table[i] = map[i];\r
4519             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
4520         }\r
4521         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
4522         table[(int) BlackKing]  = map[NrPieces-1];\r
4523 \r
4524         result = TRUE;\r
4525     }\r
4526 \r
4527     return result;\r
4528 }\r
4529 \r
4530 void Prelude(Board board)\r
4531 {       // [HGM] superchess: random selection of exo-pieces\r
4532         int i, j, k; ChessSquare p; \r
4533         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };\r
4534 \r
4535         GetPositionNumber(); // use FRC position number\r
4536 \r
4537         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table\r
4538             SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4539             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) \r
4540                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;\r
4541         }\r
4542 \r
4543         j = seed%4;                 seed /= 4; \r
4544         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4545         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4546         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4547         j = seed%3 + (seed%3 >= j); seed /= 3; \r
4548         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4549         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4550         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4551         j = seed%3;                 seed /= 3; \r
4552         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4553         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4554         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4555         j = seed%2 + (seed%2 >= j); seed /= 2; \r
4556         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4557         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4558         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4559         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);\r
4560         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);\r
4561         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);\r
4562         put(board, exoPieces[0],    0, 0, ANY);\r
4563         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];\r
4564 }\r
4565 \r
4566 void\r
4567 InitPosition(redraw)\r
4568      int redraw;\r
4569 {\r
4570     ChessSquare (* pieces)[BOARD_SIZE];\r
4571     int i, j, pawnRow, overrule,\r
4572     oldx = gameInfo.boardWidth,\r
4573     oldy = gameInfo.boardHeight,\r
4574     oldh = gameInfo.holdingsWidth,\r
4575     oldv = gameInfo.variant;\r
4576 \r
4577     currentMove = forwardMostMove = backwardMostMove = 0;\r
4578     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request\r
4579 \r
4580     /* [AS] Initialize pv info list [HGM] and game status */\r
4581     {\r
4582         for( i=0; i<MAX_MOVES; i++ ) {\r
4583             pvInfoList[i].depth = 0;\r
4584             epStatus[i]=EP_NONE;\r
4585             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
4586         }\r
4587 \r
4588         initialRulePlies = 0; /* 50-move counter start */\r
4589 \r
4590         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4591         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4592     }\r
4593 \r
4594     \r
4595     /* [HGM] logic here is completely changed. In stead of full positions */\r
4596     /* the initialized data only consist of the two backranks. The switch */\r
4597     /* selects which one we will use, which is than copied to the Board   */\r
4598     /* initialPosition, which for the rest is initialized by Pawns and    */\r
4599     /* empty squares. This initial position is then copied to boards[0],  */\r
4600     /* possibly after shuffling, so that it remains available.            */\r
4601 \r
4602     gameInfo.holdingsWidth = 0; /* default board sizes */\r
4603     gameInfo.boardWidth    = 8;\r
4604     gameInfo.boardHeight   = 8;\r
4605     gameInfo.holdingsSize  = 0;\r
4606     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
4607     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
4608     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
4609 \r
4610     switch (gameInfo.variant) {\r
4611     case VariantFischeRandom:\r
4612       shuffleOpenings = TRUE;\r
4613     default:\r
4614       pieces = FIDEArray;\r
4615       break;\r
4616     case VariantShatranj:\r
4617       pieces = ShatranjArray;\r
4618       nrCastlingRights = 0;\r
4619       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
4620       break;\r
4621     case VariantTwoKings:\r
4622       pieces = twoKingsArray;\r
4623       nrCastlingRights = 8;                 /* add rights for second King */\r
4624       castlingRights[0][6] = initialRights[2] = 5;\r
4625       castlingRights[0][7] = initialRights[5] = 5;\r
4626       castlingRank[6] = 0;\r
4627       castlingRank[7] = BOARD_HEIGHT-1;\r
4628       break;\r
4629     case VariantCapaRandom:\r
4630       shuffleOpenings = TRUE;\r
4631     case VariantCapablanca:\r
4632       pieces = CapablancaArray;\r
4633       gameInfo.boardWidth = 10;\r
4634       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4635       break;\r
4636     case VariantGothic:\r
4637       pieces = GothicArray;\r
4638       gameInfo.boardWidth = 10;\r
4639       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4640       break;\r
4641     case VariantJanus:\r
4642       pieces = JanusArray;\r
4643       gameInfo.boardWidth = 10;\r
4644       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
4645       nrCastlingRights = 6;\r
4646         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4647         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4648         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH-1>>1;\r
4649         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4650         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4651         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH-1>>1;\r
4652       break;\r
4653     case VariantFalcon:\r
4654       pieces = FalconArray;\r
4655       gameInfo.boardWidth = 10;\r
4656       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4657       break;\r
4658     case VariantXiangqi:\r
4659       pieces = XiangqiArray;\r
4660       gameInfo.boardWidth  = 9;\r
4661       gameInfo.boardHeight = 10;\r
4662       nrCastlingRights = 0;\r
4663       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4664       break;\r
4665     case VariantShogi:\r
4666       pieces = ShogiArray;\r
4667       gameInfo.boardWidth  = 9;\r
4668       gameInfo.boardHeight = 9;\r
4669       gameInfo.holdingsSize = 7;\r
4670       nrCastlingRights = 0;\r
4671       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4672       break;\r
4673     case VariantCourier:\r
4674       pieces = CourierArray;\r
4675       gameInfo.boardWidth  = 12;\r
4676       nrCastlingRights = 0;\r
4677       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4678       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4679       break;\r
4680     case VariantKnightmate:\r
4681       pieces = KnightmateArray;\r
4682       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4683       break;\r
4684     case VariantFairy:\r
4685       pieces = fairyArray;\r
4686       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4687       break;\r
4688     case VariantGreat:\r
4689       pieces = GreatArray;\r
4690       gameInfo.boardWidth = 10;\r
4691       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");\r
4692       gameInfo.holdingsSize = 8;\r
4693       break;\r
4694     case VariantSuper:\r
4695       pieces = FIDEArray;\r
4696       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
4697       gameInfo.holdingsSize = 8;\r
4698       startedFromSetupPosition = TRUE;\r
4699       break;\r
4700     case VariantCrazyhouse:\r
4701     case VariantBughouse:\r
4702       pieces = FIDEArray;\r
4703       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4704       gameInfo.holdingsSize = 5;\r
4705       break;\r
4706     case VariantWildCastle:\r
4707       pieces = FIDEArray;\r
4708       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4709       shuffleOpenings = 1;\r
4710       break;\r
4711     case VariantNoCastle:\r
4712       pieces = FIDEArray;\r
4713       nrCastlingRights = 0;\r
4714       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4715       /* !!?unconstrained back-rank shuffle */\r
4716       shuffleOpenings = 1;\r
4717       break;\r
4718     }\r
4719 \r
4720     overrule = 0;\r
4721     if(appData.NrFiles >= 0) {\r
4722         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4723         gameInfo.boardWidth = appData.NrFiles;\r
4724     }\r
4725     if(appData.NrRanks >= 0) {\r
4726         gameInfo.boardHeight = appData.NrRanks;\r
4727     }\r
4728     if(appData.holdingsSize >= 0) {\r
4729         i = appData.holdingsSize;\r
4730         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4731         gameInfo.holdingsSize = i;\r
4732     }\r
4733     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4734     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4735         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);\r
4736 \r
4737     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4738     if(pawnRow < 1) pawnRow = 1;\r
4739 \r
4740     /* User pieceToChar list overrules defaults */\r
4741     if(appData.pieceToCharTable != NULL)\r
4742         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4743 \r
4744     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4745 \r
4746         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4747             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4748         for( i=0; i<BOARD_HEIGHT; i++ )\r
4749             initialPosition[i][j] = s;\r
4750 \r
4751         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4752         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4753         initialPosition[pawnRow][j] = WhitePawn;\r
4754         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4755         if(gameInfo.variant == VariantXiangqi) {\r
4756             if(j&1) {\r
4757                 initialPosition[pawnRow][j] = \r
4758                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4759                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4760                    initialPosition[2][j] = WhiteCannon;\r
4761                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4762                 }\r
4763             }\r
4764         }\r
4765         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4766     }\r
4767     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4768 \r
4769             j=BOARD_LEFT+1;\r
4770             initialPosition[1][j] = WhiteBishop;\r
4771             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4772             j=BOARD_RGHT-2;\r
4773             initialPosition[1][j] = WhiteRook;\r
4774             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4775     }\r
4776 \r
4777     if( nrCastlingRights == -1) {\r
4778         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4779         /*       This sets default castling rights from none to normal corners   */\r
4780         /* Variants with other castling rights must set them themselves above    */\r
4781         nrCastlingRights = 6;\r
4782        \r
4783         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4784         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4785         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4786         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4787         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4788         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4789      }\r
4790 \r
4791      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
4792      if(gameInfo.variant == VariantGreat) { // promotion commoners\r
4793         initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-1] = WhiteMan;\r
4794         initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-2] = 9;\r
4795         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;\r
4796         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;\r
4797      }\r
4798 #if 0\r
4799     if(gameInfo.variant == VariantFischeRandom) {\r
4800       if( appData.defaultFrcPosition < 0 ) {\r
4801         ShuffleFRC( initialPosition );\r
4802       }\r
4803       else {\r
4804         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4805       }\r
4806       startedFromSetupPosition = TRUE;\r
4807     } else \r
4808 #else\r
4809   if (appData.debugMode) {\r
4810     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
4811   }\r
4812     if(shuffleOpenings) {\r
4813         SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
4814         startedFromSetupPosition = TRUE;\r
4815     }\r
4816 #endif\r
4817     if(startedFromPositionFile) {\r
4818       /* [HGM] loadPos: use PositionFile for every new game */\r
4819       CopyBoard(initialPosition, filePosition);\r
4820       for(i=0; i<nrCastlingRights; i++)\r
4821           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4822       startedFromSetupPosition = TRUE;\r
4823     }\r
4824 \r
4825     CopyBoard(boards[0], initialPosition);\r
4826 \r
4827     if(oldx != gameInfo.boardWidth ||\r
4828        oldy != gameInfo.boardHeight ||\r
4829        oldh != gameInfo.holdingsWidth\r
4830 #ifdef GOTHIC\r
4831        || oldv == VariantGothic ||        // For licensing popups\r
4832        gameInfo.variant == VariantGothic\r
4833 #endif\r
4834 #ifdef FALCON\r
4835        || oldv == VariantFalcon ||\r
4836        gameInfo.variant == VariantFalcon\r
4837 #endif\r
4838                                          )\r
4839             InitDrawingSizes(-2 ,0);\r
4840 \r
4841     if (redraw)\r
4842       DrawPosition(TRUE, boards[currentMove]);\r
4843 }\r
4844 \r
4845 void\r
4846 SendBoard(cps, moveNum)\r
4847      ChessProgramState *cps;\r
4848      int moveNum;\r
4849 {\r
4850     char message[MSG_SIZ];\r
4851     \r
4852     if (cps->useSetboard) {\r
4853       char* fen = PositionToFEN(moveNum, cps->useFEN960);\r
4854       sprintf(message, "setboard %s\n", fen);\r
4855       SendToProgram(message, cps);\r
4856       free(fen);\r
4857 \r
4858     } else {\r
4859       ChessSquare *bp;\r
4860       int i, j;\r
4861       /* Kludge to set black to move, avoiding the troublesome and now\r
4862        * deprecated "black" command.\r
4863        */\r
4864       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4865 \r
4866       SendToProgram("edit\n", cps);\r
4867       SendToProgram("#\n", cps);\r
4868       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4869         bp = &boards[moveNum][i][BOARD_LEFT];\r
4870         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4871           if ((int) *bp < (int) BlackPawn) {\r
4872             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4873                     AAA + j, ONE + i);\r
4874             if(message[0] == '+' || message[0] == '~') {\r
4875                 sprintf(message, "%c%c%c+\n",\r
4876                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4877                         AAA + j, ONE + i);\r
4878             }\r
4879             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4880                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4881                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4882             }\r
4883             SendToProgram(message, cps);\r
4884           }\r
4885         }\r
4886       }\r
4887     \r
4888       SendToProgram("c\n", cps);\r
4889       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4890         bp = &boards[moveNum][i][BOARD_LEFT];\r
4891         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4892           if (((int) *bp != (int) EmptySquare)\r
4893               && ((int) *bp >= (int) BlackPawn)) {\r
4894             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4895                     AAA + j, ONE + i);\r
4896             if(message[0] == '+' || message[0] == '~') {\r
4897                 sprintf(message, "%c%c%c+\n",\r
4898                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4899                         AAA + j, ONE + i);\r
4900             }\r
4901             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4902                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4903                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4904             }\r
4905             SendToProgram(message, cps);\r
4906           }\r
4907         }\r
4908       }\r
4909     \r
4910       SendToProgram(".\n", cps);\r
4911     }\r
4912     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4913 }\r
4914 \r
4915 int\r
4916 IsPromotion(fromX, fromY, toX, toY)\r
4917      int fromX, fromY, toX, toY;\r
4918 {\r
4919     /* [HGM] add Shogi promotions */\r
4920     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4921     ChessSquare piece;\r
4922 \r
4923     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4924       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4925    /* [HGM] Note to self: line above also weeds out drops */\r
4926     piece = boards[currentMove][fromY][fromX];\r
4927     if(gameInfo.variant == VariantShogi) {\r
4928         promotionZoneSize = 3;\r
4929         highestPromotingPiece = (int)WhiteKing;\r
4930         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4931            and if in normal chess we then allow promotion to King, why not\r
4932            allow promotion of other piece in Shogi?                         */\r
4933     }\r
4934     if((int)piece >= BlackPawn) {\r
4935         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4936              return FALSE;\r
4937         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4938     } else {\r
4939         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4940            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4941     }\r
4942     return ( (int)piece <= highestPromotingPiece );\r
4943 }\r
4944 \r
4945 int\r
4946 InPalace(row, column)\r
4947      int row, column;\r
4948 {   /* [HGM] for Xiangqi */\r
4949     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4950          column < (BOARD_WIDTH + 4)/2 &&\r
4951          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4952     return FALSE;\r
4953 }\r
4954 \r
4955 int\r
4956 PieceForSquare (x, y)\r
4957      int x;\r
4958      int y;\r
4959 {\r
4960   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
4961      return -1;\r
4962   else\r
4963      return boards[currentMove][y][x];\r
4964 }\r
4965 \r
4966 int\r
4967 OKToStartUserMove(x, y)\r
4968      int x, y;\r
4969 {\r
4970     ChessSquare from_piece;\r
4971     int white_piece;\r
4972 \r
4973     if (matchMode) return FALSE;\r
4974     if (gameMode == EditPosition) return TRUE;\r
4975 \r
4976     if (x >= 0 && y >= 0)\r
4977       from_piece = boards[currentMove][y][x];\r
4978     else\r
4979       from_piece = EmptySquare;\r
4980 \r
4981     if (from_piece == EmptySquare) return FALSE;\r
4982 \r
4983     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4984       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4985 \r
4986     switch (gameMode) {\r
4987       case PlayFromGameFile:\r
4988       case AnalyzeFile:\r
4989       case TwoMachinesPlay:\r
4990       case EndOfGame:\r
4991         return FALSE;\r
4992 \r
4993       case IcsObserving:\r
4994       case IcsIdle:\r
4995         return FALSE;\r
4996 \r
4997       case MachinePlaysWhite:\r
4998       case IcsPlayingBlack:\r
4999         if (appData.zippyPlay) return FALSE;\r
5000         if (white_piece) {\r
5001             DisplayMoveError(_("You are playing Black"));\r
5002             return FALSE;\r
5003         }\r
5004         break;\r
5005 \r
5006       case MachinePlaysBlack:\r
5007       case IcsPlayingWhite:\r
5008         if (appData.zippyPlay) return FALSE;\r
5009         if (!white_piece) {\r
5010             DisplayMoveError(_("You are playing White"));\r
5011             return FALSE;\r
5012         }\r
5013         break;\r
5014 \r
5015       case EditGame:\r
5016         if (!white_piece && WhiteOnMove(currentMove)) {\r
5017             DisplayMoveError(_("It is White's turn"));\r
5018             return FALSE;\r
5019         }           \r
5020         if (white_piece && !WhiteOnMove(currentMove)) {\r
5021             DisplayMoveError(_("It is Black's turn"));\r
5022             return FALSE;\r
5023         }           \r
5024         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
5025             /* Editing correspondence game history */\r
5026             /* Could disallow this or prompt for confirmation */\r
5027             cmailOldMove = -1;\r
5028         }\r
5029         if (currentMove < forwardMostMove) {\r
5030             /* Discarding moves */\r
5031             /* Could prompt for confirmation here,\r
5032                but I don't think that's such a good idea */\r
5033             forwardMostMove = currentMove;\r
5034         }\r
5035         break;\r
5036 \r
5037       case BeginningOfGame:\r
5038         if (appData.icsActive) return FALSE;\r
5039         if (!appData.noChessProgram) {\r
5040             if (!white_piece) {\r
5041                 DisplayMoveError(_("You are playing White"));\r
5042                 return FALSE;\r
5043             }\r
5044         }\r
5045         break;\r
5046         \r
5047       case Training:\r
5048         if (!white_piece && WhiteOnMove(currentMove)) {\r
5049             DisplayMoveError(_("It is White's turn"));\r
5050             return FALSE;\r
5051         }           \r
5052         if (white_piece && !WhiteOnMove(currentMove)) {\r
5053             DisplayMoveError(_("It is Black's turn"));\r
5054             return FALSE;\r
5055         }           \r
5056         break;\r
5057 \r
5058       default:\r
5059       case IcsExamining:\r
5060         break;\r
5061     }\r
5062     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
5063         && gameMode != AnalyzeFile && gameMode != Training) {\r
5064         DisplayMoveError(_("Displayed position is not current"));\r
5065         return FALSE;\r
5066     }\r
5067     return TRUE;\r
5068 }\r
5069 \r
5070 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
5071 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
5072 int lastLoadGameUseList = FALSE;\r
5073 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
5074 ChessMove lastLoadGameStart = (ChessMove) 0;\r
5075 \r
5076 \r
5077 ChessMove\r
5078 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
5079      int fromX, fromY, toX, toY;\r
5080      int promoChar;\r
5081 {\r
5082     ChessMove moveType;\r
5083     ChessSquare pdown, pup;\r
5084 \r
5085     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
5086     if ((fromX == toX) && (fromY == toY)) {\r
5087         return ImpossibleMove;\r
5088     }\r
5089 \r
5090     /* [HGM] suppress all moves into holdings area and guard band */\r
5091     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
5092             return ImpossibleMove;\r
5093 \r
5094     /* [HGM] <sameColor> moved to here from winboard.c */\r
5095     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
5096     pdown = boards[currentMove][fromY][fromX];\r
5097     pup = boards[currentMove][toY][toX];\r
5098     if (    gameMode != EditPosition &&\r
5099             (WhitePawn <= pdown && pdown < BlackPawn &&\r
5100              WhitePawn <= pup && pup < BlackPawn  ||\r
5101              BlackPawn <= pdown && pdown < EmptySquare &&\r
5102              BlackPawn <= pup && pup < EmptySquare \r
5103             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
5104                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
5105                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
5106         )           )\r
5107          return ImpossibleMove;\r
5108 \r
5109     /* Check if the user is playing in turn.  This is complicated because we\r
5110        let the user "pick up" a piece before it is his turn.  So the piece he\r
5111        tried to pick up may have been captured by the time he puts it down!\r
5112        Therefore we use the color the user is supposed to be playing in this\r
5113        test, not the color of the piece that is currently on the starting\r
5114        square---except in EditGame mode, where the user is playing both\r
5115        sides; fortunately there the capture race can't happen.  (It can\r
5116        now happen in IcsExamining mode, but that's just too bad.  The user\r
5117        will get a somewhat confusing message in that case.)\r
5118        */\r
5119 \r
5120     switch (gameMode) {\r
5121       case PlayFromGameFile:\r
5122       case AnalyzeFile:\r
5123       case TwoMachinesPlay:\r
5124       case EndOfGame:\r
5125       case IcsObserving:\r
5126       case IcsIdle:\r
5127         /* We switched into a game mode where moves are not accepted,\r
5128            perhaps while the mouse button was down. */\r
5129         return ImpossibleMove;\r
5130 \r
5131       case MachinePlaysWhite:\r
5132         /* User is moving for Black */\r
5133         if (WhiteOnMove(currentMove)) {\r
5134             DisplayMoveError(_("It is White's turn"));\r
5135             return ImpossibleMove;\r
5136         }\r
5137         break;\r
5138 \r
5139       case MachinePlaysBlack:\r
5140         /* User is moving for White */\r
5141         if (!WhiteOnMove(currentMove)) {\r
5142             DisplayMoveError(_("It is Black's turn"));\r
5143             return ImpossibleMove;\r
5144         }\r
5145         break;\r
5146 \r
5147       case EditGame:\r
5148       case IcsExamining:\r
5149       case BeginningOfGame:\r
5150       case AnalyzeMode:\r
5151       case Training:\r
5152         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
5153             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
5154             /* User is moving for Black */\r
5155             if (WhiteOnMove(currentMove)) {\r
5156                 DisplayMoveError(_("It is White's turn"));\r
5157                 return ImpossibleMove;\r
5158             }\r
5159         } else {\r
5160             /* User is moving for White */\r
5161             if (!WhiteOnMove(currentMove)) {\r
5162                 DisplayMoveError(_("It is Black's turn"));\r
5163                 return ImpossibleMove;\r
5164             }\r
5165         }\r
5166         break;\r
5167 \r
5168       case IcsPlayingBlack:\r
5169         /* User is moving for Black */\r
5170         if (WhiteOnMove(currentMove)) {\r
5171             if (!appData.premove) {\r
5172                 DisplayMoveError(_("It is White's turn"));\r
5173             } else if (toX >= 0 && toY >= 0) {\r
5174                 premoveToX = toX;\r
5175                 premoveToY = toY;\r
5176                 premoveFromX = fromX;\r
5177                 premoveFromY = fromY;\r
5178                 premovePromoChar = promoChar;\r
5179                 gotPremove = 1;\r
5180                 if (appData.debugMode) \r
5181                     fprintf(debugFP, "Got premove: fromX %d,"\r
5182                             "fromY %d, toX %d, toY %d\n",\r
5183                             fromX, fromY, toX, toY);\r
5184             }\r
5185             return ImpossibleMove;\r
5186         }\r
5187         break;\r
5188 \r
5189       case IcsPlayingWhite:\r
5190         /* User is moving for White */\r
5191         if (!WhiteOnMove(currentMove)) {\r
5192             if (!appData.premove) {\r
5193                 DisplayMoveError(_("It is Black's turn"));\r
5194             } else if (toX >= 0 && toY >= 0) {\r
5195                 premoveToX = toX;\r
5196                 premoveToY = toY;\r
5197                 premoveFromX = fromX;\r
5198                 premoveFromY = fromY;\r
5199                 premovePromoChar = promoChar;\r
5200                 gotPremove = 1;\r
5201                 if (appData.debugMode) \r
5202                     fprintf(debugFP, "Got premove: fromX %d,"\r
5203                             "fromY %d, toX %d, toY %d\n",\r
5204                             fromX, fromY, toX, toY);\r
5205             }\r
5206             return ImpossibleMove;\r
5207         }\r
5208         break;\r
5209 \r
5210       default:\r
5211         break;\r
5212 \r
5213       case EditPosition:\r
5214         /* EditPosition, empty square, or different color piece;\r
5215            click-click move is possible */\r
5216         if (toX == -2 || toY == -2) {\r
5217             boards[0][fromY][fromX] = EmptySquare;\r
5218             return AmbiguousMove;\r
5219         } else if (toX >= 0 && toY >= 0) {\r
5220             boards[0][toY][toX] = boards[0][fromY][fromX];\r
5221             boards[0][fromY][fromX] = EmptySquare;\r
5222             return AmbiguousMove;\r
5223         }\r
5224         return ImpossibleMove;\r
5225     }\r
5226 \r
5227     /* [HGM] If move started in holdings, it means a drop */\r
5228     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
5229          if( pup != EmptySquare ) return ImpossibleMove;\r
5230          if(appData.testLegality) {\r
5231              /* it would be more logical if LegalityTest() also figured out\r
5232               * which drops are legal. For now we forbid pawns on back rank.\r
5233               * Shogi is on its own here...\r
5234               */\r
5235              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
5236                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
5237                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
5238          }\r
5239          return WhiteDrop; /* Not needed to specify white or black yet */\r
5240     }\r
5241 \r
5242     userOfferedDraw = FALSE;\r
5243         \r
5244     /* [HGM] always test for legality, to get promotion info */\r
5245     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
5246                           epStatus[currentMove], castlingRights[currentMove],\r
5247                                          fromY, fromX, toY, toX, promoChar);\r
5248 \r
5249     /* [HGM] but possibly ignore an IllegalMove result */\r
5250     if (appData.testLegality) {\r
5251         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
5252             DisplayMoveError(_("Illegal move"));\r
5253             return ImpossibleMove;\r
5254         }\r
5255     }\r
5256 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);\r
5257     return moveType;\r
5258     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
5259        function is made into one that returns an OK move type if FinishMove\r
5260        should be called. This to give the calling driver routine the\r
5261        opportunity to finish the userMove input with a promotion popup,\r
5262        without bothering the user with this for invalid or illegal moves */\r
5263 \r
5264 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
5265 }\r
5266 \r
5267 /* Common tail of UserMoveEvent and DropMenuEvent */\r
5268 int\r
5269 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
5270      ChessMove moveType;\r
5271      int fromX, fromY, toX, toY;\r
5272      /*char*/int promoChar;\r
5273 {\r
5274     char *bookHit = 0;\r
5275 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);\r
5276     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { \r
5277         // [HGM] superchess: suppress promotions to non-available piece\r
5278         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5279         if(WhiteOnMove(currentMove)) {\r
5280             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;\r
5281         } else {\r
5282             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;\r
5283         }\r
5284     }\r
5285 \r
5286     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion\r
5287        move type in caller when we know the move is a legal promotion */\r
5288     if(moveType == NormalMove && promoChar)\r
5289         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
5290 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);\r
5291     /* [HGM] convert drag-and-drop piece drops to standard form */\r
5292     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
5293          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
5294          fromX = boards[currentMove][fromY][fromX];\r
5295          fromY = DROP_RANK;\r
5296     }\r
5297 \r
5298     /* [HGM] <popupFix> The following if has been moved here from\r
5299        UserMoveEvent(). Because it seemed to belon here (why not allow\r
5300        piece drops in training games?), and because it can only be\r
5301        performed after it is known to what we promote. */\r
5302     if (gameMode == Training) {\r
5303       /* compare the move played on the board to the next move in the\r
5304        * game. If they match, display the move and the opponent's response. \r
5305        * If they don't match, display an error message.\r
5306        */\r
5307       int saveAnimate;\r
5308       Board testBoard;\r
5309       CopyBoard(testBoard, boards[currentMove]);\r
5310       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
5311 \r
5312       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
5313         ForwardInner(currentMove+1);\r
5314 \r
5315         /* Autoplay the opponent's response.\r
5316          * if appData.animate was TRUE when Training mode was entered,\r
5317          * the response will be animated.\r
5318          */\r
5319         saveAnimate = appData.animate;\r
5320         appData.animate = animateTraining;\r
5321         ForwardInner(currentMove+1);\r
5322         appData.animate = saveAnimate;\r
5323 \r
5324         /* check for the end of the game */\r
5325         if (currentMove >= forwardMostMove) {\r
5326           gameMode = PlayFromGameFile;\r
5327           ModeHighlight();\r
5328           SetTrainingModeOff();\r
5329           DisplayInformation(_("End of game"));\r
5330         }\r
5331       } else {\r
5332         DisplayError(_("Incorrect move"), 0);\r
5333       }\r
5334       return 1;\r
5335     }\r
5336 \r
5337   /* Ok, now we know that the move is good, so we can kill\r
5338      the previous line in Analysis Mode */\r
5339   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
5340     forwardMostMove = currentMove;\r
5341   }\r
5342 \r
5343   /* If we need the chess program but it's dead, restart it */\r
5344   ResurrectChessProgram();\r
5345 \r
5346   /* A user move restarts a paused game*/\r
5347   if (pausing)\r
5348     PauseEvent();\r
5349 \r
5350   thinkOutput[0] = NULLCHAR;\r
5351 \r
5352   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
5353 \r
5354     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) \r
5355                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
5356         // [HGM] superchess: take promotion piece out of holdings\r
5357         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5358         if(WhiteOnMove(forwardMostMove-1)) {\r
5359             if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])\r
5360                 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;\r
5361         } else {\r
5362             if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])\r
5363                 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;\r
5364         }\r
5365     }\r
5366 \r
5367   if (gameMode == BeginningOfGame) {\r
5368     if (appData.noChessProgram) {\r
5369       gameMode = EditGame;\r
5370       SetGameInfo();\r
5371     } else {\r
5372       char buf[MSG_SIZ];\r
5373       gameMode = MachinePlaysBlack;\r
5374       StartClocks();\r
5375       SetGameInfo();\r
5376       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
5377       DisplayTitle(buf);\r
5378       if (first.sendName) {\r
5379         sprintf(buf, "name %s\n", gameInfo.white);\r
5380         SendToProgram(buf, &first);\r
5381       }\r
5382       StartClocks();\r
5383     }\r
5384     ModeHighlight();\r
5385   }\r
5386 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);\r
5387   /* Relay move to ICS or chess engine */\r
5388   if (appData.icsActive) {\r
5389     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
5390         gameMode == IcsExamining) {\r
5391       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5392       ics_user_moved = 1;\r
5393     }\r
5394   } else {\r
5395     if (first.sendTime && (gameMode == BeginningOfGame ||\r
5396                            gameMode == MachinePlaysWhite ||\r
5397                            gameMode == MachinePlaysBlack)) {\r
5398       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
5399     }\r
5400     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
5401          // [HGM] book: if program might be playing, let it use book\r
5402         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);\r
5403         first.maybeThinking = TRUE;\r
5404     } else SendMoveToProgram(forwardMostMove-1, &first);\r
5405     if (currentMove == cmailOldMove + 1) {\r
5406       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
5407     }\r
5408   }\r
5409 \r
5410   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5411 \r
5412   switch (gameMode) {\r
5413   case EditGame:\r
5414     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
5415                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
5416     case MT_NONE:\r
5417     case MT_CHECK:\r
5418       break;\r
5419     case MT_CHECKMATE:\r
5420       if (WhiteOnMove(currentMove)) {\r
5421         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
5422       } else {\r
5423         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
5424       }\r
5425       break;\r
5426     case MT_STALEMATE:\r
5427       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
5428       break;\r
5429     }\r
5430     break;\r
5431     \r
5432   case MachinePlaysBlack:\r
5433   case MachinePlaysWhite:\r
5434     /* disable certain menu options while machine is thinking */\r
5435     SetMachineThinkingEnables();\r
5436     break;\r
5437 \r
5438   default:\r
5439     break;\r
5440   }\r
5441 \r
5442   if(bookHit) { // [HGM] book: simulate book reply\r
5443         static char bookMove[MSG_SIZ]; // a bit generous?\r
5444 \r
5445         programStats.depth = programStats.nodes = programStats.time = \r
5446         programStats.score = programStats.got_only_move = 0;\r
5447         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
5448 \r
5449         strcpy(bookMove, "move ");\r
5450         strcat(bookMove, bookHit);\r
5451         HandleMachineMove(bookMove, &first);\r
5452   }\r
5453   return 1;\r
5454 }\r
5455 \r
5456 void\r
5457 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
5458      int fromX, fromY, toX, toY;\r
5459      int promoChar;\r
5460 {\r
5461     /* [HGM] This routine was added to allow calling of its two logical\r
5462        parts from other modules in the old way. Before, UserMoveEvent()\r
5463        automatically called FinishMove() if the move was OK, and returned\r
5464        otherwise. I separated the two, in order to make it possible to\r
5465        slip a promotion popup in between. But that it always needs two\r
5466        calls, to the first part, (now called UserMoveTest() ), and to\r
5467        FinishMove if the first part succeeded. Calls that do not need\r
5468        to do anything in between, can call this routine the old way. \r
5469     */\r
5470     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
5471 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);\r
5472     if(moveType != ImpossibleMove)\r
5473         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
5474 }\r
5475 \r
5476 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
5477 {\r
5478     char * hint = lastHint;\r
5479     FrontEndProgramStats stats;\r
5480 \r
5481     stats.which = cps == &first ? 0 : 1;\r
5482     stats.depth = cpstats->depth;\r
5483     stats.nodes = cpstats->nodes;\r
5484     stats.score = cpstats->score;\r
5485     stats.time = cpstats->time;\r
5486     stats.pv = cpstats->movelist;\r
5487     stats.hint = lastHint;\r
5488     stats.an_move_index = 0;\r
5489     stats.an_move_count = 0;\r
5490 \r
5491     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
5492         stats.hint = cpstats->move_name;\r
5493         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
5494         stats.an_move_count = cpstats->nr_moves;\r
5495     }\r
5496 \r
5497     SetProgramStats( &stats );\r
5498 }\r
5499 \r
5500 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)\r
5501 {   // [HGM] book: this routine intercepts moves to simulate book replies\r
5502     char *bookHit = NULL;\r
5503 \r
5504     //first determine if the incoming move brings opponent into his book\r
5505     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
5506         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
5507     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");\r
5508     if(bookHit != NULL && !cps->bookSuspend) {\r
5509         // make sure opponent is not going to reply after receiving move to book position\r
5510         SendToProgram("force\n", cps);\r
5511         cps->bookSuspend = TRUE; // flag indicating it has to be restarted\r
5512     }\r
5513     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move\r
5514     // now arrange restart after book miss\r
5515     if(bookHit) {\r
5516         // after a book hit we never send 'go', and the code after the call to this routine\r
5517         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').\r
5518         char buf[MSG_SIZ];\r
5519         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(\r
5520         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it\r
5521         SendToProgram(buf, cps);\r
5522         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'\r
5523     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine\r
5524         SendToProgram("go\n", cps);\r
5525         cps->bookSuspend = FALSE; // after a 'go' we are never suspended\r
5526     } else { // 'go' might be sent based on 'firstMove' after this routine returns\r
5527         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return\r
5528             SendToProgram("go\n", cps); \r
5529         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss\r
5530     }\r
5531     return bookHit; // notify caller of hit, so it can take action to send move to opponent\r
5532 }\r
5533 \r
5534 char *savedMessage;\r
5535 ChessProgramState *savedState;\r
5536 void DeferredBookMove(void)\r
5537 {\r
5538         if(savedState->lastPing != savedState->lastPong)\r
5539                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
5540         else\r
5541         HandleMachineMove(savedMessage, savedState);\r
5542 }\r
5543 \r
5544 void\r
5545 HandleMachineMove(message, cps)\r
5546      char *message;\r
5547      ChessProgramState *cps;\r
5548 {\r
5549     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
5550     char realname[MSG_SIZ];\r
5551     int fromX, fromY, toX, toY;\r
5552     ChessMove moveType;\r
5553     char promoChar;\r
5554     char *p;\r
5555     int machineWhite;\r
5556     char *bookHit;\r
5557 \r
5558 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit\r
5559     /*\r
5560      * Kludge to ignore BEL characters\r
5561      */\r
5562     while (*message == '\007') message++;\r
5563 \r
5564     /*\r
5565      * [HGM] engine debug message: ignore lines starting with '#' character\r
5566      */\r
5567     if(cps->debug && *message == '#') return;\r
5568 \r
5569     /*\r
5570      * Look for book output\r
5571      */\r
5572     if (cps == &first && bookRequested) {\r
5573         if (message[0] == '\t' || message[0] == ' ') {\r
5574             /* Part of the book output is here; append it */\r
5575             strcat(bookOutput, message);\r
5576             strcat(bookOutput, "  \n");\r
5577             return;\r
5578         } else if (bookOutput[0] != NULLCHAR) {\r
5579             /* All of book output has arrived; display it */\r
5580             char *p = bookOutput;\r
5581             while (*p != NULLCHAR) {\r
5582                 if (*p == '\t') *p = ' ';\r
5583                 p++;\r
5584             }\r
5585             DisplayInformation(bookOutput);\r
5586             bookRequested = FALSE;\r
5587             /* Fall through to parse the current output */\r
5588         }\r
5589     }\r
5590 \r
5591     /*\r
5592      * Look for machine move.\r
5593      */\r
5594     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
5595         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
5596     {\r
5597         /* This method is only useful on engines that support ping */\r
5598         if (cps->lastPing != cps->lastPong) {\r
5599           if (gameMode == BeginningOfGame) {\r
5600             /* Extra move from before last new; ignore */\r
5601             if (appData.debugMode) {\r
5602                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5603             }\r
5604           } else {\r
5605             if (appData.debugMode) {\r
5606                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5607                         cps->which, gameMode);\r
5608             }\r
5609 \r
5610             SendToProgram("undo\n", cps);\r
5611           }\r
5612           return;\r
5613         }\r
5614 \r
5615         switch (gameMode) {\r
5616           case BeginningOfGame:\r
5617             /* Extra move from before last reset; ignore */\r
5618             if (appData.debugMode) {\r
5619                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5620             }\r
5621             return;\r
5622 \r
5623           case EndOfGame:\r
5624           case IcsIdle:\r
5625           default:\r
5626             /* Extra move after we tried to stop.  The mode test is\r
5627                not a reliable way of detecting this problem, but it's\r
5628                the best we can do on engines that don't support ping.\r
5629             */\r
5630             if (appData.debugMode) {\r
5631                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5632                         cps->which, gameMode);\r
5633             }\r
5634             SendToProgram("undo\n", cps);\r
5635             return;\r
5636 \r
5637           case MachinePlaysWhite:\r
5638           case IcsPlayingWhite:\r
5639             machineWhite = TRUE;\r
5640             break;\r
5641 \r
5642           case MachinePlaysBlack:\r
5643           case IcsPlayingBlack:\r
5644             machineWhite = FALSE;\r
5645             break;\r
5646 \r
5647           case TwoMachinesPlay:\r
5648             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5649             break;\r
5650         }\r
5651         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5652             if (appData.debugMode) {\r
5653                 fprintf(debugFP,\r
5654                         "Ignoring move out of turn by %s, gameMode %d"\r
5655                         ", forwardMost %d\n",\r
5656                         cps->which, gameMode, forwardMostMove);\r
5657             }\r
5658             return;\r
5659         }\r
5660 \r
5661     if (appData.debugMode) { int f = forwardMostMove;\r
5662         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5663                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5664     }\r
5665         if(cps->alphaRank) AlphaRank(machineMove, 4);\r
5666         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5667                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5668             /* Machine move could not be parsed; ignore it. */\r
5669             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),\r
5670                     machineMove, cps->which);\r
5671             DisplayError(buf1, 0);\r
5672             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",\r
5673                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5674             if (gameMode == TwoMachinesPlay) {\r
5675               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5676                        buf1, GE_XBOARD);\r
5677             }\r
5678             return;\r
5679         }\r
5680 \r
5681         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5682         /* So we have to redo legality test with true e.p. status here,  */\r
5683         /* to make sure an illegal e.p. capture does not slip through,   */\r
5684         /* to cause a forfeit on a justified illegal-move complaint      */\r
5685         /* of the opponent.                                              */\r
5686         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5687             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5688                                                               ) {\r
5689            ChessMove moveType;\r
5690            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5691                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5692                              fromY, fromX, toY, toX, promoChar);\r
5693             if (appData.debugMode) {\r
5694                 int i;\r
5695                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5696                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5697                 fprintf(debugFP, "castling rights\n");\r
5698             }\r
5699             if(moveType == IllegalMove) {\r
5700                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5701                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5702                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5703                            buf1, GE_XBOARD);\r
5704            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
5705            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5706               when they shouldn't (like TSCP-Gothic) */\r
5707            switch(moveType) {\r
5708              case WhiteASideCastleFR:\r
5709              case BlackASideCastleFR:\r
5710                toX+=2;\r
5711                currentMoveString[2]++;\r
5712                break;\r
5713              case WhiteHSideCastleFR:\r
5714              case BlackHSideCastleFR:\r
5715                toX--;\r
5716                currentMoveString[2]--;\r
5717                break;\r
5718            }\r
5719         }\r
5720         hintRequested = FALSE;\r
5721         lastHint[0] = NULLCHAR;\r
5722         bookRequested = FALSE;\r
5723         /* Program may be pondering now */\r
5724         cps->maybeThinking = TRUE;\r
5725         if (cps->sendTime == 2) cps->sendTime = 1;\r
5726         if (cps->offeredDraw) cps->offeredDraw--;\r
5727 \r
5728 #if ZIPPY\r
5729         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5730             first.initDone) {\r
5731           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5732           ics_user_moved = 1;\r
5733           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
5734                 char buf[3*MSG_SIZ];\r
5735 \r
5736                 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",\r
5737                         programStats.depth,\r
5738                         programStats.score / 100.,\r
5739                         programStats.time / 100.,\r
5740                         (double) programStats.nodes,\r
5741                         programStats.nodes / (10*abs(programStats.time) + 1.),\r
5742                         programStats.movelist);\r
5743                 SendToICS(buf);\r
5744           }\r
5745         }\r
5746 #endif\r
5747         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5748         strcpy(machineMove, currentMoveString);\r
5749         strcat(machineMove, "\n");\r
5750         strcpy(moveList[forwardMostMove], machineMove);\r
5751 \r
5752         /* [AS] Save move info and clear stats for next move */\r
5753         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5754         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5755         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5756         ClearProgramStats();\r
5757         thinkOutput[0] = NULLCHAR;\r
5758         hiddenThinkOutputState = 0;\r
5759 \r
5760         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5761 \r
5762         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5763         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5764             int count = 0;\r
5765 \r
5766             while( count < adjudicateLossPlies ) {\r
5767                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5768 \r
5769                 if( count & 1 ) {\r
5770                     score = -score; /* Flip score for winning side */\r
5771                 }\r
5772 \r
5773                 if( score > adjudicateLossThreshold ) {\r
5774                     break;\r
5775                 }\r
5776 \r
5777                 count++;\r
5778             }\r
5779 \r
5780             if( count >= adjudicateLossPlies ) {\r
5781                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5782 \r
5783                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5784                     "Xboard adjudication", \r
5785                     GE_XBOARD );\r
5786 \r
5787                 return;\r
5788             }\r
5789         }\r
5790 \r
5791         if( gameMode == TwoMachinesPlay ) {\r
5792           // [HGM] some adjudications useful with buggy engines\r
5793             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
5794           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {\r
5795 \r
5796             if(appData.testLegality)\r
5797             // don't wait for engine to announce game end if we can judge ourselves\r
5798             switch (MateTest(boards[forwardMostMove],\r
5799                                  PosFlags(forwardMostMove), epFile,\r
5800                                        castlingRights[forwardMostMove]) ) {\r
5801               case MT_NONE:\r
5802               case MT_CHECK:\r
5803               default:\r
5804                 break;\r
5805               case MT_STALEMATE:\r
5806                 epStatus[forwardMostMove] = EP_STALEMATE;\r
5807                 if(appData.checkMates) {\r
5808                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5809                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5810                     GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",\r
5811                         GE_XBOARD );\r
5812                 }\r
5813                 break;\r
5814               case MT_CHECKMATE:\r
5815                 epStatus[forwardMostMove] = EP_CHECKMATE;\r
5816                 if(appData.checkMates) {\r
5817                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5818                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5819                     GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5820                     "Xboard adjudication: Checkmate", \r
5821                     GE_XBOARD );\r
5822                 }\r
5823                 break;\r
5824             }\r
5825 \r
5826             if( appData.testLegality )\r
5827             {   /* [HGM] Some more adjudications for obstinate engines */\r
5828                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5829                     NrWQ=0, NrBQ=0, NrW=0, bishopsColor = 0,\r
5830                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;\r
5831                 static int moveCount = 6;\r
5832 \r
5833                 /* First absolutely insufficient mating material. Count what is on board. */\r
5834                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5835                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5836                     int m=i;\r
5837 \r
5838                     switch((int) p)\r
5839                     {   /* count B,N,R and other of each side */\r
5840                         case WhiteKnight:\r
5841                              NrWN++; break;\r
5842                         case WhiteBishop:\r
5843                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5844                              bishopsColor |= 1 << ((i^j)&1);\r
5845                              NrWB++; break;\r
5846                         case BlackKnight:\r
5847                              NrBN++; break;\r
5848                         case BlackBishop:\r
5849                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5850                              bishopsColor |= 1 << ((i^j)&1);\r
5851                              NrBB++; break;\r
5852                         case WhiteRook:\r
5853                              NrWR++; break;\r
5854                         case BlackRook:\r
5855                              NrBR++; break;\r
5856                         case WhiteQueen:\r
5857                              NrWQ++; break;\r
5858                         case BlackQueen:\r
5859                              NrBQ++; break;\r
5860                         case EmptySquare: \r
5861                              break;\r
5862                         case BlackPawn:\r
5863                              m = 7-i;\r
5864                         case WhitePawn:\r
5865                              PawnAdvance += m; NrPawns++;\r
5866                     }\r
5867                     NrPieces += (p != EmptySquare);\r
5868                     NrW += ((int)p < (int)BlackPawn);\r
5869                     if(gameInfo.variant == VariantXiangqi && \r
5870                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {\r
5871                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces\r
5872                         NrW -= ((int)p < (int)BlackPawn);\r
5873                     }\r
5874                 }\r
5875 \r
5876                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&\r
5877                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||\r
5878                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color\r
5879                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5880 \r
5881                      /* always flag draws, for judging claims */\r
5882                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5883 \r
5884                      if(appData.materialDraws) {\r
5885                          /* but only adjudicate them if adjudication enabled */\r
5886                          SendToProgram("force\n", cps->other); // suppress reply\r
5887                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */\r
5888                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5889                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5890                          return;\r
5891                      }\r
5892                 }\r
5893 \r
5894                 /* Shatranj baring rule */\r
5895                 if( gameInfo.variant == VariantShatranj && (NrW == 1 || NrPieces - NrW == 1) )\r
5896                 {    /* bare King */\r
5897 \r
5898                      if(--bare < 0 && appData.checkMates) {\r
5899                          /* but only adjudicate them if adjudication enabled */\r
5900                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5901                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5902                          GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
5903                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5904                          return;\r
5905                      }\r
5906                 } else bare = 1;\r
5907 \r
5908                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5909                 if(NrPieces == 4 && \r
5910                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5911                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5912                    || NrWN==2 || NrBN==2     /* KNNK */\r
5913                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5914                   ) ) {\r
5915                      if(--moveCount < 0 && appData.trivialDraws)\r
5916                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5917                           SendToProgram("force\n", cps->other); // suppress reply\r
5918                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5919                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5920                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5921                           return;\r
5922                      }\r
5923                 } else moveCount = 6;\r
5924             }\r
5925           }\r
5926 #if 1\r
5927     if (appData.debugMode) { int i;\r
5928       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5929               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5930               appData.drawRepeats);\r
5931       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5932            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5933 \r
5934     }\r
5935 #endif\r
5936                 /* Check for rep-draws */\r
5937                 count = 0;\r
5938                 for(k = forwardMostMove-2;\r
5939                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5940                         epStatus[k] < EP_UNKNOWN &&\r
5941                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5942                     k-=2)\r
5943                 {   int rights=0;\r
5944 #if 0\r
5945     if (appData.debugMode) {\r
5946       fprintf(debugFP, " loop\n");\r
5947     }\r
5948 #endif\r
5949                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5950 #if 0\r
5951     if (appData.debugMode) {\r
5952       fprintf(debugFP, "match\n");\r
5953     }\r
5954 #endif\r
5955                         /* compare castling rights */\r
5956                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5957                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5958                                 rights++; /* King lost rights, while rook still had them */\r
5959                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5960                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5961                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5962                                    rights++; /* but at least one rook lost them */\r
5963                         }\r
5964                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5965                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5966                                 rights++; \r
5967                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5968                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5969                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5970                                    rights++;\r
5971                         }\r
5972 #if 0\r
5973     if (appData.debugMode) {\r
5974       for(i=0; i<nrCastlingRights; i++)\r
5975       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5976     }\r
5977 \r
5978     if (appData.debugMode) {\r
5979       fprintf(debugFP, " %d %d\n", rights, k);\r
5980     }\r
5981 #endif\r
5982                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5983                             && appData.drawRepeats > 1) {\r
5984                              /* adjudicate after user-specified nr of repeats */\r
5985                              SendToProgram("force\n", cps->other); // suppress reply\r
5986                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5987                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5988                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { \r
5989                                 // [HGM] xiangqi: check for forbidden perpetuals\r
5990                                 int m, ourPerpetual = 1, hisPerpetual = 1;\r
5991                                 for(m=forwardMostMove; m>k; m-=2) {\r
5992                                     if(MateTest(boards[m], PosFlags(m), \r
5993                                                         EP_NONE, castlingRights[m]) != MT_CHECK)\r
5994                                         ourPerpetual = 0; // the current mover did not always check\r
5995                                     if(MateTest(boards[m-1], PosFlags(m-1), \r
5996                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)\r
5997                                         hisPerpetual = 0; // the opponent did not always check\r
5998                                 }\r
5999                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
6000                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
6001                                            "Xboard adjudication: perpetual checking", GE_XBOARD );\r
6002                                     return;\r
6003                                 }\r
6004                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet\r
6005                                     break; // (or we would have caught him before). Abort repetition-checking loop.\r
6006                                 // if neither of us is checking all the time, or both are, it is draw\r
6007                                 // (illegal-chase forfeits not implemented yet!)\r
6008                              }\r
6009                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
6010                              return;\r
6011                         }\r
6012                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
6013                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
6014                     }\r
6015                 }\r
6016 \r
6017                 /* Now we test for 50-move draws. Determine ply count */\r
6018                 count = forwardMostMove;\r
6019                 /* look for last irreversble move */\r
6020                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
6021                     count--;\r
6022                 /* if we hit starting position, add initial plies */\r
6023                 if( count == backwardMostMove )\r
6024                     count -= initialRulePlies;\r
6025                 count = forwardMostMove - count; \r
6026                 if( count >= 100)\r
6027                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
6028                          /* this is used to judge if draw claims are legal */\r
6029                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
6030                          SendToProgram("force\n", cps->other); // suppress reply\r
6031                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6032                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6033                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
6034                          return;\r
6035                 }\r
6036 \r
6037                 /* if draw offer is pending, treat it as a draw claim\r
6038                  * when draw condition present, to allow engines a way to\r
6039                  * claim draws before making their move to avoid a race\r
6040                  * condition occurring after their move\r
6041                  */\r
6042                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
6043                          char *p = NULL;\r
6044                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
6045                              p = "Draw claim: 50-move rule";\r
6046                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
6047                              p = "Draw claim: 3-fold repetition";\r
6048                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
6049                              p = "Draw claim: insufficient mating material";\r
6050                          if( p != NULL ) {\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                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
6054                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6055                              return;\r
6056                          }\r
6057                 }\r
6058 \r
6059 \r
6060                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
6061                     SendToProgram("force\n", cps->other); // suppress reply\r
6062                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6063                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6064 \r
6065                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
6066 \r
6067                     return;\r
6068                 }\r
6069         }\r
6070 \r
6071         bookHit = NULL;\r
6072         if (gameMode == TwoMachinesPlay) {\r
6073             /* [HGM] relaying draw offers moved to after reception of move */\r
6074             /* and interpreting offer as claim if it brings draw condition */\r
6075             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
6076                 SendToProgram("draw\n", cps->other);\r
6077             }\r
6078             if (cps->other->sendTime) {\r
6079                 SendTimeRemaining(cps->other,\r
6080                                   cps->other->twoMachinesColor[0] == 'w');\r
6081             }\r
6082             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);\r
6083             if (firstMove && !bookHit) {\r
6084                 firstMove = FALSE;\r
6085                 if (cps->other->useColors) {\r
6086                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
6087                 }\r
6088                 SendToProgram("go\n", cps->other);\r
6089             }\r
6090             cps->other->maybeThinking = TRUE;\r
6091         }\r
6092 \r
6093         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6094         \r
6095         if (!pausing && appData.ringBellAfterMoves) {\r
6096             RingBell();\r
6097         }\r
6098 \r
6099         /* \r
6100          * Reenable menu items that were disabled while\r
6101          * machine was thinking\r
6102          */\r
6103         if (gameMode != TwoMachinesPlay)\r
6104             SetUserThinkingEnables();\r
6105 \r
6106         // [HGM] book: after book hit opponent has received move and is now in force mode\r
6107         // force the book reply into it, and then fake that it outputted this move by jumping\r
6108         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move\r
6109         if(bookHit) {\r
6110                 static char bookMove[MSG_SIZ]; // a bit generous?\r
6111 \r
6112                 strcpy(bookMove, "move ");\r
6113                 strcat(bookMove, bookHit);\r
6114                 message = bookMove;\r
6115                 cps = cps->other;\r
6116                 programStats.depth = programStats.nodes = programStats.time = \r
6117                 programStats.score = programStats.got_only_move = 0;\r
6118                 sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
6119 \r
6120                 if(cps->lastPing != cps->lastPong) {\r
6121                     savedMessage = message; // args for deferred call\r
6122                     savedState = cps;\r
6123                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
6124                     return;\r
6125                 }\r
6126                 goto FakeBookMove;\r
6127         }\r
6128 \r
6129         return;\r
6130     }\r
6131 \r
6132     /* Set special modes for chess engines.  Later something general\r
6133      *  could be added here; for now there is just one kludge feature,\r
6134      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
6135      *  when "xboard" is given as an interactive command.\r
6136      */\r
6137     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
6138         cps->useSigint = FALSE;\r
6139         cps->useSigterm = FALSE;\r
6140     }\r
6141 \r
6142     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
6143      * want this, I was asked to put it in, and obliged.\r
6144      */\r
6145     if (!strncmp(message, "setboard ", 9)) {\r
6146         Board initial_position; int i;\r
6147 \r
6148         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
6149 \r
6150         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
6151             DisplayError(_("Bad FEN received from engine"), 0);\r
6152             return ;\r
6153         } else {\r
6154            Reset(FALSE, FALSE);\r
6155            CopyBoard(boards[0], initial_position);\r
6156            initialRulePlies = FENrulePlies;\r
6157            epStatus[0] = FENepStatus;\r
6158            for( i=0; i<nrCastlingRights; i++ )\r
6159                 castlingRights[0][i] = FENcastlingRights[i];\r
6160            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
6161            else gameMode = MachinePlaysBlack;                 \r
6162            DrawPosition(FALSE, boards[currentMove]);\r
6163         }\r
6164         return;\r
6165     }\r
6166 \r
6167     /*\r
6168      * Look for communication commands\r
6169      */\r
6170     if (!strncmp(message, "telluser ", 9)) {\r
6171         DisplayNote(message + 9);\r
6172         return;\r
6173     }\r
6174     if (!strncmp(message, "tellusererror ", 14)) {\r
6175         DisplayError(message + 14, 0);\r
6176         return;\r
6177     }\r
6178     if (!strncmp(message, "tellopponent ", 13)) {\r
6179       if (appData.icsActive) {\r
6180         if (loggedOn) {\r
6181           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
6182           SendToICS(buf1);\r
6183         }\r
6184       } else {\r
6185         DisplayNote(message + 13);\r
6186       }\r
6187       return;\r
6188     }\r
6189     if (!strncmp(message, "tellothers ", 11)) {\r
6190       if (appData.icsActive) {\r
6191         if (loggedOn) {\r
6192           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
6193           SendToICS(buf1);\r
6194         }\r
6195       }\r
6196       return;\r
6197     }\r
6198     if (!strncmp(message, "tellall ", 8)) {\r
6199       if (appData.icsActive) {\r
6200         if (loggedOn) {\r
6201           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
6202           SendToICS(buf1);\r
6203         }\r
6204       } else {\r
6205         DisplayNote(message + 8);\r
6206       }\r
6207       return;\r
6208     }\r
6209     if (strncmp(message, "warning", 7) == 0) {\r
6210         /* Undocumented feature, use tellusererror in new code */\r
6211         DisplayError(message, 0);\r
6212         return;\r
6213     }\r
6214     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
6215         strcpy(realname, cps->tidy);\r
6216         strcat(realname, " query");\r
6217         AskQuestion(realname, buf2, buf1, cps->pr);\r
6218         return;\r
6219     }\r
6220     /* Commands from the engine directly to ICS.  We don't allow these to be \r
6221      *  sent until we are logged on. Crafty kibitzes have been known to \r
6222      *  interfere with the login process.\r
6223      */\r
6224     if (loggedOn) {\r
6225         if (!strncmp(message, "tellics ", 8)) {\r
6226             SendToICS(message + 8);\r
6227             SendToICS("\n");\r
6228             return;\r
6229         }\r
6230         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
6231             SendToICS(ics_prefix);\r
6232             SendToICS(message + 15);\r
6233             SendToICS("\n");\r
6234             return;\r
6235         }\r
6236         /* The following are for backward compatibility only */\r
6237         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
6238             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
6239             SendToICS(ics_prefix);\r
6240             SendToICS(message);\r
6241             SendToICS("\n");\r
6242             return;\r
6243         }\r
6244     }\r
6245     if (strncmp(message, "feature ", 8) == 0) {\r
6246       ParseFeatures(message+8, cps);\r
6247     }\r
6248     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
6249         return;\r
6250     }\r
6251     /*\r
6252      * If the move is illegal, cancel it and redraw the board.\r
6253      * Also deal with other error cases.  Matching is rather loose\r
6254      * here to accommodate engines written before the spec.\r
6255      */\r
6256     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
6257         strncmp(message, "Error", 5) == 0) {\r
6258         if (StrStr(message, "name") || \r
6259             StrStr(message, "rating") || StrStr(message, "?") ||\r
6260             StrStr(message, "result") || StrStr(message, "board") ||\r
6261             StrStr(message, "bk") || StrStr(message, "computer") ||\r
6262             StrStr(message, "variant") || StrStr(message, "hint") ||\r
6263             StrStr(message, "random") || StrStr(message, "depth") ||\r
6264             StrStr(message, "accepted")) {\r
6265             return;\r
6266         }\r
6267         if (StrStr(message, "protover")) {\r
6268           /* Program is responding to input, so it's apparently done\r
6269              initializing, and this error message indicates it is\r
6270              protocol version 1.  So we don't need to wait any longer\r
6271              for it to initialize and send feature commands. */\r
6272           FeatureDone(cps, 1);\r
6273           cps->protocolVersion = 1;\r
6274           return;\r
6275         }\r
6276         cps->maybeThinking = FALSE;\r
6277 \r
6278         if (StrStr(message, "draw")) {\r
6279             /* Program doesn't have "draw" command */\r
6280             cps->sendDrawOffers = 0;\r
6281             return;\r
6282         }\r
6283         if (cps->sendTime != 1 &&\r
6284             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
6285           /* Program apparently doesn't have "time" or "otim" command */\r
6286           cps->sendTime = 0;\r
6287           return;\r
6288         }\r
6289         if (StrStr(message, "analyze")) {\r
6290             cps->analysisSupport = FALSE;\r
6291             cps->analyzing = FALSE;\r
6292             Reset(FALSE, TRUE);\r
6293             sprintf(buf2, _("%s does not support analysis"), cps->tidy);\r
6294             DisplayError(buf2, 0);\r
6295             return;\r
6296         }\r
6297         if (StrStr(message, "(no matching move)st")) {\r
6298           /* Special kludge for GNU Chess 4 only */\r
6299           cps->stKludge = TRUE;\r
6300           SendTimeControl(cps, movesPerSession, timeControl,\r
6301                           timeIncrement, appData.searchDepth,\r
6302                           searchTime);\r
6303           return;\r
6304         }\r
6305         if (StrStr(message, "(no matching move)sd")) {\r
6306           /* Special kludge for GNU Chess 4 only */\r
6307           cps->sdKludge = TRUE;\r
6308           SendTimeControl(cps, movesPerSession, timeControl,\r
6309                           timeIncrement, appData.searchDepth,\r
6310                           searchTime);\r
6311           return;\r
6312         }\r
6313         if (!StrStr(message, "llegal")) {\r
6314             return;\r
6315         }\r
6316         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6317             gameMode == IcsIdle) return;\r
6318         if (forwardMostMove <= backwardMostMove) return;\r
6319 #if 0\r
6320         /* Following removed: it caused a bug where a real illegal move\r
6321            message in analyze mored would be ignored. */\r
6322         if (cps == &first && programStats.ok_to_send == 0) {\r
6323             /* Bogus message from Crafty responding to "."  This filtering\r
6324                can miss some of the bad messages, but fortunately the bug \r
6325                is fixed in current Crafty versions, so it doesn't matter. */\r
6326             return;\r
6327         }\r
6328 #endif\r
6329         if (pausing) PauseEvent();\r
6330         if (gameMode == PlayFromGameFile) {\r
6331             /* Stop reading this game file */\r
6332             gameMode = EditGame;\r
6333             ModeHighlight();\r
6334         }\r
6335         currentMove = --forwardMostMove;\r
6336         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
6337         SwitchClocks();\r
6338         DisplayBothClocks();\r
6339         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),\r
6340                 parseList[currentMove], cps->which);\r
6341         DisplayMoveError(buf1);\r
6342         DrawPosition(FALSE, boards[currentMove]);\r
6343 \r
6344         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
6345         /* only passes fully legal moves                            */\r
6346         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
6347             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
6348                                 "False illegal-move claim", GE_XBOARD );\r
6349         }\r
6350         return;\r
6351     }\r
6352     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
6353         /* Program has a broken "time" command that\r
6354            outputs a string not ending in newline.\r
6355            Don't use it. */\r
6356         cps->sendTime = 0;\r
6357     }\r
6358     \r
6359     /*\r
6360      * If chess program startup fails, exit with an error message.\r
6361      * Attempts to recover here are futile.\r
6362      */\r
6363     if ((StrStr(message, "unknown host") != NULL)\r
6364         || (StrStr(message, "No remote directory") != NULL)\r
6365         || (StrStr(message, "not found") != NULL)\r
6366         || (StrStr(message, "No such file") != NULL)\r
6367         || (StrStr(message, "can't alloc") != NULL)\r
6368         || (StrStr(message, "Permission denied") != NULL)) {\r
6369 \r
6370         cps->maybeThinking = FALSE;\r
6371         sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),\r
6372                 cps->which, cps->program, cps->host, message);\r
6373         RemoveInputSource(cps->isr);\r
6374         DisplayFatalError(buf1, 0, 1);\r
6375         return;\r
6376     }\r
6377     \r
6378     /* \r
6379      * Look for hint output\r
6380      */\r
6381     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
6382         if (cps == &first && hintRequested) {\r
6383             hintRequested = FALSE;\r
6384             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
6385                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
6386                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
6387                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
6388                                     fromY, fromX, toY, toX, promoChar, buf1);\r
6389                 sprintf(buf2, _("Hint: %s"), buf1);\r
6390                 DisplayInformation(buf2);\r
6391             } else {\r
6392                 /* Hint move could not be parsed!? */\r
6393                 sprintf(buf2,\r
6394                         _("Illegal hint move \"%s\"\nfrom %s chess program"),\r
6395                         buf1, cps->which);\r
6396                 DisplayError(buf2, 0);\r
6397             }\r
6398         } else {\r
6399             strcpy(lastHint, buf1);\r
6400         }\r
6401         return;\r
6402     }\r
6403 \r
6404     /*\r
6405      * Ignore other messages if game is not in progress\r
6406      */\r
6407     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6408         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
6409 \r
6410     /*\r
6411      * look for win, lose, draw, or draw offer\r
6412      */\r
6413     if (strncmp(message, "1-0", 3) == 0) {\r
6414         char *p, *q, *r = "";\r
6415         p = strchr(message, '{');\r
6416         if (p) {\r
6417             q = strchr(p, '}');\r
6418             if (q) {\r
6419                 *q = NULLCHAR;\r
6420                 r = p + 1;\r
6421             }\r
6422         }\r
6423         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
6424         return;\r
6425     } else if (strncmp(message, "0-1", 3) == 0) {\r
6426         char *p, *q, *r = "";\r
6427         p = strchr(message, '{');\r
6428         if (p) {\r
6429             q = strchr(p, '}');\r
6430             if (q) {\r
6431                 *q = NULLCHAR;\r
6432                 r = p + 1;\r
6433             }\r
6434         }\r
6435         /* Kludge for Arasan 4.1 bug */\r
6436         if (strcmp(r, "Black resigns") == 0) {\r
6437             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
6438             return;\r
6439         }\r
6440         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
6441         return;\r
6442     } else if (strncmp(message, "1/2", 3) == 0) {\r
6443         char *p, *q, *r = "";\r
6444         p = strchr(message, '{');\r
6445         if (p) {\r
6446             q = strchr(p, '}');\r
6447             if (q) {\r
6448                 *q = NULLCHAR;\r
6449                 r = p + 1;\r
6450             }\r
6451         }\r
6452             \r
6453         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
6454         return;\r
6455 \r
6456     } else if (strncmp(message, "White resign", 12) == 0) {\r
6457         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6458         return;\r
6459     } else if (strncmp(message, "Black resign", 12) == 0) {\r
6460         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6461         return;\r
6462     } else if (strncmp(message, "White matches", 13) == 0 ||\r
6463                strncmp(message, "Black matches", 13) == 0   ) {\r
6464         /* [HGM] ignore GNUShogi noises */\r
6465         return;\r
6466     } else if (strncmp(message, "White", 5) == 0 &&\r
6467                message[5] != '(' &&\r
6468                StrStr(message, "Black") == NULL) {\r
6469         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6470         return;\r
6471     } else if (strncmp(message, "Black", 5) == 0 &&\r
6472                message[5] != '(') {\r
6473         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6474         return;\r
6475     } else if (strcmp(message, "resign") == 0 ||\r
6476                strcmp(message, "computer resigns") == 0) {\r
6477         switch (gameMode) {\r
6478           case MachinePlaysBlack:\r
6479           case IcsPlayingBlack:\r
6480             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
6481             break;\r
6482           case MachinePlaysWhite:\r
6483           case IcsPlayingWhite:\r
6484             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
6485             break;\r
6486           case TwoMachinesPlay:\r
6487             if (cps->twoMachinesColor[0] == 'w')\r
6488               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6489             else\r
6490               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6491             break;\r
6492           default:\r
6493             /* can't happen */\r
6494             break;\r
6495         }\r
6496         return;\r
6497     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
6498         switch (gameMode) {\r
6499           case MachinePlaysBlack:\r
6500           case IcsPlayingBlack:\r
6501             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6502             break;\r
6503           case MachinePlaysWhite:\r
6504           case IcsPlayingWhite:\r
6505             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
6506             break;\r
6507           case TwoMachinesPlay:\r
6508             if (cps->twoMachinesColor[0] == 'w')\r
6509               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6510             else\r
6511               GameEnds(WhiteWins, "White mates", 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, "computer mates", 14) == 0) {\r
6519         switch (gameMode) {\r
6520           case MachinePlaysBlack:\r
6521           case IcsPlayingBlack:\r
6522             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
6523             break;\r
6524           case MachinePlaysWhite:\r
6525           case IcsPlayingWhite:\r
6526             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6527             break;\r
6528           case TwoMachinesPlay:\r
6529             if (cps->twoMachinesColor[0] == 'w')\r
6530               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6531             else\r
6532               GameEnds(BlackWins, "Black 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, "checkmate", 9) == 0) {\r
6540         if (WhiteOnMove(forwardMostMove)) {\r
6541             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6542         } else {\r
6543             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6544         }\r
6545         return;\r
6546     } else if (strstr(message, "Draw") != NULL ||\r
6547                strstr(message, "game is a draw") != NULL) {\r
6548         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
6549         return;\r
6550     } else if (strstr(message, "offer") != NULL &&\r
6551                strstr(message, "draw") != NULL) {\r
6552 #if ZIPPY\r
6553         if (appData.zippyPlay && first.initDone) {\r
6554             /* Relay offer to ICS */\r
6555             SendToICS(ics_prefix);\r
6556             SendToICS("draw\n");\r
6557         }\r
6558 #endif\r
6559         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
6560         if (gameMode == TwoMachinesPlay) {\r
6561             if (cps->other->offeredDraw) {\r
6562                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6563             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
6564             /* until after we also have move, to see if it is really claim */\r
6565             }\r
6566 #if 0\r
6567               else {\r
6568                 if (cps->other->sendDrawOffers) {\r
6569                     SendToProgram("draw\n", cps->other);\r
6570                 }\r
6571             }\r
6572 #endif\r
6573         } else if (gameMode == MachinePlaysWhite ||\r
6574                    gameMode == MachinePlaysBlack) {\r
6575           if (userOfferedDraw) {\r
6576             DisplayInformation(_("Machine accepts your draw offer"));\r
6577             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6578           } else {\r
6579             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));\r
6580           }\r
6581         }\r
6582     }\r
6583 \r
6584     \r
6585     /*\r
6586      * Look for thinking output\r
6587      */\r
6588     if ( appData.showThinking // [HGM] thinking: test all options that cause this output\r
6589           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
6590                                 ) {\r
6591         int plylev, mvleft, mvtot, curscore, time;\r
6592         char mvname[MOVE_LEN];\r
6593         u64 nodes; // [DM]\r
6594         char plyext;\r
6595         int ignore = FALSE;\r
6596         int prefixHint = FALSE;\r
6597         mvname[0] = NULLCHAR;\r
6598 \r
6599         switch (gameMode) {\r
6600           case MachinePlaysBlack:\r
6601           case IcsPlayingBlack:\r
6602             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6603             break;\r
6604           case MachinePlaysWhite:\r
6605           case IcsPlayingWhite:\r
6606             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6607             break;\r
6608           case AnalyzeMode:\r
6609           case AnalyzeFile:\r
6610             break;\r
6611           case IcsObserving: /* [DM] icsEngineAnalyze */\r
6612             if (!appData.icsEngineAnalyze) ignore = TRUE;\r
6613             break;\r
6614           case TwoMachinesPlay:\r
6615             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
6616                 ignore = TRUE;\r
6617             }\r
6618             break;\r
6619           default:\r
6620             ignore = TRUE;\r
6621             break;\r
6622         }\r
6623 \r
6624         if (!ignore) {\r
6625             buf1[0] = NULLCHAR;\r
6626             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6627                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
6628 \r
6629                 if (plyext != ' ' && plyext != '\t') {\r
6630                     time *= 100;\r
6631                 }\r
6632 \r
6633                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6634                 if( cps->scoreIsAbsolute && \r
6635                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
6636                 {\r
6637                     curscore = -curscore;\r
6638                 }\r
6639 \r
6640 \r
6641                 programStats.depth = plylev;\r
6642                 programStats.nodes = nodes;\r
6643                 programStats.time = time;\r
6644                 programStats.score = curscore;\r
6645                 programStats.got_only_move = 0;\r
6646 \r
6647                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
6648                         int ticklen;\r
6649 \r
6650                         if(cps->nps == 0) ticklen = 10*time;       // use engine reported time\r
6651                         else ticklen = (1000. * nodes) / cps->nps; // convert node count to time\r
6652                         if(WhiteOnMove(forwardMostMove)) \r
6653                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
6654                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
6655                 }\r
6656 \r
6657                 /* Buffer overflow protection */\r
6658                 if (buf1[0] != NULLCHAR) {\r
6659                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
6660                         && appData.debugMode) {\r
6661                         fprintf(debugFP,\r
6662                                 "PV is too long; using the first %d bytes.\n",\r
6663                                 sizeof(programStats.movelist) - 1);\r
6664                     }\r
6665 \r
6666                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
6667                 } else {\r
6668                     sprintf(programStats.movelist, " no PV\n");\r
6669                 }\r
6670 \r
6671                 if (programStats.seen_stat) {\r
6672                     programStats.ok_to_send = 1;\r
6673                 }\r
6674 \r
6675                 if (strchr(programStats.movelist, '(') != NULL) {\r
6676                     programStats.line_is_book = 1;\r
6677                     programStats.nr_moves = 0;\r
6678                     programStats.moves_left = 0;\r
6679                 } else {\r
6680                     programStats.line_is_book = 0;\r
6681                 }\r
6682 \r
6683                 SendProgramStatsToFrontend( cps, &programStats );\r
6684 \r
6685                 /* \r
6686                     [AS] Protect the thinkOutput buffer from overflow... this\r
6687                     is only useful if buf1 hasn't overflowed first!\r
6688                 */\r
6689                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
6690                         plylev, \r
6691                         (gameMode == TwoMachinesPlay ?\r
6692                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
6693                         ((double) curscore) / 100.0,\r
6694                         prefixHint ? lastHint : "",\r
6695                         prefixHint ? " " : "" );\r
6696 \r
6697                 if( buf1[0] != NULLCHAR ) {\r
6698                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
6699 \r
6700                     if( strlen(buf1) > max_len ) {\r
6701                         if( appData.debugMode) {\r
6702                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
6703                         }\r
6704                         buf1[max_len+1] = '\0';\r
6705                     }\r
6706 \r
6707                     strcat( thinkOutput, buf1 );\r
6708                 }\r
6709 \r
6710                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode\r
6711                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6712                     DisplayMove(currentMove - 1);\r
6713                     DisplayAnalysis();\r
6714                 }\r
6715                 return;\r
6716 \r
6717             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6718                 /* crafty (9.25+) says "(only move) <move>"\r
6719                  * if there is only 1 legal move\r
6720                  */\r
6721                 sscanf(p, "(only move) %s", buf1);\r
6722                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6723                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6724                 programStats.depth = 1;\r
6725                 programStats.nr_moves = 1;\r
6726                 programStats.moves_left = 1;\r
6727                 programStats.nodes = 1;\r
6728                 programStats.time = 1;\r
6729                 programStats.got_only_move = 1;\r
6730 \r
6731                 /* Not really, but we also use this member to\r
6732                    mean "line isn't going to change" (Crafty\r
6733                    isn't searching, so stats won't change) */\r
6734                 programStats.line_is_book = 1;\r
6735 \r
6736                 SendProgramStatsToFrontend( cps, &programStats );\r
6737                 \r
6738                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || \r
6739                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6740                     DisplayMove(currentMove - 1);\r
6741                     DisplayAnalysis();\r
6742                 }\r
6743                 return;\r
6744             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",\r
6745                               &time, &nodes, &plylev, &mvleft,\r
6746                               &mvtot, mvname) >= 5) {\r
6747                 /* The stat01: line is from Crafty (9.29+) in response\r
6748                    to the "." command */\r
6749                 programStats.seen_stat = 1;\r
6750                 cps->maybeThinking = TRUE;\r
6751 \r
6752                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6753                   return;\r
6754 \r
6755                 programStats.depth = plylev;\r
6756                 programStats.time = time;\r
6757                 programStats.nodes = nodes;\r
6758                 programStats.moves_left = mvleft;\r
6759                 programStats.nr_moves = mvtot;\r
6760                 strcpy(programStats.move_name, mvname);\r
6761                 programStats.ok_to_send = 1;\r
6762                 programStats.movelist[0] = '\0';\r
6763 \r
6764                 SendProgramStatsToFrontend( cps, &programStats );\r
6765 \r
6766                 DisplayAnalysis();\r
6767                 return;\r
6768 \r
6769             } else if (strncmp(message,"++",2) == 0) {\r
6770                 /* Crafty 9.29+ outputs this */\r
6771                 programStats.got_fail = 2;\r
6772                 return;\r
6773 \r
6774             } else if (strncmp(message,"--",2) == 0) {\r
6775                 /* Crafty 9.29+ outputs this */\r
6776                 programStats.got_fail = 1;\r
6777                 return;\r
6778 \r
6779             } else if (thinkOutput[0] != NULLCHAR &&\r
6780                        strncmp(message, "    ", 4) == 0) {\r
6781                 unsigned message_len;\r
6782 \r
6783                 p = message;\r
6784                 while (*p && *p == ' ') p++;\r
6785 \r
6786                 message_len = strlen( p );\r
6787 \r
6788                 /* [AS] Avoid buffer overflow */\r
6789                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6790                     strcat(thinkOutput, " ");\r
6791                     strcat(thinkOutput, p);\r
6792                 }\r
6793 \r
6794                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6795                     strcat(programStats.movelist, " ");\r
6796                     strcat(programStats.movelist, p);\r
6797                 }\r
6798 \r
6799                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||\r
6800                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6801                     DisplayMove(currentMove - 1);\r
6802                     DisplayAnalysis();\r
6803                 }\r
6804                 return;\r
6805             }\r
6806         }\r
6807         else {\r
6808             buf1[0] = NULLCHAR;\r
6809 \r
6810             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
6811                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6812             {\r
6813                 ChessProgramStats cpstats;\r
6814 \r
6815                 if (plyext != ' ' && plyext != '\t') {\r
6816                     time *= 100;\r
6817                 }\r
6818 \r
6819                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6820                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6821                     curscore = -curscore;\r
6822                 }\r
6823 \r
6824                 cpstats.depth = plylev;\r
6825                 cpstats.nodes = nodes;\r
6826                 cpstats.time = time;\r
6827                 cpstats.score = curscore;\r
6828                 cpstats.got_only_move = 0;\r
6829                 cpstats.movelist[0] = '\0';\r
6830 \r
6831                 if (buf1[0] != NULLCHAR) {\r
6832                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6833                 }\r
6834 \r
6835                 cpstats.ok_to_send = 0;\r
6836                 cpstats.line_is_book = 0;\r
6837                 cpstats.nr_moves = 0;\r
6838                 cpstats.moves_left = 0;\r
6839 \r
6840                 SendProgramStatsToFrontend( cps, &cpstats );\r
6841             }\r
6842         }\r
6843     }\r
6844 }\r
6845 \r
6846 \r
6847 /* Parse a game score from the character string "game", and\r
6848    record it as the history of the current game.  The game\r
6849    score is NOT assumed to start from the standard position. \r
6850    The display is not updated in any way.\r
6851    */\r
6852 void\r
6853 ParseGameHistory(game)\r
6854      char *game;\r
6855 {\r
6856     ChessMove moveType;\r
6857     int fromX, fromY, toX, toY, boardIndex;\r
6858     char promoChar;\r
6859     char *p, *q;\r
6860     char buf[MSG_SIZ];\r
6861 \r
6862     if (appData.debugMode)\r
6863       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6864 \r
6865     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6866     gameInfo.site = StrSave(appData.icsHost);\r
6867     gameInfo.date = PGNDate();\r
6868     gameInfo.round = StrSave("-");\r
6869 \r
6870     /* Parse out names of players */\r
6871     while (*game == ' ') game++;\r
6872     p = buf;\r
6873     while (*game != ' ') *p++ = *game++;\r
6874     *p = NULLCHAR;\r
6875     gameInfo.white = StrSave(buf);\r
6876     while (*game == ' ') game++;\r
6877     p = buf;\r
6878     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6879     *p = NULLCHAR;\r
6880     gameInfo.black = StrSave(buf);\r
6881 \r
6882     /* Parse moves */\r
6883     boardIndex = blackPlaysFirst ? 1 : 0;\r
6884     yynewstr(game);\r
6885     for (;;) {\r
6886         yyboardindex = boardIndex;\r
6887         moveType = (ChessMove) yylex();\r
6888         switch (moveType) {\r
6889           case IllegalMove:             /* maybe suicide chess, etc. */\r
6890   if (appData.debugMode) {\r
6891     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6892     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6893     setbuf(debugFP, NULL);\r
6894   }\r
6895           case WhitePromotionChancellor:\r
6896           case BlackPromotionChancellor:\r
6897           case WhitePromotionArchbishop:\r
6898           case BlackPromotionArchbishop:\r
6899           case WhitePromotionQueen:\r
6900           case BlackPromotionQueen:\r
6901           case WhitePromotionRook:\r
6902           case BlackPromotionRook:\r
6903           case WhitePromotionBishop:\r
6904           case BlackPromotionBishop:\r
6905           case WhitePromotionKnight:\r
6906           case BlackPromotionKnight:\r
6907           case WhitePromotionKing:\r
6908           case BlackPromotionKing:\r
6909           case NormalMove:\r
6910           case WhiteCapturesEnPassant:\r
6911           case BlackCapturesEnPassant:\r
6912           case WhiteKingSideCastle:\r
6913           case WhiteQueenSideCastle:\r
6914           case BlackKingSideCastle:\r
6915           case BlackQueenSideCastle:\r
6916           case WhiteKingSideCastleWild:\r
6917           case WhiteQueenSideCastleWild:\r
6918           case BlackKingSideCastleWild:\r
6919           case BlackQueenSideCastleWild:\r
6920           /* PUSH Fabien */\r
6921           case WhiteHSideCastleFR:\r
6922           case WhiteASideCastleFR:\r
6923           case BlackHSideCastleFR:\r
6924           case BlackASideCastleFR:\r
6925           /* POP Fabien */\r
6926             fromX = currentMoveString[0] - AAA;\r
6927             fromY = currentMoveString[1] - ONE;\r
6928             toX = currentMoveString[2] - AAA;\r
6929             toY = currentMoveString[3] - ONE;\r
6930             promoChar = currentMoveString[4];\r
6931             break;\r
6932           case WhiteDrop:\r
6933           case BlackDrop:\r
6934             fromX = moveType == WhiteDrop ?\r
6935               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6936             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6937             fromY = DROP_RANK;\r
6938             toX = currentMoveString[2] - AAA;\r
6939             toY = currentMoveString[3] - ONE;\r
6940             promoChar = NULLCHAR;\r
6941             break;\r
6942           case AmbiguousMove:\r
6943             /* bug? */\r
6944             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);\r
6945   if (appData.debugMode) {\r
6946     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
6947     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6948     setbuf(debugFP, NULL);\r
6949   }\r
6950             DisplayError(buf, 0);\r
6951             return;\r
6952           case ImpossibleMove:\r
6953             /* bug? */\r
6954             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);\r
6955   if (appData.debugMode) {\r
6956     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
6957     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6958     setbuf(debugFP, NULL);\r
6959   }\r
6960             DisplayError(buf, 0);\r
6961             return;\r
6962           case (ChessMove) 0:   /* end of file */\r
6963             if (boardIndex < backwardMostMove) {\r
6964                 /* Oops, gap.  How did that happen? */\r
6965                 DisplayError(_("Gap in move list"), 0);\r
6966                 return;\r
6967             }\r
6968             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
6969             if (boardIndex > forwardMostMove) {\r
6970                 forwardMostMove = boardIndex;\r
6971             }\r
6972             return;\r
6973           case ElapsedTime:\r
6974             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
6975                 strcat(parseList[boardIndex-1], " ");\r
6976                 strcat(parseList[boardIndex-1], yy_text);\r
6977             }\r
6978             continue;\r
6979           case Comment:\r
6980           case PGNTag:\r
6981           case NAG:\r
6982           default:\r
6983             /* ignore */\r
6984             continue;\r
6985           case WhiteWins:\r
6986           case BlackWins:\r
6987           case GameIsDrawn:\r
6988           case GameUnfinished:\r
6989             if (gameMode == IcsExamining) {\r
6990                 if (boardIndex < backwardMostMove) {\r
6991                     /* Oops, gap.  How did that happen? */\r
6992                     return;\r
6993                 }\r
6994                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6995                 return;\r
6996             }\r
6997             gameInfo.result = moveType;\r
6998             p = strchr(yy_text, '{');\r
6999             if (p == NULL) p = strchr(yy_text, '(');\r
7000             if (p == NULL) {\r
7001                 p = yy_text;\r
7002                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
7003             } else {\r
7004                 q = strchr(p, *p == '{' ? '}' : ')');\r
7005                 if (q != NULL) *q = NULLCHAR;\r
7006                 p++;\r
7007             }\r
7008             gameInfo.resultDetails = StrSave(p);\r
7009             continue;\r
7010         }\r
7011         if (boardIndex >= forwardMostMove &&\r
7012             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
7013             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
7014             return;\r
7015         }\r
7016         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
7017                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
7018                                  parseList[boardIndex]);\r
7019         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
7020         /* currentMoveString is set as a side-effect of yylex */\r
7021         strcpy(moveList[boardIndex], currentMoveString);\r
7022         strcat(moveList[boardIndex], "\n");\r
7023         boardIndex++;\r
7024         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
7025         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
7026                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
7027           case MT_NONE:\r
7028           case MT_STALEMATE:\r
7029           default:\r
7030             break;\r
7031           case MT_CHECK:\r
7032             if(gameInfo.variant != VariantShogi)\r
7033                 strcat(parseList[boardIndex - 1], "+");\r
7034             break;\r
7035           case MT_CHECKMATE:\r
7036             strcat(parseList[boardIndex - 1], "#");\r
7037             break;\r
7038         }\r
7039     }\r
7040 }\r
7041 \r
7042 \r
7043 /* Apply a move to the given board  */\r
7044 void\r
7045 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
7046      int fromX, fromY, toX, toY;\r
7047      int promoChar;\r
7048      Board board;\r
7049 {\r
7050   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
7051 \r
7052     /* [HGM] compute & store e.p. status and castling rights for new position */\r
7053     /* if we are updating a board for which those exist (i.e. in boards[])    */\r
7054     if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)\r
7055     { int i, j;\r
7056 \r
7057       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
7058       oldEP = epStatus[p-1];\r
7059       epStatus[p] = EP_NONE;\r
7060 \r
7061       if( board[toY][toX] != EmptySquare ) \r
7062            epStatus[p] = EP_CAPTURE;  \r
7063 \r
7064       if( board[fromY][fromX] == WhitePawn ) {\r
7065            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7066                epStatus[p] = EP_PAWN_MOVE;\r
7067            if( toY-fromY==2) {\r
7068                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
7069                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7070                       epStatus[p] = toX | berolina;\r
7071                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
7072                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7073                       epStatus[p] = toX;\r
7074            }\r
7075       } else \r
7076       if( board[fromY][fromX] == BlackPawn ) {\r
7077            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7078                epStatus[p] = EP_PAWN_MOVE; \r
7079            if( toY-fromY== -2) {\r
7080                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
7081                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7082                       epStatus[p] = toX | berolina;\r
7083                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
7084                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7085                       epStatus[p] = toX;\r
7086            }\r
7087        }\r
7088 \r
7089        for(i=0; i<nrCastlingRights; i++) {\r
7090            castlingRights[p][i] = castlingRights[p-1][i];\r
7091            if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||\r
7092               castlingRights[p][i] == toX   && castlingRank[i] == toY   \r
7093              ) castlingRights[p][i] = -1; // revoke for moved or captured piece\r
7094        }\r
7095 \r
7096     }\r
7097 \r
7098   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
7099   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
7100        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
7101          \r
7102   if (fromX == toX && fromY == toY) return;\r
7103 \r
7104   if (fromY == DROP_RANK) {\r
7105         /* must be first */\r
7106         piece = board[toY][toX] = (ChessSquare) fromX;\r
7107   } else {\r
7108      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
7109      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
7110      if(gameInfo.variant == VariantKnightmate)\r
7111          king += (int) WhiteUnicorn - (int) WhiteKing;\r
7112 \r
7113     /* Code added by Tord: */\r
7114     /* FRC castling assumed when king captures friendly rook. */\r
7115     if (board[fromY][fromX] == WhiteKing &&\r
7116              board[toY][toX] == WhiteRook) {\r
7117       board[fromY][fromX] = EmptySquare;\r
7118       board[toY][toX] = EmptySquare;\r
7119       if(toX > fromX) {\r
7120         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
7121       } else {\r
7122         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
7123       }\r
7124     } else if (board[fromY][fromX] == BlackKing &&\r
7125                board[toY][toX] == BlackRook) {\r
7126       board[fromY][fromX] = EmptySquare;\r
7127       board[toY][toX] = EmptySquare;\r
7128       if(toX > fromX) {\r
7129         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
7130       } else {\r
7131         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
7132       }\r
7133     /* End of code added by Tord */\r
7134 \r
7135     } else if (board[fromY][fromX] == king\r
7136         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7137         && toY == fromY && toX > fromX+1) {\r
7138         board[fromY][fromX] = EmptySquare;\r
7139         board[toY][toX] = king;\r
7140         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7141         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7142     } else if (board[fromY][fromX] == king\r
7143         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7144                && toY == fromY && toX < fromX-1) {\r
7145         board[fromY][fromX] = EmptySquare;\r
7146         board[toY][toX] = king;\r
7147         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7148         board[fromY][BOARD_LEFT] = EmptySquare;\r
7149     } else if (board[fromY][fromX] == WhitePawn\r
7150                && toY == BOARD_HEIGHT-1\r
7151                && gameInfo.variant != VariantXiangqi\r
7152                ) {\r
7153         /* white pawn promotion */\r
7154         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
7155         if (board[toY][toX] == EmptySquare) {\r
7156             board[toY][toX] = WhiteQueen;\r
7157         }\r
7158         if(gameInfo.variant==VariantBughouse ||\r
7159            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7160             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7161         board[fromY][fromX] = EmptySquare;\r
7162     } else if ((fromY == BOARD_HEIGHT-4)\r
7163                && (toX != fromX)\r
7164                && gameInfo.variant != VariantXiangqi\r
7165                && gameInfo.variant != VariantBerolina\r
7166                && (board[fromY][fromX] == WhitePawn)\r
7167                && (board[toY][toX] == EmptySquare)) {\r
7168         board[fromY][fromX] = EmptySquare;\r
7169         board[toY][toX] = WhitePawn;\r
7170         captured = board[toY - 1][toX];\r
7171         board[toY - 1][toX] = EmptySquare;\r
7172     } else if ((fromY == BOARD_HEIGHT-4)\r
7173                && (toX == fromX)\r
7174                && gameInfo.variant == VariantBerolina\r
7175                && (board[fromY][fromX] == WhitePawn)\r
7176                && (board[toY][toX] == EmptySquare)) {\r
7177         board[fromY][fromX] = EmptySquare;\r
7178         board[toY][toX] = WhitePawn;\r
7179         if(oldEP & EP_BEROLIN_A) {\r
7180                 captured = board[fromY][fromX-1];\r
7181                 board[fromY][fromX-1] = EmptySquare;\r
7182         }else{  captured = board[fromY][fromX+1];\r
7183                 board[fromY][fromX+1] = EmptySquare;\r
7184         }\r
7185     } else if (board[fromY][fromX] == king\r
7186         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7187                && toY == fromY && toX > fromX+1) {\r
7188         board[fromY][fromX] = EmptySquare;\r
7189         board[toY][toX] = king;\r
7190         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7191         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7192     } else if (board[fromY][fromX] == king\r
7193         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7194                && toY == fromY && toX < fromX-1) {\r
7195         board[fromY][fromX] = EmptySquare;\r
7196         board[toY][toX] = king;\r
7197         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7198         board[fromY][BOARD_LEFT] = EmptySquare;\r
7199     } else if (fromY == 7 && fromX == 3\r
7200                && board[fromY][fromX] == BlackKing\r
7201                && toY == 7 && toX == 5) {\r
7202         board[fromY][fromX] = EmptySquare;\r
7203         board[toY][toX] = BlackKing;\r
7204         board[fromY][7] = EmptySquare;\r
7205         board[toY][4] = BlackRook;\r
7206     } else if (fromY == 7 && fromX == 3\r
7207                && board[fromY][fromX] == BlackKing\r
7208                && toY == 7 && toX == 1) {\r
7209         board[fromY][fromX] = EmptySquare;\r
7210         board[toY][toX] = BlackKing;\r
7211         board[fromY][0] = EmptySquare;\r
7212         board[toY][2] = BlackRook;\r
7213     } else if (board[fromY][fromX] == BlackPawn\r
7214                && toY == 0\r
7215                && gameInfo.variant != VariantXiangqi\r
7216                ) {\r
7217         /* black pawn promotion */\r
7218         board[0][toX] = CharToPiece(ToLower(promoChar));\r
7219         if (board[0][toX] == EmptySquare) {\r
7220             board[0][toX] = BlackQueen;\r
7221         }\r
7222         if(gameInfo.variant==VariantBughouse ||\r
7223            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7224             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7225         board[fromY][fromX] = EmptySquare;\r
7226     } else if ((fromY == 3)\r
7227                && (toX != fromX)\r
7228                && gameInfo.variant != VariantXiangqi\r
7229                && gameInfo.variant != VariantBerolina\r
7230                && (board[fromY][fromX] == BlackPawn)\r
7231                && (board[toY][toX] == EmptySquare)) {\r
7232         board[fromY][fromX] = EmptySquare;\r
7233         board[toY][toX] = BlackPawn;\r
7234         captured = board[toY + 1][toX];\r
7235         board[toY + 1][toX] = EmptySquare;\r
7236     } else if ((fromY == 3)\r
7237                && (toX == fromX)\r
7238                && gameInfo.variant == VariantBerolina\r
7239                && (board[fromY][fromX] == BlackPawn)\r
7240                && (board[toY][toX] == EmptySquare)) {\r
7241         board[fromY][fromX] = EmptySquare;\r
7242         board[toY][toX] = BlackPawn;\r
7243         if(oldEP & EP_BEROLIN_A) {\r
7244                 captured = board[fromY][fromX-1];\r
7245                 board[fromY][fromX-1] = EmptySquare;\r
7246         }else{  captured = board[fromY][fromX+1];\r
7247                 board[fromY][fromX+1] = EmptySquare;\r
7248         }\r
7249     } else {\r
7250         board[toY][toX] = board[fromY][fromX];\r
7251         board[fromY][fromX] = EmptySquare;\r
7252     }\r
7253 \r
7254     /* [HGM] now we promote for Shogi, if needed */\r
7255     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
7256         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7257   }\r
7258 \r
7259     if (gameInfo.holdingsWidth != 0) {\r
7260 \r
7261       /* !!A lot more code needs to be written to support holdings  */\r
7262       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
7263       /* penultimate board files, so they are automaticlly stored   */\r
7264       /* in the game history.                                       */\r
7265       if (fromY == DROP_RANK) {\r
7266         /* Delete from holdings, by decreasing count */\r
7267         /* and erasing image if necessary            */\r
7268         p = (int) fromX;\r
7269         if(p < (int) BlackPawn) { /* white drop */\r
7270              p -= (int)WhitePawn;\r
7271              if(p >= gameInfo.holdingsSize) p = 0;\r
7272              if(--board[p][BOARD_WIDTH-2] == 0)\r
7273                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
7274         } else {                  /* black drop */\r
7275              p -= (int)BlackPawn;\r
7276              if(p >= gameInfo.holdingsSize) p = 0;\r
7277              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
7278                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
7279         }\r
7280       }\r
7281       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
7282           && gameInfo.variant != VariantBughouse        ) {\r
7283         /* [HGM] holdings: Add to holdings, if holdings exist */\r
7284         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { \r
7285                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
7286                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
7287         }\r
7288         p = (int) captured;\r
7289         if (p >= (int) BlackPawn) {\r
7290           p -= (int)BlackPawn;\r
7291           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7292                   /* in Shogi restore piece to its original  first */\r
7293                   captured = (ChessSquare) (DEMOTED captured);\r
7294                   p = DEMOTED p;\r
7295           }\r
7296           p = PieceToNumber((ChessSquare)p);\r
7297           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
7298           board[p][BOARD_WIDTH-2]++;\r
7299           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;\r
7300         } else {\r
7301           p -= (int)WhitePawn;\r
7302           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7303                   captured = (ChessSquare) (DEMOTED captured);\r
7304                   p = DEMOTED p;\r
7305           }\r
7306           p = PieceToNumber((ChessSquare)p);\r
7307           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
7308           board[BOARD_HEIGHT-1-p][1]++;\r
7309           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;\r
7310         }\r
7311       }\r
7312 \r
7313     } else if (gameInfo.variant == VariantAtomic) {\r
7314       if (captured != EmptySquare) {\r
7315         int y, x;\r
7316         for (y = toY-1; y <= toY+1; y++) {\r
7317           for (x = toX-1; x <= toX+1; x++) {\r
7318             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
7319                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
7320               board[y][x] = EmptySquare;\r
7321             }\r
7322           }\r
7323         }\r
7324         board[toY][toX] = EmptySquare;\r
7325       }\r
7326     }\r
7327     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
7328         /* [HGM] Shogi promotions */\r
7329         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7330     }\r
7331 \r
7332 }\r
7333 \r
7334 /* Updates forwardMostMove */\r
7335 void\r
7336 MakeMove(fromX, fromY, toX, toY, promoChar)\r
7337      int fromX, fromY, toX, toY;\r
7338      int promoChar;\r
7339 {\r
7340 //    forwardMostMove++; // [HGM] bare: moved downstream\r
7341 \r
7342     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */\r
7343         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
7344         piece = boards[forwardMostMove][fromY][fromX];\r
7345         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
7346         if(gameInfo.variant == VariantKnightmate)\r
7347             king += (int) WhiteUnicorn - (int) WhiteKing;\r
7348         if(forwardMostMove == 0) {\r
7349             if(blackPlaysFirst) \r
7350                 fprintf(serverMoves, "%s;", second.tidy);\r
7351             fprintf(serverMoves, "%s;", first.tidy);\r
7352             if(!blackPlaysFirst) \r
7353                 fprintf(serverMoves, "%s;", second.tidy);\r
7354         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
7355         lastLoadFlag = loadFlag;\r
7356         // print base move\r
7357         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
7358         // print castling suffix\r
7359         if( toY == fromY && piece == king ) {\r
7360             if(toX-fromX > 1)\r
7361                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
7362             if(fromX-toX >1)\r
7363                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
7364         }\r
7365         // e.p. suffix\r
7366         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||\r
7367              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&\r
7368              boards[forwardMostMove][toY][toX] == EmptySquare\r
7369              && fromX != toX )\r
7370                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
7371         // promotion suffix\r
7372         if(promoChar != NULLCHAR)\r
7373                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
7374         if(!loadFlag) {\r
7375             fprintf(serverMoves, "/%d/%d",\r
7376                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);\r
7377             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;\r
7378             else                      timeLeft = blackTimeRemaining/1000;\r
7379             fprintf(serverMoves, "/%d", timeLeft);\r
7380         }\r
7381         fflush(serverMoves);\r
7382     }\r
7383 \r
7384     if (forwardMostMove+1 >= MAX_MOVES) {\r
7385       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
7386                         0, 1);\r
7387       return;\r
7388     }\r
7389     SwitchClocks();\r
7390     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;\r
7391     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;\r
7392     if (commentList[forwardMostMove+1] != NULL) {\r
7393         free(commentList[forwardMostMove+1]);\r
7394         commentList[forwardMostMove+1] = NULL;\r
7395     }\r
7396     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);\r
7397     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);\r
7398     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board\r
7399     gameInfo.result = GameUnfinished;\r
7400     if (gameInfo.resultDetails != NULL) {\r
7401         free(gameInfo.resultDetails);\r
7402         gameInfo.resultDetails = NULL;\r
7403     }\r
7404     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
7405                               moveList[forwardMostMove - 1]);\r
7406     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
7407                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
7408                              fromY, fromX, toY, toX, promoChar,\r
7409                              parseList[forwardMostMove - 1]);\r
7410     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
7411                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
7412                             castlingRights[forwardMostMove]) ) {\r
7413       case MT_NONE:\r
7414       case MT_STALEMATE:\r
7415       default:\r
7416         break;\r
7417       case MT_CHECK:\r
7418         if(gameInfo.variant != VariantShogi)\r
7419             strcat(parseList[forwardMostMove - 1], "+");\r
7420         break;\r
7421       case MT_CHECKMATE:\r
7422         strcat(parseList[forwardMostMove - 1], "#");\r
7423         break;\r
7424     }\r
7425     if (appData.debugMode) {\r
7426         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
7427     }\r
7428 \r
7429 }\r
7430 \r
7431 /* Updates currentMove if not pausing */\r
7432 void\r
7433 ShowMove(fromX, fromY, toX, toY)\r
7434 {\r
7435     int instant = (gameMode == PlayFromGameFile) ?\r
7436         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
7437     if(appData.noGUI) return;\r
7438     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
7439         if (!instant) {\r
7440             if (forwardMostMove == currentMove + 1) {\r
7441                 AnimateMove(boards[forwardMostMove - 1],\r
7442                             fromX, fromY, toX, toY);\r
7443             }\r
7444             if (appData.highlightLastMove) {\r
7445                 SetHighlights(fromX, fromY, toX, toY);\r
7446             }\r
7447         }\r
7448         currentMove = forwardMostMove;\r
7449     }\r
7450 \r
7451     if (instant) return;\r
7452 \r
7453     DisplayMove(currentMove - 1);\r
7454     DrawPosition(FALSE, boards[currentMove]);\r
7455     DisplayBothClocks();\r
7456     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
7457 }\r
7458 \r
7459 void SendEgtPath(ChessProgramState *cps)\r
7460 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */\r
7461         char buf[MSG_SIZ], name[MSG_SIZ], *p;\r
7462 \r
7463         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;\r
7464 \r
7465         while(*p) {\r
7466             char c, *q = name+1, *r, *s;\r
7467 \r
7468             name[0] = ','; // extract next format name from feature and copy with prefixed ','\r
7469             while(*p && *p != ',') *q++ = *p++;\r
7470             *q++ = ':'; *q = 0;\r
7471             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && \r
7472                 strcmp(name, ",nalimov:") == 0 ) {\r
7473                 // take nalimov path from the menu-changeable option first, if it is defined\r
7474                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);\r
7475                 SendToProgram(buf,cps);     // send egtbpath command for nalimov\r
7476             } else\r
7477             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||\r
7478                 (s = StrStr(appData.egtFormats, name)) != NULL) {\r
7479                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma\r
7480                 s = r = StrStr(s, ":") + 1; // beginning of path info\r
7481                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string\r
7482                 c = *r; *r = 0;             // temporarily null-terminate path info\r
7483                     *--q = 0;               // strip of trailig ':' from name\r
7484                     sprintf(buf, "egtbpath %s %s\n", name+1, s);\r
7485                 *r = c;\r
7486                 SendToProgram(buf,cps);     // send egtbpath command for this format\r
7487             }\r
7488             if(*p == ',') p++; // read away comma to position for next format name\r
7489         }\r
7490 }\r
7491 \r
7492 void\r
7493 InitChessProgram(cps, setup)\r
7494      ChessProgramState *cps;\r
7495      int setup; /* [HGM] needed to setup FRC opening position */\r
7496 {\r
7497     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
7498     if (appData.noChessProgram) return;\r
7499     hintRequested = FALSE;\r
7500     bookRequested = FALSE;\r
7501 \r
7502     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */\r
7503     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */\r
7504     if(cps->memSize) { /* [HGM] memory */\r
7505         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);\r
7506         SendToProgram(buf, cps);\r
7507     }\r
7508     SendEgtPath(cps); /* [HGM] EGT */\r
7509     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */\r
7510         sprintf(buf, "cores %d\n", appData.smpCores);\r
7511         SendToProgram(buf, cps);\r
7512     }\r
7513 \r
7514     SendToProgram(cps->initString, cps);\r
7515     if (gameInfo.variant != VariantNormal &&\r
7516         gameInfo.variant != VariantLoadable\r
7517         /* [HGM] also send variant if board size non-standard */\r
7518         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
7519                                             ) {\r
7520       char *v = VariantName(gameInfo.variant);\r
7521       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
7522         /* [HGM] in protocol 1 we have to assume all variants valid */\r
7523         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);\r
7524         DisplayFatalError(buf, 0, 1);\r
7525         return;\r
7526       }\r
7527 \r
7528       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
7529       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7530       if( gameInfo.variant == VariantXiangqi )\r
7531            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
7532       if( gameInfo.variant == VariantShogi )\r
7533            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
7534       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
7535            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
7536       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
7537                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
7538            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7539       if( gameInfo.variant == VariantCourier )\r
7540            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7541       if( gameInfo.variant == VariantSuper )\r
7542            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7543       if( gameInfo.variant == VariantGreat )\r
7544            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7545 \r
7546       if(overruled) {\r
7547            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
7548                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
7549            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
7550            if(StrStr(cps->variants, b) == NULL) { \r
7551                // specific sized variant not known, check if general sizing allowed\r
7552                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
7553                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
7554                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
7555                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
7556                        DisplayFatalError(buf, 0, 1);\r
7557                        return;\r
7558                    }\r
7559                    /* [HGM] here we really should compare with the maximum supported board size */\r
7560                }\r
7561            }\r
7562       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
7563       sprintf(buf, "variant %s\n", b);\r
7564       SendToProgram(buf, cps);\r
7565     }\r
7566     currentlyInitializedVariant = gameInfo.variant;\r
7567 \r
7568     /* [HGM] send opening position in FRC to first engine */\r
7569     if(setup) {\r
7570           SendToProgram("force\n", cps);\r
7571           SendBoard(cps, 0);\r
7572           /* engine is now in force mode! Set flag to wake it up after first move. */\r
7573           setboardSpoiledMachineBlack = 1;\r
7574     }\r
7575 \r
7576     if (cps->sendICS) {\r
7577       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
7578       SendToProgram(buf, cps);\r
7579     }\r
7580     cps->maybeThinking = FALSE;\r
7581     cps->offeredDraw = 0;\r
7582     if (!appData.icsActive) {\r
7583         SendTimeControl(cps, movesPerSession, timeControl,\r
7584                         timeIncrement, appData.searchDepth,\r
7585                         searchTime);\r
7586     }\r
7587     if (appData.showThinking \r
7588         // [HGM] thinking: four options require thinking output to be sent\r
7589         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
7590                                 ) {\r
7591         SendToProgram("post\n", cps);\r
7592     }\r
7593     SendToProgram("hard\n", cps);\r
7594     if (!appData.ponderNextMove) {\r
7595         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
7596            it without being sure what state we are in first.  "hard"\r
7597            is not a toggle, so that one is OK.\r
7598          */\r
7599         SendToProgram("easy\n", cps);\r
7600     }\r
7601     if (cps->usePing) {\r
7602       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
7603       SendToProgram(buf, cps);\r
7604     }\r
7605     cps->initDone = TRUE;\r
7606 }   \r
7607 \r
7608 \r
7609 void\r
7610 StartChessProgram(cps)\r
7611      ChessProgramState *cps;\r
7612 {\r
7613     char buf[MSG_SIZ];\r
7614     int err;\r
7615 \r
7616     if (appData.noChessProgram) return;\r
7617     cps->initDone = FALSE;\r
7618 \r
7619     if (strcmp(cps->host, "localhost") == 0) {\r
7620         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
7621     } else if (*appData.remoteShell == NULLCHAR) {\r
7622         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
7623     } else {\r
7624         if (*appData.remoteUser == NULLCHAR) {\r
7625             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
7626                     cps->program);\r
7627         } else {\r
7628             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
7629                     cps->host, appData.remoteUser, cps->program);\r
7630         }\r
7631         err = StartChildProcess(buf, "", &cps->pr);\r
7632     }\r
7633     \r
7634     if (err != 0) {\r
7635         sprintf(buf, _("Startup failure on '%s'"), cps->program);\r
7636         DisplayFatalError(buf, err, 1);\r
7637         cps->pr = NoProc;\r
7638         cps->isr = NULL;\r
7639         return;\r
7640     }\r
7641     \r
7642     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
7643     if (cps->protocolVersion > 1) {\r
7644       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
7645       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options\r
7646       cps->comboCnt = 0;  //                and values of combo boxes\r
7647       SendToProgram(buf, cps);\r
7648     } else {\r
7649       SendToProgram("xboard\n", cps);\r
7650     }\r
7651 }\r
7652 \r
7653 \r
7654 void\r
7655 TwoMachinesEventIfReady P((void))\r
7656 {\r
7657   if (first.lastPing != first.lastPong) {\r
7658     DisplayMessage("", _("Waiting for first chess program"));\r
7659     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7660     return;\r
7661   }\r
7662   if (second.lastPing != second.lastPong) {\r
7663     DisplayMessage("", _("Waiting for second chess program"));\r
7664     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7665     return;\r
7666   }\r
7667   ThawUI();\r
7668   TwoMachinesEvent();\r
7669 }\r
7670 \r
7671 void\r
7672 NextMatchGame P((void))\r
7673 {\r
7674     int index; /* [HGM] autoinc: step lod index during match */\r
7675     Reset(FALSE, TRUE);\r
7676     if (*appData.loadGameFile != NULLCHAR) {\r
7677         index = appData.loadGameIndex;\r
7678         if(index < 0) { // [HGM] autoinc\r
7679             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7680             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7681         } \r
7682         LoadGameFromFile(appData.loadGameFile,\r
7683                          index,\r
7684                          appData.loadGameFile, FALSE);\r
7685     } else if (*appData.loadPositionFile != NULLCHAR) {\r
7686         index = appData.loadPositionIndex;\r
7687         if(index < 0) { // [HGM] autoinc\r
7688             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7689             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7690         } \r
7691         LoadPositionFromFile(appData.loadPositionFile,\r
7692                              index,\r
7693                              appData.loadPositionFile);\r
7694     }\r
7695     TwoMachinesEventIfReady();\r
7696 }\r
7697 \r
7698 void UserAdjudicationEvent( int result )\r
7699 {\r
7700     ChessMove gameResult = GameIsDrawn;\r
7701 \r
7702     if( result > 0 ) {\r
7703         gameResult = WhiteWins;\r
7704     }\r
7705     else if( result < 0 ) {\r
7706         gameResult = BlackWins;\r
7707     }\r
7708 \r
7709     if( gameMode == TwoMachinesPlay ) {\r
7710         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
7711     }\r
7712 }\r
7713 \r
7714 \r
7715 void\r
7716 GameEnds(result, resultDetails, whosays)\r
7717      ChessMove result;\r
7718      char *resultDetails;\r
7719      int whosays;\r
7720 {\r
7721     GameMode nextGameMode;\r
7722     int isIcsGame;\r
7723     char buf[MSG_SIZ];\r
7724 \r
7725     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
7726     endingGame = 1;\r
7727 \r
7728     if (appData.debugMode) {\r
7729       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
7730               result, resultDetails ? resultDetails : "(null)", whosays);\r
7731     }\r
7732 \r
7733     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
7734         /* If we are playing on ICS, the server decides when the\r
7735            game is over, but the engine can offer to draw, claim \r
7736            a draw, or resign. \r
7737          */\r
7738 #if ZIPPY\r
7739         if (appData.zippyPlay && first.initDone) {\r
7740             if (result == GameIsDrawn) {\r
7741                 /* In case draw still needs to be claimed */\r
7742                 SendToICS(ics_prefix);\r
7743                 SendToICS("draw\n");\r
7744             } else if (StrCaseStr(resultDetails, "resign")) {\r
7745                 SendToICS(ics_prefix);\r
7746                 SendToICS("resign\n");\r
7747             }\r
7748         }\r
7749 #endif\r
7750         endingGame = 0; /* [HGM] crash */\r
7751         return;\r
7752     }\r
7753 \r
7754     /* If we're loading the game from a file, stop */\r
7755     if (whosays == GE_FILE) {\r
7756       (void) StopLoadGameTimer();\r
7757       gameFileFP = NULL;\r
7758     }\r
7759 \r
7760     /* Cancel draw offers */\r
7761     first.offeredDraw = second.offeredDraw = 0;\r
7762 \r
7763     /* If this is an ICS game, only ICS can really say it's done;\r
7764        if not, anyone can. */\r
7765     isIcsGame = (gameMode == IcsPlayingWhite || \r
7766                  gameMode == IcsPlayingBlack || \r
7767                  gameMode == IcsObserving    || \r
7768                  gameMode == IcsExamining);\r
7769 \r
7770     if (!isIcsGame || whosays == GE_ICS) {\r
7771         /* OK -- not an ICS game, or ICS said it was done */\r
7772         StopClocks();\r
7773         if (!isIcsGame && !appData.noChessProgram) \r
7774           SetUserThinkingEnables();\r
7775     \r
7776         /* [HGM] if a machine claims the game end we verify this claim */\r
7777         if(gameMode == TwoMachinesPlay && appData.testClaims) {\r
7778             if(appData.testLegality && whosays >= GE_ENGINE1 ) {\r
7779                 char claimer;\r
7780 \r
7781                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
7782                                             first.twoMachinesColor[0] :\r
7783                                             second.twoMachinesColor[0] ;\r
7784                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) &&\r
7785                     (result == WhiteWins && claimer == 'w' ||\r
7786                      result == BlackWins && claimer == 'b'   ) ) {\r
7787                 if (appData.debugMode) {\r
7788                      fprintf(debugFP, "result=%d sp=%d move=%d\n",\r
7789                         result, epStatus[forwardMostMove], forwardMostMove);\r
7790                 }\r
7791                       /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7792                       if(epStatus[forwardMostMove] != EP_CHECKMATE &&\r
7793                          result != (WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins)) {\r
7794                               sprintf(buf, "False win claim: '%s'", resultDetails);\r
7795                               result = claimer == 'w' ? BlackWins : WhiteWins;\r
7796                               resultDetails = buf;\r
7797                       }\r
7798                 } else\r
7799                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
7800                     && (forwardMostMove <= backwardMostMove ||\r
7801                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
7802                         (claimer=='b')==(forwardMostMove&1))\r
7803                                                                                   ) {\r
7804                       /* [HGM] verify: draws that were not flagged are false claims */\r
7805                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
7806                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7807                       resultDetails = buf;\r
7808                 }\r
7809                 /* (Claiming a loss is accepted no questions asked!) */\r
7810             }\r
7811             /* [HGM] bare: don't allow bare King to win */\r
7812             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)\r
7813                          && result != GameIsDrawn)\r
7814             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
7815                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
7816                         int p = (int)boards[forwardMostMove][i][j] - color;\r
7817                         if(p >= 0 && p <= (int)WhiteKing) k++;\r
7818                 }\r
7819                 if (appData.debugMode) {\r
7820                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",\r
7821                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);\r
7822                 }\r
7823                 if(k <= 1) {\r
7824                         result = GameIsDrawn;\r
7825                         sprintf(buf, "%s but bare king", resultDetails);\r
7826                         resultDetails = buf;\r
7827                 }\r
7828             }\r
7829         }\r
7830 \r
7831 \r
7832         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
7833             if(result==WhiteWins) c = '+';\r
7834             if(result==BlackWins) c = '-';\r
7835             if(resultDetails != NULL)\r
7836                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
7837         }\r
7838         if (resultDetails != NULL) {\r
7839             gameInfo.result = result;\r
7840             gameInfo.resultDetails = StrSave(resultDetails);\r
7841 \r
7842             /* display last move only if game was not loaded from file */\r
7843             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7844                 DisplayMove(currentMove - 1);\r
7845     \r
7846             if (forwardMostMove != 0) {\r
7847                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7848                     if (*appData.saveGameFile != NULLCHAR) {\r
7849                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7850                     } else if (appData.autoSaveGames) {\r
7851                         AutoSaveGame();\r
7852                     }\r
7853                     if (*appData.savePositionFile != NULLCHAR) {\r
7854                         SavePositionToFile(appData.savePositionFile);\r
7855                     }\r
7856                 }\r
7857             }\r
7858 \r
7859             /* Tell program how game ended in case it is learning */\r
7860             /* [HGM] Moved this to after saving the PGN, just in case */\r
7861             /* engine died and we got here through time loss. In that */\r
7862             /* case we will get a fatal error writing the pipe, which */\r
7863             /* would otherwise lose us the PGN.                       */\r
7864             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7865             /* output during GameEnds should never be fatal anymore   */\r
7866             if (gameMode == MachinePlaysWhite ||\r
7867                 gameMode == MachinePlaysBlack ||\r
7868                 gameMode == TwoMachinesPlay ||\r
7869                 gameMode == IcsPlayingWhite ||\r
7870                 gameMode == IcsPlayingBlack ||\r
7871                 gameMode == BeginningOfGame) {\r
7872                 char buf[MSG_SIZ];\r
7873                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7874                         resultDetails);\r
7875                 if (first.pr != NoProc) {\r
7876                     SendToProgram(buf, &first);\r
7877                 }\r
7878                 if (second.pr != NoProc &&\r
7879                     gameMode == TwoMachinesPlay) {\r
7880                     SendToProgram(buf, &second);\r
7881                 }\r
7882             }\r
7883         }\r
7884 \r
7885         if (appData.icsActive) {\r
7886             if (appData.quietPlay &&\r
7887                 (gameMode == IcsPlayingWhite ||\r
7888                  gameMode == IcsPlayingBlack)) {\r
7889                 SendToICS(ics_prefix);\r
7890                 SendToICS("set shout 1\n");\r
7891             }\r
7892             nextGameMode = IcsIdle;\r
7893             ics_user_moved = FALSE;\r
7894             /* clean up premove.  It's ugly when the game has ended and the\r
7895              * premove highlights are still on the board.\r
7896              */\r
7897             if (gotPremove) {\r
7898               gotPremove = FALSE;\r
7899               ClearPremoveHighlights();\r
7900               DrawPosition(FALSE, boards[currentMove]);\r
7901             }\r
7902             if (whosays == GE_ICS) {\r
7903                 switch (result) {\r
7904                 case WhiteWins:\r
7905                     if (gameMode == IcsPlayingWhite)\r
7906                         PlayIcsWinSound();\r
7907                     else if(gameMode == IcsPlayingBlack)\r
7908                         PlayIcsLossSound();\r
7909                     break;\r
7910                 case BlackWins:\r
7911                     if (gameMode == IcsPlayingBlack)\r
7912                         PlayIcsWinSound();\r
7913                     else if(gameMode == IcsPlayingWhite)\r
7914                         PlayIcsLossSound();\r
7915                     break;\r
7916                 case GameIsDrawn:\r
7917                     PlayIcsDrawSound();\r
7918                     break;\r
7919                 default:\r
7920                     PlayIcsUnfinishedSound();\r
7921                 }\r
7922             }\r
7923         } else if (gameMode == EditGame ||\r
7924                    gameMode == PlayFromGameFile || \r
7925                    gameMode == AnalyzeMode || \r
7926                    gameMode == AnalyzeFile) {\r
7927             nextGameMode = gameMode;\r
7928         } else {\r
7929             nextGameMode = EndOfGame;\r
7930         }\r
7931         pausing = FALSE;\r
7932         ModeHighlight();\r
7933     } else {\r
7934         nextGameMode = gameMode;\r
7935     }\r
7936 \r
7937     if (appData.noChessProgram) {\r
7938         gameMode = nextGameMode;\r
7939         ModeHighlight();\r
7940         endingGame = 0; /* [HGM] crash */\r
7941         return;\r
7942     }\r
7943 \r
7944     if (first.reuse) {\r
7945         /* Put first chess program into idle state */\r
7946         if (first.pr != NoProc &&\r
7947             (gameMode == MachinePlaysWhite ||\r
7948              gameMode == MachinePlaysBlack ||\r
7949              gameMode == TwoMachinesPlay ||\r
7950              gameMode == IcsPlayingWhite ||\r
7951              gameMode == IcsPlayingBlack ||\r
7952              gameMode == BeginningOfGame)) {\r
7953             SendToProgram("force\n", &first);\r
7954             if (first.usePing) {\r
7955               char buf[MSG_SIZ];\r
7956               sprintf(buf, "ping %d\n", ++first.lastPing);\r
7957               SendToProgram(buf, &first);\r
7958             }\r
7959         }\r
7960     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7961         /* Kill off first chess program */\r
7962         if (first.isr != NULL)\r
7963           RemoveInputSource(first.isr);\r
7964         first.isr = NULL;\r
7965     \r
7966         if (first.pr != NoProc) {\r
7967             ExitAnalyzeMode();\r
7968             DoSleep( appData.delayBeforeQuit );\r
7969             SendToProgram("quit\n", &first);\r
7970             DoSleep( appData.delayAfterQuit );\r
7971             DestroyChildProcess(first.pr, first.useSigterm);\r
7972         }\r
7973         first.pr = NoProc;\r
7974     }\r
7975     if (second.reuse) {\r
7976         /* Put second chess program into idle state */\r
7977         if (second.pr != NoProc &&\r
7978             gameMode == TwoMachinesPlay) {\r
7979             SendToProgram("force\n", &second);\r
7980             if (second.usePing) {\r
7981               char buf[MSG_SIZ];\r
7982               sprintf(buf, "ping %d\n", ++second.lastPing);\r
7983               SendToProgram(buf, &second);\r
7984             }\r
7985         }\r
7986     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7987         /* Kill off second chess program */\r
7988         if (second.isr != NULL)\r
7989           RemoveInputSource(second.isr);\r
7990         second.isr = NULL;\r
7991     \r
7992         if (second.pr != NoProc) {\r
7993             DoSleep( appData.delayBeforeQuit );\r
7994             SendToProgram("quit\n", &second);\r
7995             DoSleep( appData.delayAfterQuit );\r
7996             DestroyChildProcess(second.pr, second.useSigterm);\r
7997         }\r
7998         second.pr = NoProc;\r
7999     }\r
8000 \r
8001     if (matchMode && gameMode == TwoMachinesPlay) {\r
8002         switch (result) {\r
8003         case WhiteWins:\r
8004           if (first.twoMachinesColor[0] == 'w') {\r
8005             first.matchWins++;\r
8006           } else {\r
8007             second.matchWins++;\r
8008           }\r
8009           break;\r
8010         case BlackWins:\r
8011           if (first.twoMachinesColor[0] == 'b') {\r
8012             first.matchWins++;\r
8013           } else {\r
8014             second.matchWins++;\r
8015           }\r
8016           break;\r
8017         default:\r
8018           break;\r
8019         }\r
8020         if (matchGame < appData.matchGames) {\r
8021             char *tmp;\r
8022             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */\r
8023                 tmp = first.twoMachinesColor;\r
8024                 first.twoMachinesColor = second.twoMachinesColor;\r
8025                 second.twoMachinesColor = tmp;\r
8026             }\r
8027             gameMode = nextGameMode;\r
8028             matchGame++;\r
8029             if(appData.matchPause>10000 || appData.matchPause<10)\r
8030                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
8031             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
8032             endingGame = 0; /* [HGM] crash */\r
8033             return;\r
8034         } else {\r
8035             char buf[MSG_SIZ];\r
8036             gameMode = nextGameMode;\r
8037             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),\r
8038                     first.tidy, second.tidy,\r
8039                     first.matchWins, second.matchWins,\r
8040                     appData.matchGames - (first.matchWins + second.matchWins));\r
8041             DisplayFatalError(buf, 0, 0);\r
8042         }\r
8043     }\r
8044     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
8045         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
8046       ExitAnalyzeMode();\r
8047     gameMode = nextGameMode;\r
8048     ModeHighlight();\r
8049     endingGame = 0;  /* [HGM] crash */\r
8050 }\r
8051 \r
8052 /* Assumes program was just initialized (initString sent).\r
8053    Leaves program in force mode. */\r
8054 void\r
8055 FeedMovesToProgram(cps, upto) \r
8056      ChessProgramState *cps;\r
8057      int upto;\r
8058 {\r
8059     int i;\r
8060     \r
8061     if (appData.debugMode)\r
8062       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
8063               startedFromSetupPosition ? "position and " : "",\r
8064               backwardMostMove, upto, cps->which);\r
8065     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
8066         // [HGM] variantswitch: make engine aware of new variant\r
8067         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
8068                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
8069         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
8070         SendToProgram(buf, cps);\r
8071         currentlyInitializedVariant = gameInfo.variant;\r
8072     }\r
8073     SendToProgram("force\n", cps);\r
8074     if (startedFromSetupPosition) {\r
8075         SendBoard(cps, backwardMostMove);\r
8076     if (appData.debugMode) {\r
8077         fprintf(debugFP, "feedMoves\n");\r
8078     }\r
8079     }\r
8080     for (i = backwardMostMove; i < upto; i++) {\r
8081         SendMoveToProgram(i, cps);\r
8082     }\r
8083 }\r
8084 \r
8085 \r
8086 void\r
8087 ResurrectChessProgram()\r
8088 {\r
8089      /* The chess program may have exited.\r
8090         If so, restart it and feed it all the moves made so far. */\r
8091 \r
8092     if (appData.noChessProgram || first.pr != NoProc) return;\r
8093     \r
8094     StartChessProgram(&first);\r
8095     InitChessProgram(&first, FALSE);\r
8096     FeedMovesToProgram(&first, currentMove);\r
8097 \r
8098     if (!first.sendTime) {\r
8099         /* can't tell gnuchess what its clock should read,\r
8100            so we bow to its notion. */\r
8101         ResetClocks();\r
8102         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
8103         timeRemaining[1][currentMove] = blackTimeRemaining;\r
8104     }\r
8105 \r
8106     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||\r
8107                 appData.icsEngineAnalyze) && first.analysisSupport) {\r
8108       SendToProgram("analyze\n", &first);\r
8109       first.analyzing = TRUE;\r
8110     }\r
8111 }\r
8112 \r
8113 /*\r
8114  * Button procedures\r
8115  */\r
8116 void\r
8117 Reset(redraw, init)\r
8118      int redraw, init;\r
8119 {\r
8120     int i;\r
8121 \r
8122     if (appData.debugMode) {\r
8123         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
8124                 redraw, init, gameMode);\r
8125     }\r
8126     pausing = pauseExamInvalid = FALSE;\r
8127     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
8128     firstMove = TRUE;\r
8129     whiteFlag = blackFlag = FALSE;\r
8130     userOfferedDraw = FALSE;\r
8131     hintRequested = bookRequested = FALSE;\r
8132     first.maybeThinking = FALSE;\r
8133     second.maybeThinking = FALSE;\r
8134     first.bookSuspend = FALSE; // [HGM] book\r
8135     second.bookSuspend = FALSE;\r
8136     thinkOutput[0] = NULLCHAR;\r
8137     lastHint[0] = NULLCHAR;\r
8138     ClearGameInfo(&gameInfo);\r
8139     gameInfo.variant = StringToVariant(appData.variant);\r
8140     ics_user_moved = ics_clock_paused = FALSE;\r
8141     ics_getting_history = H_FALSE;\r
8142     ics_gamenum = -1;\r
8143     white_holding[0] = black_holding[0] = NULLCHAR;\r
8144     ClearProgramStats();\r
8145     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode\r
8146     \r
8147     ResetFrontEnd();\r
8148     ClearHighlights();\r
8149     flipView = appData.flipView;\r
8150     ClearPremoveHighlights();\r
8151     gotPremove = FALSE;\r
8152     alarmSounded = FALSE;\r
8153 \r
8154     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8155     if(appData.serverMovesName != NULL) {\r
8156         /* [HGM] prepare to make moves file for broadcasting */\r
8157         clock_t t = clock();\r
8158         if(serverMoves != NULL) fclose(serverMoves);\r
8159         serverMoves = fopen(appData.serverMovesName, "r");\r
8160         if(serverMoves != NULL) {\r
8161             fclose(serverMoves);\r
8162             /* delay 15 sec before overwriting, so all clients can see end */\r
8163             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
8164         }\r
8165         serverMoves = fopen(appData.serverMovesName, "w");\r
8166     }\r
8167 \r
8168     ExitAnalyzeMode();\r
8169     gameMode = BeginningOfGame;\r
8170     ModeHighlight();\r
8171     if(appData.icsActive) gameInfo.variant = VariantNormal;\r
8172     InitPosition(redraw);\r
8173     for (i = 0; i < MAX_MOVES; i++) {\r
8174         if (commentList[i] != NULL) {\r
8175             free(commentList[i]);\r
8176             commentList[i] = NULL;\r
8177         }\r
8178     }\r
8179     ResetClocks();\r
8180     timeRemaining[0][0] = whiteTimeRemaining;\r
8181     timeRemaining[1][0] = blackTimeRemaining;\r
8182     if (first.pr == NULL) {\r
8183         StartChessProgram(&first);\r
8184     }\r
8185     if (init) {\r
8186             InitChessProgram(&first, startedFromSetupPosition);\r
8187     }\r
8188     DisplayTitle("");\r
8189     DisplayMessage("", "");\r
8190     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8191 }\r
8192 \r
8193 void\r
8194 AutoPlayGameLoop()\r
8195 {\r
8196     for (;;) {\r
8197         if (!AutoPlayOneMove())\r
8198           return;\r
8199         if (matchMode || appData.timeDelay == 0)\r
8200           continue;\r
8201         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
8202           return;\r
8203         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
8204         break;\r
8205     }\r
8206 }\r
8207 \r
8208 \r
8209 int\r
8210 AutoPlayOneMove()\r
8211 {\r
8212     int fromX, fromY, toX, toY;\r
8213 \r
8214     if (appData.debugMode) {\r
8215       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
8216     }\r
8217 \r
8218     if (gameMode != PlayFromGameFile)\r
8219       return FALSE;\r
8220 \r
8221     if (currentMove >= forwardMostMove) {\r
8222       gameMode = EditGame;\r
8223       ModeHighlight();\r
8224 \r
8225       /* [AS] Clear current move marker at the end of a game */\r
8226       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
8227 \r
8228       return FALSE;\r
8229     }\r
8230     \r
8231     toX = moveList[currentMove][2] - AAA;\r
8232     toY = moveList[currentMove][3] - ONE;\r
8233 \r
8234     if (moveList[currentMove][1] == '@') {\r
8235         if (appData.highlightLastMove) {\r
8236             SetHighlights(-1, -1, toX, toY);\r
8237         }\r
8238     } else {\r
8239         fromX = moveList[currentMove][0] - AAA;\r
8240         fromY = moveList[currentMove][1] - ONE;\r
8241 \r
8242         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
8243 \r
8244         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
8245 \r
8246         if (appData.highlightLastMove) {\r
8247             SetHighlights(fromX, fromY, toX, toY);\r
8248         }\r
8249     }\r
8250     DisplayMove(currentMove);\r
8251     SendMoveToProgram(currentMove++, &first);\r
8252     DisplayBothClocks();\r
8253     DrawPosition(FALSE, boards[currentMove]);\r
8254     // [HGM] PV info: always display, routine tests if empty\r
8255     DisplayComment(currentMove - 1, commentList[currentMove]);\r
8256     return TRUE;\r
8257 }\r
8258 \r
8259 \r
8260 int\r
8261 LoadGameOneMove(readAhead)\r
8262      ChessMove readAhead;\r
8263 {\r
8264     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
8265     char promoChar = NULLCHAR;\r
8266     ChessMove moveType;\r
8267     char move[MSG_SIZ];\r
8268     char *p, *q;\r
8269     \r
8270     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
8271         gameMode != AnalyzeMode && gameMode != Training) {\r
8272         gameFileFP = NULL;\r
8273         return FALSE;\r
8274     }\r
8275     \r
8276     yyboardindex = forwardMostMove;\r
8277     if (readAhead != (ChessMove)0) {\r
8278       moveType = readAhead;\r
8279     } else {\r
8280       if (gameFileFP == NULL)\r
8281           return FALSE;\r
8282       moveType = (ChessMove) yylex();\r
8283     }\r
8284     \r
8285     done = FALSE;\r
8286     switch (moveType) {\r
8287       case Comment:\r
8288         if (appData.debugMode) \r
8289           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8290         p = yy_text;\r
8291         if (*p == '{' || *p == '[' || *p == '(') {\r
8292             p[strlen(p) - 1] = NULLCHAR;\r
8293             p++;\r
8294         }\r
8295 \r
8296         /* append the comment but don't display it */\r
8297         while (*p == '\n') p++;\r
8298         AppendComment(currentMove, p);\r
8299         return TRUE;\r
8300 \r
8301       case WhiteCapturesEnPassant:\r
8302       case BlackCapturesEnPassant:\r
8303       case WhitePromotionChancellor:\r
8304       case BlackPromotionChancellor:\r
8305       case WhitePromotionArchbishop:\r
8306       case BlackPromotionArchbishop:\r
8307       case WhitePromotionCentaur:\r
8308       case BlackPromotionCentaur:\r
8309       case WhitePromotionQueen:\r
8310       case BlackPromotionQueen:\r
8311       case WhitePromotionRook:\r
8312       case BlackPromotionRook:\r
8313       case WhitePromotionBishop:\r
8314       case BlackPromotionBishop:\r
8315       case WhitePromotionKnight:\r
8316       case BlackPromotionKnight:\r
8317       case WhitePromotionKing:\r
8318       case BlackPromotionKing:\r
8319       case NormalMove:\r
8320       case WhiteKingSideCastle:\r
8321       case WhiteQueenSideCastle:\r
8322       case BlackKingSideCastle:\r
8323       case BlackQueenSideCastle:\r
8324       case WhiteKingSideCastleWild:\r
8325       case WhiteQueenSideCastleWild:\r
8326       case BlackKingSideCastleWild:\r
8327       case BlackQueenSideCastleWild:\r
8328       /* PUSH Fabien */\r
8329       case WhiteHSideCastleFR:\r
8330       case WhiteASideCastleFR:\r
8331       case BlackHSideCastleFR:\r
8332       case BlackASideCastleFR:\r
8333       /* POP Fabien */\r
8334         if (appData.debugMode)\r
8335           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8336         fromX = currentMoveString[0] - AAA;\r
8337         fromY = currentMoveString[1] - ONE;\r
8338         toX = currentMoveString[2] - AAA;\r
8339         toY = currentMoveString[3] - ONE;\r
8340         promoChar = currentMoveString[4];\r
8341         break;\r
8342 \r
8343       case WhiteDrop:\r
8344       case BlackDrop:\r
8345         if (appData.debugMode)\r
8346           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8347         fromX = moveType == WhiteDrop ?\r
8348           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
8349         (int) CharToPiece(ToLower(currentMoveString[0]));\r
8350         fromY = DROP_RANK;\r
8351         toX = currentMoveString[2] - AAA;\r
8352         toY = currentMoveString[3] - ONE;\r
8353         break;\r
8354 \r
8355       case WhiteWins:\r
8356       case BlackWins:\r
8357       case GameIsDrawn:\r
8358       case GameUnfinished:\r
8359         if (appData.debugMode)\r
8360           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
8361         p = strchr(yy_text, '{');\r
8362         if (p == NULL) p = strchr(yy_text, '(');\r
8363         if (p == NULL) {\r
8364             p = yy_text;\r
8365             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
8366         } else {\r
8367             q = strchr(p, *p == '{' ? '}' : ')');\r
8368             if (q != NULL) *q = NULLCHAR;\r
8369             p++;\r
8370         }\r
8371         GameEnds(moveType, p, GE_FILE);\r
8372         done = TRUE;\r
8373         if (cmailMsgLoaded) {\r
8374             ClearHighlights();\r
8375             flipView = WhiteOnMove(currentMove);\r
8376             if (moveType == GameUnfinished) flipView = !flipView;\r
8377             if (appData.debugMode)\r
8378               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
8379         }\r
8380         break;\r
8381 \r
8382       case (ChessMove) 0:       /* end of file */\r
8383         if (appData.debugMode)\r
8384           fprintf(debugFP, "Parser hit end of file\n");\r
8385         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8386                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8387           case MT_NONE:\r
8388           case MT_CHECK:\r
8389             break;\r
8390           case MT_CHECKMATE:\r
8391             if (WhiteOnMove(currentMove)) {\r
8392                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8393             } else {\r
8394                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8395             }\r
8396             break;\r
8397           case MT_STALEMATE:\r
8398             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8399             break;\r
8400         }\r
8401         done = TRUE;\r
8402         break;\r
8403 \r
8404       case MoveNumberOne:\r
8405         if (lastLoadGameStart == GNUChessGame) {\r
8406             /* GNUChessGames have numbers, but they aren't move numbers */\r
8407             if (appData.debugMode)\r
8408               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8409                       yy_text, (int) moveType);\r
8410             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8411         }\r
8412         /* else fall thru */\r
8413 \r
8414       case XBoardGame:\r
8415       case GNUChessGame:\r
8416       case PGNTag:\r
8417         /* Reached start of next game in file */\r
8418         if (appData.debugMode)\r
8419           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
8420         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8421                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8422           case MT_NONE:\r
8423           case MT_CHECK:\r
8424             break;\r
8425           case MT_CHECKMATE:\r
8426             if (WhiteOnMove(currentMove)) {\r
8427                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8428             } else {\r
8429                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8430             }\r
8431             break;\r
8432           case MT_STALEMATE:\r
8433             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8434             break;\r
8435         }\r
8436         done = TRUE;\r
8437         break;\r
8438 \r
8439       case PositionDiagram:     /* should not happen; ignore */\r
8440       case ElapsedTime:         /* ignore */\r
8441       case NAG:                 /* ignore */\r
8442         if (appData.debugMode)\r
8443           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8444                   yy_text, (int) moveType);\r
8445         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8446 \r
8447       case IllegalMove:\r
8448         if (appData.testLegality) {\r
8449             if (appData.debugMode)\r
8450               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
8451             sprintf(move, _("Illegal move: %d.%s%s"),\r
8452                     (forwardMostMove / 2) + 1,\r
8453                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8454             DisplayError(move, 0);\r
8455             done = TRUE;\r
8456         } else {\r
8457             if (appData.debugMode)\r
8458               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
8459                       yy_text, currentMoveString);\r
8460             fromX = currentMoveString[0] - AAA;\r
8461             fromY = currentMoveString[1] - ONE;\r
8462             toX = currentMoveString[2] - AAA;\r
8463             toY = currentMoveString[3] - ONE;\r
8464             promoChar = currentMoveString[4];\r
8465         }\r
8466         break;\r
8467 \r
8468       case AmbiguousMove:\r
8469         if (appData.debugMode)\r
8470           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
8471         sprintf(move, _("Ambiguous move: %d.%s%s"),\r
8472                 (forwardMostMove / 2) + 1,\r
8473                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8474         DisplayError(move, 0);\r
8475         done = TRUE;\r
8476         break;\r
8477 \r
8478       default:\r
8479       case ImpossibleMove:\r
8480         if (appData.debugMode)\r
8481           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);\r
8482         sprintf(move, _("Illegal move: %d.%s%s"),\r
8483                 (forwardMostMove / 2) + 1,\r
8484                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8485         DisplayError(move, 0);\r
8486         done = TRUE;\r
8487         break;\r
8488     }\r
8489 \r
8490     if (done) {\r
8491         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
8492             DrawPosition(FALSE, boards[currentMove]);\r
8493             DisplayBothClocks();\r
8494             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
8495               DisplayComment(currentMove - 1, commentList[currentMove]);\r
8496         }\r
8497         (void) StopLoadGameTimer();\r
8498         gameFileFP = NULL;\r
8499         cmailOldMove = forwardMostMove;\r
8500         return FALSE;\r
8501     } else {\r
8502         /* currentMoveString is set as a side-effect of yylex */\r
8503         strcat(currentMoveString, "\n");\r
8504         strcpy(moveList[forwardMostMove], currentMoveString);\r
8505         \r
8506         thinkOutput[0] = NULLCHAR;\r
8507         MakeMove(fromX, fromY, toX, toY, promoChar);\r
8508         currentMove = forwardMostMove;\r
8509         return TRUE;\r
8510     }\r
8511 }\r
8512 \r
8513 /* Load the nth game from the given file */\r
8514 int\r
8515 LoadGameFromFile(filename, n, title, useList)\r
8516      char *filename;\r
8517      int n;\r
8518      char *title;\r
8519      /*Boolean*/ int useList;\r
8520 {\r
8521     FILE *f;\r
8522     char buf[MSG_SIZ];\r
8523 \r
8524     if (strcmp(filename, "-") == 0) {\r
8525         f = stdin;\r
8526         title = "stdin";\r
8527     } else {\r
8528         f = fopen(filename, "rb");\r
8529         if (f == NULL) {\r
8530             sprintf(buf, _("Can't open \"%s\""), filename);\r
8531             DisplayError(buf, errno);\r
8532             return FALSE;\r
8533         }\r
8534     }\r
8535     if (fseek(f, 0, 0) == -1) {\r
8536         /* f is not seekable; probably a pipe */\r
8537         useList = FALSE;\r
8538     }\r
8539     if (useList && n == 0) {\r
8540         int error = GameListBuild(f);\r
8541         if (error) {\r
8542             DisplayError(_("Cannot build game list"), error);\r
8543         } else if (!ListEmpty(&gameList) &&\r
8544                    ((ListGame *) gameList.tailPred)->number > 1) {\r
8545             GameListPopUp(f, title);\r
8546             return TRUE;\r
8547         }\r
8548         GameListDestroy();\r
8549         n = 1;\r
8550     }\r
8551     if (n == 0) n = 1;\r
8552     return LoadGame(f, n, title, FALSE);\r
8553 }\r
8554 \r
8555 \r
8556 void\r
8557 MakeRegisteredMove()\r
8558 {\r
8559     int fromX, fromY, toX, toY;\r
8560     char promoChar;\r
8561     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8562         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
8563           case CMAIL_MOVE:\r
8564           case CMAIL_DRAW:\r
8565             if (appData.debugMode)\r
8566               fprintf(debugFP, "Restoring %s for game %d\n",\r
8567                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8568     \r
8569             thinkOutput[0] = NULLCHAR;\r
8570             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
8571             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
8572             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
8573             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
8574             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
8575             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
8576             MakeMove(fromX, fromY, toX, toY, promoChar);\r
8577             ShowMove(fromX, fromY, toX, toY);\r
8578               \r
8579             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8580                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8581               case MT_NONE:\r
8582               case MT_CHECK:\r
8583                 break;\r
8584                 \r
8585               case MT_CHECKMATE:\r
8586                 if (WhiteOnMove(currentMove)) {\r
8587                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
8588                 } else {\r
8589                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
8590                 }\r
8591                 break;\r
8592                 \r
8593               case MT_STALEMATE:\r
8594                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
8595                 break;\r
8596             }\r
8597 \r
8598             break;\r
8599             \r
8600           case CMAIL_RESIGN:\r
8601             if (WhiteOnMove(currentMove)) {\r
8602                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
8603             } else {\r
8604                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
8605             }\r
8606             break;\r
8607             \r
8608           case CMAIL_ACCEPT:\r
8609             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
8610             break;\r
8611               \r
8612           default:\r
8613             break;\r
8614         }\r
8615     }\r
8616 \r
8617     return;\r
8618 }\r
8619 \r
8620 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
8621 int\r
8622 CmailLoadGame(f, gameNumber, title, useList)\r
8623      FILE *f;\r
8624      int gameNumber;\r
8625      char *title;\r
8626      int useList;\r
8627 {\r
8628     int retVal;\r
8629 \r
8630     if (gameNumber > nCmailGames) {\r
8631         DisplayError(_("No more games in this message"), 0);\r
8632         return FALSE;\r
8633     }\r
8634     if (f == lastLoadGameFP) {\r
8635         int offset = gameNumber - lastLoadGameNumber;\r
8636         if (offset == 0) {\r
8637             cmailMsg[0] = NULLCHAR;\r
8638             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8639                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8640                 nCmailMovesRegistered--;\r
8641             }\r
8642             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
8643             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
8644                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
8645             }\r
8646         } else {\r
8647             if (! RegisterMove()) return FALSE;\r
8648         }\r
8649     }\r
8650 \r
8651     retVal = LoadGame(f, gameNumber, title, useList);\r
8652 \r
8653     /* Make move registered during previous look at this game, if any */\r
8654     MakeRegisteredMove();\r
8655 \r
8656     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
8657         commentList[currentMove]\r
8658           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
8659         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8660     }\r
8661 \r
8662     return retVal;\r
8663 }\r
8664 \r
8665 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
8666 int\r
8667 ReloadGame(offset)\r
8668      int offset;\r
8669 {\r
8670     int gameNumber = lastLoadGameNumber + offset;\r
8671     if (lastLoadGameFP == NULL) {\r
8672         DisplayError(_("No game has been loaded yet"), 0);\r
8673         return FALSE;\r
8674     }\r
8675     if (gameNumber <= 0) {\r
8676         DisplayError(_("Can't back up any further"), 0);\r
8677         return FALSE;\r
8678     }\r
8679     if (cmailMsgLoaded) {\r
8680         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
8681                              lastLoadGameTitle, lastLoadGameUseList);\r
8682     } else {\r
8683         return LoadGame(lastLoadGameFP, gameNumber,\r
8684                         lastLoadGameTitle, lastLoadGameUseList);\r
8685     }\r
8686 }\r
8687 \r
8688 \r
8689 \r
8690 /* Load the nth game from open file f */\r
8691 int\r
8692 LoadGame(f, gameNumber, title, useList)\r
8693      FILE *f;\r
8694      int gameNumber;\r
8695      char *title;\r
8696      int useList;\r
8697 {\r
8698     ChessMove cm;\r
8699     char buf[MSG_SIZ];\r
8700     int gn = gameNumber;\r
8701     ListGame *lg = NULL;\r
8702     int numPGNTags = 0;\r
8703     int err;\r
8704     GameMode oldGameMode;\r
8705     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
8706 \r
8707     if (appData.debugMode) \r
8708         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
8709 \r
8710     if (gameMode == Training )\r
8711         SetTrainingModeOff();\r
8712 \r
8713     oldGameMode = gameMode;\r
8714     if (gameMode != BeginningOfGame) {\r
8715       Reset(FALSE, TRUE);\r
8716     }\r
8717 \r
8718     gameFileFP = f;\r
8719     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
8720         fclose(lastLoadGameFP);\r
8721     }\r
8722 \r
8723     if (useList) {\r
8724         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
8725         \r
8726         if (lg) {\r
8727             fseek(f, lg->offset, 0);\r
8728             GameListHighlight(gameNumber);\r
8729             gn = 1;\r
8730         }\r
8731         else {\r
8732             DisplayError(_("Game number out of range"), 0);\r
8733             return FALSE;\r
8734         }\r
8735     } else {\r
8736         GameListDestroy();\r
8737         if (fseek(f, 0, 0) == -1) {\r
8738             if (f == lastLoadGameFP ?\r
8739                 gameNumber == lastLoadGameNumber + 1 :\r
8740                 gameNumber == 1) {\r
8741                 gn = 1;\r
8742             } else {\r
8743                 DisplayError(_("Can't seek on game file"), 0);\r
8744                 return FALSE;\r
8745             }\r
8746         }\r
8747     }\r
8748     lastLoadGameFP = f;\r
8749     lastLoadGameNumber = gameNumber;\r
8750     strcpy(lastLoadGameTitle, title);\r
8751     lastLoadGameUseList = useList;\r
8752 \r
8753     yynewfile(f);\r
8754 \r
8755     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
8756         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
8757                 lg->gameInfo.black);\r
8758             DisplayTitle(buf);\r
8759     } else if (*title != NULLCHAR) {\r
8760         if (gameNumber > 1) {\r
8761             sprintf(buf, "%s %d", title, gameNumber);\r
8762             DisplayTitle(buf);\r
8763         } else {\r
8764             DisplayTitle(title);\r
8765         }\r
8766     }\r
8767 \r
8768     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
8769         gameMode = PlayFromGameFile;\r
8770         ModeHighlight();\r
8771     }\r
8772 \r
8773     currentMove = forwardMostMove = backwardMostMove = 0;\r
8774     CopyBoard(boards[0], initialPosition);\r
8775     StopClocks();\r
8776 \r
8777     /*\r
8778      * Skip the first gn-1 games in the file.\r
8779      * Also skip over anything that precedes an identifiable \r
8780      * start of game marker, to avoid being confused by \r
8781      * garbage at the start of the file.  Currently \r
8782      * recognized start of game markers are the move number "1",\r
8783      * the pattern "gnuchess .* game", the pattern\r
8784      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
8785      * A game that starts with one of the latter two patterns\r
8786      * will also have a move number 1, possibly\r
8787      * following a position diagram.\r
8788      * 5-4-02: Let's try being more lenient and allowing a game to\r
8789      * start with an unnumbered move.  Does that break anything?\r
8790      */\r
8791     cm = lastLoadGameStart = (ChessMove) 0;\r
8792     while (gn > 0) {\r
8793         yyboardindex = forwardMostMove;\r
8794         cm = (ChessMove) yylex();\r
8795         switch (cm) {\r
8796           case (ChessMove) 0:\r
8797             if (cmailMsgLoaded) {\r
8798                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
8799             } else {\r
8800                 Reset(TRUE, TRUE);\r
8801                 DisplayError(_("Game not found in file"), 0);\r
8802             }\r
8803             return FALSE;\r
8804 \r
8805           case GNUChessGame:\r
8806           case XBoardGame:\r
8807             gn--;\r
8808             lastLoadGameStart = cm;\r
8809             break;\r
8810             \r
8811           case MoveNumberOne:\r
8812             switch (lastLoadGameStart) {\r
8813               case GNUChessGame:\r
8814               case XBoardGame:\r
8815               case PGNTag:\r
8816                 break;\r
8817               case MoveNumberOne:\r
8818               case (ChessMove) 0:\r
8819                 gn--;           /* count this game */\r
8820                 lastLoadGameStart = cm;\r
8821                 break;\r
8822               default:\r
8823                 /* impossible */\r
8824                 break;\r
8825             }\r
8826             break;\r
8827 \r
8828           case PGNTag:\r
8829             switch (lastLoadGameStart) {\r
8830               case GNUChessGame:\r
8831               case PGNTag:\r
8832               case MoveNumberOne:\r
8833               case (ChessMove) 0:\r
8834                 gn--;           /* count this game */\r
8835                 lastLoadGameStart = cm;\r
8836                 break;\r
8837               case XBoardGame:\r
8838                 lastLoadGameStart = cm; /* game counted already */\r
8839                 break;\r
8840               default:\r
8841                 /* impossible */\r
8842                 break;\r
8843             }\r
8844             if (gn > 0) {\r
8845                 do {\r
8846                     yyboardindex = forwardMostMove;\r
8847                     cm = (ChessMove) yylex();\r
8848                 } while (cm == PGNTag || cm == Comment);\r
8849             }\r
8850             break;\r
8851 \r
8852           case WhiteWins:\r
8853           case BlackWins:\r
8854           case GameIsDrawn:\r
8855             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8856                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8857                     != CMAIL_OLD_RESULT) {\r
8858                     nCmailResults ++ ;\r
8859                     cmailResult[  CMAIL_MAX_GAMES\r
8860                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8861                 }\r
8862             }\r
8863             break;\r
8864 \r
8865           case NormalMove:\r
8866             /* Only a NormalMove can be at the start of a game\r
8867              * without a position diagram. */\r
8868             if (lastLoadGameStart == (ChessMove) 0) {\r
8869               gn--;\r
8870               lastLoadGameStart = MoveNumberOne;\r
8871             }\r
8872             break;\r
8873 \r
8874           default:\r
8875             break;\r
8876         }\r
8877     }\r
8878     \r
8879     if (appData.debugMode)\r
8880       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8881 \r
8882     if (cm == XBoardGame) {\r
8883         /* Skip any header junk before position diagram and/or move 1 */\r
8884         for (;;) {\r
8885             yyboardindex = forwardMostMove;\r
8886             cm = (ChessMove) yylex();\r
8887 \r
8888             if (cm == (ChessMove) 0 ||\r
8889                 cm == GNUChessGame || cm == XBoardGame) {\r
8890                 /* Empty game; pretend end-of-file and handle later */\r
8891                 cm = (ChessMove) 0;\r
8892                 break;\r
8893             }\r
8894 \r
8895             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8896                 cm == PGNTag || cm == Comment)\r
8897               break;\r
8898         }\r
8899     } else if (cm == GNUChessGame) {\r
8900         if (gameInfo.event != NULL) {\r
8901             free(gameInfo.event);\r
8902         }\r
8903         gameInfo.event = StrSave(yy_text);\r
8904     }   \r
8905 \r
8906     startedFromSetupPosition = FALSE;\r
8907     while (cm == PGNTag) {\r
8908         if (appData.debugMode) \r
8909           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8910         err = ParsePGNTag(yy_text, &gameInfo);\r
8911         if (!err) numPGNTags++;\r
8912 \r
8913         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8914         if(gameInfo.variant != oldVariant) {\r
8915             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8916             InitPosition(TRUE);\r
8917             oldVariant = gameInfo.variant;\r
8918             if (appData.debugMode) \r
8919               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8920         }\r
8921 \r
8922 \r
8923         if (gameInfo.fen != NULL) {\r
8924           Board initial_position;\r
8925           startedFromSetupPosition = TRUE;\r
8926           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8927             Reset(TRUE, TRUE);\r
8928             DisplayError(_("Bad FEN position in file"), 0);\r
8929             return FALSE;\r
8930           }\r
8931           CopyBoard(boards[0], initial_position);\r
8932           if (blackPlaysFirst) {\r
8933             currentMove = forwardMostMove = backwardMostMove = 1;\r
8934             CopyBoard(boards[1], initial_position);\r
8935             strcpy(moveList[0], "");\r
8936             strcpy(parseList[0], "");\r
8937             timeRemaining[0][1] = whiteTimeRemaining;\r
8938             timeRemaining[1][1] = blackTimeRemaining;\r
8939             if (commentList[0] != NULL) {\r
8940               commentList[1] = commentList[0];\r
8941               commentList[0] = NULL;\r
8942             }\r
8943           } else {\r
8944             currentMove = forwardMostMove = backwardMostMove = 0;\r
8945           }\r
8946           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */\r
8947           {   int i;\r
8948               initialRulePlies = FENrulePlies;\r
8949               epStatus[forwardMostMove] = FENepStatus;\r
8950               for( i=0; i< nrCastlingRights; i++ )\r
8951                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
8952           }\r
8953           yyboardindex = forwardMostMove;\r
8954           free(gameInfo.fen);\r
8955           gameInfo.fen = NULL;\r
8956         }\r
8957 \r
8958         yyboardindex = forwardMostMove;\r
8959         cm = (ChessMove) yylex();\r
8960 \r
8961         /* Handle comments interspersed among the tags */\r
8962         while (cm == Comment) {\r
8963             char *p;\r
8964             if (appData.debugMode) \r
8965               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8966             p = yy_text;\r
8967             if (*p == '{' || *p == '[' || *p == '(') {\r
8968                 p[strlen(p) - 1] = NULLCHAR;\r
8969                 p++;\r
8970             }\r
8971             while (*p == '\n') p++;\r
8972             AppendComment(currentMove, p);\r
8973             yyboardindex = forwardMostMove;\r
8974             cm = (ChessMove) yylex();\r
8975         }\r
8976     }\r
8977 \r
8978     /* don't rely on existence of Event tag since if game was\r
8979      * pasted from clipboard the Event tag may not exist\r
8980      */\r
8981     if (numPGNTags > 0){\r
8982         char *tags;\r
8983         if (gameInfo.variant == VariantNormal) {\r
8984           gameInfo.variant = StringToVariant(gameInfo.event);\r
8985         }\r
8986         if (!matchMode) {\r
8987           if( appData.autoDisplayTags ) {\r
8988             tags = PGNTags(&gameInfo);\r
8989             TagsPopUp(tags, CmailMsg());\r
8990             free(tags);\r
8991           }\r
8992         }\r
8993     } else {\r
8994         /* Make something up, but don't display it now */\r
8995         SetGameInfo();\r
8996         TagsPopDown();\r
8997     }\r
8998 \r
8999     if (cm == PositionDiagram) {\r
9000         int i, j;\r
9001         char *p;\r
9002         Board initial_position;\r
9003 \r
9004         if (appData.debugMode)\r
9005           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
9006 \r
9007         if (!startedFromSetupPosition) {\r
9008             p = yy_text;\r
9009             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
9010               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
9011                 switch (*p) {\r
9012                   case '[':\r
9013                   case '-':\r
9014                   case ' ':\r
9015                   case '\t':\r
9016                   case '\n':\r
9017                   case '\r':\r
9018                     break;\r
9019                   default:\r
9020                     initial_position[i][j++] = CharToPiece(*p);\r
9021                     break;\r
9022                 }\r
9023             while (*p == ' ' || *p == '\t' ||\r
9024                    *p == '\n' || *p == '\r') p++;\r
9025         \r
9026             if (strncmp(p, "black", strlen("black"))==0)\r
9027               blackPlaysFirst = TRUE;\r
9028             else\r
9029               blackPlaysFirst = FALSE;\r
9030             startedFromSetupPosition = TRUE;\r
9031         \r
9032             CopyBoard(boards[0], initial_position);\r
9033             if (blackPlaysFirst) {\r
9034                 currentMove = forwardMostMove = backwardMostMove = 1;\r
9035                 CopyBoard(boards[1], initial_position);\r
9036                 strcpy(moveList[0], "");\r
9037                 strcpy(parseList[0], "");\r
9038                 timeRemaining[0][1] = whiteTimeRemaining;\r
9039                 timeRemaining[1][1] = blackTimeRemaining;\r
9040                 if (commentList[0] != NULL) {\r
9041                     commentList[1] = commentList[0];\r
9042                     commentList[0] = NULL;\r
9043                 }\r
9044             } else {\r
9045                 currentMove = forwardMostMove = backwardMostMove = 0;\r
9046             }\r
9047         }\r
9048         yyboardindex = forwardMostMove;\r
9049         cm = (ChessMove) yylex();\r
9050     }\r
9051 \r
9052     if (first.pr == NoProc) {\r
9053         StartChessProgram(&first);\r
9054     }\r
9055     InitChessProgram(&first, FALSE);\r
9056     SendToProgram("force\n", &first);\r
9057     if (startedFromSetupPosition) {\r
9058         SendBoard(&first, forwardMostMove);\r
9059     if (appData.debugMode) {\r
9060         fprintf(debugFP, "Load Game\n");\r
9061     }\r
9062         DisplayBothClocks();\r
9063     }      \r
9064 \r
9065     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
9066     loadFlag = appData.suppressLoadMoves;\r
9067 \r
9068     while (cm == Comment) {\r
9069         char *p;\r
9070         if (appData.debugMode) \r
9071           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
9072         p = yy_text;\r
9073         if (*p == '{' || *p == '[' || *p == '(') {\r
9074             p[strlen(p) - 1] = NULLCHAR;\r
9075             p++;\r
9076         }\r
9077         while (*p == '\n') p++;\r
9078         AppendComment(currentMove, p);\r
9079         yyboardindex = forwardMostMove;\r
9080         cm = (ChessMove) yylex();\r
9081     }\r
9082 \r
9083     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
9084         cm == WhiteWins || cm == BlackWins ||\r
9085         cm == GameIsDrawn || cm == GameUnfinished) {\r
9086         DisplayMessage("", _("No moves in game"));\r
9087         if (cmailMsgLoaded) {\r
9088             if (appData.debugMode)\r
9089               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
9090             ClearHighlights();\r
9091             flipView = FALSE;\r
9092         }\r
9093         DrawPosition(FALSE, boards[currentMove]);\r
9094         DisplayBothClocks();\r
9095         gameMode = EditGame;\r
9096         ModeHighlight();\r
9097         gameFileFP = NULL;\r
9098         cmailOldMove = 0;\r
9099         return TRUE;\r
9100     }\r
9101 \r
9102     // [HGM] PV info: routine tests if comment empty\r
9103     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
9104         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9105     }\r
9106     if (!matchMode && appData.timeDelay != 0) \r
9107       DrawPosition(FALSE, boards[currentMove]);\r
9108 \r
9109     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
9110       programStats.ok_to_send = 1;\r
9111     }\r
9112 \r
9113     /* if the first token after the PGN tags is a move\r
9114      * and not move number 1, retrieve it from the parser \r
9115      */\r
9116     if (cm != MoveNumberOne)\r
9117         LoadGameOneMove(cm);\r
9118 \r
9119     /* load the remaining moves from the file */\r
9120     while (LoadGameOneMove((ChessMove)0)) {\r
9121       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9122       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9123     }\r
9124 \r
9125     /* rewind to the start of the game */\r
9126     currentMove = backwardMostMove;\r
9127 \r
9128     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9129 \r
9130     if (oldGameMode == AnalyzeFile ||\r
9131         oldGameMode == AnalyzeMode) {\r
9132       AnalyzeFileEvent();\r
9133     }\r
9134 \r
9135     if (matchMode || appData.timeDelay == 0) {\r
9136       ToEndEvent();\r
9137       gameMode = EditGame;\r
9138       ModeHighlight();\r
9139     } else if (appData.timeDelay > 0) {\r
9140       AutoPlayGameLoop();\r
9141     }\r
9142 \r
9143     if (appData.debugMode) \r
9144         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
9145 \r
9146     loadFlag = 0; /* [HGM] true game starts */\r
9147     return TRUE;\r
9148 }\r
9149 \r
9150 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
9151 int\r
9152 ReloadPosition(offset)\r
9153      int offset;\r
9154 {\r
9155     int positionNumber = lastLoadPositionNumber + offset;\r
9156     if (lastLoadPositionFP == NULL) {\r
9157         DisplayError(_("No position has been loaded yet"), 0);\r
9158         return FALSE;\r
9159     }\r
9160     if (positionNumber <= 0) {\r
9161         DisplayError(_("Can't back up any further"), 0);\r
9162         return FALSE;\r
9163     }\r
9164     return LoadPosition(lastLoadPositionFP, positionNumber,\r
9165                         lastLoadPositionTitle);\r
9166 }\r
9167 \r
9168 /* Load the nth position from the given file */\r
9169 int\r
9170 LoadPositionFromFile(filename, n, title)\r
9171      char *filename;\r
9172      int n;\r
9173      char *title;\r
9174 {\r
9175     FILE *f;\r
9176     char buf[MSG_SIZ];\r
9177 \r
9178     if (strcmp(filename, "-") == 0) {\r
9179         return LoadPosition(stdin, n, "stdin");\r
9180     } else {\r
9181         f = fopen(filename, "rb");\r
9182         if (f == NULL) {\r
9183             sprintf(buf, _("Can't open \"%s\""), filename);\r
9184             DisplayError(buf, errno);\r
9185             return FALSE;\r
9186         } else {\r
9187             return LoadPosition(f, n, title);\r
9188         }\r
9189     }\r
9190 }\r
9191 \r
9192 /* Load the nth position from the given open file, and close it */\r
9193 int\r
9194 LoadPosition(f, positionNumber, title)\r
9195      FILE *f;\r
9196      int positionNumber;\r
9197      char *title;\r
9198 {\r
9199     char *p, line[MSG_SIZ];\r
9200     Board initial_position;\r
9201     int i, j, fenMode, pn;\r
9202     \r
9203     if (gameMode == Training )\r
9204         SetTrainingModeOff();\r
9205 \r
9206     if (gameMode != BeginningOfGame) {\r
9207         Reset(FALSE, TRUE);\r
9208     }\r
9209     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
9210         fclose(lastLoadPositionFP);\r
9211     }\r
9212     if (positionNumber == 0) positionNumber = 1;\r
9213     lastLoadPositionFP = f;\r
9214     lastLoadPositionNumber = positionNumber;\r
9215     strcpy(lastLoadPositionTitle, title);\r
9216     if (first.pr == NoProc) {\r
9217       StartChessProgram(&first);\r
9218       InitChessProgram(&first, FALSE);\r
9219     }    \r
9220     pn = positionNumber;\r
9221     if (positionNumber < 0) {\r
9222         /* Negative position number means to seek to that byte offset */\r
9223         if (fseek(f, -positionNumber, 0) == -1) {\r
9224             DisplayError(_("Can't seek on position file"), 0);\r
9225             return FALSE;\r
9226         };\r
9227         pn = 1;\r
9228     } else {\r
9229         if (fseek(f, 0, 0) == -1) {\r
9230             if (f == lastLoadPositionFP ?\r
9231                 positionNumber == lastLoadPositionNumber + 1 :\r
9232                 positionNumber == 1) {\r
9233                 pn = 1;\r
9234             } else {\r
9235                 DisplayError(_("Can't seek on position file"), 0);\r
9236                 return FALSE;\r
9237             }\r
9238         }\r
9239     }\r
9240     /* See if this file is FEN or old-style xboard */\r
9241     if (fgets(line, MSG_SIZ, f) == NULL) {\r
9242         DisplayError(_("Position not found in file"), 0);\r
9243         return FALSE;\r
9244     }\r
9245 #if 0\r
9246     switch (line[0]) {\r
9247       case '#':  case 'x':\r
9248       default:\r
9249         fenMode = FALSE;\r
9250         break;\r
9251       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
9252       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
9253       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
9254       case '7':  case '8':  case '9':\r
9255       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
9256       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
9257       case 'C':  case 'W':             case 'c':  case 'w': \r
9258         fenMode = TRUE;\r
9259         break;\r
9260     }\r
9261 #else\r
9262     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
9263     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
9264 #endif\r
9265 \r
9266     if (pn >= 2) {\r
9267         if (fenMode || line[0] == '#') pn--;\r
9268         while (pn > 0) {\r
9269             /* skip positions before number pn */\r
9270             if (fgets(line, MSG_SIZ, f) == NULL) {\r
9271                 Reset(TRUE, TRUE);\r
9272                 DisplayError(_("Position not found in file"), 0);\r
9273                 return FALSE;\r
9274             }\r
9275             if (fenMode || line[0] == '#') pn--;\r
9276         }\r
9277     }\r
9278 \r
9279     if (fenMode) {\r
9280         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
9281             DisplayError(_("Bad FEN position in file"), 0);\r
9282             return FALSE;\r
9283         }\r
9284     } else {\r
9285         (void) fgets(line, MSG_SIZ, f);\r
9286         (void) fgets(line, MSG_SIZ, f);\r
9287     \r
9288         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
9289             (void) fgets(line, MSG_SIZ, f);\r
9290             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
9291                 if (*p == ' ')\r
9292                   continue;\r
9293                 initial_position[i][j++] = CharToPiece(*p);\r
9294             }\r
9295         }\r
9296     \r
9297         blackPlaysFirst = FALSE;\r
9298         if (!feof(f)) {\r
9299             (void) fgets(line, MSG_SIZ, f);\r
9300             if (strncmp(line, "black", strlen("black"))==0)\r
9301               blackPlaysFirst = TRUE;\r
9302         }\r
9303     }\r
9304     startedFromSetupPosition = TRUE;\r
9305     \r
9306     SendToProgram("force\n", &first);\r
9307     CopyBoard(boards[0], initial_position);\r
9308     if (blackPlaysFirst) {\r
9309         currentMove = forwardMostMove = backwardMostMove = 1;\r
9310         strcpy(moveList[0], "");\r
9311         strcpy(parseList[0], "");\r
9312         CopyBoard(boards[1], initial_position);\r
9313         DisplayMessage("", _("Black to play"));\r
9314     } else {\r
9315         currentMove = forwardMostMove = backwardMostMove = 0;\r
9316         DisplayMessage("", _("White to play"));\r
9317     }\r
9318           /* [HGM] copy FEN attributes as well */\r
9319           {   int i;\r
9320               initialRulePlies = FENrulePlies;\r
9321               epStatus[forwardMostMove] = FENepStatus;\r
9322               for( i=0; i< nrCastlingRights; i++ )\r
9323                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
9324           }\r
9325     SendBoard(&first, forwardMostMove);\r
9326     if (appData.debugMode) {\r
9327 int i, j;\r
9328   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}\r
9329   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");\r
9330         fprintf(debugFP, "Load Position\n");\r
9331     }\r
9332 \r
9333     if (positionNumber > 1) {\r
9334         sprintf(line, "%s %d", title, positionNumber);\r
9335         DisplayTitle(line);\r
9336     } else {\r
9337         DisplayTitle(title);\r
9338     }\r
9339     gameMode = EditGame;\r
9340     ModeHighlight();\r
9341     ResetClocks();\r
9342     timeRemaining[0][1] = whiteTimeRemaining;\r
9343     timeRemaining[1][1] = blackTimeRemaining;\r
9344     DrawPosition(FALSE, boards[currentMove]);\r
9345    \r
9346     return TRUE;\r
9347 }\r
9348 \r
9349 \r
9350 void\r
9351 CopyPlayerNameIntoFileName(dest, src)\r
9352      char **dest, *src;\r
9353 {\r
9354     while (*src != NULLCHAR && *src != ',') {\r
9355         if (*src == ' ') {\r
9356             *(*dest)++ = '_';\r
9357             src++;\r
9358         } else {\r
9359             *(*dest)++ = *src++;\r
9360         }\r
9361     }\r
9362 }\r
9363 \r
9364 char *DefaultFileName(ext)\r
9365      char *ext;\r
9366 {\r
9367     static char def[MSG_SIZ];\r
9368     char *p;\r
9369 \r
9370     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
9371         p = def;\r
9372         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
9373         *p++ = '-';\r
9374         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
9375         *p++ = '.';\r
9376         strcpy(p, ext);\r
9377     } else {\r
9378         def[0] = NULLCHAR;\r
9379     }\r
9380     return def;\r
9381 }\r
9382 \r
9383 /* Save the current game to the given file */\r
9384 int\r
9385 SaveGameToFile(filename, append)\r
9386      char *filename;\r
9387      int append;\r
9388 {\r
9389     FILE *f;\r
9390     char buf[MSG_SIZ];\r
9391 \r
9392     if (strcmp(filename, "-") == 0) {\r
9393         return SaveGame(stdout, 0, NULL);\r
9394     } else {\r
9395         f = fopen(filename, append ? "a" : "w");\r
9396         if (f == NULL) {\r
9397             sprintf(buf, _("Can't open \"%s\""), filename);\r
9398             DisplayError(buf, errno);\r
9399             return FALSE;\r
9400         } else {\r
9401             return SaveGame(f, 0, NULL);\r
9402         }\r
9403     }\r
9404 }\r
9405 \r
9406 char *\r
9407 SavePart(str)\r
9408      char *str;\r
9409 {\r
9410     static char buf[MSG_SIZ];\r
9411     char *p;\r
9412     \r
9413     p = strchr(str, ' ');\r
9414     if (p == NULL) return str;\r
9415     strncpy(buf, str, p - str);\r
9416     buf[p - str] = NULLCHAR;\r
9417     return buf;\r
9418 }\r
9419 \r
9420 #define PGN_MAX_LINE 75\r
9421 \r
9422 #define PGN_SIDE_WHITE  0\r
9423 #define PGN_SIDE_BLACK  1\r
9424 \r
9425 /* [AS] */\r
9426 static int FindFirstMoveOutOfBook( int side )\r
9427 {\r
9428     int result = -1;\r
9429 \r
9430     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
9431         int index = backwardMostMove;\r
9432         int has_book_hit = 0;\r
9433 \r
9434         if( (index % 2) != side ) {\r
9435             index++;\r
9436         }\r
9437 \r
9438         while( index < forwardMostMove ) {\r
9439             /* Check to see if engine is in book */\r
9440             int depth = pvInfoList[index].depth;\r
9441             int score = pvInfoList[index].score;\r
9442             int in_book = 0;\r
9443 \r
9444             if( depth <= 2 ) {\r
9445                 in_book = 1;\r
9446             }\r
9447             else if( score == 0 && depth == 63 ) {\r
9448                 in_book = 1; /* Zappa */\r
9449             }\r
9450             else if( score == 2 && depth == 99 ) {\r
9451                 in_book = 1; /* Abrok */\r
9452             }\r
9453 \r
9454             has_book_hit += in_book;\r
9455 \r
9456             if( ! in_book ) {\r
9457                 result = index;\r
9458 \r
9459                 break;\r
9460             }\r
9461 \r
9462             index += 2;\r
9463         }\r
9464     }\r
9465 \r
9466     return result;\r
9467 }\r
9468 \r
9469 /* [AS] */\r
9470 void GetOutOfBookInfo( char * buf )\r
9471 {\r
9472     int oob[2];\r
9473     int i;\r
9474     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9475 \r
9476     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
9477     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
9478 \r
9479     *buf = '\0';\r
9480 \r
9481     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
9482         for( i=0; i<2; i++ ) {\r
9483             int idx = oob[i];\r
9484 \r
9485             if( idx >= 0 ) {\r
9486                 if( i > 0 && oob[0] >= 0 ) {\r
9487                     strcat( buf, "   " );\r
9488                 }\r
9489 \r
9490                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
9491                 sprintf( buf+strlen(buf), "%s%.2f", \r
9492                     pvInfoList[idx].score >= 0 ? "+" : "",\r
9493                     pvInfoList[idx].score / 100.0 );\r
9494             }\r
9495         }\r
9496     }\r
9497 }\r
9498 \r
9499 /* Save game in PGN style and close the file */\r
9500 int\r
9501 SaveGamePGN(f)\r
9502      FILE *f;\r
9503 {\r
9504     int i, offset, linelen, newblock;\r
9505     time_t tm;\r
9506     char *movetext;\r
9507     char numtext[32];\r
9508     int movelen, numlen, blank;\r
9509     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
9510 \r
9511     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9512     \r
9513     tm = time((time_t *) NULL);\r
9514     \r
9515     PrintPGNTags(f, &gameInfo);\r
9516     \r
9517     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9518         char *fen = PositionToFEN(backwardMostMove, 1);\r
9519         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
9520         fprintf(f, "\n{--------------\n");\r
9521         PrintPosition(f, backwardMostMove);\r
9522         fprintf(f, "--------------}\n");\r
9523         free(fen);\r
9524     }\r
9525     else {\r
9526         /* [AS] Out of book annotation */\r
9527         if( appData.saveOutOfBookInfo ) {\r
9528             char buf[64];\r
9529 \r
9530             GetOutOfBookInfo( buf );\r
9531 \r
9532             if( buf[0] != '\0' ) {\r
9533                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
9534             }\r
9535         }\r
9536 \r
9537         fprintf(f, "\n");\r
9538     }\r
9539 \r
9540     i = backwardMostMove;\r
9541     linelen = 0;\r
9542     newblock = TRUE;\r
9543 \r
9544     while (i < forwardMostMove) {\r
9545         /* Print comments preceding this move */\r
9546         if (commentList[i] != NULL) {\r
9547             if (linelen > 0) fprintf(f, "\n");\r
9548             fprintf(f, "{\n%s}\n", commentList[i]);\r
9549             linelen = 0;\r
9550             newblock = TRUE;\r
9551         }\r
9552 \r
9553         /* Format move number */\r
9554         if ((i % 2) == 0) {\r
9555             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
9556         } else {\r
9557             if (newblock) {\r
9558                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
9559             } else {\r
9560                 numtext[0] = NULLCHAR;\r
9561             }\r
9562         }\r
9563         numlen = strlen(numtext);\r
9564         newblock = FALSE;\r
9565 \r
9566         /* Print move number */\r
9567         blank = linelen > 0 && numlen > 0;\r
9568         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
9569             fprintf(f, "\n");\r
9570             linelen = 0;\r
9571             blank = 0;\r
9572         }\r
9573         if (blank) {\r
9574             fprintf(f, " ");\r
9575             linelen++;\r
9576         }\r
9577         fprintf(f, numtext);\r
9578         linelen += numlen;\r
9579 \r
9580         /* Get move */\r
9581         movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */\r
9582 \r
9583         /* Print move */\r
9584         blank = linelen > 0 && movelen > 0;\r
9585         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9586             fprintf(f, "\n");\r
9587             linelen = 0;\r
9588             blank = 0;\r
9589         }\r
9590         if (blank) {\r
9591             fprintf(f, " ");\r
9592             linelen++;\r
9593         }\r
9594         fprintf(f, parseList[i]);\r
9595         linelen += movelen;\r
9596 \r
9597         /* [AS] Add PV info if present */\r
9598         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9599             /* [HGM] add time */\r
9600             char buf[MSG_SIZ]; int seconds = 0;\r
9601 \r
9602 #if 0\r
9603             if(i >= backwardMostMove) {\r
9604                 if(WhiteOnMove(i))\r
9605                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]\r
9606                                   + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;\r
9607                 else\r
9608                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]\r
9609                                   + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;\r
9610             }\r
9611             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest\r
9612 #else\r
9613             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time\r
9614 #endif\r
9615     if (appData.debugMode,0) {\r
9616         fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",\r
9617                 timeRemaining[0][i+1], timeRemaining[0][i],\r
9618                      timeRemaining[1][i+1], timeRemaining[1][i], seconds\r
9619         );\r
9620     }\r
9621 \r
9622             if( seconds <= 0) buf[0] = 0; else\r
9623             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {\r
9624                 seconds = (seconds + 4)/10; // round to full seconds\r
9625                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else\r
9626                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
9627             }\r
9628 \r
9629             sprintf( move_buffer, "{%s%.2f/%d%s}", \r
9630                 pvInfoList[i].score >= 0 ? "+" : "",\r
9631                 pvInfoList[i].score / 100.0,\r
9632                 pvInfoList[i].depth,\r
9633                 buf );\r
9634 \r
9635             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */\r
9636 \r
9637             /* Print score/depth */\r
9638             blank = linelen > 0 && movelen > 0;\r
9639             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9640                 fprintf(f, "\n");\r
9641                 linelen = 0;\r
9642                 blank = 0;\r
9643             }\r
9644             if (blank) {\r
9645                 fprintf(f, " ");\r
9646                 linelen++;\r
9647             }\r
9648             fprintf(f, move_buffer);\r
9649             linelen += movelen;\r
9650         }\r
9651 \r
9652         i++;\r
9653     }\r
9654     \r
9655     /* Start a new line */\r
9656     if (linelen > 0) fprintf(f, "\n");\r
9657 \r
9658     /* Print comments after last move */\r
9659     if (commentList[i] != NULL) {\r
9660         fprintf(f, "{\n%s}\n", commentList[i]);\r
9661     }\r
9662 \r
9663     /* Print result */\r
9664     if (gameInfo.resultDetails != NULL &&\r
9665         gameInfo.resultDetails[0] != NULLCHAR) {\r
9666         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
9667                 PGNResult(gameInfo.result));\r
9668     } else {\r
9669         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9670     }\r
9671 \r
9672     fclose(f);\r
9673     return TRUE;\r
9674 }\r
9675 \r
9676 /* Save game in old style and close the file */\r
9677 int\r
9678 SaveGameOldStyle(f)\r
9679      FILE *f;\r
9680 {\r
9681     int i, offset;\r
9682     time_t tm;\r
9683     \r
9684     tm = time((time_t *) NULL);\r
9685     \r
9686     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
9687     PrintOpponents(f);\r
9688     \r
9689     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9690         fprintf(f, "\n[--------------\n");\r
9691         PrintPosition(f, backwardMostMove);\r
9692         fprintf(f, "--------------]\n");\r
9693     } else {\r
9694         fprintf(f, "\n");\r
9695     }\r
9696 \r
9697     i = backwardMostMove;\r
9698     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9699 \r
9700     while (i < forwardMostMove) {\r
9701         if (commentList[i] != NULL) {\r
9702             fprintf(f, "[%s]\n", commentList[i]);\r
9703         }\r
9704 \r
9705         if ((i % 2) == 1) {\r
9706             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
9707             i++;\r
9708         } else {\r
9709             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
9710             i++;\r
9711             if (commentList[i] != NULL) {\r
9712                 fprintf(f, "\n");\r
9713                 continue;\r
9714             }\r
9715             if (i >= forwardMostMove) {\r
9716                 fprintf(f, "\n");\r
9717                 break;\r
9718             }\r
9719             fprintf(f, "%s\n", parseList[i]);\r
9720             i++;\r
9721         }\r
9722     }\r
9723     \r
9724     if (commentList[i] != NULL) {\r
9725         fprintf(f, "[%s]\n", commentList[i]);\r
9726     }\r
9727 \r
9728     /* This isn't really the old style, but it's close enough */\r
9729     if (gameInfo.resultDetails != NULL &&\r
9730         gameInfo.resultDetails[0] != NULLCHAR) {\r
9731         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
9732                 gameInfo.resultDetails);\r
9733     } else {\r
9734         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9735     }\r
9736 \r
9737     fclose(f);\r
9738     return TRUE;\r
9739 }\r
9740 \r
9741 /* Save the current game to open file f and close the file */\r
9742 int\r
9743 SaveGame(f, dummy, dummy2)\r
9744      FILE *f;\r
9745      int dummy;\r
9746      char *dummy2;\r
9747 {\r
9748     if (gameMode == EditPosition) EditPositionDone();\r
9749     if (appData.oldSaveStyle)\r
9750       return SaveGameOldStyle(f);\r
9751     else\r
9752       return SaveGamePGN(f);\r
9753 }\r
9754 \r
9755 /* Save the current position to the given file */\r
9756 int\r
9757 SavePositionToFile(filename)\r
9758      char *filename;\r
9759 {\r
9760     FILE *f;\r
9761     char buf[MSG_SIZ];\r
9762 \r
9763     if (strcmp(filename, "-") == 0) {\r
9764         return SavePosition(stdout, 0, NULL);\r
9765     } else {\r
9766         f = fopen(filename, "a");\r
9767         if (f == NULL) {\r
9768             sprintf(buf, _("Can't open \"%s\""), filename);\r
9769             DisplayError(buf, errno);\r
9770             return FALSE;\r
9771         } else {\r
9772             SavePosition(f, 0, NULL);\r
9773             return TRUE;\r
9774         }\r
9775     }\r
9776 }\r
9777 \r
9778 /* Save the current position to the given open file and close the file */\r
9779 int\r
9780 SavePosition(f, dummy, dummy2)\r
9781      FILE *f;\r
9782      int dummy;\r
9783      char *dummy2;\r
9784 {\r
9785     time_t tm;\r
9786     char *fen;\r
9787     \r
9788     if (appData.oldSaveStyle) {\r
9789         tm = time((time_t *) NULL);\r
9790     \r
9791         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
9792         PrintOpponents(f);\r
9793         fprintf(f, "[--------------\n");\r
9794         PrintPosition(f, currentMove);\r
9795         fprintf(f, "--------------]\n");\r
9796     } else {\r
9797         fen = PositionToFEN(currentMove, 1);\r
9798         fprintf(f, "%s\n", fen);\r
9799         free(fen);\r
9800     }\r
9801     fclose(f);\r
9802     return TRUE;\r
9803 }\r
9804 \r
9805 void\r
9806 ReloadCmailMsgEvent(unregister)\r
9807      int unregister;\r
9808 {\r
9809 #if !WIN32\r
9810     static char *inFilename = NULL;\r
9811     static char *outFilename;\r
9812     int i;\r
9813     struct stat inbuf, outbuf;\r
9814     int status;\r
9815     \r
9816     /* Any registered moves are unregistered if unregister is set, */\r
9817     /* i.e. invoked by the signal handler */\r
9818     if (unregister) {\r
9819         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9820             cmailMoveRegistered[i] = FALSE;\r
9821             if (cmailCommentList[i] != NULL) {\r
9822                 free(cmailCommentList[i]);\r
9823                 cmailCommentList[i] = NULL;\r
9824             }\r
9825         }\r
9826         nCmailMovesRegistered = 0;\r
9827     }\r
9828 \r
9829     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9830         cmailResult[i] = CMAIL_NOT_RESULT;\r
9831     }\r
9832     nCmailResults = 0;\r
9833 \r
9834     if (inFilename == NULL) {\r
9835         /* Because the filenames are static they only get malloced once  */\r
9836         /* and they never get freed                                      */\r
9837         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
9838         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
9839 \r
9840         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
9841         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
9842     }\r
9843     \r
9844     status = stat(outFilename, &outbuf);\r
9845     if (status < 0) {\r
9846         cmailMailedMove = FALSE;\r
9847     } else {\r
9848         status = stat(inFilename, &inbuf);\r
9849         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
9850     }\r
9851     \r
9852     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
9853        counts the games, notes how each one terminated, etc.\r
9854        \r
9855        It would be nice to remove this kludge and instead gather all\r
9856        the information while building the game list.  (And to keep it\r
9857        in the game list nodes instead of having a bunch of fixed-size\r
9858        parallel arrays.)  Note this will require getting each game's\r
9859        termination from the PGN tags, as the game list builder does\r
9860        not process the game moves.  --mann\r
9861        */\r
9862     cmailMsgLoaded = TRUE;\r
9863     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
9864     \r
9865     /* Load first game in the file or popup game menu */\r
9866     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
9867 \r
9868 #endif /* !WIN32 */\r
9869     return;\r
9870 }\r
9871 \r
9872 int\r
9873 RegisterMove()\r
9874 {\r
9875     FILE *f;\r
9876     char string[MSG_SIZ];\r
9877 \r
9878     if (   cmailMailedMove\r
9879         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9880         return TRUE;            /* Allow free viewing  */\r
9881     }\r
9882 \r
9883     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9884     /* with the move registered when the conditions for registering no   */\r
9885     /* longer hold                                                       */\r
9886     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9887         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9888         nCmailMovesRegistered --;\r
9889 \r
9890         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9891           {\r
9892               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9893               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9894           }\r
9895     }\r
9896 \r
9897     if (cmailOldMove == -1) {\r
9898         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);\r
9899         return FALSE;\r
9900     }\r
9901 \r
9902     if (currentMove > cmailOldMove + 1) {\r
9903         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);\r
9904         return FALSE;\r
9905     }\r
9906 \r
9907     if (currentMove < cmailOldMove) {\r
9908         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);\r
9909         return FALSE;\r
9910     }\r
9911 \r
9912     if (forwardMostMove > currentMove) {\r
9913         /* Silently truncate extra moves */\r
9914         TruncateGame();\r
9915     }\r
9916 \r
9917     if (   (currentMove == cmailOldMove + 1)\r
9918         || (   (currentMove == cmailOldMove)\r
9919             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9920                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9921         if (gameInfo.result != GameUnfinished) {\r
9922             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9923         }\r
9924 \r
9925         if (commentList[currentMove] != NULL) {\r
9926             cmailCommentList[lastLoadGameNumber - 1]\r
9927               = StrSave(commentList[currentMove]);\r
9928         }\r
9929         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9930 \r
9931         if (appData.debugMode)\r
9932           fprintf(debugFP, "Saving %s for game %d\n",\r
9933                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9934 \r
9935         sprintf(string,\r
9936                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
9937         \r
9938         f = fopen(string, "w");\r
9939         if (appData.oldSaveStyle) {\r
9940             SaveGameOldStyle(f); /* also closes the file */\r
9941             \r
9942             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
9943             f = fopen(string, "w");\r
9944             SavePosition(f, 0, NULL); /* also closes the file */\r
9945         } else {\r
9946             fprintf(f, "{--------------\n");\r
9947             PrintPosition(f, currentMove);\r
9948             fprintf(f, "--------------}\n\n");\r
9949             \r
9950             SaveGame(f, 0, NULL); /* also closes the file*/\r
9951         }\r
9952         \r
9953         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
9954         nCmailMovesRegistered ++;\r
9955     } else if (nCmailGames == 1) {\r
9956         DisplayError(_("You have not made a move yet"), 0);\r
9957         return FALSE;\r
9958     }\r
9959 \r
9960     return TRUE;\r
9961 }\r
9962 \r
9963 void\r
9964 MailMoveEvent()\r
9965 {\r
9966 #if !WIN32\r
9967     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
9968     FILE *commandOutput;\r
9969     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
9970     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
9971     int nBuffers;\r
9972     int i;\r
9973     int archived;\r
9974     char *arcDir;\r
9975 \r
9976     if (! cmailMsgLoaded) {\r
9977         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);\r
9978         return;\r
9979     }\r
9980 \r
9981     if (nCmailGames == nCmailResults) {\r
9982         DisplayError(_("No unfinished games"), 0);\r
9983         return;\r
9984     }\r
9985 \r
9986 #if CMAIL_PROHIBIT_REMAIL\r
9987     if (cmailMailedMove) {\r
9988         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
9989         DisplayError(msg, 0);\r
9990         return;\r
9991     }\r
9992 #endif\r
9993 \r
9994     if (! (cmailMailedMove || RegisterMove())) return;\r
9995     \r
9996     if (   cmailMailedMove\r
9997         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
9998         sprintf(string, partCommandString,\r
9999                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
10000         commandOutput = popen(string, "r");\r
10001 \r
10002         if (commandOutput == NULL) {\r
10003             DisplayError(_("Failed to invoke cmail"), 0);\r
10004         } else {\r
10005             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
10006                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
10007             }\r
10008             if (nBuffers > 1) {\r
10009                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
10010                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
10011                 nBytes = MSG_SIZ - 1;\r
10012             } else {\r
10013                 (void) memcpy(msg, buffer, nBytes);\r
10014             }\r
10015             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
10016 \r
10017             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
10018                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
10019 \r
10020                 archived = TRUE;\r
10021                 for (i = 0; i < nCmailGames; i ++) {\r
10022                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
10023                         archived = FALSE;\r
10024                     }\r
10025                 }\r
10026                 if (   archived\r
10027                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
10028                         != NULL)) {\r
10029                     sprintf(buffer, "%s/%s.%s.archive",\r
10030                             arcDir,\r
10031                             appData.cmailGameName,\r
10032                             gameInfo.date);\r
10033                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
10034                     cmailMsgLoaded = FALSE;\r
10035                 }\r
10036             }\r
10037 \r
10038             DisplayInformation(msg);\r
10039             pclose(commandOutput);\r
10040         }\r
10041     } else {\r
10042         if ((*cmailMsg) != '\0') {\r
10043             DisplayInformation(cmailMsg);\r
10044         }\r
10045     }\r
10046 \r
10047     return;\r
10048 #endif /* !WIN32 */\r
10049 }\r
10050 \r
10051 char *\r
10052 CmailMsg()\r
10053 {\r
10054 #if WIN32\r
10055     return NULL;\r
10056 #else\r
10057     int  prependComma = 0;\r
10058     char number[5];\r
10059     char string[MSG_SIZ];       /* Space for game-list */\r
10060     int  i;\r
10061     \r
10062     if (!cmailMsgLoaded) return "";\r
10063 \r
10064     if (cmailMailedMove) {\r
10065         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));\r
10066     } else {\r
10067         /* Create a list of games left */\r
10068         sprintf(string, "[");\r
10069         for (i = 0; i < nCmailGames; i ++) {\r
10070             if (! (   cmailMoveRegistered[i]\r
10071                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
10072                 if (prependComma) {\r
10073                     sprintf(number, ",%d", i + 1);\r
10074                 } else {\r
10075                     sprintf(number, "%d", i + 1);\r
10076                     prependComma = 1;\r
10077                 }\r
10078                 \r
10079                 strcat(string, number);\r
10080             }\r
10081         }\r
10082         strcat(string, "]");\r
10083 \r
10084         if (nCmailMovesRegistered + nCmailResults == 0) {\r
10085             switch (nCmailGames) {\r
10086               case 1:\r
10087                 sprintf(cmailMsg,\r
10088                         _("Still need to make move for game\n"));\r
10089                 break;\r
10090                 \r
10091               case 2:\r
10092                 sprintf(cmailMsg,\r
10093                         _("Still need to make moves for both games\n"));\r
10094                 break;\r
10095                 \r
10096               default:\r
10097                 sprintf(cmailMsg,\r
10098                         _("Still need to make moves for all %d games\n"),\r
10099                         nCmailGames);\r
10100                 break;\r
10101             }\r
10102         } else {\r
10103             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
10104               case 1:\r
10105                 sprintf(cmailMsg,\r
10106                         _("Still need to make a move for game %s\n"),\r
10107                         string);\r
10108                 break;\r
10109                 \r
10110               case 0:\r
10111                 if (nCmailResults == nCmailGames) {\r
10112                     sprintf(cmailMsg, _("No unfinished games\n"));\r
10113                 } else {\r
10114                     sprintf(cmailMsg, _("Ready to send mail\n"));\r
10115                 }\r
10116                 break;\r
10117                 \r
10118               default:\r
10119                 sprintf(cmailMsg,\r
10120                         _("Still need to make moves for games %s\n"),\r
10121                         string);\r
10122             }\r
10123         }\r
10124     }\r
10125     return cmailMsg;\r
10126 #endif /* WIN32 */\r
10127 }\r
10128 \r
10129 void\r
10130 ResetGameEvent()\r
10131 {\r
10132     if (gameMode == Training)\r
10133       SetTrainingModeOff();\r
10134 \r
10135     Reset(TRUE, TRUE);\r
10136     cmailMsgLoaded = FALSE;\r
10137     if (appData.icsActive) {\r
10138       SendToICS(ics_prefix);\r
10139       SendToICS("refresh\n");\r
10140     }\r
10141 }\r
10142 \r
10143 void\r
10144 ExitEvent(status)\r
10145      int status;\r
10146 {\r
10147     exiting++;\r
10148     if (exiting > 2) {\r
10149       /* Give up on clean exit */\r
10150       exit(status);\r
10151     }\r
10152     if (exiting > 1) {\r
10153       /* Keep trying for clean exit */\r
10154       return;\r
10155     }\r
10156 \r
10157     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
10158 \r
10159     if (telnetISR != NULL) {\r
10160       RemoveInputSource(telnetISR);\r
10161     }\r
10162     if (icsPR != NoProc) {\r
10163       DestroyChildProcess(icsPR, TRUE);\r
10164     }\r
10165 #if 0\r
10166     /* Save game if resource set and not already saved by GameEnds() */\r
10167     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
10168                              && forwardMostMove > 0) {\r
10169       if (*appData.saveGameFile != NULLCHAR) {\r
10170         SaveGameToFile(appData.saveGameFile, TRUE);\r
10171       } else if (appData.autoSaveGames) {\r
10172         AutoSaveGame();\r
10173       }\r
10174       if (*appData.savePositionFile != NULLCHAR) {\r
10175         SavePositionToFile(appData.savePositionFile);\r
10176       }\r
10177     }\r
10178     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10179 #else\r
10180     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
10181     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);\r
10182 #endif\r
10183     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
10184     /* make sure this other one finishes before killing it!                  */\r
10185     if(endingGame) { int count = 0;\r
10186         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
10187         while(endingGame && count++ < 10) DoSleep(1);\r
10188         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
10189     }\r
10190 \r
10191     /* Kill off chess programs */\r
10192     if (first.pr != NoProc) {\r
10193         ExitAnalyzeMode();\r
10194         \r
10195         DoSleep( appData.delayBeforeQuit );\r
10196         SendToProgram("quit\n", &first);\r
10197         DoSleep( appData.delayAfterQuit );\r
10198         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
10199     }\r
10200     if (second.pr != NoProc) {\r
10201         DoSleep( appData.delayBeforeQuit );\r
10202         SendToProgram("quit\n", &second);\r
10203         DoSleep( appData.delayAfterQuit );\r
10204         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
10205     }\r
10206     if (first.isr != NULL) {\r
10207         RemoveInputSource(first.isr);\r
10208     }\r
10209     if (second.isr != NULL) {\r
10210         RemoveInputSource(second.isr);\r
10211     }\r
10212 \r
10213     ShutDownFrontEnd();\r
10214     exit(status);\r
10215 }\r
10216 \r
10217 void\r
10218 PauseEvent()\r
10219 {\r
10220     if (appData.debugMode)\r
10221         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
10222     if (pausing) {\r
10223         pausing = FALSE;\r
10224         ModeHighlight();\r
10225         if (gameMode == MachinePlaysWhite ||\r
10226             gameMode == MachinePlaysBlack) {\r
10227             StartClocks();\r
10228         } else {\r
10229             DisplayBothClocks();\r
10230         }\r
10231         if (gameMode == PlayFromGameFile) {\r
10232             if (appData.timeDelay >= 0) \r
10233                 AutoPlayGameLoop();\r
10234         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
10235             Reset(FALSE, TRUE);\r
10236             SendToICS(ics_prefix);\r
10237             SendToICS("refresh\n");\r
10238         } else if (currentMove < forwardMostMove) {\r
10239             ForwardInner(forwardMostMove);\r
10240         }\r
10241         pauseExamInvalid = FALSE;\r
10242     } else {\r
10243         switch (gameMode) {\r
10244           default:\r
10245             return;\r
10246           case IcsExamining:\r
10247             pauseExamForwardMostMove = forwardMostMove;\r
10248             pauseExamInvalid = FALSE;\r
10249             /* fall through */\r
10250           case IcsObserving:\r
10251           case IcsPlayingWhite:\r
10252           case IcsPlayingBlack:\r
10253             pausing = TRUE;\r
10254             ModeHighlight();\r
10255             return;\r
10256           case PlayFromGameFile:\r
10257             (void) StopLoadGameTimer();\r
10258             pausing = TRUE;\r
10259             ModeHighlight();\r
10260             break;\r
10261           case BeginningOfGame:\r
10262             if (appData.icsActive) return;\r
10263             /* else fall through */\r
10264           case MachinePlaysWhite:\r
10265           case MachinePlaysBlack:\r
10266           case TwoMachinesPlay:\r
10267             if (forwardMostMove == 0)\r
10268               return;           /* don't pause if no one has moved */\r
10269             if ((gameMode == MachinePlaysWhite &&\r
10270                  !WhiteOnMove(forwardMostMove)) ||\r
10271                 (gameMode == MachinePlaysBlack &&\r
10272                  WhiteOnMove(forwardMostMove))) {\r
10273                 StopClocks();\r
10274             }\r
10275             pausing = TRUE;\r
10276             ModeHighlight();\r
10277             break;\r
10278         }\r
10279     }\r
10280 }\r
10281 \r
10282 void\r
10283 EditCommentEvent()\r
10284 {\r
10285     char title[MSG_SIZ];\r
10286 \r
10287     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
10288         strcpy(title, _("Edit comment"));\r
10289     } else {\r
10290         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,\r
10291                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
10292                 parseList[currentMove - 1]);\r
10293     }\r
10294 \r
10295     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
10296 }\r
10297 \r
10298 \r
10299 void\r
10300 EditTagsEvent()\r
10301 {\r
10302     char *tags = PGNTags(&gameInfo);\r
10303     EditTagsPopUp(tags);\r
10304     free(tags);\r
10305 }\r
10306 \r
10307 void\r
10308 AnalyzeModeEvent()\r
10309 {\r
10310     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
10311       return;\r
10312 \r
10313     if (gameMode != AnalyzeFile) {\r
10314         if (!appData.icsEngineAnalyze) {\r
10315                EditGameEvent();\r
10316                if (gameMode != EditGame) return;\r
10317         }\r
10318         ResurrectChessProgram();\r
10319         SendToProgram("analyze\n", &first);\r
10320         first.analyzing = TRUE;\r
10321         /*first.maybeThinking = TRUE;*/\r
10322         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10323         AnalysisPopUp(_("Analysis"),\r
10324                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10325     }\r
10326     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;\r
10327     pausing = FALSE;\r
10328     ModeHighlight();\r
10329     SetGameInfo();\r
10330 \r
10331     StartAnalysisClock();\r
10332     GetTimeMark(&lastNodeCountTime);\r
10333     lastNodeCount = 0;\r
10334 }\r
10335 \r
10336 void\r
10337 AnalyzeFileEvent()\r
10338 {\r
10339     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
10340       return;\r
10341 \r
10342     if (gameMode != AnalyzeMode) {\r
10343         EditGameEvent();\r
10344         if (gameMode != EditGame) return;\r
10345         ResurrectChessProgram();\r
10346         SendToProgram("analyze\n", &first);\r
10347         first.analyzing = TRUE;\r
10348         /*first.maybeThinking = TRUE;*/\r
10349         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10350         AnalysisPopUp(_("Analysis"),\r
10351                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10352     }\r
10353     gameMode = AnalyzeFile;\r
10354     pausing = FALSE;\r
10355     ModeHighlight();\r
10356     SetGameInfo();\r
10357 \r
10358     StartAnalysisClock();\r
10359     GetTimeMark(&lastNodeCountTime);\r
10360     lastNodeCount = 0;\r
10361 }\r
10362 \r
10363 void\r
10364 MachineWhiteEvent()\r
10365 {\r
10366     char buf[MSG_SIZ];\r
10367     char *bookHit = NULL;\r
10368 \r
10369     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
10370       return;\r
10371 \r
10372 \r
10373     if (gameMode == PlayFromGameFile || \r
10374         gameMode == TwoMachinesPlay  || \r
10375         gameMode == Training         || \r
10376         gameMode == AnalyzeMode      || \r
10377         gameMode == EndOfGame)\r
10378         EditGameEvent();\r
10379 \r
10380     if (gameMode == EditPosition) \r
10381         EditPositionDone();\r
10382 \r
10383     if (!WhiteOnMove(currentMove)) {\r
10384         DisplayError(_("It is not White's turn"), 0);\r
10385         return;\r
10386     }\r
10387   \r
10388     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10389       ExitAnalyzeMode();\r
10390 \r
10391     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10392         gameMode == AnalyzeFile)\r
10393         TruncateGame();\r
10394 \r
10395     ResurrectChessProgram();    /* in case it isn't running */\r
10396     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */\r
10397         gameMode = MachinePlaysWhite;\r
10398         ResetClocks();\r
10399     } else\r
10400     gameMode = MachinePlaysWhite;\r
10401     pausing = FALSE;\r
10402     ModeHighlight();\r
10403     SetGameInfo();\r
10404     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10405     DisplayTitle(buf);\r
10406     if (first.sendName) {\r
10407       sprintf(buf, "name %s\n", gameInfo.black);\r
10408       SendToProgram(buf, &first);\r
10409     }\r
10410     if (first.sendTime) {\r
10411       if (first.useColors) {\r
10412         SendToProgram("black\n", &first); /*gnu kludge*/\r
10413       }\r
10414       SendTimeRemaining(&first, TRUE);\r
10415     }\r
10416     if (first.useColors) {\r
10417       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately\r
10418     }\r
10419     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10420     SetMachineThinkingEnables();\r
10421     first.maybeThinking = TRUE;\r
10422     StartClocks();\r
10423 \r
10424     if (appData.autoFlipView && !flipView) {\r
10425       flipView = !flipView;\r
10426       DrawPosition(FALSE, NULL);\r
10427       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10428     }\r
10429 \r
10430     if(bookHit) { // [HGM] book: simulate book reply\r
10431         static char bookMove[MSG_SIZ]; // a bit generous?\r
10432 \r
10433         programStats.depth = programStats.nodes = programStats.time = \r
10434         programStats.score = programStats.got_only_move = 0;\r
10435         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10436 \r
10437         strcpy(bookMove, "move ");\r
10438         strcat(bookMove, bookHit);\r
10439         HandleMachineMove(bookMove, &first);\r
10440     }\r
10441 }\r
10442 \r
10443 void\r
10444 MachineBlackEvent()\r
10445 {\r
10446     char buf[MSG_SIZ];\r
10447    char *bookHit = NULL;\r
10448 \r
10449     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
10450         return;\r
10451 \r
10452 \r
10453     if (gameMode == PlayFromGameFile || \r
10454         gameMode == TwoMachinesPlay  || \r
10455         gameMode == Training         || \r
10456         gameMode == AnalyzeMode      || \r
10457         gameMode == EndOfGame)\r
10458         EditGameEvent();\r
10459 \r
10460     if (gameMode == EditPosition) \r
10461         EditPositionDone();\r
10462 \r
10463     if (WhiteOnMove(currentMove)) {\r
10464         DisplayError(_("It is not Black's turn"), 0);\r
10465         return;\r
10466     }\r
10467     \r
10468     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10469       ExitAnalyzeMode();\r
10470 \r
10471     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10472         gameMode == AnalyzeFile)\r
10473         TruncateGame();\r
10474 \r
10475     ResurrectChessProgram();    /* in case it isn't running */\r
10476     gameMode = MachinePlaysBlack;\r
10477     pausing = FALSE;\r
10478     ModeHighlight();\r
10479     SetGameInfo();\r
10480     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10481     DisplayTitle(buf);\r
10482     if (first.sendName) {\r
10483       sprintf(buf, "name %s\n", gameInfo.white);\r
10484       SendToProgram(buf, &first);\r
10485     }\r
10486     if (first.sendTime) {\r
10487       if (first.useColors) {\r
10488         SendToProgram("white\n", &first); /*gnu kludge*/\r
10489       }\r
10490       SendTimeRemaining(&first, FALSE);\r
10491     }\r
10492     if (first.useColors) {\r
10493       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately\r
10494     }\r
10495     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10496     SetMachineThinkingEnables();\r
10497     first.maybeThinking = TRUE;\r
10498     StartClocks();\r
10499 \r
10500     if (appData.autoFlipView && flipView) {\r
10501       flipView = !flipView;\r
10502       DrawPosition(FALSE, NULL);\r
10503       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10504     }\r
10505     if(bookHit) { // [HGM] book: simulate book reply\r
10506         static char bookMove[MSG_SIZ]; // a bit generous?\r
10507 \r
10508         programStats.depth = programStats.nodes = programStats.time = \r
10509         programStats.score = programStats.got_only_move = 0;\r
10510         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10511 \r
10512         strcpy(bookMove, "move ");\r
10513         strcat(bookMove, bookHit);\r
10514         HandleMachineMove(bookMove, &first);\r
10515     }\r
10516 }\r
10517 \r
10518 \r
10519 void\r
10520 DisplayTwoMachinesTitle()\r
10521 {\r
10522     char buf[MSG_SIZ];\r
10523     if (appData.matchGames > 0) {\r
10524         if (first.twoMachinesColor[0] == 'w') {\r
10525             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10526                     gameInfo.white, gameInfo.black,\r
10527                     first.matchWins, second.matchWins,\r
10528                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10529         } else {\r
10530             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10531                     gameInfo.white, gameInfo.black,\r
10532                     second.matchWins, first.matchWins,\r
10533                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10534         }\r
10535     } else {\r
10536         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10537     }\r
10538     DisplayTitle(buf);\r
10539 }\r
10540 \r
10541 void\r
10542 TwoMachinesEvent P((void))\r
10543 {\r
10544     int i;\r
10545     char buf[MSG_SIZ];\r
10546     ChessProgramState *onmove;\r
10547     char *bookHit = NULL;\r
10548     \r
10549     if (appData.noChessProgram) return;\r
10550 \r
10551     switch (gameMode) {\r
10552       case TwoMachinesPlay:\r
10553         return;\r
10554       case MachinePlaysWhite:\r
10555       case MachinePlaysBlack:\r
10556         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10557             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
10558             return;\r
10559         }\r
10560         /* fall through */\r
10561       case BeginningOfGame:\r
10562       case PlayFromGameFile:\r
10563       case EndOfGame:\r
10564         EditGameEvent();\r
10565         if (gameMode != EditGame) return;\r
10566         break;\r
10567       case EditPosition:\r
10568         EditPositionDone();\r
10569         break;\r
10570       case AnalyzeMode:\r
10571       case AnalyzeFile:\r
10572         ExitAnalyzeMode();\r
10573         break;\r
10574       case EditGame:\r
10575       default:\r
10576         break;\r
10577     }\r
10578 \r
10579     forwardMostMove = currentMove;\r
10580     ResurrectChessProgram();    /* in case first program isn't running */\r
10581 \r
10582     if (second.pr == NULL) {\r
10583         StartChessProgram(&second);\r
10584         if (second.protocolVersion == 1) {\r
10585           TwoMachinesEventIfReady();\r
10586         } else {\r
10587           /* kludge: allow timeout for initial "feature" command */\r
10588           FreezeUI();\r
10589           DisplayMessage("", _("Starting second chess program"));\r
10590           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
10591         }\r
10592         return;\r
10593     }\r
10594     DisplayMessage("", "");\r
10595     InitChessProgram(&second, FALSE);\r
10596     SendToProgram("force\n", &second);\r
10597     if (startedFromSetupPosition) {\r
10598         SendBoard(&second, backwardMostMove);\r
10599     if (appData.debugMode) {\r
10600         fprintf(debugFP, "Two Machines\n");\r
10601     }\r
10602     }\r
10603     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
10604         SendMoveToProgram(i, &second);\r
10605     }\r
10606 \r
10607     gameMode = TwoMachinesPlay;\r
10608     pausing = FALSE;\r
10609     ModeHighlight();\r
10610     SetGameInfo();\r
10611     DisplayTwoMachinesTitle();\r
10612     firstMove = TRUE;\r
10613     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
10614         onmove = &first;\r
10615     } else {\r
10616         onmove = &second;\r
10617     }\r
10618 \r
10619     SendToProgram(first.computerString, &first);\r
10620     if (first.sendName) {\r
10621       sprintf(buf, "name %s\n", second.tidy);\r
10622       SendToProgram(buf, &first);\r
10623     }\r
10624     SendToProgram(second.computerString, &second);\r
10625     if (second.sendName) {\r
10626       sprintf(buf, "name %s\n", first.tidy);\r
10627       SendToProgram(buf, &second);\r
10628     }\r
10629 \r
10630     ResetClocks();\r
10631     if (!first.sendTime || !second.sendTime) {\r
10632         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10633         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10634     }\r
10635     if (onmove->sendTime) {\r
10636       if (onmove->useColors) {\r
10637         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
10638       }\r
10639       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
10640     }\r
10641     if (onmove->useColors) {\r
10642       SendToProgram(onmove->twoMachinesColor, onmove);\r
10643     }\r
10644     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move\r
10645 //    SendToProgram("go\n", onmove);\r
10646     onmove->maybeThinking = TRUE;\r
10647     SetMachineThinkingEnables();\r
10648 \r
10649     StartClocks();\r
10650 \r
10651     if(bookHit) { // [HGM] book: simulate book reply\r
10652         static char bookMove[MSG_SIZ]; // a bit generous?\r
10653 \r
10654         programStats.depth = programStats.nodes = programStats.time = \r
10655         programStats.score = programStats.got_only_move = 0;\r
10656         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10657 \r
10658         strcpy(bookMove, "move ");\r
10659         strcat(bookMove, bookHit);\r
10660         HandleMachineMove(bookMove, &first);\r
10661     }\r
10662 }\r
10663 \r
10664 void\r
10665 TrainingEvent()\r
10666 {\r
10667     if (gameMode == Training) {\r
10668       SetTrainingModeOff();\r
10669       gameMode = PlayFromGameFile;\r
10670       DisplayMessage("", _("Training mode off"));\r
10671     } else {\r
10672       gameMode = Training;\r
10673       animateTraining = appData.animate;\r
10674 \r
10675       /* make sure we are not already at the end of the game */\r
10676       if (currentMove < forwardMostMove) {\r
10677         SetTrainingModeOn();\r
10678         DisplayMessage("", _("Training mode on"));\r
10679       } else {\r
10680         gameMode = PlayFromGameFile;\r
10681         DisplayError(_("Already at end of game"), 0);\r
10682       }\r
10683     }\r
10684     ModeHighlight();\r
10685 }\r
10686 \r
10687 void\r
10688 IcsClientEvent()\r
10689 {\r
10690     if (!appData.icsActive) return;\r
10691     switch (gameMode) {\r
10692       case IcsPlayingWhite:\r
10693       case IcsPlayingBlack:\r
10694       case IcsObserving:\r
10695       case IcsIdle:\r
10696       case BeginningOfGame:\r
10697       case IcsExamining:\r
10698         return;\r
10699 \r
10700       case EditGame:\r
10701         break;\r
10702 \r
10703       case EditPosition:\r
10704         EditPositionDone();\r
10705         break;\r
10706 \r
10707       case AnalyzeMode:\r
10708       case AnalyzeFile:\r
10709         ExitAnalyzeMode();\r
10710         break;\r
10711         \r
10712       default:\r
10713         EditGameEvent();\r
10714         break;\r
10715     }\r
10716 \r
10717     gameMode = IcsIdle;\r
10718     ModeHighlight();\r
10719     return;\r
10720 }\r
10721 \r
10722 \r
10723 void\r
10724 EditGameEvent()\r
10725 {\r
10726     int i;\r
10727 \r
10728     switch (gameMode) {\r
10729       case Training:\r
10730         SetTrainingModeOff();\r
10731         break;\r
10732       case MachinePlaysWhite:\r
10733       case MachinePlaysBlack:\r
10734       case BeginningOfGame:\r
10735         SendToProgram("force\n", &first);\r
10736         SetUserThinkingEnables();\r
10737         break;\r
10738       case PlayFromGameFile:\r
10739         (void) StopLoadGameTimer();\r
10740         if (gameFileFP != NULL) {\r
10741             gameFileFP = NULL;\r
10742         }\r
10743         break;\r
10744       case EditPosition:\r
10745         EditPositionDone();\r
10746         break;\r
10747       case AnalyzeMode:\r
10748       case AnalyzeFile:\r
10749         ExitAnalyzeMode();\r
10750         SendToProgram("force\n", &first);\r
10751         break;\r
10752       case TwoMachinesPlay:\r
10753         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10754         ResurrectChessProgram();\r
10755         SetUserThinkingEnables();\r
10756         break;\r
10757       case EndOfGame:\r
10758         ResurrectChessProgram();\r
10759         break;\r
10760       case IcsPlayingBlack:\r
10761       case IcsPlayingWhite:\r
10762         DisplayError(_("Warning: You are still playing a game"), 0);\r
10763         break;\r
10764       case IcsObserving:\r
10765         DisplayError(_("Warning: You are still observing a game"), 0);\r
10766         break;\r
10767       case IcsExamining:\r
10768         DisplayError(_("Warning: You are still examining a game"), 0);\r
10769         break;\r
10770       case IcsIdle:\r
10771         break;\r
10772       case EditGame:\r
10773       default:\r
10774         return;\r
10775     }\r
10776     \r
10777     pausing = FALSE;\r
10778     StopClocks();\r
10779     first.offeredDraw = second.offeredDraw = 0;\r
10780 \r
10781     if (gameMode == PlayFromGameFile) {\r
10782         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10783         blackTimeRemaining = timeRemaining[1][currentMove];\r
10784         DisplayTitle("");\r
10785     }\r
10786 \r
10787     if (gameMode == MachinePlaysWhite ||\r
10788         gameMode == MachinePlaysBlack ||\r
10789         gameMode == TwoMachinesPlay ||\r
10790         gameMode == EndOfGame) {\r
10791         i = forwardMostMove;\r
10792         while (i > currentMove) {\r
10793             SendToProgram("undo\n", &first);\r
10794             i--;\r
10795         }\r
10796         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10797         blackTimeRemaining = timeRemaining[1][currentMove];\r
10798         DisplayBothClocks();\r
10799         if (whiteFlag || blackFlag) {\r
10800             whiteFlag = blackFlag = 0;\r
10801         }\r
10802         DisplayTitle("");\r
10803     }           \r
10804     \r
10805     gameMode = EditGame;\r
10806     ModeHighlight();\r
10807     SetGameInfo();\r
10808 }\r
10809 \r
10810 \r
10811 void\r
10812 EditPositionEvent()\r
10813 {\r
10814     if (gameMode == EditPosition) {\r
10815         EditGameEvent();\r
10816         return;\r
10817     }\r
10818     \r
10819     EditGameEvent();\r
10820     if (gameMode != EditGame) return;\r
10821     \r
10822     gameMode = EditPosition;\r
10823     ModeHighlight();\r
10824     SetGameInfo();\r
10825     if (currentMove > 0)\r
10826       CopyBoard(boards[0], boards[currentMove]);\r
10827     \r
10828     blackPlaysFirst = !WhiteOnMove(currentMove);\r
10829     ResetClocks();\r
10830     currentMove = forwardMostMove = backwardMostMove = 0;\r
10831     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10832     DisplayMove(-1);\r
10833 }\r
10834 \r
10835 void\r
10836 ExitAnalyzeMode()\r
10837 {\r
10838     /* [DM] icsEngineAnalyze - possible call from other functions */\r
10839     if (appData.icsEngineAnalyze) {\r
10840         appData.icsEngineAnalyze = FALSE;\r
10841 \r
10842         DisplayMessage("",_("Close ICS engine analyze..."));\r
10843     }\r
10844     if (first.analysisSupport && first.analyzing) {\r
10845       SendToProgram("exit\n", &first);\r
10846       first.analyzing = FALSE;\r
10847     }\r
10848     AnalysisPopDown();\r
10849     thinkOutput[0] = NULLCHAR;\r
10850 }\r
10851 \r
10852 void\r
10853 EditPositionDone()\r
10854 {\r
10855     startedFromSetupPosition = TRUE;\r
10856     InitChessProgram(&first, FALSE);\r
10857     SendToProgram("force\n", &first);\r
10858     if (blackPlaysFirst) {\r
10859         strcpy(moveList[0], "");\r
10860         strcpy(parseList[0], "");\r
10861         currentMove = forwardMostMove = backwardMostMove = 1;\r
10862         CopyBoard(boards[1], boards[0]);\r
10863         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */\r
10864         { int i;\r
10865           epStatus[1] = epStatus[0];\r
10866           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];\r
10867         }\r
10868     } else {\r
10869         currentMove = forwardMostMove = backwardMostMove = 0;\r
10870     }\r
10871     SendBoard(&first, forwardMostMove);\r
10872     if (appData.debugMode) {\r
10873         fprintf(debugFP, "EditPosDone\n");\r
10874     }\r
10875     DisplayTitle("");\r
10876     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10877     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10878     gameMode = EditGame;\r
10879     ModeHighlight();\r
10880     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10881     ClearHighlights(); /* [AS] */\r
10882 }\r
10883 \r
10884 /* Pause for `ms' milliseconds */\r
10885 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10886 void\r
10887 TimeDelay(ms)\r
10888      long ms;\r
10889 {\r
10890     TimeMark m1, m2;\r
10891 \r
10892     GetTimeMark(&m1);\r
10893     do {\r
10894         GetTimeMark(&m2);\r
10895     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
10896 }\r
10897 \r
10898 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10899 void\r
10900 SendMultiLineToICS(buf)\r
10901      char *buf;\r
10902 {\r
10903     char temp[MSG_SIZ+1], *p;\r
10904     int len;\r
10905 \r
10906     len = strlen(buf);\r
10907     if (len > MSG_SIZ)\r
10908       len = MSG_SIZ;\r
10909   \r
10910     strncpy(temp, buf, len);\r
10911     temp[len] = 0;\r
10912 \r
10913     p = temp;\r
10914     while (*p) {\r
10915         if (*p == '\n' || *p == '\r')\r
10916           *p = ' ';\r
10917         ++p;\r
10918     }\r
10919 \r
10920     strcat(temp, "\n");\r
10921     SendToICS(temp);\r
10922     SendToPlayer(temp, strlen(temp));\r
10923 }\r
10924 \r
10925 void\r
10926 SetWhiteToPlayEvent()\r
10927 {\r
10928     if (gameMode == EditPosition) {\r
10929         blackPlaysFirst = FALSE;\r
10930         DisplayBothClocks();    /* works because currentMove is 0 */\r
10931     } else if (gameMode == IcsExamining) {\r
10932         SendToICS(ics_prefix);\r
10933         SendToICS("tomove white\n");\r
10934     }\r
10935 }\r
10936 \r
10937 void\r
10938 SetBlackToPlayEvent()\r
10939 {\r
10940     if (gameMode == EditPosition) {\r
10941         blackPlaysFirst = TRUE;\r
10942         currentMove = 1;        /* kludge */\r
10943         DisplayBothClocks();\r
10944         currentMove = 0;\r
10945     } else if (gameMode == IcsExamining) {\r
10946         SendToICS(ics_prefix);\r
10947         SendToICS("tomove black\n");\r
10948     }\r
10949 }\r
10950 \r
10951 void\r
10952 EditPositionMenuEvent(selection, x, y)\r
10953      ChessSquare selection;\r
10954      int x, y;\r
10955 {\r
10956     char buf[MSG_SIZ];\r
10957     ChessSquare piece = boards[0][y][x];\r
10958 \r
10959     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
10960 \r
10961     switch (selection) {\r
10962       case ClearBoard:\r
10963         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
10964             SendToICS(ics_prefix);\r
10965             SendToICS("bsetup clear\n");\r
10966         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
10967             SendToICS(ics_prefix);\r
10968             SendToICS("clearboard\n");\r
10969         } else {\r
10970             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;\r
10971                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */\r
10972                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
10973                     if (gameMode == IcsExamining) {\r
10974                         if (boards[currentMove][y][x] != EmptySquare) {\r
10975                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
10976                                     AAA + x, ONE + y);\r
10977                             SendToICS(buf);\r
10978                         }\r
10979                     } else {\r
10980                         boards[0][y][x] = p;\r
10981                     }\r
10982                 }\r
10983             }\r
10984         }\r
10985         if (gameMode == EditPosition) {\r
10986             DrawPosition(FALSE, boards[0]);\r
10987         }\r
10988         break;\r
10989 \r
10990       case WhitePlay:\r
10991         SetWhiteToPlayEvent();\r
10992         break;\r
10993 \r
10994       case BlackPlay:\r
10995         SetBlackToPlayEvent();\r
10996         break;\r
10997 \r
10998       case EmptySquare:\r
10999         if (gameMode == IcsExamining) {\r
11000             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
11001             SendToICS(buf);\r
11002         } else {\r
11003             boards[0][y][x] = EmptySquare;\r
11004             DrawPosition(FALSE, boards[0]);\r
11005         }\r
11006         break;\r
11007 \r
11008       case PromotePiece:\r
11009         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
11010            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
11011             selection = (ChessSquare) (PROMOTED piece);\r
11012         } else if(piece == EmptySquare) selection = WhiteSilver;\r
11013         else selection = (ChessSquare)((int)piece - 1);\r
11014         goto defaultlabel;\r
11015 \r
11016       case DemotePiece:\r
11017         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
11018            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
11019             selection = (ChessSquare) (DEMOTED piece);\r
11020         } else if(piece == EmptySquare) selection = BlackSilver;\r
11021         else selection = (ChessSquare)((int)piece + 1);       \r
11022         goto defaultlabel;\r
11023 \r
11024       case WhiteQueen:\r
11025       case BlackQueen:\r
11026         if(gameInfo.variant == VariantShatranj ||\r
11027            gameInfo.variant == VariantXiangqi  ||\r
11028            gameInfo.variant == VariantCourier    )\r
11029             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
11030         goto defaultlabel;\r
11031 \r
11032       case WhiteKing:\r
11033       case BlackKing:\r
11034         if(gameInfo.variant == VariantXiangqi)\r
11035             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
11036         if(gameInfo.variant == VariantKnightmate)\r
11037             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
11038       default:\r
11039         defaultlabel:\r
11040         if (gameMode == IcsExamining) {\r
11041             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
11042                     PieceToChar(selection), AAA + x, ONE + y);\r
11043             SendToICS(buf);\r
11044         } else {\r
11045             boards[0][y][x] = selection;\r
11046             DrawPosition(FALSE, boards[0]);\r
11047         }\r
11048         break;\r
11049     }\r
11050 }\r
11051 \r
11052 \r
11053 void\r
11054 DropMenuEvent(selection, x, y)\r
11055      ChessSquare selection;\r
11056      int x, y;\r
11057 {\r
11058     ChessMove moveType;\r
11059 \r
11060     switch (gameMode) {\r
11061       case IcsPlayingWhite:\r
11062       case MachinePlaysBlack:\r
11063         if (!WhiteOnMove(currentMove)) {\r
11064             DisplayMoveError(_("It is Black's turn"));\r
11065             return;\r
11066         }\r
11067         moveType = WhiteDrop;\r
11068         break;\r
11069       case IcsPlayingBlack:\r
11070       case MachinePlaysWhite:\r
11071         if (WhiteOnMove(currentMove)) {\r
11072             DisplayMoveError(_("It is White's turn"));\r
11073             return;\r
11074         }\r
11075         moveType = BlackDrop;\r
11076         break;\r
11077       case EditGame:\r
11078         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
11079         break;\r
11080       default:\r
11081         return;\r
11082     }\r
11083 \r
11084     if (moveType == BlackDrop && selection < BlackPawn) {\r
11085       selection = (ChessSquare) ((int) selection\r
11086                                  + (int) BlackPawn - (int) WhitePawn);\r
11087     }\r
11088     if (boards[currentMove][y][x] != EmptySquare) {\r
11089         DisplayMoveError(_("That square is occupied"));\r
11090         return;\r
11091     }\r
11092 \r
11093     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
11094 }\r
11095 \r
11096 void\r
11097 AcceptEvent()\r
11098 {\r
11099     /* Accept a pending offer of any kind from opponent */\r
11100     \r
11101     if (appData.icsActive) {\r
11102         SendToICS(ics_prefix);\r
11103         SendToICS("accept\n");\r
11104     } else if (cmailMsgLoaded) {\r
11105         if (currentMove == cmailOldMove &&\r
11106             commentList[cmailOldMove] != NULL &&\r
11107             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11108                    "Black offers a draw" : "White offers a draw")) {\r
11109             TruncateGame();\r
11110             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11111             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11112         } else {\r
11113             DisplayError(_("There is no pending offer on this move"), 0);\r
11114             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11115         }\r
11116     } else {\r
11117         /* Not used for offers from chess program */\r
11118     }\r
11119 }\r
11120 \r
11121 void\r
11122 DeclineEvent()\r
11123 {\r
11124     /* Decline a pending offer of any kind from opponent */\r
11125     \r
11126     if (appData.icsActive) {\r
11127         SendToICS(ics_prefix);\r
11128         SendToICS("decline\n");\r
11129     } else if (cmailMsgLoaded) {\r
11130         if (currentMove == cmailOldMove &&\r
11131             commentList[cmailOldMove] != NULL &&\r
11132             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11133                    "Black offers a draw" : "White offers a draw")) {\r
11134 #ifdef NOTDEF\r
11135             AppendComment(cmailOldMove, "Draw declined");\r
11136             DisplayComment(cmailOldMove - 1, "Draw declined");\r
11137 #endif /*NOTDEF*/\r
11138         } else {\r
11139             DisplayError(_("There is no pending offer on this move"), 0);\r
11140         }\r
11141     } else {\r
11142         /* Not used for offers from chess program */\r
11143     }\r
11144 }\r
11145 \r
11146 void\r
11147 RematchEvent()\r
11148 {\r
11149     /* Issue ICS rematch command */\r
11150     if (appData.icsActive) {\r
11151         SendToICS(ics_prefix);\r
11152         SendToICS("rematch\n");\r
11153     }\r
11154 }\r
11155 \r
11156 void\r
11157 CallFlagEvent()\r
11158 {\r
11159     /* Call your opponent's flag (claim a win on time) */\r
11160     if (appData.icsActive) {\r
11161         SendToICS(ics_prefix);\r
11162         SendToICS("flag\n");\r
11163     } else {\r
11164         switch (gameMode) {\r
11165           default:\r
11166             return;\r
11167           case MachinePlaysWhite:\r
11168             if (whiteFlag) {\r
11169                 if (blackFlag)\r
11170                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11171                            GE_PLAYER);\r
11172                 else\r
11173                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
11174             } else {\r
11175                 DisplayError(_("Your opponent is not out of time"), 0);\r
11176             }\r
11177             break;\r
11178           case MachinePlaysBlack:\r
11179             if (blackFlag) {\r
11180                 if (whiteFlag)\r
11181                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11182                            GE_PLAYER);\r
11183                 else\r
11184                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
11185             } else {\r
11186                 DisplayError(_("Your opponent is not out of time"), 0);\r
11187             }\r
11188             break;\r
11189         }\r
11190     }\r
11191 }\r
11192 \r
11193 void\r
11194 DrawEvent()\r
11195 {\r
11196     /* Offer draw or accept pending draw offer from opponent */\r
11197     \r
11198     if (appData.icsActive) {\r
11199         /* Note: tournament rules require draw offers to be\r
11200            made after you make your move but before you punch\r
11201            your clock.  Currently ICS doesn't let you do that;\r
11202            instead, you immediately punch your clock after making\r
11203            a move, but you can offer a draw at any time. */\r
11204         \r
11205         SendToICS(ics_prefix);\r
11206         SendToICS("draw\n");\r
11207     } else if (cmailMsgLoaded) {\r
11208         if (currentMove == cmailOldMove &&\r
11209             commentList[cmailOldMove] != NULL &&\r
11210             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11211                    "Black offers a draw" : "White offers a draw")) {\r
11212             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11213             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11214         } else if (currentMove == cmailOldMove + 1) {\r
11215             char *offer = WhiteOnMove(cmailOldMove) ?\r
11216               "White offers a draw" : "Black offers a draw";\r
11217             AppendComment(currentMove, offer);\r
11218             DisplayComment(currentMove - 1, offer);\r
11219             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
11220         } else {\r
11221             DisplayError(_("You must make your move before offering a draw"), 0);\r
11222             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11223         }\r
11224     } else if (first.offeredDraw) {\r
11225         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
11226     } else {\r
11227         if (first.sendDrawOffers) {\r
11228             SendToProgram("draw\n", &first);\r
11229             userOfferedDraw = TRUE;\r
11230         }\r
11231     }\r
11232 }\r
11233 \r
11234 void\r
11235 AdjournEvent()\r
11236 {\r
11237     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
11238     \r
11239     if (appData.icsActive) {\r
11240         SendToICS(ics_prefix);\r
11241         SendToICS("adjourn\n");\r
11242     } else {\r
11243         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
11244     }\r
11245 }\r
11246 \r
11247 \r
11248 void\r
11249 AbortEvent()\r
11250 {\r
11251     /* Offer Abort or accept pending Abort offer from opponent */\r
11252     \r
11253     if (appData.icsActive) {\r
11254         SendToICS(ics_prefix);\r
11255         SendToICS("abort\n");\r
11256     } else {\r
11257         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
11258     }\r
11259 }\r
11260 \r
11261 void\r
11262 ResignEvent()\r
11263 {\r
11264     /* Resign.  You can do this even if it's not your turn. */\r
11265     \r
11266     if (appData.icsActive) {\r
11267         SendToICS(ics_prefix);\r
11268         SendToICS("resign\n");\r
11269     } else {\r
11270         switch (gameMode) {\r
11271           case MachinePlaysWhite:\r
11272             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11273             break;\r
11274           case MachinePlaysBlack:\r
11275             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11276             break;\r
11277           case EditGame:\r
11278             if (cmailMsgLoaded) {\r
11279                 TruncateGame();\r
11280                 if (WhiteOnMove(cmailOldMove)) {\r
11281                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11282                 } else {\r
11283                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11284                 }\r
11285                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
11286             }\r
11287             break;\r
11288           default:\r
11289             break;\r
11290         }\r
11291     }\r
11292 }\r
11293 \r
11294 \r
11295 void\r
11296 StopObservingEvent()\r
11297 {\r
11298     /* Stop observing current games */\r
11299     SendToICS(ics_prefix);\r
11300     SendToICS("unobserve\n");\r
11301 }\r
11302 \r
11303 void\r
11304 StopExaminingEvent()\r
11305 {\r
11306     /* Stop observing current game */\r
11307     SendToICS(ics_prefix);\r
11308     SendToICS("unexamine\n");\r
11309 }\r
11310 \r
11311 void\r
11312 ForwardInner(target)\r
11313      int target;\r
11314 {\r
11315     int limit;\r
11316 \r
11317     if (appData.debugMode)\r
11318         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
11319                 target, currentMove, forwardMostMove);\r
11320 \r
11321     if (gameMode == EditPosition)\r
11322       return;\r
11323 \r
11324     if (gameMode == PlayFromGameFile && !pausing)\r
11325       PauseEvent();\r
11326     \r
11327     if (gameMode == IcsExamining && pausing)\r
11328       limit = pauseExamForwardMostMove;\r
11329     else\r
11330       limit = forwardMostMove;\r
11331     \r
11332     if (target > limit) target = limit;\r
11333 \r
11334     if (target > 0 && moveList[target - 1][0]) {\r
11335         int fromX, fromY, toX, toY;\r
11336         toX = moveList[target - 1][2] - AAA;\r
11337         toY = moveList[target - 1][3] - ONE;\r
11338         if (moveList[target - 1][1] == '@') {\r
11339             if (appData.highlightLastMove) {\r
11340                 SetHighlights(-1, -1, toX, toY);\r
11341             }\r
11342         } else {\r
11343             fromX = moveList[target - 1][0] - AAA;\r
11344             fromY = moveList[target - 1][1] - ONE;\r
11345             if (target == currentMove + 1) {\r
11346                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
11347             }\r
11348             if (appData.highlightLastMove) {\r
11349                 SetHighlights(fromX, fromY, toX, toY);\r
11350             }\r
11351         }\r
11352     }\r
11353     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
11354         gameMode == Training || gameMode == PlayFromGameFile || \r
11355         gameMode == AnalyzeFile) {\r
11356         while (currentMove < target) {\r
11357             SendMoveToProgram(currentMove++, &first);\r
11358         }\r
11359     } else {\r
11360         currentMove = target;\r
11361     }\r
11362     \r
11363     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11364         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11365         blackTimeRemaining = timeRemaining[1][currentMove];\r
11366     }\r
11367     DisplayBothClocks();\r
11368     DisplayMove(currentMove - 1);\r
11369     DrawPosition(FALSE, boards[currentMove]);\r
11370     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11371     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
11372         DisplayComment(currentMove - 1, commentList[currentMove]);\r
11373     }\r
11374 }\r
11375 \r
11376 \r
11377 void\r
11378 ForwardEvent()\r
11379 {\r
11380     if (gameMode == IcsExamining && !pausing) {\r
11381         SendToICS(ics_prefix);\r
11382         SendToICS("forward\n");\r
11383     } else {\r
11384         ForwardInner(currentMove + 1);\r
11385     }\r
11386 }\r
11387 \r
11388 void\r
11389 ToEndEvent()\r
11390 {\r
11391     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11392         /* to optimze, we temporarily turn off analysis mode while we feed\r
11393          * the remaining moves to the engine. Otherwise we get analysis output\r
11394          * after each move.\r
11395          */ \r
11396         if (first.analysisSupport) {\r
11397           SendToProgram("exit\nforce\n", &first);\r
11398           first.analyzing = FALSE;\r
11399         }\r
11400     }\r
11401         \r
11402     if (gameMode == IcsExamining && !pausing) {\r
11403         SendToICS(ics_prefix);\r
11404         SendToICS("forward 999999\n");\r
11405     } else {\r
11406         ForwardInner(forwardMostMove);\r
11407     }\r
11408 \r
11409     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11410         /* we have fed all the moves, so reactivate analysis mode */\r
11411         SendToProgram("analyze\n", &first);\r
11412         first.analyzing = TRUE;\r
11413         /*first.maybeThinking = TRUE;*/\r
11414         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11415     }\r
11416 }\r
11417 \r
11418 void\r
11419 BackwardInner(target)\r
11420      int target;\r
11421 {\r
11422     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
11423 \r
11424     if (appData.debugMode)\r
11425         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
11426                 target, currentMove, forwardMostMove);\r
11427 \r
11428     if (gameMode == EditPosition) return;\r
11429     if (currentMove <= backwardMostMove) {\r
11430         ClearHighlights();\r
11431         DrawPosition(full_redraw, boards[currentMove]);\r
11432         return;\r
11433     }\r
11434     if (gameMode == PlayFromGameFile && !pausing)\r
11435       PauseEvent();\r
11436     \r
11437     if (moveList[target][0]) {\r
11438         int fromX, fromY, toX, toY;\r
11439         toX = moveList[target][2] - AAA;\r
11440         toY = moveList[target][3] - ONE;\r
11441         if (moveList[target][1] == '@') {\r
11442             if (appData.highlightLastMove) {\r
11443                 SetHighlights(-1, -1, toX, toY);\r
11444             }\r
11445         } else {\r
11446             fromX = moveList[target][0] - AAA;\r
11447             fromY = moveList[target][1] - ONE;\r
11448             if (target == currentMove - 1) {\r
11449                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
11450             }\r
11451             if (appData.highlightLastMove) {\r
11452                 SetHighlights(fromX, fromY, toX, toY);\r
11453             }\r
11454         }\r
11455     }\r
11456     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
11457         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
11458         while (currentMove > target) {\r
11459             SendToProgram("undo\n", &first);\r
11460             currentMove--;\r
11461         }\r
11462     } else {\r
11463         currentMove = target;\r
11464     }\r
11465     \r
11466     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11467         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11468         blackTimeRemaining = timeRemaining[1][currentMove];\r
11469     }\r
11470     DisplayBothClocks();\r
11471     DisplayMove(currentMove - 1);\r
11472     DrawPosition(full_redraw, boards[currentMove]);\r
11473     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11474     // [HGM] PV info: routine tests if comment empty\r
11475     DisplayComment(currentMove - 1, commentList[currentMove]);\r
11476 }\r
11477 \r
11478 void\r
11479 BackwardEvent()\r
11480 {\r
11481     if (gameMode == IcsExamining && !pausing) {\r
11482         SendToICS(ics_prefix);\r
11483         SendToICS("backward\n");\r
11484     } else {\r
11485         BackwardInner(currentMove - 1);\r
11486     }\r
11487 }\r
11488 \r
11489 void\r
11490 ToStartEvent()\r
11491 {\r
11492     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11493         /* to optimze, we temporarily turn off analysis mode while we undo\r
11494          * all the moves. Otherwise we get analysis output after each undo.\r
11495          */ \r
11496         if (first.analysisSupport) {\r
11497           SendToProgram("exit\nforce\n", &first);\r
11498           first.analyzing = FALSE;\r
11499         }\r
11500     }\r
11501 \r
11502     if (gameMode == IcsExamining && !pausing) {\r
11503         SendToICS(ics_prefix);\r
11504         SendToICS("backward 999999\n");\r
11505     } else {\r
11506         BackwardInner(backwardMostMove);\r
11507     }\r
11508 \r
11509     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11510         /* we have fed all the moves, so reactivate analysis mode */\r
11511         SendToProgram("analyze\n", &first);\r
11512         first.analyzing = TRUE;\r
11513         /*first.maybeThinking = TRUE;*/\r
11514         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11515     }\r
11516 }\r
11517 \r
11518 void\r
11519 ToNrEvent(int to)\r
11520 {\r
11521   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
11522   if (to >= forwardMostMove) to = forwardMostMove;\r
11523   if (to <= backwardMostMove) to = backwardMostMove;\r
11524   if (to < currentMove) {\r
11525     BackwardInner(to);\r
11526   } else {\r
11527     ForwardInner(to);\r
11528   }\r
11529 }\r
11530 \r
11531 void\r
11532 RevertEvent()\r
11533 {\r
11534     if (gameMode != IcsExamining) {\r
11535         DisplayError(_("You are not examining a game"), 0);\r
11536         return;\r
11537     }\r
11538     if (pausing) {\r
11539         DisplayError(_("You can't revert while pausing"), 0);\r
11540         return;\r
11541     }\r
11542     SendToICS(ics_prefix);\r
11543     SendToICS("revert\n");\r
11544 }\r
11545 \r
11546 void\r
11547 RetractMoveEvent()\r
11548 {\r
11549     switch (gameMode) {\r
11550       case MachinePlaysWhite:\r
11551       case MachinePlaysBlack:\r
11552         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
11553             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
11554             return;\r
11555         }\r
11556         if (forwardMostMove < 2) return;\r
11557         currentMove = forwardMostMove = forwardMostMove - 2;\r
11558         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11559         blackTimeRemaining = timeRemaining[1][currentMove];\r
11560         DisplayBothClocks();\r
11561         DisplayMove(currentMove - 1);\r
11562         ClearHighlights();/*!! could figure this out*/\r
11563         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
11564         SendToProgram("remove\n", &first);\r
11565         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
11566         break;\r
11567 \r
11568       case BeginningOfGame:\r
11569       default:\r
11570         break;\r
11571 \r
11572       case IcsPlayingWhite:\r
11573       case IcsPlayingBlack:\r
11574         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
11575             SendToICS(ics_prefix);\r
11576             SendToICS("takeback 2\n");\r
11577         } else {\r
11578             SendToICS(ics_prefix);\r
11579             SendToICS("takeback 1\n");\r
11580         }\r
11581         break;\r
11582     }\r
11583 }\r
11584 \r
11585 void\r
11586 MoveNowEvent()\r
11587 {\r
11588     ChessProgramState *cps;\r
11589 \r
11590     switch (gameMode) {\r
11591       case MachinePlaysWhite:\r
11592         if (!WhiteOnMove(forwardMostMove)) {\r
11593             DisplayError(_("It is your turn"), 0);\r
11594             return;\r
11595         }\r
11596         cps = &first;\r
11597         break;\r
11598       case MachinePlaysBlack:\r
11599         if (WhiteOnMove(forwardMostMove)) {\r
11600             DisplayError(_("It is your turn"), 0);\r
11601             return;\r
11602         }\r
11603         cps = &first;\r
11604         break;\r
11605       case TwoMachinesPlay:\r
11606         if (WhiteOnMove(forwardMostMove) ==\r
11607             (first.twoMachinesColor[0] == 'w')) {\r
11608             cps = &first;\r
11609         } else {\r
11610             cps = &second;\r
11611         }\r
11612         break;\r
11613       case BeginningOfGame:\r
11614       default:\r
11615         return;\r
11616     }\r
11617     SendToProgram("?\n", cps);\r
11618 }\r
11619 \r
11620 void\r
11621 TruncateGameEvent()\r
11622 {\r
11623     EditGameEvent();\r
11624     if (gameMode != EditGame) return;\r
11625     TruncateGame();\r
11626 }\r
11627 \r
11628 void\r
11629 TruncateGame()\r
11630 {\r
11631     if (forwardMostMove > currentMove) {\r
11632         if (gameInfo.resultDetails != NULL) {\r
11633             free(gameInfo.resultDetails);\r
11634             gameInfo.resultDetails = NULL;\r
11635             gameInfo.result = GameUnfinished;\r
11636         }\r
11637         forwardMostMove = currentMove;\r
11638         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
11639                    currentMove-1);\r
11640     }\r
11641 }\r
11642 \r
11643 void\r
11644 HintEvent()\r
11645 {\r
11646     if (appData.noChessProgram) return;\r
11647     switch (gameMode) {\r
11648       case MachinePlaysWhite:\r
11649         if (WhiteOnMove(forwardMostMove)) {\r
11650             DisplayError(_("Wait until your turn"), 0);\r
11651             return;\r
11652         }\r
11653         break;\r
11654       case BeginningOfGame:\r
11655       case MachinePlaysBlack:\r
11656         if (!WhiteOnMove(forwardMostMove)) {\r
11657             DisplayError(_("Wait until your turn"), 0);\r
11658             return;\r
11659         }\r
11660         break;\r
11661       default:\r
11662         DisplayError(_("No hint available"), 0);\r
11663         return;\r
11664     }\r
11665     SendToProgram("hint\n", &first);\r
11666     hintRequested = TRUE;\r
11667 }\r
11668 \r
11669 void\r
11670 BookEvent()\r
11671 {\r
11672     if (appData.noChessProgram) return;\r
11673     switch (gameMode) {\r
11674       case MachinePlaysWhite:\r
11675         if (WhiteOnMove(forwardMostMove)) {\r
11676             DisplayError(_("Wait until your turn"), 0);\r
11677             return;\r
11678         }\r
11679         break;\r
11680       case BeginningOfGame:\r
11681       case MachinePlaysBlack:\r
11682         if (!WhiteOnMove(forwardMostMove)) {\r
11683             DisplayError(_("Wait until your turn"), 0);\r
11684             return;\r
11685         }\r
11686         break;\r
11687       case EditPosition:\r
11688         EditPositionDone();\r
11689         break;\r
11690       case TwoMachinesPlay:\r
11691         return;\r
11692       default:\r
11693         break;\r
11694     }\r
11695     SendToProgram("bk\n", &first);\r
11696     bookOutput[0] = NULLCHAR;\r
11697     bookRequested = TRUE;\r
11698 }\r
11699 \r
11700 void\r
11701 AboutGameEvent()\r
11702 {\r
11703     char *tags = PGNTags(&gameInfo);\r
11704     TagsPopUp(tags, CmailMsg());\r
11705     free(tags);\r
11706 }\r
11707 \r
11708 /* end button procedures */\r
11709 \r
11710 void\r
11711 PrintPosition(fp, move)\r
11712      FILE *fp;\r
11713      int move;\r
11714 {\r
11715     int i, j;\r
11716     \r
11717     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11718         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11719             char c = PieceToChar(boards[move][i][j]);\r
11720             fputc(c == 'x' ? '.' : c, fp);\r
11721             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
11722         }\r
11723     }\r
11724     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
11725       fprintf(fp, "white to play\n");\r
11726     else\r
11727       fprintf(fp, "black to play\n");\r
11728 }\r
11729 \r
11730 void\r
11731 PrintOpponents(fp)\r
11732      FILE *fp;\r
11733 {\r
11734     if (gameInfo.white != NULL) {\r
11735         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
11736     } else {\r
11737         fprintf(fp, "\n");\r
11738     }\r
11739 }\r
11740 \r
11741 /* Find last component of program's own name, using some heuristics */\r
11742 void\r
11743 TidyProgramName(prog, host, buf)\r
11744      char *prog, *host, buf[MSG_SIZ];\r
11745 {\r
11746     char *p, *q;\r
11747     int local = (strcmp(host, "localhost") == 0);\r
11748     while (!local && (p = strchr(prog, ';')) != NULL) {\r
11749         p++;\r
11750         while (*p == ' ') p++;\r
11751         prog = p;\r
11752     }\r
11753     if (*prog == '"' || *prog == '\'') {\r
11754         q = strchr(prog + 1, *prog);\r
11755     } else {\r
11756         q = strchr(prog, ' ');\r
11757     }\r
11758     if (q == NULL) q = prog + strlen(prog);\r
11759     p = q;\r
11760     while (p >= prog && *p != '/' && *p != '\\') p--;\r
11761     p++;\r
11762     if(p == prog && *p == '"') p++;\r
11763     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
11764     memcpy(buf, p, q - p);\r
11765     buf[q - p] = NULLCHAR;\r
11766     if (!local) {\r
11767         strcat(buf, "@");\r
11768         strcat(buf, host);\r
11769     }\r
11770 }\r
11771 \r
11772 char *\r
11773 TimeControlTagValue()\r
11774 {\r
11775     char buf[MSG_SIZ];\r
11776     if (!appData.clockMode) {\r
11777         strcpy(buf, "-");\r
11778     } else if (movesPerSession > 0) {\r
11779         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
11780     } else if (timeIncrement == 0) {\r
11781         sprintf(buf, "%ld", timeControl/1000);\r
11782     } else {\r
11783         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
11784     }\r
11785     return StrSave(buf);\r
11786 }\r
11787 \r
11788 void\r
11789 SetGameInfo()\r
11790 {\r
11791     /* This routine is used only for certain modes */\r
11792     VariantClass v = gameInfo.variant;\r
11793     ClearGameInfo(&gameInfo);\r
11794     gameInfo.variant = v;\r
11795 \r
11796     switch (gameMode) {\r
11797       case MachinePlaysWhite:\r
11798         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11799         gameInfo.site = StrSave(HostName());\r
11800         gameInfo.date = PGNDate();\r
11801         gameInfo.round = StrSave("-");\r
11802         gameInfo.white = StrSave(first.tidy);\r
11803         gameInfo.black = StrSave(UserName());\r
11804         gameInfo.timeControl = TimeControlTagValue();\r
11805         break;\r
11806 \r
11807       case MachinePlaysBlack:\r
11808         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11809         gameInfo.site = StrSave(HostName());\r
11810         gameInfo.date = PGNDate();\r
11811         gameInfo.round = StrSave("-");\r
11812         gameInfo.white = StrSave(UserName());\r
11813         gameInfo.black = StrSave(first.tidy);\r
11814         gameInfo.timeControl = TimeControlTagValue();\r
11815         break;\r
11816 \r
11817       case TwoMachinesPlay:\r
11818         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11819         gameInfo.site = StrSave(HostName());\r
11820         gameInfo.date = PGNDate();\r
11821         if (matchGame > 0) {\r
11822             char buf[MSG_SIZ];\r
11823             sprintf(buf, "%d", matchGame);\r
11824             gameInfo.round = StrSave(buf);\r
11825         } else {\r
11826             gameInfo.round = StrSave("-");\r
11827         }\r
11828         if (first.twoMachinesColor[0] == 'w') {\r
11829             gameInfo.white = StrSave(first.tidy);\r
11830             gameInfo.black = StrSave(second.tidy);\r
11831         } else {\r
11832             gameInfo.white = StrSave(second.tidy);\r
11833             gameInfo.black = StrSave(first.tidy);\r
11834         }\r
11835         gameInfo.timeControl = TimeControlTagValue();\r
11836         break;\r
11837 \r
11838       case EditGame:\r
11839         gameInfo.event = StrSave("Edited game");\r
11840         gameInfo.site = StrSave(HostName());\r
11841         gameInfo.date = PGNDate();\r
11842         gameInfo.round = StrSave("-");\r
11843         gameInfo.white = StrSave("-");\r
11844         gameInfo.black = StrSave("-");\r
11845         break;\r
11846 \r
11847       case EditPosition:\r
11848         gameInfo.event = StrSave("Edited position");\r
11849         gameInfo.site = StrSave(HostName());\r
11850         gameInfo.date = PGNDate();\r
11851         gameInfo.round = StrSave("-");\r
11852         gameInfo.white = StrSave("-");\r
11853         gameInfo.black = StrSave("-");\r
11854         break;\r
11855 \r
11856       case IcsPlayingWhite:\r
11857       case IcsPlayingBlack:\r
11858       case IcsObserving:\r
11859       case IcsExamining:\r
11860         break;\r
11861 \r
11862       case PlayFromGameFile:\r
11863         gameInfo.event = StrSave("Game from non-PGN file");\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       default:\r
11872         break;\r
11873     }\r
11874 }\r
11875 \r
11876 void\r
11877 ReplaceComment(index, text)\r
11878      int index;\r
11879      char *text;\r
11880 {\r
11881     int len;\r
11882 \r
11883     while (*text == '\n') text++;\r
11884     len = strlen(text);\r
11885     while (len > 0 && text[len - 1] == '\n') len--;\r
11886 \r
11887     if (commentList[index] != NULL)\r
11888       free(commentList[index]);\r
11889 \r
11890     if (len == 0) {\r
11891         commentList[index] = NULL;\r
11892         return;\r
11893     }\r
11894     commentList[index] = (char *) malloc(len + 2);\r
11895     strncpy(commentList[index], text, len);\r
11896     commentList[index][len] = '\n';\r
11897     commentList[index][len + 1] = NULLCHAR;\r
11898 }\r
11899 \r
11900 void\r
11901 CrushCRs(text)\r
11902      char *text;\r
11903 {\r
11904   char *p = text;\r
11905   char *q = text;\r
11906   char ch;\r
11907 \r
11908   do {\r
11909     ch = *p++;\r
11910     if (ch == '\r') continue;\r
11911     *q++ = ch;\r
11912   } while (ch != '\0');\r
11913 }\r
11914 \r
11915 void\r
11916 AppendComment(index, text)\r
11917      int index;\r
11918      char *text;\r
11919 {\r
11920     int oldlen, len;\r
11921     char *old;\r
11922 \r
11923     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11924 \r
11925     CrushCRs(text);\r
11926     while (*text == '\n') text++;\r
11927     len = strlen(text);\r
11928     while (len > 0 && text[len - 1] == '\n') len--;\r
11929 \r
11930     if (len == 0) return;\r
11931 \r
11932     if (commentList[index] != NULL) {\r
11933         old = commentList[index];\r
11934         oldlen = strlen(old);\r
11935         commentList[index] = (char *) malloc(oldlen + len + 2);\r
11936         strcpy(commentList[index], old);\r
11937         free(old);\r
11938         strncpy(&commentList[index][oldlen], text, len);\r
11939         commentList[index][oldlen + len] = '\n';\r
11940         commentList[index][oldlen + len + 1] = NULLCHAR;\r
11941     } else {\r
11942         commentList[index] = (char *) malloc(len + 2);\r
11943         strncpy(commentList[index], text, len);\r
11944         commentList[index][len] = '\n';\r
11945         commentList[index][len + 1] = NULLCHAR;\r
11946     }\r
11947 }\r
11948 \r
11949 static char * FindStr( char * text, char * sub_text )\r
11950 {\r
11951     char * result = strstr( text, sub_text );\r
11952 \r
11953     if( result != NULL ) {\r
11954         result += strlen( sub_text );\r
11955     }\r
11956 \r
11957     return result;\r
11958 }\r
11959 \r
11960 /* [AS] Try to extract PV info from PGN comment */\r
11961 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
11962 char *GetInfoFromComment( int index, char * text )\r
11963 {\r
11964     char * sep = text;\r
11965 \r
11966     if( text != NULL && index > 0 ) {\r
11967         int score = 0;\r
11968         int depth = 0;\r
11969         int time = -1, sec = 0, deci;\r
11970         char * s_eval = FindStr( text, "[%eval " );\r
11971         char * s_emt = FindStr( text, "[%emt " );\r
11972 \r
11973         if( s_eval != NULL || s_emt != NULL ) {\r
11974             /* New style */\r
11975             char delim;\r
11976 \r
11977             if( s_eval != NULL ) {\r
11978                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
11979                     return text;\r
11980                 }\r
11981 \r
11982                 if( delim != ']' ) {\r
11983                     return text;\r
11984                 }\r
11985             }\r
11986 \r
11987             if( s_emt != NULL ) {\r
11988             }\r
11989         }\r
11990         else {\r
11991             /* We expect something like: [+|-]nnn.nn/dd */\r
11992             int score_lo = 0;\r
11993 \r
11994             sep = strchr( text, '/' );\r
11995             if( sep == NULL || sep < (text+4) ) {\r
11996                 return text;\r
11997             }\r
11998 \r
11999             time = -1; sec = -1; deci = -1;\r
12000             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
12001                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&\r
12002                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
12003                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
12004                 return text;\r
12005             }\r
12006 \r
12007             if( score_lo < 0 || score_lo >= 100 ) {\r
12008                 return text;\r
12009             }\r
12010 \r
12011             if(sec >= 0) time = 600*time + 10*sec; else\r
12012             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec\r
12013 \r
12014             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
12015 \r
12016             /* [HGM] PV time: now locate end of PV info */\r
12017             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
12018             if(time >= 0)\r
12019             while( *++sep >= '0' && *sep <= '9'); // strip time\r
12020             if(sec >= 0)\r
12021             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
12022             if(deci >= 0)\r
12023             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds\r
12024             while(*sep == ' ') sep++;\r
12025         }\r
12026 \r
12027         if( depth <= 0 ) {\r
12028             return text;\r
12029         }\r
12030 \r
12031         if( time < 0 ) {\r
12032             time = -1;\r
12033         }\r
12034 \r
12035         pvInfoList[index-1].depth = depth;\r
12036         pvInfoList[index-1].score = score;\r
12037         pvInfoList[index-1].time  = 10*time; // centi-sec\r
12038     }\r
12039     return sep;\r
12040 }\r
12041 \r
12042 void\r
12043 SendToProgram(message, cps)\r
12044      char *message;\r
12045      ChessProgramState *cps;\r
12046 {\r
12047     int count, outCount, error;\r
12048     char buf[MSG_SIZ];\r
12049 \r
12050     if (cps->pr == NULL) return;\r
12051     Attention(cps);\r
12052     \r
12053     if (appData.debugMode) {\r
12054         TimeMark now;\r
12055         GetTimeMark(&now);\r
12056         fprintf(debugFP, "%ld >%-6s: %s", \r
12057                 SubtractTimeMarks(&now, &programStartTime),\r
12058                 cps->which, message);\r
12059     }\r
12060     \r
12061     count = strlen(message);\r
12062     outCount = OutputToProcess(cps->pr, message, count, &error);\r
12063     if (outCount < count && !exiting \r
12064                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
12065         sprintf(buf, _("Error writing to %s chess program"), cps->which);\r
12066         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12067             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12068                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12069                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
12070             } else {\r
12071                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12072             }\r
12073             gameInfo.resultDetails = buf;\r
12074         }\r
12075         DisplayFatalError(buf, error, 1);\r
12076     }\r
12077 }\r
12078 \r
12079 void\r
12080 ReceiveFromProgram(isr, closure, message, count, error)\r
12081      InputSourceRef isr;\r
12082      VOIDSTAR closure;\r
12083      char *message;\r
12084      int count;\r
12085      int error;\r
12086 {\r
12087     char *end_str;\r
12088     char buf[MSG_SIZ];\r
12089     ChessProgramState *cps = (ChessProgramState *)closure;\r
12090 \r
12091     if (isr != cps->isr) return; /* Killed intentionally */\r
12092     if (count <= 0) {\r
12093         if (count == 0) {\r
12094             sprintf(buf,\r
12095                     _("Error: %s chess program (%s) exited unexpectedly"),\r
12096                     cps->which, cps->program);\r
12097         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12098                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12099                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12100                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);\r
12101                 } else {\r
12102                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12103                 }\r
12104                 gameInfo.resultDetails = buf;\r
12105             }\r
12106             RemoveInputSource(cps->isr);\r
12107             DisplayFatalError(buf, 0, 1);\r
12108         } else {\r
12109             sprintf(buf,\r
12110                     _("Error reading from %s chess program (%s)"),\r
12111                     cps->which, cps->program);\r
12112             RemoveInputSource(cps->isr);\r
12113 \r
12114             /* [AS] Program is misbehaving badly... kill it */\r
12115             if( count == -2 ) {\r
12116                 DestroyChildProcess( cps->pr, 9 );\r
12117                 cps->pr = NoProc;\r
12118             }\r
12119 \r
12120             DisplayFatalError(buf, error, 1);\r
12121         }\r
12122         return;\r
12123     }\r
12124     \r
12125     if ((end_str = strchr(message, '\r')) != NULL)\r
12126       *end_str = NULLCHAR;\r
12127     if ((end_str = strchr(message, '\n')) != NULL)\r
12128       *end_str = NULLCHAR;\r
12129     \r
12130     if (appData.debugMode) {\r
12131         TimeMark now; int print = 1;\r
12132         char *quote = ""; char c; int i;\r
12133 \r
12134         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
12135                 char start = message[0];\r
12136                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
12137                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
12138                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&\r
12139                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
12140                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
12141                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&\r
12142                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
12143                         { quote = "# "; print = (appData.engineComments == 2); }\r
12144                 message[0] = start; // restore original message\r
12145         }\r
12146         if(print) {\r
12147                 GetTimeMark(&now);\r
12148                 fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
12149                         SubtractTimeMarks(&now, &programStartTime), cps->which, \r
12150                         quote,\r
12151                         message);\r
12152         }\r
12153     }\r
12154 \r
12155     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */\r
12156     if (appData.icsEngineAnalyze) {\r
12157         if (strstr(message, "whisper") != NULL ||\r
12158              strstr(message, "kibitz") != NULL || \r
12159             strstr(message, "tellics") != NULL) return;\r
12160     }\r
12161 \r
12162     HandleMachineMove(message, cps);\r
12163 }\r
12164 \r
12165 \r
12166 void\r
12167 SendTimeControl(cps, mps, tc, inc, sd, st)\r
12168      ChessProgramState *cps;\r
12169      int mps, inc, sd, st;\r
12170      long tc;\r
12171 {\r
12172     char buf[MSG_SIZ];\r
12173     int seconds;\r
12174 \r
12175     if( timeControl_2 > 0 ) {\r
12176         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
12177             tc = timeControl_2;\r
12178         }\r
12179     }\r
12180     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
12181     inc /= cps->timeOdds;\r
12182     st  /= cps->timeOdds;\r
12183 \r
12184     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
12185 \r
12186     if (st > 0) {\r
12187       /* Set exact time per move, normally using st command */\r
12188       if (cps->stKludge) {\r
12189         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
12190         seconds = st % 60;\r
12191         if (seconds == 0) {\r
12192           sprintf(buf, "level 1 %d\n", st/60);\r
12193         } else {\r
12194           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
12195         }\r
12196       } else {\r
12197         sprintf(buf, "st %d\n", st);\r
12198       }\r
12199     } else {\r
12200       /* Set conventional or incremental time control, using level command */\r
12201       if (seconds == 0) {\r
12202         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
12203            Fixed in later versions, but still avoid :seconds\r
12204            when seconds is 0. */\r
12205         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
12206       } else {\r
12207         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
12208                 seconds, inc/1000);\r
12209       }\r
12210     }\r
12211     SendToProgram(buf, cps);\r
12212 \r
12213     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
12214     /* Orthogonally, limit search to given depth */\r
12215     if (sd > 0) {\r
12216       if (cps->sdKludge) {\r
12217         sprintf(buf, "depth\n%d\n", sd);\r
12218       } else {\r
12219         sprintf(buf, "sd %d\n", sd);\r
12220       }\r
12221       SendToProgram(buf, cps);\r
12222     }\r
12223 \r
12224     if(cps->nps > 0) { /* [HGM] nps */\r
12225         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
12226         else {\r
12227                 sprintf(buf, "nps %d\n", cps->nps);\r
12228               SendToProgram(buf, cps);\r
12229         }\r
12230     }\r
12231 }\r
12232 \r
12233 ChessProgramState *WhitePlayer()\r
12234 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
12235 {\r
12236     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || \r
12237        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)\r
12238         return &second;\r
12239     return &first;\r
12240 }\r
12241 \r
12242 void\r
12243 SendTimeRemaining(cps, machineWhite)\r
12244      ChessProgramState *cps;\r
12245      int /*boolean*/ machineWhite;\r
12246 {\r
12247     char message[MSG_SIZ];\r
12248     long time, otime;\r
12249 \r
12250     /* Note: this routine must be called when the clocks are stopped\r
12251        or when they have *just* been set or switched; otherwise\r
12252        it will be off by the time since the current tick started.\r
12253     */\r
12254     if (machineWhite) {\r
12255         time = whiteTimeRemaining / 10;\r
12256         otime = blackTimeRemaining / 10;\r
12257     } else {\r
12258         time = blackTimeRemaining / 10;\r
12259         otime = whiteTimeRemaining / 10;\r
12260     }\r
12261     /* [HGM] translate opponent's time by time-odds factor */\r
12262     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
12263     if (appData.debugMode) {\r
12264         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
12265     }\r
12266 \r
12267     if (time <= 0) time = 1;\r
12268     if (otime <= 0) otime = 1;\r
12269     \r
12270     sprintf(message, "time %ld\n", time);\r
12271     SendToProgram(message, cps);\r
12272 \r
12273     sprintf(message, "otim %ld\n", otime);\r
12274     SendToProgram(message, cps);\r
12275 }\r
12276 \r
12277 int\r
12278 BoolFeature(p, name, loc, cps)\r
12279      char **p;\r
12280      char *name;\r
12281      int *loc;\r
12282      ChessProgramState *cps;\r
12283 {\r
12284   char buf[MSG_SIZ];\r
12285   int len = strlen(name);\r
12286   int val;\r
12287   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12288     (*p) += len + 1;\r
12289     sscanf(*p, "%d", &val);\r
12290     *loc = (val != 0);\r
12291     while (**p && **p != ' ') (*p)++;\r
12292     sprintf(buf, "accepted %s\n", name);\r
12293     SendToProgram(buf, cps);\r
12294     return TRUE;\r
12295   }\r
12296   return FALSE;\r
12297 }\r
12298 \r
12299 int\r
12300 IntFeature(p, name, loc, cps)\r
12301      char **p;\r
12302      char *name;\r
12303      int *loc;\r
12304      ChessProgramState *cps;\r
12305 {\r
12306   char buf[MSG_SIZ];\r
12307   int len = strlen(name);\r
12308   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12309     (*p) += len + 1;\r
12310     sscanf(*p, "%d", loc);\r
12311     while (**p && **p != ' ') (*p)++;\r
12312     sprintf(buf, "accepted %s\n", name);\r
12313     SendToProgram(buf, cps);\r
12314     return TRUE;\r
12315   }\r
12316   return FALSE;\r
12317 }\r
12318 \r
12319 int\r
12320 StringFeature(p, name, loc, cps)\r
12321      char **p;\r
12322      char *name;\r
12323      char loc[];\r
12324      ChessProgramState *cps;\r
12325 {\r
12326   char buf[MSG_SIZ];\r
12327   int len = strlen(name);\r
12328   if (strncmp((*p), name, len) == 0\r
12329       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
12330     (*p) += len + 2;\r
12331     sscanf(*p, "%[^\"]", loc);\r
12332     while (**p && **p != '\"') (*p)++;\r
12333     if (**p == '\"') (*p)++;\r
12334     sprintf(buf, "accepted %s\n", name);\r
12335     SendToProgram(buf, cps);\r
12336     return TRUE;\r
12337   }\r
12338   return FALSE;\r
12339 }\r
12340 \r
12341 int \r
12342 ParseOption(Option *opt, ChessProgramState *cps)\r
12343 // [HGM] options: process the string that defines an engine option, and determine\r
12344 // name, type, default value, and allowed value range\r
12345 {\r
12346         char *p, *q, buf[MSG_SIZ];\r
12347         int n, min = (-1)<<31, max = 1<<31, def;\r
12348 \r
12349         if(p = strstr(opt->name, " -spin ")) {\r
12350             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;\r
12351             if(max < min) max = min; // enforce consistency\r
12352             if(def < min) def = min;\r
12353             if(def > max) def = max;\r
12354             opt->value = def;\r
12355             opt->min = min;\r
12356             opt->max = max;\r
12357             opt->type = Spin;\r
12358         } else if(p = strstr(opt->name, " -string ")) {\r
12359             opt->textValue = p+9;\r
12360             opt->type = TextBox;\r
12361         } else if(p = strstr(opt->name, " -check ")) {\r
12362             if(sscanf(p, " -check %d", &def) < 1) return FALSE;\r
12363             opt->value = (def != 0);\r
12364             opt->type = CheckBox;\r
12365         } else if(p = strstr(opt->name, " -combo ")) {\r
12366             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type\r
12367             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices\r
12368             opt->value = n = 0;\r
12369             while(q = StrStr(q, " /// ")) {\r
12370                 n++; *q = 0;    // count choices, and null-terminate each of them\r
12371                 q += 5;\r
12372                 if(*q == '*') { // remember default, which is marked with * prefix\r
12373                     q++;\r
12374                     opt->value = n;\r
12375                 }\r
12376                 cps->comboList[cps->comboCnt++] = q;\r
12377             }\r
12378             cps->comboList[cps->comboCnt++] = NULL;\r
12379             opt->max = n + 1;\r
12380             opt->type = ComboBox;\r
12381         } else if(p = strstr(opt->name, " -button")) {\r
12382             opt->type = Button;\r
12383         } else if(p = strstr(opt->name, " -save")) {\r
12384             opt->type = SaveButton;\r
12385         } else return FALSE;\r
12386         *p = 0; // terminate option name\r
12387         // now look if the command-line options define a setting for this engine option.\r
12388         p = strstr(cps->optionSettings, opt->name);\r
12389         if(p == cps->optionSettings || p[-1] == ',') {\r
12390                 sprintf(buf, "option %s", p);\r
12391                 if(p = strstr(buf, ",")) *p = 0;\r
12392                 strcat(buf, "\n");\r
12393                 SendToProgram(buf, cps);\r
12394         }\r
12395         return TRUE;\r
12396 }\r
12397 \r
12398 void\r
12399 FeatureDone(cps, val)\r
12400      ChessProgramState* cps;\r
12401      int val;\r
12402 {\r
12403   DelayedEventCallback cb = GetDelayedEvent();\r
12404   if ((cb == InitBackEnd3 && cps == &first) ||\r
12405       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
12406     CancelDelayedEvent();\r
12407     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
12408   }\r
12409   cps->initDone = val;\r
12410 }\r
12411 \r
12412 /* Parse feature command from engine */\r
12413 void\r
12414 ParseFeatures(args, cps)\r
12415      char* args;\r
12416      ChessProgramState *cps;  \r
12417 {\r
12418   char *p = args;\r
12419   char *q;\r
12420   int val;\r
12421   char buf[MSG_SIZ];\r
12422 \r
12423   for (;;) {\r
12424     while (*p == ' ') p++;\r
12425     if (*p == NULLCHAR) return;\r
12426 \r
12427     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
12428     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
12429     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
12430     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
12431     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
12432     if (BoolFeature(&p, "reuse", &val, cps)) {\r
12433       /* Engine can disable reuse, but can't enable it if user said no */\r
12434       if (!val) cps->reuse = FALSE;\r
12435       continue;\r
12436     }\r
12437     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
12438     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
12439       if (gameMode == TwoMachinesPlay) {\r
12440         DisplayTwoMachinesTitle();\r
12441       } else {\r
12442         DisplayTitle("");\r
12443       }\r
12444       continue;\r
12445     }\r
12446     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
12447     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
12448     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
12449     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
12450     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
12451     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
12452     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
12453     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
12454     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
12455     if (IntFeature(&p, "done", &val, cps)) {\r
12456       FeatureDone(cps, val);\r
12457       continue;\r
12458     }\r
12459     /* Added by Tord: */\r
12460     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
12461     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
12462     /* End of additions by Tord */\r
12463 \r
12464     /* [HGM] added features: */\r
12465     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
12466     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
12467     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
12468     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
12469     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12470     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
12471     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {\r
12472         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature\r
12473         if(cps->nrOptions >= MAX_OPTIONS) {\r
12474             cps->nrOptions--;\r
12475             sprintf(buf, "%s engine has too many options\n", cps->which);\r
12476             DisplayError(buf, 0);\r
12477         }\r
12478         continue;\r
12479     }\r
12480     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12481     /* End of additions by HGM */\r
12482 \r
12483     /* unknown feature: complain and skip */\r
12484     q = p;\r
12485     while (*q && *q != '=') q++;\r
12486     sprintf(buf, "rejected %.*s\n", q-p, p);\r
12487     SendToProgram(buf, cps);\r
12488     p = q;\r
12489     if (*p == '=') {\r
12490       p++;\r
12491       if (*p == '\"') {\r
12492         p++;\r
12493         while (*p && *p != '\"') p++;\r
12494         if (*p == '\"') p++;\r
12495       } else {\r
12496         while (*p && *p != ' ') p++;\r
12497       }\r
12498     }\r
12499   }\r
12500 \r
12501 }\r
12502 \r
12503 void\r
12504 PeriodicUpdatesEvent(newState)\r
12505      int newState;\r
12506 {\r
12507     if (newState == appData.periodicUpdates)\r
12508       return;\r
12509 \r
12510     appData.periodicUpdates=newState;\r
12511 \r
12512     /* Display type changes, so update it now */\r
12513     DisplayAnalysis();\r
12514 \r
12515     /* Get the ball rolling again... */\r
12516     if (newState) {\r
12517         AnalysisPeriodicEvent(1);\r
12518         StartAnalysisClock();\r
12519     }\r
12520 }\r
12521 \r
12522 void\r
12523 PonderNextMoveEvent(newState)\r
12524      int newState;\r
12525 {\r
12526     if (newState == appData.ponderNextMove) return;\r
12527     if (gameMode == EditPosition) EditPositionDone();\r
12528     if (newState) {\r
12529         SendToProgram("hard\n", &first);\r
12530         if (gameMode == TwoMachinesPlay) {\r
12531             SendToProgram("hard\n", &second);\r
12532         }\r
12533     } else {\r
12534         SendToProgram("easy\n", &first);\r
12535         thinkOutput[0] = NULLCHAR;\r
12536         if (gameMode == TwoMachinesPlay) {\r
12537             SendToProgram("easy\n", &second);\r
12538         }\r
12539     }\r
12540     appData.ponderNextMove = newState;\r
12541 }\r
12542 \r
12543 void\r
12544 NewSettingEvent(option, command, value)\r
12545      char *command;\r
12546      int option, value;\r
12547 {\r
12548     char buf[MSG_SIZ];\r
12549 \r
12550     if (gameMode == EditPosition) EditPositionDone();\r
12551     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);\r
12552     SendToProgram(buf, &first);\r
12553     if (gameMode == TwoMachinesPlay) {\r
12554         SendToProgram(buf, &second);\r
12555     }\r
12556 }\r
12557 \r
12558 void\r
12559 ShowThinkingEvent()\r
12560 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup\r
12561 {\r
12562     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated\r
12563     int newState = appData.showThinking\r
12564         // [HGM] thinking: other features now need thinking output as well\r
12565         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();\r
12566     \r
12567     if (oldState == newState) return;\r
12568     oldState = newState;\r
12569     if (gameMode == EditPosition) EditPositionDone();\r
12570     if (oldState) {\r
12571         SendToProgram("post\n", &first);\r
12572         if (gameMode == TwoMachinesPlay) {\r
12573             SendToProgram("post\n", &second);\r
12574         }\r
12575     } else {\r
12576         SendToProgram("nopost\n", &first);\r
12577         thinkOutput[0] = NULLCHAR;\r
12578         if (gameMode == TwoMachinesPlay) {\r
12579             SendToProgram("nopost\n", &second);\r
12580         }\r
12581     }\r
12582 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!\r
12583 }\r
12584 \r
12585 void\r
12586 AskQuestionEvent(title, question, replyPrefix, which)\r
12587      char *title; char *question; char *replyPrefix; char *which;\r
12588 {\r
12589   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
12590   if (pr == NoProc) return;\r
12591   AskQuestion(title, question, replyPrefix, pr);\r
12592 }\r
12593 \r
12594 void\r
12595 DisplayMove(moveNumber)\r
12596      int moveNumber;\r
12597 {\r
12598     char message[MSG_SIZ];\r
12599     char res[MSG_SIZ];\r
12600     char cpThinkOutput[MSG_SIZ];\r
12601 \r
12602     if(appData.noGUI) return; // [HGM] fast: suppress display of moves\r
12603     \r
12604     if (moveNumber == forwardMostMove - 1 || \r
12605         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12606 \r
12607         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
12608 \r
12609         if (strchr(cpThinkOutput, '\n')) {\r
12610             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
12611         }\r
12612     } else {\r
12613         *cpThinkOutput = NULLCHAR;\r
12614     }\r
12615 \r
12616     /* [AS] Hide thinking from human user */\r
12617     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
12618         *cpThinkOutput = NULLCHAR;\r
12619         if( thinkOutput[0] != NULLCHAR ) {\r
12620             int i;\r
12621 \r
12622             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
12623                 cpThinkOutput[i] = '.';\r
12624             }\r
12625             cpThinkOutput[i] = NULLCHAR;\r
12626             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
12627         }\r
12628     }\r
12629 \r
12630     if (moveNumber == forwardMostMove - 1 &&\r
12631         gameInfo.resultDetails != NULL) {\r
12632         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
12633             sprintf(res, " %s", PGNResult(gameInfo.result));\r
12634         } else {\r
12635             sprintf(res, " {%s} %s",\r
12636                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
12637         }\r
12638     } else {\r
12639         res[0] = NULLCHAR;\r
12640     }\r
12641 \r
12642     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12643         DisplayMessage(res, cpThinkOutput);\r
12644     } else {\r
12645         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
12646                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
12647                 parseList[moveNumber], res);\r
12648         DisplayMessage(message, cpThinkOutput);\r
12649     }\r
12650 }\r
12651 \r
12652 void\r
12653 DisplayAnalysisText(text)\r
12654      char *text;\r
12655 {\r
12656     char buf[MSG_SIZ];\r
12657 \r
12658     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile \r
12659                || appData.icsEngineAnalyze) {\r
12660         sprintf(buf, "Analysis (%s)", first.tidy);\r
12661         AnalysisPopUp(buf, text);\r
12662     }\r
12663 }\r
12664 \r
12665 static int\r
12666 only_one_move(str)\r
12667      char *str;\r
12668 {\r
12669     while (*str && isspace(*str)) ++str;\r
12670     while (*str && !isspace(*str)) ++str;\r
12671     if (!*str) return 1;\r
12672     while (*str && isspace(*str)) ++str;\r
12673     if (!*str) return 1;\r
12674     return 0;\r
12675 }\r
12676 \r
12677 void\r
12678 DisplayAnalysis()\r
12679 {\r
12680     char buf[MSG_SIZ];\r
12681     char lst[MSG_SIZ / 2];\r
12682     double nps;\r
12683     static char *xtra[] = { "", " (--)", " (++)" };\r
12684     int h, m, s, cs;\r
12685   \r
12686     if (programStats.time == 0) {\r
12687         programStats.time = 1;\r
12688     }\r
12689   \r
12690     if (programStats.got_only_move) {\r
12691         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
12692     } else {\r
12693         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
12694 \r
12695         nps = (u64ToDouble(programStats.nodes) /\r
12696              ((double)programStats.time /100.0));\r
12697 \r
12698         cs = programStats.time % 100;\r
12699         s = programStats.time / 100;\r
12700         h = (s / (60*60));\r
12701         s = s - h*60*60;\r
12702         m = (s/60);\r
12703         s = s - m*60;\r
12704 \r
12705         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
12706           if (programStats.move_name[0] != NULLCHAR) {\r
12707             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12708                     programStats.depth,\r
12709                     programStats.nr_moves-programStats.moves_left,\r
12710                     programStats.nr_moves, programStats.move_name,\r
12711                     ((float)programStats.score)/100.0, lst,\r
12712                     only_one_move(lst)?\r
12713                     xtra[programStats.got_fail] : "",\r
12714                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12715           } else {\r
12716             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12717                     programStats.depth,\r
12718                     programStats.nr_moves-programStats.moves_left,\r
12719                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
12720                     lst,\r
12721                     only_one_move(lst)?\r
12722                     xtra[programStats.got_fail] : "",\r
12723                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12724           }\r
12725         } else {\r
12726             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12727                     programStats.depth,\r
12728                     ((float)programStats.score)/100.0,\r
12729                     lst,\r
12730                     only_one_move(lst)?\r
12731                     xtra[programStats.got_fail] : "",\r
12732                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12733         }\r
12734     }\r
12735     DisplayAnalysisText(buf);\r
12736 }\r
12737 \r
12738 void\r
12739 DisplayComment(moveNumber, text)\r
12740      int moveNumber;\r
12741      char *text;\r
12742 {\r
12743     char title[MSG_SIZ];\r
12744     char buf[8000]; // comment can be long!\r
12745     int score, depth;\r
12746 \r
12747     if( appData.autoDisplayComment ) {\r
12748         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12749             strcpy(title, "Comment");\r
12750         } else {\r
12751             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
12752                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
12753                     parseList[moveNumber]);\r
12754         }\r
12755     } else title[0] = 0;\r
12756 \r
12757     // [HGM] PV info: display PV info together with (or as) comment\r
12758     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
12759         if(text == NULL) text = "";                                           \r
12760         score = pvInfoList[moveNumber].score;\r
12761         sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
12762                               depth, (pvInfoList[moveNumber].time+50)/100, text);\r
12763         CommentPopUp(title, buf);\r
12764     } else\r
12765     if (text != NULL)\r
12766         CommentPopUp(title, text);\r
12767 }\r
12768 \r
12769 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
12770  * might be busy thinking or pondering.  It can be omitted if your\r
12771  * gnuchess is configured to stop thinking immediately on any user\r
12772  * input.  However, that gnuchess feature depends on the FIONREAD\r
12773  * ioctl, which does not work properly on some flavors of Unix.\r
12774  */\r
12775 void\r
12776 Attention(cps)\r
12777      ChessProgramState *cps;\r
12778 {\r
12779 #if ATTENTION\r
12780     if (!cps->useSigint) return;\r
12781     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
12782     switch (gameMode) {\r
12783       case MachinePlaysWhite:\r
12784       case MachinePlaysBlack:\r
12785       case TwoMachinesPlay:\r
12786       case IcsPlayingWhite:\r
12787       case IcsPlayingBlack:\r
12788       case AnalyzeMode:\r
12789       case AnalyzeFile:\r
12790         /* Skip if we know it isn't thinking */\r
12791         if (!cps->maybeThinking) return;\r
12792         if (appData.debugMode)\r
12793           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
12794         InterruptChildProcess(cps->pr);\r
12795         cps->maybeThinking = FALSE;\r
12796         break;\r
12797       default:\r
12798         break;\r
12799     }\r
12800 #endif /*ATTENTION*/\r
12801 }\r
12802 \r
12803 int\r
12804 CheckFlags()\r
12805 {\r
12806     if (whiteTimeRemaining <= 0) {\r
12807         if (!whiteFlag) {\r
12808             whiteFlag = TRUE;\r
12809             if (appData.icsActive) {\r
12810                 if (appData.autoCallFlag &&\r
12811                     gameMode == IcsPlayingBlack && !blackFlag) {\r
12812                   SendToICS(ics_prefix);\r
12813                   SendToICS("flag\n");\r
12814                 }\r
12815             } else {\r
12816                 if (blackFlag) {\r
12817                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12818                 } else {\r
12819                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));\r
12820                     if (appData.autoCallFlag) {\r
12821                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
12822                         return TRUE;\r
12823                     }\r
12824                 }\r
12825             }\r
12826         }\r
12827     }\r
12828     if (blackTimeRemaining <= 0) {\r
12829         if (!blackFlag) {\r
12830             blackFlag = TRUE;\r
12831             if (appData.icsActive) {\r
12832                 if (appData.autoCallFlag &&\r
12833                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
12834                   SendToICS(ics_prefix);\r
12835                   SendToICS("flag\n");\r
12836                 }\r
12837             } else {\r
12838                 if (whiteFlag) {\r
12839                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12840                 } else {\r
12841                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));\r
12842                     if (appData.autoCallFlag) {\r
12843                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
12844                         return TRUE;\r
12845                     }\r
12846                 }\r
12847             }\r
12848         }\r
12849     }\r
12850     return FALSE;\r
12851 }\r
12852 \r
12853 void\r
12854 CheckTimeControl()\r
12855 {\r
12856     if (!appData.clockMode || appData.icsActive ||\r
12857         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
12858 \r
12859     /*\r
12860      * add time to clocks when time control is achieved ([HGM] now also used for increment)\r
12861      */\r
12862     if ( !WhiteOnMove(forwardMostMove) )\r
12863         /* White made time control */\r
12864         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12865         /* [HGM] time odds: correct new time quota for time odds! */\r
12866                                             / WhitePlayer()->timeOdds;\r
12867       else\r
12868         /* Black made time control */\r
12869         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12870                                             / WhitePlayer()->other->timeOdds;\r
12871 }\r
12872 \r
12873 void\r
12874 DisplayBothClocks()\r
12875 {\r
12876     int wom = gameMode == EditPosition ?\r
12877       !blackPlaysFirst : WhiteOnMove(currentMove);\r
12878     DisplayWhiteClock(whiteTimeRemaining, wom);\r
12879     DisplayBlackClock(blackTimeRemaining, !wom);\r
12880 }\r
12881 \r
12882 \r
12883 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
12884    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
12885    to use other calls if you don't.  Clocks will be less accurate if\r
12886    you have neither ftime nor gettimeofday.\r
12887 */\r
12888 \r
12889 /* Get the current time as a TimeMark */\r
12890 void\r
12891 GetTimeMark(tm)\r
12892      TimeMark *tm;\r
12893 {\r
12894 #if HAVE_GETTIMEOFDAY\r
12895 \r
12896     struct timeval timeVal;\r
12897     struct timezone timeZone;\r
12898 \r
12899     gettimeofday(&timeVal, &timeZone);\r
12900     tm->sec = (long) timeVal.tv_sec; \r
12901     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
12902 \r
12903 #else /*!HAVE_GETTIMEOFDAY*/\r
12904 #if HAVE_FTIME\r
12905 \r
12906 #include <sys/timeb.h>\r
12907     struct timeb timeB;\r
12908 \r
12909     ftime(&timeB);\r
12910     tm->sec = (long) timeB.time;\r
12911     tm->ms = (int) timeB.millitm;\r
12912 \r
12913 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
12914     tm->sec = (long) time(NULL);\r
12915     tm->ms = 0;\r
12916 #endif\r
12917 #endif\r
12918 }\r
12919 \r
12920 /* Return the difference in milliseconds between two\r
12921    time marks.  We assume the difference will fit in a long!\r
12922 */\r
12923 long\r
12924 SubtractTimeMarks(tm2, tm1)\r
12925      TimeMark *tm2, *tm1;\r
12926 {\r
12927     return 1000L*(tm2->sec - tm1->sec) +\r
12928            (long) (tm2->ms - tm1->ms);\r
12929 }\r
12930 \r
12931 \r
12932 /*\r
12933  * Code to manage the game clocks.\r
12934  *\r
12935  * In tournament play, black starts the clock and then white makes a move.\r
12936  * We give the human user a slight advantage if he is playing white---the\r
12937  * clocks don't run until he makes his first move, so it takes zero time.\r
12938  * Also, we don't account for network lag, so we could get out of sync\r
12939  * with GNU Chess's clock -- but then, referees are always right.  \r
12940  */\r
12941 \r
12942 static TimeMark tickStartTM;\r
12943 static long intendedTickLength;\r
12944 \r
12945 long\r
12946 NextTickLength(timeRemaining)\r
12947      long timeRemaining;\r
12948 {\r
12949     long nominalTickLength, nextTickLength;\r
12950 \r
12951     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
12952       nominalTickLength = 100L;\r
12953     else\r
12954       nominalTickLength = 1000L;\r
12955     nextTickLength = timeRemaining % nominalTickLength;\r
12956     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
12957 \r
12958     return nextTickLength;\r
12959 }\r
12960 \r
12961 /* Adjust clock one minute up or down */\r
12962 void\r
12963 AdjustClock(Boolean which, int dir)\r
12964 {\r
12965     if(which) blackTimeRemaining += 60000*dir;\r
12966     else      whiteTimeRemaining += 60000*dir;\r
12967     DisplayBothClocks();\r
12968 }\r
12969 \r
12970 /* Stop clocks and reset to a fresh time control */\r
12971 void\r
12972 ResetClocks() \r
12973 {\r
12974     (void) StopClockTimer();\r
12975     if (appData.icsActive) {\r
12976         whiteTimeRemaining = blackTimeRemaining = 0;\r
12977     } else { /* [HGM] correct new time quote for time odds */\r
12978         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
12979         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
12980     }\r
12981     if (whiteFlag || blackFlag) {\r
12982         DisplayTitle("");\r
12983         whiteFlag = blackFlag = FALSE;\r
12984     }\r
12985     DisplayBothClocks();\r
12986 }\r
12987 \r
12988 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
12989 \r
12990 /* Decrement running clock by amount of time that has passed */\r
12991 void\r
12992 DecrementClocks()\r
12993 {\r
12994     long timeRemaining;\r
12995     long lastTickLength, fudge;\r
12996     TimeMark now;\r
12997 \r
12998     if (!appData.clockMode) return;\r
12999     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
13000         \r
13001     GetTimeMark(&now);\r
13002 \r
13003     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13004 \r
13005     /* Fudge if we woke up a little too soon */\r
13006     fudge = intendedTickLength - lastTickLength;\r
13007     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
13008 \r
13009     if (WhiteOnMove(forwardMostMove)) {\r
13010         if(whiteNPS >= 0) lastTickLength = 0;\r
13011         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
13012         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
13013                           WhiteOnMove(currentMove));\r
13014     } else {\r
13015         if(blackNPS >= 0) lastTickLength = 0;\r
13016         timeRemaining = blackTimeRemaining -= lastTickLength;\r
13017         DisplayBlackClock(blackTimeRemaining - fudge,\r
13018                           !WhiteOnMove(currentMove));\r
13019     }\r
13020 \r
13021     if (CheckFlags()) return;\r
13022         \r
13023     tickStartTM = now;\r
13024     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
13025     StartClockTimer(intendedTickLength);\r
13026 \r
13027     /* if the time remaining has fallen below the alarm threshold, sound the\r
13028      * alarm. if the alarm has sounded and (due to a takeback or time control\r
13029      * with increment) the time remaining has increased to a level above the\r
13030      * threshold, reset the alarm so it can sound again. \r
13031      */\r
13032     \r
13033     if (appData.icsActive && appData.icsAlarm) {\r
13034 \r
13035         /* make sure we are dealing with the user's clock */\r
13036         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
13037                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
13038            )) return;\r
13039 \r
13040         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
13041             alarmSounded = FALSE;\r
13042         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
13043             PlayAlarmSound();\r
13044             alarmSounded = TRUE;\r
13045         }\r
13046     }\r
13047 }\r
13048 \r
13049 \r
13050 /* A player has just moved, so stop the previously running\r
13051    clock and (if in clock mode) start the other one.\r
13052    We redisplay both clocks in case we're in ICS mode, because\r
13053    ICS gives us an update to both clocks after every move.\r
13054    Note that this routine is called *after* forwardMostMove\r
13055    is updated, so the last fractional tick must be subtracted\r
13056    from the color that is *not* on move now.\r
13057 */\r
13058 void\r
13059 SwitchClocks()\r
13060 {\r
13061     long lastTickLength;\r
13062     TimeMark now;\r
13063     int flagged = FALSE;\r
13064 \r
13065     GetTimeMark(&now);\r
13066 \r
13067     if (StopClockTimer() && appData.clockMode) {\r
13068         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13069         if (WhiteOnMove(forwardMostMove)) {\r
13070             if(blackNPS >= 0) lastTickLength = 0;\r
13071             blackTimeRemaining -= lastTickLength;\r
13072            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13073 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13074                  pvInfoList[forwardMostMove-1].time =               // use GUI time\r
13075                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
13076         } else {\r
13077            if(whiteNPS >= 0) lastTickLength = 0;\r
13078            whiteTimeRemaining -= lastTickLength;\r
13079            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13080 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13081                  pvInfoList[forwardMostMove-1].time = \r
13082                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
13083         }\r
13084         flagged = CheckFlags();\r
13085     }\r
13086     CheckTimeControl();\r
13087 \r
13088     if (flagged || !appData.clockMode) return;\r
13089 \r
13090     switch (gameMode) {\r
13091       case MachinePlaysBlack:\r
13092       case MachinePlaysWhite:\r
13093       case BeginningOfGame:\r
13094         if (pausing) return;\r
13095         break;\r
13096 \r
13097       case EditGame:\r
13098       case PlayFromGameFile:\r
13099       case IcsExamining:\r
13100         return;\r
13101 \r
13102       default:\r
13103         break;\r
13104     }\r
13105 \r
13106     tickStartTM = now;\r
13107     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13108       whiteTimeRemaining : blackTimeRemaining);\r
13109     StartClockTimer(intendedTickLength);\r
13110 }\r
13111         \r
13112 \r
13113 /* Stop both clocks */\r
13114 void\r
13115 StopClocks()\r
13116 {       \r
13117     long lastTickLength;\r
13118     TimeMark now;\r
13119 \r
13120     if (!StopClockTimer()) return;\r
13121     if (!appData.clockMode) return;\r
13122 \r
13123     GetTimeMark(&now);\r
13124 \r
13125     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13126     if (WhiteOnMove(forwardMostMove)) {\r
13127         if(whiteNPS >= 0) lastTickLength = 0;\r
13128         whiteTimeRemaining -= lastTickLength;\r
13129         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
13130     } else {\r
13131         if(blackNPS >= 0) lastTickLength = 0;\r
13132         blackTimeRemaining -= lastTickLength;\r
13133         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
13134     }\r
13135     CheckFlags();\r
13136 }\r
13137         \r
13138 /* Start clock of player on move.  Time may have been reset, so\r
13139    if clock is already running, stop and restart it. */\r
13140 void\r
13141 StartClocks()\r
13142 {\r
13143     (void) StopClockTimer(); /* in case it was running already */\r
13144     DisplayBothClocks();\r
13145     if (CheckFlags()) return;\r
13146 \r
13147     if (!appData.clockMode) return;\r
13148     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
13149 \r
13150     GetTimeMark(&tickStartTM);\r
13151     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13152       whiteTimeRemaining : blackTimeRemaining);\r
13153 \r
13154    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
13155     whiteNPS = blackNPS = -1; \r
13156     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
13157        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
13158         whiteNPS = first.nps;\r
13159     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
13160        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
13161         blackNPS = first.nps;\r
13162     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
13163         whiteNPS = second.nps;\r
13164     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
13165         blackNPS = second.nps;\r
13166     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
13167 \r
13168     StartClockTimer(intendedTickLength);\r
13169 }\r
13170 \r
13171 char *\r
13172 TimeString(ms)\r
13173      long ms;\r
13174 {\r
13175     long second, minute, hour, day;\r
13176     char *sign = "";\r
13177     static char buf[32];\r
13178     \r
13179     if (ms > 0 && ms <= 9900) {\r
13180       /* convert milliseconds to tenths, rounding up */\r
13181       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
13182 \r
13183       sprintf(buf, " %03.1f ", tenths/10.0);\r
13184       return buf;\r
13185     }\r
13186 \r
13187     /* convert milliseconds to seconds, rounding up */\r
13188     /* use floating point to avoid strangeness of integer division\r
13189        with negative dividends on many machines */\r
13190     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
13191 \r
13192     if (second < 0) {\r
13193         sign = "-";\r
13194         second = -second;\r
13195     }\r
13196     \r
13197     day = second / (60 * 60 * 24);\r
13198     second = second % (60 * 60 * 24);\r
13199     hour = second / (60 * 60);\r
13200     second = second % (60 * 60);\r
13201     minute = second / 60;\r
13202     second = second % 60;\r
13203     \r
13204     if (day > 0)\r
13205       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
13206               sign, day, hour, minute, second);\r
13207     else if (hour > 0)\r
13208       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
13209     else\r
13210       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
13211     \r
13212     return buf;\r
13213 }\r
13214 \r
13215 \r
13216 /*\r
13217  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
13218  */\r
13219 char *\r
13220 StrStr(string, match)\r
13221      char *string, *match;\r
13222 {\r
13223     int i, length;\r
13224     \r
13225     length = strlen(match);\r
13226     \r
13227     for (i = strlen(string) - length; i >= 0; i--, string++)\r
13228       if (!strncmp(match, string, length))\r
13229         return string;\r
13230     \r
13231     return NULL;\r
13232 }\r
13233 \r
13234 char *\r
13235 StrCaseStr(string, match)\r
13236      char *string, *match;\r
13237 {\r
13238     int i, j, length;\r
13239     \r
13240     length = strlen(match);\r
13241     \r
13242     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
13243         for (j = 0; j < length; j++) {\r
13244             if (ToLower(match[j]) != ToLower(string[j]))\r
13245               break;\r
13246         }\r
13247         if (j == length) return string;\r
13248     }\r
13249 \r
13250     return NULL;\r
13251 }\r
13252 \r
13253 #ifndef _amigados\r
13254 int\r
13255 StrCaseCmp(s1, s2)\r
13256      char *s1, *s2;\r
13257 {\r
13258     char c1, c2;\r
13259     \r
13260     for (;;) {\r
13261         c1 = ToLower(*s1++);\r
13262         c2 = ToLower(*s2++);\r
13263         if (c1 > c2) return 1;\r
13264         if (c1 < c2) return -1;\r
13265         if (c1 == NULLCHAR) return 0;\r
13266     }\r
13267 }\r
13268 \r
13269 \r
13270 int\r
13271 ToLower(c)\r
13272      int c;\r
13273 {\r
13274     return isupper(c) ? tolower(c) : c;\r
13275 }\r
13276 \r
13277 \r
13278 int\r
13279 ToUpper(c)\r
13280      int c;\r
13281 {\r
13282     return islower(c) ? toupper(c) : c;\r
13283 }\r
13284 #endif /* !_amigados    */\r
13285 \r
13286 char *\r
13287 StrSave(s)\r
13288      char *s;\r
13289 {\r
13290     char *ret;\r
13291 \r
13292     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
13293         strcpy(ret, s);\r
13294     }\r
13295     return ret;\r
13296 }\r
13297 \r
13298 char *\r
13299 StrSavePtr(s, savePtr)\r
13300      char *s, **savePtr;\r
13301 {\r
13302     if (*savePtr) {\r
13303         free(*savePtr);\r
13304     }\r
13305     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
13306         strcpy(*savePtr, s);\r
13307     }\r
13308     return(*savePtr);\r
13309 }\r
13310 \r
13311 char *\r
13312 PGNDate()\r
13313 {\r
13314     time_t clock;\r
13315     struct tm *tm;\r
13316     char buf[MSG_SIZ];\r
13317 \r
13318     clock = time((time_t *)NULL);\r
13319     tm = localtime(&clock);\r
13320     sprintf(buf, "%04d.%02d.%02d",\r
13321             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
13322     return StrSave(buf);\r
13323 }\r
13324 \r
13325 \r
13326 char *\r
13327 PositionToFEN(move, useFEN960)\r
13328      int move;\r
13329      int useFEN960;\r
13330 {\r
13331     int i, j, fromX, fromY, toX, toY;\r
13332     int whiteToPlay;\r
13333     char buf[128];\r
13334     char *p, *q;\r
13335     int emptycount;\r
13336     ChessSquare piece;\r
13337 \r
13338     whiteToPlay = (gameMode == EditPosition) ?\r
13339       !blackPlaysFirst : (move % 2 == 0);\r
13340     p = buf;\r
13341 \r
13342     /* Piece placement data */\r
13343     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13344         emptycount = 0;\r
13345         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
13346             if (boards[move][i][j] == EmptySquare) {\r
13347                 emptycount++;\r
13348             } else { ChessSquare piece = boards[move][i][j];\r
13349                 if (emptycount > 0) {\r
13350                     if(emptycount<10) /* [HGM] can be >= 10 */\r
13351                         *p++ = '0' + emptycount;\r
13352                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13353                     emptycount = 0;\r
13354                 }\r
13355                 if(PieceToChar(piece) == '+') {\r
13356                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
13357                     *p++ = '+';\r
13358                     piece = (ChessSquare)(DEMOTED piece);\r
13359                 } \r
13360                 *p++ = PieceToChar(piece);\r
13361                 if(p[-1] == '~') {\r
13362                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
13363                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
13364                     *p++ = '~';\r
13365                 }\r
13366             }\r
13367         }\r
13368         if (emptycount > 0) {\r
13369             if(emptycount<10) /* [HGM] can be >= 10 */\r
13370                 *p++ = '0' + emptycount;\r
13371             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13372             emptycount = 0;\r
13373         }\r
13374         *p++ = '/';\r
13375     }\r
13376     *(p - 1) = ' ';\r
13377 \r
13378     /* [HGM] print Crazyhouse or Shogi holdings */\r
13379     if( gameInfo.holdingsWidth ) {\r
13380         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
13381         q = p;\r
13382         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
13383             piece = boards[move][i][BOARD_WIDTH-1];\r
13384             if( piece != EmptySquare )\r
13385               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
13386                   *p++ = PieceToChar(piece);\r
13387         }\r
13388         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
13389             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
13390             if( piece != EmptySquare )\r
13391               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
13392                   *p++ = PieceToChar(piece);\r
13393         }\r
13394 \r
13395         if( q == p ) *p++ = '-';\r
13396         *p++ = ']';\r
13397         *p++ = ' ';\r
13398     }\r
13399 \r
13400     /* Active color */\r
13401     *p++ = whiteToPlay ? 'w' : 'b';\r
13402     *p++ = ' ';\r
13403 \r
13404   if(nrCastlingRights) {\r
13405      q = p;\r
13406      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
13407        /* [HGM] write directly from rights */\r
13408            if(castlingRights[move][2] >= 0 &&\r
13409               castlingRights[move][0] >= 0   )\r
13410                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
13411            if(castlingRights[move][2] >= 0 &&\r
13412               castlingRights[move][1] >= 0   )\r
13413                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
13414            if(castlingRights[move][5] >= 0 &&\r
13415               castlingRights[move][3] >= 0   )\r
13416                 *p++ = castlingRights[move][3] + AAA;\r
13417            if(castlingRights[move][5] >= 0 &&\r
13418               castlingRights[move][4] >= 0   )\r
13419                 *p++ = castlingRights[move][4] + AAA;\r
13420      } else {\r
13421 \r
13422         /* [HGM] write true castling rights */\r
13423         if( nrCastlingRights == 6 ) {\r
13424             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
13425                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
13426             if(castlingRights[move][1] == BOARD_LEFT &&\r
13427                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
13428             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
13429                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
13430             if(castlingRights[move][4] == BOARD_LEFT &&\r
13431                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
13432         }\r
13433      }\r
13434      if (q == p) *p++ = '-'; /* No castling rights */\r
13435      *p++ = ' ';\r
13436   }\r
13437 \r
13438   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13439      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13440     /* En passant target square */\r
13441     if (move > backwardMostMove) {\r
13442         fromX = moveList[move - 1][0] - AAA;\r
13443         fromY = moveList[move - 1][1] - ONE;\r
13444         toX = moveList[move - 1][2] - AAA;\r
13445         toY = moveList[move - 1][3] - ONE;\r
13446         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
13447             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
13448             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
13449             fromX == toX) {\r
13450             /* 2-square pawn move just happened */\r
13451             *p++ = toX + AAA;\r
13452             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
13453         } else {\r
13454             *p++ = '-';\r
13455         }\r
13456     } else {\r
13457         *p++ = '-';\r
13458     }\r
13459     *p++ = ' ';\r
13460   }\r
13461 \r
13462     /* [HGM] find reversible plies */\r
13463     {   int i = 0, j=move;\r
13464 \r
13465         if (appData.debugMode) { int k;\r
13466             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
13467             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
13468                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
13469 \r
13470         }\r
13471 \r
13472         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
13473         if( j == backwardMostMove ) i += initialRulePlies;\r
13474         sprintf(p, "%d ", i);\r
13475         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
13476     }\r
13477     /* Fullmove number */\r
13478     sprintf(p, "%d", (move / 2) + 1);\r
13479     \r
13480     return StrSave(buf);\r
13481 }\r
13482 \r
13483 Boolean\r
13484 ParseFEN(board, blackPlaysFirst, fen)\r
13485     Board board;\r
13486      int *blackPlaysFirst;\r
13487      char *fen;\r
13488 {\r
13489     int i, j;\r
13490     char *p;\r
13491     int emptycount;\r
13492     ChessSquare piece;\r
13493 \r
13494     p = fen;\r
13495 \r
13496     /* [HGM] by default clear Crazyhouse holdings, if present */\r
13497     if(gameInfo.holdingsWidth) {\r
13498        for(i=0; i<BOARD_HEIGHT; i++) {\r
13499            board[i][0]             = EmptySquare; /* black holdings */\r
13500            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
13501            board[i][1]             = (ChessSquare) 0; /* black counts */\r
13502            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
13503        }\r
13504     }\r
13505 \r
13506     /* Piece placement data */\r
13507     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13508         j = 0;\r
13509         for (;;) {\r
13510             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
13511                 if (*p == '/') p++;\r
13512                 emptycount = gameInfo.boardWidth - j;\r
13513                 while (emptycount--)\r
13514                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13515                 break;\r
13516 #if(BOARD_SIZE >= 10)\r
13517             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
13518                 p++; emptycount=10;\r
13519                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13520                 while (emptycount--)\r
13521                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13522 #endif\r
13523             } else if (isdigit(*p)) {\r
13524                 emptycount = *p++ - '0';\r
13525                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
13526                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13527                 while (emptycount--)\r
13528                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13529             } else if (*p == '+' || isalpha(*p)) {\r
13530                 if (j >= gameInfo.boardWidth) return FALSE;\r
13531                 if(*p=='+') {\r
13532                     piece = CharToPiece(*++p);\r
13533                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
13534                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
13535                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
13536                 } else piece = CharToPiece(*p++);\r
13537 \r
13538                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
13539                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
13540                     piece = (ChessSquare) (PROMOTED piece);\r
13541                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
13542                     p++;\r
13543                 }\r
13544                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
13545             } else {\r
13546                 return FALSE;\r
13547             }\r
13548         }\r
13549     }\r
13550     while (*p == '/' || *p == ' ') p++;\r
13551 \r
13552     /* [HGM] look for Crazyhouse holdings here */\r
13553     while(*p==' ') p++;\r
13554     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
13555         if(*p == '[') p++;\r
13556         if(*p == '-' ) *p++; /* empty holdings */ else {\r
13557             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
13558             /* if we would allow FEN reading to set board size, we would   */\r
13559             /* have to add holdings and shift the board read so far here   */\r
13560             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
13561                 *p++;\r
13562                 if((int) piece >= (int) BlackPawn ) {\r
13563                     i = (int)piece - (int)BlackPawn;\r
13564                     i = PieceToNumber((ChessSquare)i);\r
13565                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13566                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
13567                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
13568                 } else {\r
13569                     i = (int)piece - (int)WhitePawn;\r
13570                     i = PieceToNumber((ChessSquare)i);\r
13571                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13572                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
13573                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
13574                 }\r
13575             }\r
13576         }\r
13577         if(*p == ']') *p++;\r
13578     }\r
13579 \r
13580     while(*p == ' ') p++;\r
13581 \r
13582     /* Active color */\r
13583     switch (*p++) {\r
13584       case 'w':\r
13585         *blackPlaysFirst = FALSE;\r
13586         break;\r
13587       case 'b': \r
13588         *blackPlaysFirst = TRUE;\r
13589         break;\r
13590       default:\r
13591         return FALSE;\r
13592     }\r
13593 \r
13594     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
13595     /* return the extra info in global variiables             */\r
13596 \r
13597     /* set defaults in case FEN is incomplete */\r
13598     FENepStatus = EP_UNKNOWN;\r
13599     for(i=0; i<nrCastlingRights; i++ ) {\r
13600         FENcastlingRights[i] =\r
13601             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
13602     }   /* assume possible unless obviously impossible */\r
13603     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
13604     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
13605     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
13606     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
13607     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
13608     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
13609     FENrulePlies = 0;\r
13610 \r
13611     while(*p==' ') p++;\r
13612     if(nrCastlingRights) {\r
13613       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
13614           /* castling indicator present, so default becomes no castlings */\r
13615           for(i=0; i<nrCastlingRights; i++ ) {\r
13616                  FENcastlingRights[i] = -1;\r
13617           }\r
13618       }\r
13619       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
13620              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
13621              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
13622              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
13623         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
13624 \r
13625         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
13626             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
13627             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
13628         }\r
13629         switch(c) {\r
13630           case'K':\r
13631               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
13632               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
13633               FENcastlingRights[2] = whiteKingFile;\r
13634               break;\r
13635           case'Q':\r
13636               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
13637               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
13638               FENcastlingRights[2] = whiteKingFile;\r
13639               break;\r
13640           case'k':\r
13641               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
13642               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
13643               FENcastlingRights[5] = blackKingFile;\r
13644               break;\r
13645           case'q':\r
13646               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
13647               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
13648               FENcastlingRights[5] = blackKingFile;\r
13649           case '-':\r
13650               break;\r
13651           default: /* FRC castlings */\r
13652               if(c >= 'a') { /* black rights */\r
13653                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13654                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
13655                   if(i == BOARD_RGHT) break;\r
13656                   FENcastlingRights[5] = i;\r
13657                   c -= AAA;\r
13658                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
13659                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
13660                   if(c > i)\r
13661                       FENcastlingRights[3] = c;\r
13662                   else\r
13663                       FENcastlingRights[4] = c;\r
13664               } else { /* white rights */\r
13665                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13666                     if(board[0][i] == WhiteKing) break;\r
13667                   if(i == BOARD_RGHT) break;\r
13668                   FENcastlingRights[2] = i;\r
13669                   c -= AAA - 'a' + 'A';\r
13670                   if(board[0][c] >= WhiteKing) break;\r
13671                   if(c > i)\r
13672                       FENcastlingRights[0] = c;\r
13673                   else\r
13674                       FENcastlingRights[1] = c;\r
13675               }\r
13676         }\r
13677       }\r
13678     if (appData.debugMode) {\r
13679         fprintf(debugFP, "FEN castling rights:");\r
13680         for(i=0; i<nrCastlingRights; i++)\r
13681         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
13682         fprintf(debugFP, "\n");\r
13683     }\r
13684 \r
13685       while(*p==' ') p++;\r
13686     }\r
13687 \r
13688     /* read e.p. field in games that know e.p. capture */\r
13689     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13690        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13691       if(*p=='-') {\r
13692         p++; FENepStatus = EP_NONE;\r
13693       } else {\r
13694          char c = *p++ - AAA;\r
13695 \r
13696          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
13697          if(*p >= '0' && *p <='9') *p++;\r
13698          FENepStatus = c;\r
13699       }\r
13700     }\r
13701 \r
13702 \r
13703     if(sscanf(p, "%d", &i) == 1) {\r
13704         FENrulePlies = i; /* 50-move ply counter */\r
13705         /* (The move number is still ignored)    */\r
13706     }\r
13707 \r
13708     return TRUE;\r
13709 }\r
13710       \r
13711 void\r
13712 EditPositionPasteFEN(char *fen)\r
13713 {\r
13714   if (fen != NULL) {\r
13715     Board initial_position;\r
13716 \r
13717     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
13718       DisplayError(_("Bad FEN position in clipboard"), 0);\r
13719       return ;\r
13720     } else {\r
13721       int savedBlackPlaysFirst = blackPlaysFirst;\r
13722       EditPositionEvent();\r
13723       blackPlaysFirst = savedBlackPlaysFirst;\r
13724       CopyBoard(boards[0], initial_position);\r
13725           /* [HGM] copy FEN attributes as well */\r
13726           {   int i;\r
13727               initialRulePlies = FENrulePlies;\r
13728               epStatus[0] = FENepStatus;\r
13729               for( i=0; i<nrCastlingRights; i++ )\r
13730                   castlingRights[0][i] = FENcastlingRights[i];\r
13731           }\r
13732       EditPositionDone();\r
13733       DisplayBothClocks();\r
13734       DrawPosition(FALSE, boards[currentMove]);\r
13735     }\r
13736   }\r
13737 }\r