Merge commit 'v4.3.16'
[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, Massachusetts.\r
6  * Enhancements Copyright 1992-2001 Free Software Foundation, Inc.\r
7  *\r
8  * The following terms apply to Digital Equipment Corporation's copyright\r
9  * interest in XBoard:\r
10  * ------------------------------------------------------------------------\r
11  * All Rights Reserved\r
12  *\r
13  * Permission to use, copy, modify, and distribute this software and its\r
14  * documentation for any purpose and without fee is hereby granted,\r
15  * provided that the above copyright notice appear in all copies and that\r
16  * both that copyright notice and this permission notice appear in\r
17  * supporting documentation, and that the name of Digital not be\r
18  * used in advertising or publicity pertaining to distribution of the\r
19  * software without specific, written prior permission.\r
20  *\r
21  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING\r
22  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL\r
23  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR\r
24  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,\r
25  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\r
26  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS\r
27  * SOFTWARE.\r
28  * ------------------------------------------------------------------------\r
29  *\r
30  * The following terms apply to the enhanced version of XBoard distributed\r
31  * by the Free Software Foundation:\r
32  * ------------------------------------------------------------------------\r
33  * This program is free software; you can redistribute it and/or modify\r
34  * it under the terms of the GNU General Public License as published by\r
35  * the Free Software Foundation; either version 2 of the License, or\r
36  * (at your option) any later version.\r
37  *\r
38  * This program is distributed in the hope that it will be useful,\r
39  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
40  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
41  * GNU General Public License for more details.\r
42  *\r
43  * You should have received a copy of the GNU General Public License\r
44  * along with this program; if not, write to the Free Software\r
45  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.\r
46  * ------------------------------------------------------------------------\r
47  *\r
48  * See the file ChangeLog for a revision history.  */\r
49 \r
50 /* [AS] Also useful here for debugging */\r
51 #ifdef WIN32\r
52 #include <windows.h>\r
53 \r
54 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );\r
55 \r
56 #else\r
57 \r
58 #define DoSleep( n ) if( (n) >= 0) sleep(n)\r
59 \r
60 #endif\r
61 \r
62 #include "config.h"\r
63 \r
64 #include <assert.h>\r
65 #include <stdio.h>\r
66 #include <ctype.h>\r
67 #include <errno.h>\r
68 #include <sys/types.h>\r
69 #include <sys/stat.h>\r
70 #include <math.h>\r
71 \r
72 #if STDC_HEADERS\r
73 # include <stdlib.h>\r
74 # include <string.h>\r
75 #else /* not STDC_HEADERS */\r
76 # if HAVE_STRING_H\r
77 #  include <string.h>\r
78 # else /* not HAVE_STRING_H */\r
79 #  include <strings.h>\r
80 # endif /* not HAVE_STRING_H */\r
81 #endif /* not STDC_HEADERS */\r
82 \r
83 #if HAVE_SYS_FCNTL_H\r
84 # include <sys/fcntl.h>\r
85 #else /* not HAVE_SYS_FCNTL_H */\r
86 # if HAVE_FCNTL_H\r
87 #  include <fcntl.h>\r
88 # endif /* HAVE_FCNTL_H */\r
89 #endif /* not HAVE_SYS_FCNTL_H */\r
90 \r
91 #if TIME_WITH_SYS_TIME\r
92 # include <sys/time.h>\r
93 # include <time.h>\r
94 #else\r
95 # if HAVE_SYS_TIME_H\r
96 #  include <sys/time.h>\r
97 # else\r
98 #  include <time.h>\r
99 # endif\r
100 #endif\r
101 \r
102 #if defined(_amigados) && !defined(__GNUC__)\r
103 struct timezone {\r
104     int tz_minuteswest;\r
105     int tz_dsttime;\r
106 };\r
107 extern int gettimeofday(struct timeval *, struct timezone *);\r
108 #endif\r
109 \r
110 #if HAVE_UNISTD_H\r
111 # include <unistd.h>\r
112 #endif\r
113 \r
114 #include "common.h"\r
115 #include "frontend.h"\r
116 #include "backend.h"\r
117 #include "parser.h"\r
118 #include "moves.h"\r
119 #if ZIPPY\r
120 # include "zippy.h"\r
121 #endif\r
122 #include "backendz.h"\r
123 #include "gettext.h" \r
124  \r
125 #ifdef ENABLE_NLS \r
126 # define _(s) gettext (s) \r
127 # define N_(s) gettext_noop (s) \r
128 #else \r
129 # define _(s) (s) \r
130 # define N_(s) s \r
131 #endif \r
132 \r
133 \r
134 /* A point in time */\r
135 typedef struct {\r
136     long sec;  /* Assuming this is >= 32 bits */\r
137     int ms;    /* Assuming this is >= 16 bits */\r
138 } TimeMark;\r
139 \r
140 int establish P((void));\r
141 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,\r
142                          char *buf, int count, int error));\r
143 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,\r
144                       char *buf, int count, int error));\r
145 void SendToICS P((char *s));\r
146 void SendToICSDelayed P((char *s, long msdelay));\r
147 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,\r
148                       int toX, int toY));\r
149 void InitPosition P((int redraw));\r
150 void HandleMachineMove P((char *message, ChessProgramState *cps));\r
151 int AutoPlayOneMove P((void));\r
152 int LoadGameOneMove P((ChessMove readAhead));\r
153 int LoadGameFromFile P((char *filename, int n, char *title, int useList));\r
154 int LoadPositionFromFile P((char *filename, int n, char *title));\r
155 int SavePositionToFile P((char *filename));\r
156 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,\r
157                   Board board));\r
158 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));\r
159 void ShowMove P((int fromX, int fromY, int toX, int toY));\r
160 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,\r
161                    /*char*/int promoChar));\r
162 void BackwardInner P((int target));\r
163 void ForwardInner P((int target));\r
164 void GameEnds P((ChessMove result, char *resultDetails, int whosays));\r
165 void EditPositionDone P((void));\r
166 void PrintOpponents P((FILE *fp));\r
167 void PrintPosition P((FILE *fp, int move));\r
168 void StartChessProgram P((ChessProgramState *cps));\r
169 void SendToProgram P((char *message, ChessProgramState *cps));\r
170 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));\r
171 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,\r
172                            char *buf, int count, int error));\r
173 void SendTimeControl P((ChessProgramState *cps,\r
174                         int mps, long tc, int inc, int sd, int st));\r
175 char *TimeControlTagValue P((void));\r
176 void Attention P((ChessProgramState *cps));\r
177 void FeedMovesToProgram P((ChessProgramState *cps, int upto));\r
178 void ResurrectChessProgram P((void));\r
179 void DisplayComment P((int moveNumber, char *text));\r
180 void DisplayMove P((int moveNumber));\r
181 void DisplayAnalysis P((void));\r
182 \r
183 void ParseGameHistory P((char *game));\r
184 void ParseBoard12 P((char *string));\r
185 void StartClocks P((void));\r
186 void SwitchClocks P((void));\r
187 void StopClocks P((void));\r
188 void ResetClocks P((void));\r
189 char *PGNDate P((void));\r
190 void SetGameInfo P((void));\r
191 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));\r
192 int RegisterMove P((void));\r
193 void MakeRegisteredMove P((void));\r
194 void TruncateGame P((void));\r
195 int looking_at P((char *, int *, char *));\r
196 void CopyPlayerNameIntoFileName P((char **, char *));\r
197 char *SavePart P((char *));\r
198 int SaveGameOldStyle P((FILE *));\r
199 int SaveGamePGN P((FILE *));\r
200 void GetTimeMark P((TimeMark *));\r
201 long SubtractTimeMarks P((TimeMark *, TimeMark *));\r
202 int CheckFlags P((void));\r
203 long NextTickLength P((long));\r
204 void CheckTimeControl P((void));\r
205 void show_bytes P((FILE *, char *, int));\r
206 int string_to_rating P((char *str));\r
207 void ParseFeatures P((char* args, ChessProgramState *cps));\r
208 void InitBackEnd3 P((void));\r
209 void FeatureDone P((ChessProgramState* cps, int val));\r
210 void InitChessProgram P((ChessProgramState *cps, int setup));\r
211 \r
212 #ifdef WIN32\r
213        extern void ConsoleCreate();\r
214 #endif\r
215 \r
216 ChessProgramState *WhitePlayer();\r
217 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c\r
218 int VerifyDisplayMode P(());\r
219 \r
220 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment\r
221 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c\r
222 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move\r
223 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book\r
224 extern char installDir[MSG_SIZ];\r
225 \r
226 extern int tinyLayout, smallLayout;\r
227 ChessProgramStats programStats;\r
228 static int exiting = 0; /* [HGM] moved to top */\r
229 static int setboardSpoiledMachineBlack = 0, errorExitFlag = 0;\r
230 extern int startedFromPositionFile;\r
231 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */\r
232 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */\r
233 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */\r
234 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */\r
235 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */\r
236 int opponentKibitzes;\r
237 \r
238 /* States for ics_getting_history */\r
239 #define H_FALSE 0\r
240 #define H_REQUESTED 1\r
241 #define H_GOT_REQ_HEADER 2\r
242 #define H_GOT_UNREQ_HEADER 3\r
243 #define H_GETTING_MOVES 4\r
244 #define H_GOT_UNWANTED_HEADER 5\r
245 \r
246 /* whosays values for GameEnds */\r
247 #define GE_ICS 0\r
248 #define GE_ENGINE 1\r
249 #define GE_PLAYER 2\r
250 #define GE_FILE 3\r
251 #define GE_XBOARD 4\r
252 #define GE_ENGINE1 5\r
253 #define GE_ENGINE2 6\r
254 \r
255 /* Maximum number of games in a cmail message */\r
256 #define CMAIL_MAX_GAMES 20\r
257 \r
258 /* Different types of move when calling RegisterMove */\r
259 #define CMAIL_MOVE   0\r
260 #define CMAIL_RESIGN 1\r
261 #define CMAIL_DRAW   2\r
262 #define CMAIL_ACCEPT 3\r
263 \r
264 /* Different types of result to remember for each game */\r
265 #define CMAIL_NOT_RESULT 0\r
266 #define CMAIL_OLD_RESULT 1\r
267 #define CMAIL_NEW_RESULT 2\r
268 \r
269 /* Telnet protocol constants */\r
270 #define TN_WILL 0373\r
271 #define TN_WONT 0374\r
272 #define TN_DO   0375\r
273 #define TN_DONT 0376\r
274 #define TN_IAC  0377\r
275 #define TN_ECHO 0001\r
276 #define TN_SGA  0003\r
277 #define TN_PORT 23\r
278 \r
279 /* [AS] */\r
280 static char * safeStrCpy( char * dst, const char * src, size_t count )\r
281 {\r
282     assert( dst != NULL );\r
283     assert( src != NULL );\r
284     assert( count > 0 );\r
285 \r
286     strncpy( dst, src, count );\r
287     dst[ count-1 ] = '\0';\r
288     return dst;\r
289 }\r
290 \r
291 static char * safeStrCat( char * dst, const char * src, size_t count )\r
292 {\r
293     size_t  dst_len;\r
294 \r
295     assert( dst != NULL );\r
296     assert( src != NULL );\r
297     assert( count > 0 );\r
298 \r
299     dst_len = strlen(dst);\r
300 \r
301     assert( count > dst_len ); /* Buffer size must be greater than current length */\r
302 \r
303     safeStrCpy( dst + dst_len, src, count - dst_len );\r
304 \r
305     return dst;\r
306 }\r
307 \r
308 /* Some compiler can't cast u64 to double\r
309  * This function do the job for us:\r
310 \r
311  * We use the highest bit for cast, this only\r
312  * works if the highest bit is not\r
313  * in use (This should not happen)\r
314  *\r
315  * We used this for all compiler\r
316  */\r
317 double\r
318 u64ToDouble(u64 value)\r
319 {\r
320   double r;\r
321   u64 tmp = value & u64Const(0x7fffffffffffffff);\r
322   r = (double)(s64)tmp;\r
323   if (value & u64Const(0x8000000000000000))\r
324        r +=  9.2233720368547758080e18; /* 2^63 */\r
325  return r;\r
326 }\r
327 \r
328 /* Fake up flags for now, as we aren't keeping track of castling\r
329    availability yet. [HGM] Change of logic: the flag now only\r
330    indicates the type of castlings allowed by the rule of the game.\r
331    The actual rights themselves are maintained in the array\r
332    castlingRights, as part of the game history, and are not probed\r
333    by this function.\r
334  */\r
335 int\r
336 PosFlags(index)\r
337 {\r
338   int flags = F_ALL_CASTLE_OK;\r
339   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;\r
340   switch (gameInfo.variant) {\r
341   case VariantSuicide:\r
342     flags &= ~F_ALL_CASTLE_OK;\r
343   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!\r
344     flags |= F_IGNORE_CHECK;\r
345     break;\r
346   case VariantAtomic:\r
347     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;\r
348     break;\r
349   case VariantKriegspiel:\r
350     flags |= F_KRIEGSPIEL_CAPTURE;\r
351     break;\r
352   case VariantCapaRandom: \r
353   case VariantFischeRandom:\r
354     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */\r
355   case VariantNoCastle:\r
356   case VariantShatranj:\r
357   case VariantCourier:\r
358     flags &= ~F_ALL_CASTLE_OK;\r
359     break;\r
360   default:\r
361     break;\r
362   }\r
363   return flags;\r
364 }\r
365 \r
366 FILE *gameFileFP, *debugFP;\r
367 \r
368 /* \r
369     [AS] Note: sometimes, the sscanf() function is used to parse the input\r
370     into a fixed-size buffer. Because of this, we must be prepared to\r
371     receive strings as long as the size of the input buffer, which is currently\r
372     set to 4K for Windows and 8K for the rest.\r
373     So, we must either allocate sufficiently large buffers here, or\r
374     reduce the size of the input buffer in the input reading part.\r
375 */\r
376 \r
377 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];\r
378 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];\r
379 char thinkOutput1[MSG_SIZ*10];\r
380 \r
381 ChessProgramState first, second;\r
382 \r
383 /* premove variables */\r
384 int premoveToX = 0;\r
385 int premoveToY = 0;\r
386 int premoveFromX = 0;\r
387 int premoveFromY = 0;\r
388 int premovePromoChar = 0;\r
389 int gotPremove = 0;\r
390 Boolean alarmSounded;\r
391 /* end premove variables */\r
392 \r
393 char *ics_prefix = "$";\r
394 int ics_type = ICS_GENERIC;\r
395 \r
396 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;\r
397 int pauseExamForwardMostMove = 0;\r
398 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;\r
399 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];\r
400 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;\r
401 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;\r
402 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;\r
403 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;\r
404 int whiteFlag = FALSE, blackFlag = FALSE;\r
405 int userOfferedDraw = FALSE;\r
406 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;\r
407 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;\r
408 int cmailMoveType[CMAIL_MAX_GAMES];\r
409 long ics_clock_paused = 0;\r
410 ProcRef icsPR = NoProc, cmailPR = NoProc;\r
411 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;\r
412 GameMode gameMode = BeginningOfGame;\r
413 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];\r
414 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];\r
415 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */\r
416 int hiddenThinkOutputState = 0; /* [AS] */\r
417 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */\r
418 int adjudicateLossPlies = 6;\r
419 char white_holding[64], black_holding[64];\r
420 TimeMark lastNodeCountTime;\r
421 long lastNodeCount=0;\r
422 int have_sent_ICS_logon = 0;\r
423 int movesPerSession;\r
424 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;\r
425 long timeControl_2; /* [AS] Allow separate time controls */\r
426 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */\r
427 long timeRemaining[2][MAX_MOVES];\r
428 int matchGame = 0;\r
429 TimeMark programStartTime;\r
430 char ics_handle[MSG_SIZ];\r
431 int have_set_title = 0;\r
432 \r
433 /* animateTraining preserves the state of appData.animate\r
434  * when Training mode is activated. This allows the\r
435  * response to be animated when appData.animate == TRUE and\r
436  * appData.animateDragging == TRUE.\r
437  */\r
438 Boolean animateTraining;\r
439 \r
440 GameInfo gameInfo;\r
441 \r
442 AppData appData;\r
443 \r
444 Board boards[MAX_MOVES];\r
445 /* [HGM] Following 7 needed for accurate legality tests: */\r
446 char  epStatus[MAX_MOVES];\r
447 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1\r
448 char  castlingRank[BOARD_SIZE]; // and corresponding ranks\r
449 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];\r
450 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status\r
451 int   initialRulePlies, FENrulePlies;\r
452 char  FENepStatus;\r
453 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)\r
454 int loadFlag = 0; \r
455 int shuffleOpenings;\r
456 \r
457 ChessSquare  FIDEArray[2][BOARD_SIZE] = {\r
458     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
459         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
460     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
461         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
462 };\r
463 \r
464 ChessSquare twoKingsArray[2][BOARD_SIZE] = {\r
465     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
466         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },\r
467     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
468         BlackKing, BlackKing, BlackKnight, BlackRook }\r
469 };\r
470 \r
471 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {\r
472     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,\r
473         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },\r
474     { BlackRook, BlackMan, BlackBishop, BlackQueen,\r
475         BlackUnicorn, BlackBishop, BlackMan, BlackRook }\r
476 };\r
477 \r
478 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */\r
479     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,\r
480         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
481     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,\r
482         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
483 };\r
484 \r
485 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */\r
486     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,\r
487         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
488     { BlackRook, BlackKnight, BlackAlfil, BlackKing,\r
489         BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
490 };\r
491 \r
492 \r
493 #if (BOARD_SIZE>=10)\r
494 ChessSquare ShogiArray[2][BOARD_SIZE] = {\r
495     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,\r
496         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },\r
497     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,\r
498         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }\r
499 };\r
500 \r
501 ChessSquare XiangqiArray[2][BOARD_SIZE] = {\r
502     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,\r
503         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
504     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,\r
505         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
506 };\r
507 \r
508 ChessSquare CapablancaArray[2][BOARD_SIZE] = {\r
509     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, \r
510         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },\r
511     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, \r
512         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
513 };\r
514 \r
515 ChessSquare GreatArray[2][BOARD_SIZE] = {\r
516     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, \r
517         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },\r
518     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, \r
519         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },\r
520 };\r
521 \r
522 ChessSquare JanusArray[2][BOARD_SIZE] = {\r
523     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, \r
524         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },\r
525     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, \r
526         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }\r
527 };\r
528 \r
529 #ifdef GOTHIC\r
530 ChessSquare GothicArray[2][BOARD_SIZE] = {\r
531     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, \r
532         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },\r
533     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, \r
534         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }\r
535 };\r
536 #else // !GOTHIC\r
537 #define GothicArray CapablancaArray\r
538 #endif // !GOTHIC\r
539 \r
540 #ifdef FALCON\r
541 ChessSquare FalconArray[2][BOARD_SIZE] = {\r
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, \r
543         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },\r
544     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, \r
545         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }\r
546 };\r
547 #else // !FALCON\r
548 #define FalconArray CapablancaArray\r
549 #endif // !FALCON\r
550 \r
551 #else // !(BOARD_SIZE>=10)\r
552 #define XiangqiPosition FIDEArray\r
553 #define CapablancaArray FIDEArray\r
554 #define GothicArray FIDEArray\r
555 #define GreatArray FIDEArray\r
556 #endif // !(BOARD_SIZE>=10)\r
557 \r
558 #if (BOARD_SIZE>=12)\r
559 ChessSquare CourierArray[2][BOARD_SIZE] = {\r
560     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,\r
561         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },\r
562     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,\r
563         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }\r
564 };\r
565 #else // !(BOARD_SIZE>=12)\r
566 #define CourierArray CapablancaArray\r
567 #endif // !(BOARD_SIZE>=12)\r
568 \r
569 \r
570 Board initialPosition;\r
571 \r
572 \r
573 /* Convert str to a rating. Checks for special cases of "----",\r
574 \r
575    "++++", etc. Also strips ()'s */\r
576 int\r
577 string_to_rating(str)\r
578   char *str;\r
579 {\r
580   while(*str && !isdigit(*str)) ++str;\r
581   if (!*str)\r
582     return 0;   /* One of the special "no rating" cases */\r
583   else\r
584     return atoi(str);\r
585 }\r
586 \r
587 void\r
588 ClearProgramStats()\r
589 {\r
590     /* Init programStats */\r
591     programStats.movelist[0] = 0;\r
592     programStats.depth = 0;\r
593     programStats.nr_moves = 0;\r
594     programStats.moves_left = 0;\r
595     programStats.nodes = 0;\r
596     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output\r
597     programStats.score = 0;\r
598     programStats.got_only_move = 0;\r
599     programStats.got_fail = 0;\r
600     programStats.line_is_book = 0;\r
601 }\r
602 \r
603 void\r
604 InitBackEnd1()\r
605 {\r
606     int matched, min, sec;\r
607 \r
608     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options\r
609 \r
610     GetTimeMark(&programStartTime);\r
611 \r
612     ClearProgramStats();\r
613     programStats.ok_to_send = 1;\r
614     programStats.seen_stat = 0;\r
615 \r
616     /*\r
617      * Initialize game list\r
618      */\r
619     ListNew(&gameList);\r
620 \r
621 \r
622     /*\r
623      * Internet chess server status\r
624      */\r
625     if (appData.icsActive) {\r
626         appData.matchMode = FALSE;\r
627         appData.matchGames = 0;\r
628 #if ZIPPY       \r
629         appData.noChessProgram = !appData.zippyPlay;\r
630 #else\r
631         appData.zippyPlay = FALSE;\r
632         appData.zippyTalk = FALSE;\r
633         appData.noChessProgram = TRUE;\r
634 #endif\r
635         if (*appData.icsHelper != NULLCHAR) {\r
636             appData.useTelnet = TRUE;\r
637             appData.telnetProgram = appData.icsHelper;\r
638         }\r
639     } else {\r
640         appData.zippyTalk = appData.zippyPlay = FALSE;\r
641     }\r
642 \r
643     /* [AS] Initialize pv info list [HGM] and game state */\r
644     {\r
645         int i, j;\r
646 \r
647         for( i=0; i<MAX_MOVES; i++ ) {\r
648             pvInfoList[i].depth = -1;\r
649             epStatus[i]=EP_NONE;\r
650             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
651         }\r
652     }\r
653 \r
654     /*\r
655      * Parse timeControl resource\r
656      */\r
657     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,\r
658                           appData.movesPerSession)) {\r
659         char buf[MSG_SIZ];\r
660         sprintf(buf, _("bad timeControl option %s"), appData.timeControl);\r
661         DisplayFatalError(buf, 0, 2);\r
662     }\r
663 \r
664     /*\r
665      * Parse searchTime resource\r
666      */\r
667     if (*appData.searchTime != NULLCHAR) {\r
668         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);\r
669         if (matched == 1) {\r
670             searchTime = min * 60;\r
671         } else if (matched == 2) {\r
672             searchTime = min * 60 + sec;\r
673         } else {\r
674             char buf[MSG_SIZ];\r
675             sprintf(buf, _("bad searchTime option %s"), appData.searchTime);\r
676             DisplayFatalError(buf, 0, 2);\r
677         }\r
678     }\r
679 \r
680     /* [AS] Adjudication threshold */\r
681     adjudicateLossThreshold = appData.adjudicateLossThreshold;\r
682     \r
683     first.which = "first";\r
684     second.which = "second";\r
685     first.maybeThinking = second.maybeThinking = FALSE;\r
686     first.pr = second.pr = NoProc;\r
687     first.isr = second.isr = NULL;\r
688     first.sendTime = second.sendTime = 2;\r
689     first.sendDrawOffers = 1;\r
690     if (appData.firstPlaysBlack) {\r
691         first.twoMachinesColor = "black\n";\r
692         second.twoMachinesColor = "white\n";\r
693     } else {\r
694         first.twoMachinesColor = "white\n";\r
695         second.twoMachinesColor = "black\n";\r
696     }\r
697     first.program = appData.firstChessProgram;\r
698     second.program = appData.secondChessProgram;\r
699     first.host = appData.firstHost;\r
700     second.host = appData.secondHost;\r
701     first.dir = appData.firstDirectory;\r
702     second.dir = appData.secondDirectory;\r
703     first.other = &second;\r
704     second.other = &first;\r
705     first.initString = appData.initString;\r
706     second.initString = appData.secondInitString;\r
707     first.computerString = appData.firstComputerString;\r
708     second.computerString = appData.secondComputerString;\r
709     first.useSigint = second.useSigint = TRUE;\r
710     first.useSigterm = second.useSigterm = TRUE;\r
711     first.reuse = appData.reuseFirst;\r
712     second.reuse = appData.reuseSecond;\r
713     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second\r
714     second.nps = appData.secondNPS;\r
715     first.useSetboard = second.useSetboard = FALSE;\r
716     first.useSAN = second.useSAN = FALSE;\r
717     first.usePing = second.usePing = FALSE;\r
718     first.lastPing = second.lastPing = 0;\r
719     first.lastPong = second.lastPong = 0;\r
720     first.usePlayother = second.usePlayother = FALSE;\r
721     first.useColors = second.useColors = TRUE;\r
722     first.useUsermove = second.useUsermove = FALSE;\r
723     first.sendICS = second.sendICS = FALSE;\r
724     first.sendName = second.sendName = appData.icsActive;\r
725     first.sdKludge = second.sdKludge = FALSE;\r
726     first.stKludge = second.stKludge = FALSE;\r
727     TidyProgramName(first.program, first.host, first.tidy);\r
728     TidyProgramName(second.program, second.host, second.tidy);\r
729     first.matchWins = second.matchWins = 0;\r
730     strcpy(first.variants, appData.variant);\r
731     strcpy(second.variants, appData.variant);\r
732     first.analysisSupport = second.analysisSupport = 2; /* detect */\r
733     first.analyzing = second.analyzing = FALSE;\r
734     first.initDone = second.initDone = FALSE;\r
735 \r
736     /* New features added by Tord: */\r
737     first.useFEN960 = FALSE; second.useFEN960 = FALSE;\r
738     first.useOOCastle = TRUE; second.useOOCastle = TRUE;\r
739     /* End of new features added by Tord. */\r
740 \r
741     /* [HGM] time odds: set factor for each machine */\r
742     first.timeOdds  = appData.firstTimeOdds;\r
743     second.timeOdds = appData.secondTimeOdds;\r
744     { int norm = 1;\r
745         if(appData.timeOddsMode) {\r
746             norm = first.timeOdds;\r
747             if(norm > second.timeOdds) norm = second.timeOdds;\r
748         }\r
749         first.timeOdds /= norm;\r
750         second.timeOdds /= norm;\r
751     }\r
752 \r
753     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/\r
754     first.accumulateTC = appData.firstAccumulateTC;\r
755     second.accumulateTC = appData.secondAccumulateTC;\r
756     first.maxNrOfSessions = second.maxNrOfSessions = 1;\r
757 \r
758     /* [HGM] debug */\r
759     first.debug = second.debug = FALSE;\r
760     first.supportsNPS = second.supportsNPS = UNKNOWN;\r
761 \r
762     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
763     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
764     first.isUCI = appData.firstIsUCI; /* [AS] */\r
765     second.isUCI = appData.secondIsUCI; /* [AS] */\r
766     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
767     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
768 \r
769     if (appData.firstProtocolVersion > PROTOVER ||\r
770         appData.firstProtocolVersion < 1) {\r
771       char buf[MSG_SIZ];\r
772       sprintf(buf, _("protocol version %d not supported"),\r
773               appData.firstProtocolVersion);\r
774       DisplayFatalError(buf, 0, 2);\r
775     } else {\r
776       first.protocolVersion = appData.firstProtocolVersion;\r
777     }\r
778 \r
779     if (appData.secondProtocolVersion > PROTOVER ||\r
780         appData.secondProtocolVersion < 1) {\r
781       char buf[MSG_SIZ];\r
782       sprintf(buf, _("protocol version %d not supported"),\r
783               appData.secondProtocolVersion);\r
784       DisplayFatalError(buf, 0, 2);\r
785     } else {\r
786       second.protocolVersion = appData.secondProtocolVersion;\r
787     }\r
788 \r
789     if (appData.icsActive) {\r
790         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */\r
791     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
792         appData.clockMode = FALSE;\r
793         first.sendTime = second.sendTime = 0;\r
794     }\r
795     \r
796 #if ZIPPY\r
797     /* Override some settings from environment variables, for backward\r
798        compatibility.  Unfortunately it's not feasible to have the env\r
799        vars just set defaults, at least in xboard.  Ugh.\r
800     */\r
801     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
802       ZippyInit();\r
803     }\r
804 #endif\r
805     \r
806     if (appData.noChessProgram) {\r
807         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
808                                         + strlen(PATCHLEVEL));\r
809         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
810     } else {\r
811 #if 0\r
812         char *p, *q;\r
813         q = first.program;\r
814         while (*q != ' ' && *q != NULLCHAR) q++;\r
815         p = q;\r
816         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */\r
817         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
818                                         + strlen(PATCHLEVEL) + (q - p));\r
819         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
820         strncat(programVersion, p, q - p);\r
821 #else\r
822         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */\r
823         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
824                                         + strlen(PATCHLEVEL) + strlen(first.tidy));\r
825         sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);\r
826 #endif\r
827     }\r
828 \r
829     if (!appData.icsActive) {\r
830       char buf[MSG_SIZ];\r
831       /* Check for variants that are supported only in ICS mode,\r
832          or not at all.  Some that are accepted here nevertheless\r
833          have bugs; see comments below.\r
834       */\r
835       VariantClass variant = StringToVariant(appData.variant);\r
836       switch (variant) {\r
837       case VariantBughouse:     /* need four players and two boards */\r
838       case VariantKriegspiel:   /* need to hide pieces and move details */\r
839       /* case VariantFischeRandom: (Fabien: moved below) */\r
840         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);\r
841         DisplayFatalError(buf, 0, 2);\r
842         return;\r
843 \r
844       case VariantUnknown:\r
845       case VariantLoadable:\r
846       case Variant29:\r
847       case Variant30:\r
848       case Variant31:\r
849       case Variant32:\r
850       case Variant33:\r
851       case Variant34:\r
852       case Variant35:\r
853       case Variant36:\r
854       default:\r
855         sprintf(buf, _("Unknown variant name %s"), appData.variant);\r
856         DisplayFatalError(buf, 0, 2);\r
857         return;\r
858 \r
859       case VariantXiangqi:    /* [HGM] repetition rules not implemented */\r
860       case VariantFairy:      /* [HGM] TestLegality definitely off! */\r
861       case VariantGothic:     /* [HGM] should work */\r
862       case VariantCapablanca: /* [HGM] should work */\r
863       case VariantCourier:    /* [HGM] initial forced moves not implemented */\r
864       case VariantShogi:      /* [HGM] drops not tested for legality */\r
865       case VariantKnightmate: /* [HGM] should work */\r
866       case VariantCylinder:   /* [HGM] untested */\r
867       case VariantFalcon:     /* [HGM] untested */\r
868       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)\r
869                                  offboard interposition not understood */\r
870       case VariantNormal:     /* definitely works! */\r
871       case VariantWildCastle: /* pieces not automatically shuffled */\r
872       case VariantNoCastle:   /* pieces not automatically shuffled */\r
873       case VariantFischeRandom: /* [HGM] works and shuffles pieces */\r
874       case VariantLosers:     /* should work except for win condition,\r
875                                  and doesn't know captures are mandatory */\r
876       case VariantSuicide:    /* should work except for win condition,\r
877                                  and doesn't know captures are mandatory */\r
878       case VariantGiveaway:   /* should work except for win condition,\r
879                                  and doesn't know captures are mandatory */\r
880       case VariantTwoKings:   /* should work */\r
881       case VariantAtomic:     /* should work except for win condition */\r
882       case Variant3Check:     /* should work except for win condition */\r
883       case VariantShatranj:   /* should work except for all win conditions */\r
884       case VariantBerolina:   /* might work if TestLegality is off */\r
885       case VariantCapaRandom: /* should work */\r
886       case VariantJanus:      /* should work */\r
887       case VariantSuper:      /* experimental */\r
888       case VariantGreat:      /* experimental, requires legality testing to be off */\r
889         break;\r
890       }\r
891     }\r
892 \r
893     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard\r
894     InitEngineUCI( installDir, &second );\r
895 }\r
896 \r
897 int NextIntegerFromString( char ** str, long * value )\r
898 {\r
899     int result = -1;\r
900     char * s = *str;\r
901 \r
902     while( *s == ' ' || *s == '\t' ) {\r
903         s++;\r
904     }\r
905 \r
906     *value = 0;\r
907 \r
908     if( *s >= '0' && *s <= '9' ) {\r
909         while( *s >= '0' && *s <= '9' ) {\r
910             *value = *value * 10 + (*s - '0');\r
911             s++;\r
912         }\r
913 \r
914         result = 0;\r
915     }\r
916 \r
917     *str = s;\r
918 \r
919     return result;\r
920 }\r
921 \r
922 int NextTimeControlFromString( char ** str, long * value )\r
923 {\r
924     long temp;\r
925     int result = NextIntegerFromString( str, &temp );\r
926 \r
927     if( result == 0 ) {\r
928         *value = temp * 60; /* Minutes */\r
929         if( **str == ':' ) {\r
930             (*str)++;\r
931             result = NextIntegerFromString( str, &temp );\r
932             *value += temp; /* Seconds */\r
933         }\r
934     }\r
935 \r
936     return result;\r
937 }\r
938 \r
939 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)\r
940 {   /* [HGM] routine added to read '+moves/time' for secondary time control */\r
941     int result = -1; long temp, temp2;\r
942 \r
943     if(**str != '+') return -1; // old params remain in force!\r
944     (*str)++;\r
945     if( NextTimeControlFromString( str, &temp ) ) return -1;\r
946 \r
947     if(**str != '/') {\r
948         /* time only: incremental or sudden-death time control */\r
949         if(**str == '+') { /* increment follows; read it */\r
950             (*str)++;\r
951             if(result = NextIntegerFromString( str, &temp2)) return -1;\r
952             *inc = temp2 * 1000;\r
953         } else *inc = 0;\r
954         *moves = 0; *tc = temp * 1000; \r
955         return 0;\r
956     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */\r
957 \r
958     (*str)++; /* classical time control */\r
959     result = NextTimeControlFromString( str, &temp2);\r
960     if(result == 0) {\r
961         *moves = temp/60;\r
962         *tc    = temp2 * 1000;\r
963         *inc   = 0;\r
964     }\r
965     return result;\r
966 }\r
967 \r
968 int GetTimeQuota(int movenr)\r
969 {   /* [HGM] get time to add from the multi-session time-control string */\r
970     int moves=1; /* kludge to force reading of first session */\r
971     long time, increment;\r
972     char *s = fullTimeControlString;\r
973 \r
974     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);\r
975     do {\r
976         if(moves) NextSessionFromString(&s, &moves, &time, &increment);\r
977         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);\r
978         if(movenr == -1) return time;    /* last move before new session     */\r
979         if(!moves) return increment;     /* current session is incremental   */\r
980         if(movenr >= 0) movenr -= moves; /* we already finished this session */\r
981     } while(movenr >= -1);               /* try again for next session       */\r
982 \r
983     return 0; // no new time quota on this move\r
984 }\r
985 \r
986 int\r
987 ParseTimeControl(tc, ti, mps)\r
988      char *tc;\r
989      int ti;\r
990      int mps;\r
991 {\r
992 #if 0\r
993     int matched, min, sec;\r
994 \r
995     matched = sscanf(tc, "%d:%d", &min, &sec);\r
996     if (matched == 1) {\r
997         timeControl = min * 60 * 1000;\r
998     } else if (matched == 2) {\r
999         timeControl = (min * 60 + sec) * 1000;\r
1000     } else {\r
1001         return FALSE;\r
1002     }\r
1003 #else\r
1004     long tc1;\r
1005     long tc2;\r
1006     char buf[MSG_SIZ];\r
1007 \r
1008     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;\r
1009     if(ti > 0) {\r
1010         if(mps)\r
1011              sprintf(buf, "+%d/%s+%d", mps, tc, ti);\r
1012         else sprintf(buf, "+%s+%d", tc, ti);\r
1013     } else {\r
1014         if(mps)\r
1015              sprintf(buf, "+%d/%s", mps, tc);\r
1016         else sprintf(buf, "+%s", tc);\r
1017     }\r
1018     fullTimeControlString = StrSave(buf);\r
1019 \r
1020     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
1021         return FALSE;\r
1022     }\r
1023 \r
1024     if( *tc == '/' ) {\r
1025         /* Parse second time control */\r
1026         tc++;\r
1027 \r
1028         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
1029             return FALSE;\r
1030         }\r
1031 \r
1032         if( tc2 == 0 ) {\r
1033             return FALSE;\r
1034         }\r
1035 \r
1036         timeControl_2 = tc2 * 1000;\r
1037     }\r
1038     else {\r
1039         timeControl_2 = 0;\r
1040     }\r
1041 \r
1042     if( tc1 == 0 ) {\r
1043         return FALSE;\r
1044     }\r
1045 \r
1046     timeControl = tc1 * 1000;\r
1047 #endif\r
1048 \r
1049     if (ti >= 0) {\r
1050         timeIncrement = ti * 1000;  /* convert to ms */\r
1051         movesPerSession = 0;\r
1052     } else {\r
1053         timeIncrement = 0;\r
1054         movesPerSession = mps;\r
1055     }\r
1056     return TRUE;\r
1057 }\r
1058 \r
1059 void\r
1060 InitBackEnd2()\r
1061 {\r
1062     if (appData.debugMode) {\r
1063         fprintf(debugFP, "%s\n", programVersion);\r
1064     }\r
1065 \r
1066     if (appData.matchGames > 0) {\r
1067         appData.matchMode = TRUE;\r
1068     } else if (appData.matchMode) {\r
1069         appData.matchGames = 1;\r
1070     }\r
1071     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */\r
1072         appData.matchGames = appData.sameColorGames;\r
1073     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */\r
1074         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;\r
1075         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;\r
1076     }\r
1077     Reset(TRUE, FALSE);\r
1078     if (appData.noChessProgram || first.protocolVersion == 1) {\r
1079       InitBackEnd3();\r
1080     } else {\r
1081       /* kludge: allow timeout for initial "feature" commands */\r
1082       FreezeUI();\r
1083       DisplayMessage("", _("Starting chess program"));\r
1084       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
1085     }\r
1086 }\r
1087 \r
1088 void\r
1089 InitBackEnd3 P((void))\r
1090 {\r
1091     GameMode initialMode;\r
1092     char buf[MSG_SIZ];\r
1093     int err;\r
1094 \r
1095     InitChessProgram(&first, startedFromSetupPosition);\r
1096 \r
1097 \r
1098     if (appData.icsActive) {\r
1099 #ifdef WIN32\r
1100         /* [DM] Make a console window if needed [HGM] merged ifs */\r
1101         ConsoleCreate(); \r
1102 #endif\r
1103         err = establish();\r
1104         if (err != 0) {\r
1105             if (*appData.icsCommPort != NULLCHAR) {\r
1106                 sprintf(buf, _("Could not open comm port %s"),  \r
1107                         appData.icsCommPort);\r
1108             } else {\r
1109                 sprintf(buf, _("Could not connect to host %s, port %s"),  \r
1110                         appData.icsHost, appData.icsPort);\r
1111             }\r
1112             DisplayFatalError(buf, err, 1);\r
1113             return;\r
1114         }\r
1115         SetICSMode();\r
1116         telnetISR =\r
1117           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
1118         fromUserISR =\r
1119           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
1120     } else if (appData.noChessProgram) {\r
1121         SetNCPMode();\r
1122     } else {\r
1123         SetGNUMode();\r
1124     }\r
1125 \r
1126     if (*appData.cmailGameName != NULLCHAR) {\r
1127         SetCmailMode();\r
1128         OpenLoopback(&cmailPR);\r
1129         cmailISR =\r
1130           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
1131     }\r
1132     \r
1133     ThawUI();\r
1134     DisplayMessage("", "");\r
1135     if (StrCaseCmp(appData.initialMode, "") == 0) {\r
1136       initialMode = BeginningOfGame;\r
1137     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
1138       initialMode = TwoMachinesPlay;\r
1139     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
1140       initialMode = AnalyzeFile; \r
1141     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
1142       initialMode = AnalyzeMode;\r
1143     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
1144       initialMode = MachinePlaysWhite;\r
1145     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
1146       initialMode = MachinePlaysBlack;\r
1147     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
1148       initialMode = EditGame;\r
1149     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
1150       initialMode = EditPosition;\r
1151     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
1152       initialMode = Training;\r
1153     } else {\r
1154       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);\r
1155       DisplayFatalError(buf, 0, 2);\r
1156       return;\r
1157     }\r
1158 \r
1159     if (appData.matchMode) {\r
1160         /* Set up machine vs. machine match */\r
1161         if (appData.noChessProgram) {\r
1162             DisplayFatalError(_("Can't have a match with no chess programs"),\r
1163                               0, 2);\r
1164             return;\r
1165         }\r
1166         matchMode = TRUE;\r
1167         matchGame = 1;\r
1168         if (*appData.loadGameFile != NULLCHAR) {\r
1169             int index = appData.loadGameIndex; // [HGM] autoinc\r
1170             if(index<0) lastIndex = index = 1;\r
1171             if (!LoadGameFromFile(appData.loadGameFile,\r
1172                                   index,\r
1173                                   appData.loadGameFile, FALSE)) {\r
1174                 DisplayFatalError(_("Bad game file"), 0, 1);\r
1175                 return;\r
1176             }\r
1177         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1178             int index = appData.loadPositionIndex; // [HGM] autoinc\r
1179             if(index<0) lastIndex = index = 1;\r
1180             if (!LoadPositionFromFile(appData.loadPositionFile,\r
1181                                       index,\r
1182                                       appData.loadPositionFile)) {\r
1183                 DisplayFatalError(_("Bad position file"), 0, 1);\r
1184                 return;\r
1185             }\r
1186         }\r
1187         TwoMachinesEvent();\r
1188     } else if (*appData.cmailGameName != NULLCHAR) {\r
1189         /* Set up cmail mode */\r
1190         ReloadCmailMsgEvent(TRUE);\r
1191     } else {\r
1192         /* Set up other modes */\r
1193         if (initialMode == AnalyzeFile) {\r
1194           if (*appData.loadGameFile == NULLCHAR) {\r
1195             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);\r
1196             return;\r
1197           }\r
1198         }\r
1199         if (*appData.loadGameFile != NULLCHAR) {\r
1200             (void) LoadGameFromFile(appData.loadGameFile,\r
1201                                     appData.loadGameIndex,\r
1202                                     appData.loadGameFile, TRUE);\r
1203         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1204             (void) LoadPositionFromFile(appData.loadPositionFile,\r
1205                                         appData.loadPositionIndex,\r
1206                                         appData.loadPositionFile);\r
1207             /* [HGM] try to make self-starting even after FEN load */\r
1208             /* to allow automatic setup of fairy variants with wtm */\r
1209             if(initialMode == BeginningOfGame && !blackPlaysFirst) {\r
1210                 gameMode = BeginningOfGame;\r
1211                 setboardSpoiledMachineBlack = 1;\r
1212             }\r
1213             /* [HGM] loadPos: make that every new game uses the setup */\r
1214             /* from file as long as we do not switch variant          */\r
1215             if(!blackPlaysFirst) { int i;\r
1216                 startedFromPositionFile = TRUE;\r
1217                 CopyBoard(filePosition, boards[0]);\r
1218                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];\r
1219             }\r
1220         }\r
1221         if (initialMode == AnalyzeMode) {\r
1222           if (appData.noChessProgram) {\r
1223             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);\r
1224             return;\r
1225           }\r
1226           if (appData.icsActive) {\r
1227             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);\r
1228             return;\r
1229           }\r
1230           AnalyzeModeEvent();\r
1231         } else if (initialMode == AnalyzeFile) {\r
1232           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent\r
1233           ShowThinkingEvent();\r
1234           AnalyzeFileEvent();\r
1235           AnalysisPeriodicEvent(1);\r
1236         } else if (initialMode == MachinePlaysWhite) {\r
1237           if (appData.noChessProgram) {\r
1238             DisplayFatalError(_("MachineWhite mode requires a chess engine"),\r
1239                               0, 2);\r
1240             return;\r
1241           }\r
1242           if (appData.icsActive) {\r
1243             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),\r
1244                               0, 2);\r
1245             return;\r
1246           }\r
1247           MachineWhiteEvent();\r
1248         } else if (initialMode == MachinePlaysBlack) {\r
1249           if (appData.noChessProgram) {\r
1250             DisplayFatalError(_("MachineBlack mode requires a chess engine"),\r
1251                               0, 2);\r
1252             return;\r
1253           }\r
1254           if (appData.icsActive) {\r
1255             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),\r
1256                               0, 2);\r
1257             return;\r
1258           }\r
1259           MachineBlackEvent();\r
1260         } else if (initialMode == TwoMachinesPlay) {\r
1261           if (appData.noChessProgram) {\r
1262             DisplayFatalError(_("TwoMachines mode requires a chess engine"),\r
1263                               0, 2);\r
1264             return;\r
1265           }\r
1266           if (appData.icsActive) {\r
1267             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),\r
1268                               0, 2);\r
1269             return;\r
1270           }\r
1271           TwoMachinesEvent();\r
1272         } else if (initialMode == EditGame) {\r
1273           EditGameEvent();\r
1274         } else if (initialMode == EditPosition) {\r
1275           EditPositionEvent();\r
1276         } else if (initialMode == Training) {\r
1277           if (*appData.loadGameFile == NULLCHAR) {\r
1278             DisplayFatalError(_("Training mode requires a game file"), 0, 2);\r
1279             return;\r
1280           }\r
1281           TrainingEvent();\r
1282         }\r
1283     }\r
1284 }\r
1285 \r
1286 /*\r
1287  * Establish will establish a contact to a remote host.port.\r
1288  * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
1289  *  used to talk to the host.\r
1290  * Returns 0 if okay, error code if not.\r
1291  */\r
1292 int\r
1293 establish()\r
1294 {\r
1295     char buf[MSG_SIZ];\r
1296 \r
1297     if (*appData.icsCommPort != NULLCHAR) {\r
1298         /* Talk to the host through a serial comm port */\r
1299         return OpenCommPort(appData.icsCommPort, &icsPR);\r
1300 \r
1301     } else if (*appData.gateway != NULLCHAR) {\r
1302         if (*appData.remoteShell == NULLCHAR) {\r
1303             /* Use the rcmd protocol to run telnet program on a gateway host */\r
1304             sprintf(buf, "%s %s %s",\r
1305                     appData.telnetProgram, appData.icsHost, appData.icsPort);\r
1306             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
1307 \r
1308         } else {\r
1309             /* Use the rsh program to run telnet program on a gateway host */\r
1310             if (*appData.remoteUser == NULLCHAR) {\r
1311                 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,\r
1312                         appData.gateway, appData.telnetProgram,\r
1313                         appData.icsHost, appData.icsPort);\r
1314             } else {\r
1315                 sprintf(buf, "%s %s -l %s %s %s %s",\r
1316                         appData.remoteShell, appData.gateway, \r
1317                         appData.remoteUser, appData.telnetProgram,\r
1318                         appData.icsHost, appData.icsPort);\r
1319             }\r
1320             return StartChildProcess(buf, "", &icsPR);\r
1321 \r
1322         }\r
1323     } else if (appData.useTelnet) {\r
1324         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
1325 \r
1326     } else {\r
1327         /* TCP socket interface differs somewhat between\r
1328            Unix and NT; handle details in the front end.\r
1329            */\r
1330         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
1331     }\r
1332 }\r
1333 \r
1334 void\r
1335 show_bytes(fp, buf, count)\r
1336      FILE *fp;\r
1337      char *buf;\r
1338      int count;\r
1339 {\r
1340     while (count--) {\r
1341         if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
1342             fprintf(fp, "\\%03o", *buf & 0xff);\r
1343         } else {\r
1344             putc(*buf, fp);\r
1345         }\r
1346         buf++;\r
1347     }\r
1348     fflush(fp);\r
1349 }\r
1350 \r
1351 /* Returns an errno value */\r
1352 int\r
1353 OutputMaybeTelnet(pr, message, count, outError)\r
1354      ProcRef pr;\r
1355      char *message;\r
1356      int count;\r
1357      int *outError;\r
1358 {\r
1359     char buf[8192], *p, *q, *buflim;\r
1360     int left, newcount, outcount;\r
1361 \r
1362     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
1363         *appData.gateway != NULLCHAR) {\r
1364         if (appData.debugMode) {\r
1365             fprintf(debugFP, ">ICS: ");\r
1366             show_bytes(debugFP, message, count);\r
1367             fprintf(debugFP, "\n");\r
1368         }\r
1369         return OutputToProcess(pr, message, count, outError);\r
1370     }\r
1371 \r
1372     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
1373     p = message;\r
1374     q = buf;\r
1375     left = count;\r
1376     newcount = 0;\r
1377     while (left) {\r
1378         if (q >= buflim) {\r
1379             if (appData.debugMode) {\r
1380                 fprintf(debugFP, ">ICS: ");\r
1381                 show_bytes(debugFP, buf, newcount);\r
1382                 fprintf(debugFP, "\n");\r
1383             }\r
1384             outcount = OutputToProcess(pr, buf, newcount, outError);\r
1385             if (outcount < newcount) return -1; /* to be sure */\r
1386             q = buf;\r
1387             newcount = 0;\r
1388         }\r
1389         if (*p == '\n') {\r
1390             *q++ = '\r';\r
1391             newcount++;\r
1392         } else if (((unsigned char) *p) == TN_IAC) {\r
1393             *q++ = (char) TN_IAC;\r
1394             newcount ++;\r
1395         }\r
1396         *q++ = *p++;\r
1397         newcount++;\r
1398         left--;\r
1399     }\r
1400     if (appData.debugMode) {\r
1401         fprintf(debugFP, ">ICS: ");\r
1402         show_bytes(debugFP, buf, newcount);\r
1403         fprintf(debugFP, "\n");\r
1404     }\r
1405     outcount = OutputToProcess(pr, buf, newcount, outError);\r
1406     if (outcount < newcount) return -1; /* to be sure */\r
1407     return count;\r
1408 }\r
1409 \r
1410 void\r
1411 read_from_player(isr, closure, message, count, error)\r
1412      InputSourceRef isr;\r
1413      VOIDSTAR closure;\r
1414      char *message;\r
1415      int count;\r
1416      int error;\r
1417 {\r
1418     int outError, outCount;\r
1419     static int gotEof = 0;\r
1420 \r
1421     /* Pass data read from player on to ICS */\r
1422     if (count > 0) {\r
1423         gotEof = 0;\r
1424         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
1425         if (outCount < count) {\r
1426             DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1427         }\r
1428     } else if (count < 0) {\r
1429         RemoveInputSource(isr);\r
1430         DisplayFatalError(_("Error reading from keyboard"), error, 1);\r
1431     } else if (gotEof++ > 0) {\r
1432         RemoveInputSource(isr);\r
1433         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);\r
1434     }\r
1435 }\r
1436 \r
1437 void\r
1438 SendToICS(s)\r
1439      char *s;\r
1440 {\r
1441     int count, outCount, outError;\r
1442 \r
1443     if (icsPR == NULL) return;\r
1444 \r
1445     count = strlen(s);\r
1446     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
1447     if (outCount < count) {\r
1448         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1449     }\r
1450 }\r
1451 \r
1452 /* This is used for sending logon scripts to the ICS. Sending\r
1453    without a delay causes problems when using timestamp on ICC\r
1454    (at least on my machine). */\r
1455 void\r
1456 SendToICSDelayed(s,msdelay)\r
1457      char *s;\r
1458      long msdelay;\r
1459 {\r
1460     int count, outCount, outError;\r
1461 \r
1462     if (icsPR == NULL) return;\r
1463 \r
1464     count = strlen(s);\r
1465     if (appData.debugMode) {\r
1466         fprintf(debugFP, ">ICS: ");\r
1467         show_bytes(debugFP, s, count);\r
1468         fprintf(debugFP, "\n");\r
1469     }\r
1470     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
1471                                       msdelay);\r
1472     if (outCount < count) {\r
1473         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1474     }\r
1475 }\r
1476 \r
1477 \r
1478 /* Remove all highlighting escape sequences in s\r
1479    Also deletes any suffix starting with '(' \r
1480    */\r
1481 char *\r
1482 StripHighlightAndTitle(s)\r
1483      char *s;\r
1484 {\r
1485     static char retbuf[MSG_SIZ];\r
1486     char *p = retbuf;\r
1487 \r
1488     while (*s != NULLCHAR) {\r
1489         while (*s == '\033') {\r
1490             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1491             if (*s != NULLCHAR) s++;\r
1492         }\r
1493         while (*s != NULLCHAR && *s != '\033') {\r
1494             if (*s == '(' || *s == '[') {\r
1495                 *p = NULLCHAR;\r
1496                 return retbuf;\r
1497             }\r
1498             *p++ = *s++;\r
1499         }\r
1500     }\r
1501     *p = NULLCHAR;\r
1502     return retbuf;\r
1503 }\r
1504 \r
1505 /* Remove all highlighting escape sequences in s */\r
1506 char *\r
1507 StripHighlight(s)\r
1508      char *s;\r
1509 {\r
1510     static char retbuf[MSG_SIZ];\r
1511     char *p = retbuf;\r
1512 \r
1513     while (*s != NULLCHAR) {\r
1514         while (*s == '\033') {\r
1515             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1516             if (*s != NULLCHAR) s++;\r
1517         }\r
1518         while (*s != NULLCHAR && *s != '\033') {\r
1519             *p++ = *s++;\r
1520         }\r
1521     }\r
1522     *p = NULLCHAR;\r
1523     return retbuf;\r
1524 }\r
1525 \r
1526 char *variantNames[] = VARIANT_NAMES;\r
1527 char *\r
1528 VariantName(v)\r
1529      VariantClass v;\r
1530 {\r
1531     return variantNames[v];\r
1532 }\r
1533 \r
1534 \r
1535 /* Identify a variant from the strings the chess servers use or the\r
1536    PGN Variant tag names we use. */\r
1537 VariantClass\r
1538 StringToVariant(e)\r
1539      char *e;\r
1540 {\r
1541     char *p;\r
1542     int wnum = -1;\r
1543     VariantClass v = VariantNormal;\r
1544     int i, found = FALSE;\r
1545     char buf[MSG_SIZ];\r
1546 \r
1547     if (!e) return v;\r
1548 \r
1549     /* [HGM] skip over optional board-size prefixes */\r
1550     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||\r
1551         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
1552         while( *e++ != '_');\r
1553     }\r
1554 \r
1555     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1556       if (StrCaseStr(e, variantNames[i])) {\r
1557         v = (VariantClass) i;\r
1558         found = TRUE;\r
1559         break;\r
1560       }\r
1561     }\r
1562 \r
1563     if (!found) {\r
1564       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1565           || StrCaseStr(e, "wild/fr") \r
1566           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {\r
1567         v = VariantFischeRandom;\r
1568       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1569                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1570         p += i;\r
1571         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1572         if (isdigit(*p)) {\r
1573           wnum = atoi(p);\r
1574         } else {\r
1575           wnum = -1;\r
1576         }\r
1577         switch (wnum) {\r
1578         case 0: /* FICS only, actually */\r
1579         case 1:\r
1580           /* Castling legal even if K starts on d-file */\r
1581           v = VariantWildCastle;\r
1582           break;\r
1583         case 2:\r
1584         case 3:\r
1585         case 4:\r
1586           /* Castling illegal even if K & R happen to start in\r
1587              normal positions. */\r
1588           v = VariantNoCastle;\r
1589           break;\r
1590         case 5:\r
1591         case 7:\r
1592         case 8:\r
1593         case 10:\r
1594         case 11:\r
1595         case 12:\r
1596         case 13:\r
1597         case 14:\r
1598         case 15:\r
1599         case 18:\r
1600         case 19:\r
1601           /* Castling legal iff K & R start in normal positions */\r
1602           v = VariantNormal;\r
1603           break;\r
1604         case 6:\r
1605         case 20:\r
1606         case 21:\r
1607           /* Special wilds for position setup; unclear what to do here */\r
1608           v = VariantLoadable;\r
1609           break;\r
1610         case 9:\r
1611           /* Bizarre ICC game */\r
1612           v = VariantTwoKings;\r
1613           break;\r
1614         case 16:\r
1615           v = VariantKriegspiel;\r
1616           break;\r
1617         case 17:\r
1618           v = VariantLosers;\r
1619           break;\r
1620         case 22:\r
1621           v = VariantFischeRandom;\r
1622           break;\r
1623         case 23:\r
1624           v = VariantCrazyhouse;\r
1625           break;\r
1626         case 24:\r
1627           v = VariantBughouse;\r
1628           break;\r
1629         case 25:\r
1630           v = Variant3Check;\r
1631           break;\r
1632         case 26:\r
1633           /* Not quite the same as FICS suicide! */\r
1634           v = VariantGiveaway;\r
1635           break;\r
1636         case 27:\r
1637           v = VariantAtomic;\r
1638           break;\r
1639         case 28:\r
1640           v = VariantShatranj;\r
1641           break;\r
1642 \r
1643         /* Temporary names for future ICC types.  The name *will* change in \r
1644            the next xboard/WinBoard release after ICC defines it. */\r
1645         case 29:\r
1646           v = Variant29;\r
1647           break;\r
1648         case 30:\r
1649           v = Variant30;\r
1650           break;\r
1651         case 31:\r
1652           v = Variant31;\r
1653           break;\r
1654         case 32:\r
1655           v = Variant32;\r
1656           break;\r
1657         case 33:\r
1658           v = Variant33;\r
1659           break;\r
1660         case 34:\r
1661           v = Variant34;\r
1662           break;\r
1663         case 35:\r
1664           v = Variant35;\r
1665           break;\r
1666         case 36:\r
1667           v = Variant36;\r
1668           break;\r
1669         case 37:\r
1670           v = VariantShogi;\r
1671           break;\r
1672         case 38:\r
1673           v = VariantXiangqi;\r
1674           break;\r
1675         case 39:\r
1676           v = VariantCourier;\r
1677           break;\r
1678         case 40:\r
1679           v = VariantGothic;\r
1680           break;\r
1681         case 41:\r
1682           v = VariantCapablanca;\r
1683           break;\r
1684         case 42:\r
1685           v = VariantKnightmate;\r
1686           break;\r
1687         case 43:\r
1688           v = VariantFairy;\r
1689           break;\r
1690         case 44:\r
1691           v = VariantCylinder;\r
1692           break;\r
1693         case 45:\r
1694           v = VariantFalcon;\r
1695           break;\r
1696         case 46:\r
1697           v = VariantCapaRandom;\r
1698           break;\r
1699         case 47:\r
1700           v = VariantBerolina;\r
1701           break;\r
1702         case 48:\r
1703           v = VariantJanus;\r
1704           break;\r
1705         case 49:\r
1706           v = VariantSuper;\r
1707           break;\r
1708         case 50:\r
1709           v = VariantGreat;\r
1710           break;\r
1711         case -1:\r
1712           /* Found "wild" or "w" in the string but no number;\r
1713              must assume it's normal chess. */\r
1714           v = VariantNormal;\r
1715           break;\r
1716         default:\r
1717           sprintf(buf, _("Unknown wild type %d"), wnum);\r
1718           DisplayError(buf, 0);\r
1719           v = VariantUnknown;\r
1720           break;\r
1721         }\r
1722       }\r
1723     }\r
1724     if (appData.debugMode) {\r
1725       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),\r
1726               e, wnum, VariantName(v));\r
1727     }\r
1728     return v;\r
1729 }\r
1730 \r
1731 static int leftover_start = 0, leftover_len = 0;\r
1732 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1733 \r
1734 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1735    advance *index beyond it, and set leftover_start to the new value of\r
1736    *index; else return FALSE.  If pattern contains the character '*', it\r
1737    matches any sequence of characters not containing '\r', '\n', or the\r
1738    character following the '*' (if any), and the matched sequence(s) are\r
1739    copied into star_match.\r
1740    */\r
1741 int\r
1742 looking_at(buf, index, pattern)\r
1743      char *buf;\r
1744      int *index;\r
1745      char *pattern;\r
1746 {\r
1747     char *bufp = &buf[*index], *patternp = pattern;\r
1748     int star_count = 0;\r
1749     char *matchp = star_match[0];\r
1750     \r
1751     for (;;) {\r
1752         if (*patternp == NULLCHAR) {\r
1753             *index = leftover_start = bufp - buf;\r
1754             *matchp = NULLCHAR;\r
1755             return TRUE;\r
1756         }\r
1757         if (*bufp == NULLCHAR) return FALSE;\r
1758         if (*patternp == '*') {\r
1759             if (*bufp == *(patternp + 1)) {\r
1760                 *matchp = NULLCHAR;\r
1761                 matchp = star_match[++star_count];\r
1762                 patternp += 2;\r
1763                 bufp++;\r
1764                 continue;\r
1765             } else if (*bufp == '\n' || *bufp == '\r') {\r
1766                 patternp++;\r
1767                 if (*patternp == NULLCHAR)\r
1768                   continue;\r
1769                 else\r
1770                   return FALSE;\r
1771             } else {\r
1772                 *matchp++ = *bufp++;\r
1773                 continue;\r
1774             }\r
1775         }\r
1776         if (*patternp != *bufp) return FALSE;\r
1777         patternp++;\r
1778         bufp++;\r
1779     }\r
1780 }\r
1781 \r
1782 void\r
1783 SendToPlayer(data, length)\r
1784      char *data;\r
1785      int length;\r
1786 {\r
1787     int error, outCount;\r
1788     outCount = OutputToProcess(NoProc, data, length, &error);\r
1789     if (outCount < length) {\r
1790         DisplayFatalError(_("Error writing to display"), error, 1);\r
1791     }\r
1792 }\r
1793 \r
1794 void\r
1795 PackHolding(packed, holding)\r
1796      char packed[];\r
1797      char *holding;\r
1798 {\r
1799     char *p = holding;\r
1800     char *q = packed;\r
1801     int runlength = 0;\r
1802     int curr = 9999;\r
1803     do {\r
1804         if (*p == curr) {\r
1805             runlength++;\r
1806         } else {\r
1807             switch (runlength) {\r
1808               case 0:\r
1809                 break;\r
1810               case 1:\r
1811                 *q++ = curr;\r
1812                 break;\r
1813               case 2:\r
1814                 *q++ = curr;\r
1815                 *q++ = curr;\r
1816                 break;\r
1817               default:\r
1818                 sprintf(q, "%d", runlength);\r
1819                 while (*q) q++;\r
1820                 *q++ = curr;\r
1821                 break;\r
1822             }\r
1823             runlength = 1;\r
1824             curr = *p;\r
1825         }\r
1826     } while (*p++);\r
1827     *q = NULLCHAR;\r
1828 }\r
1829 \r
1830 /* Telnet protocol requests from the front end */\r
1831 void\r
1832 TelnetRequest(ddww, option)\r
1833      unsigned char ddww, option;\r
1834 {\r
1835     unsigned char msg[3];\r
1836     int outCount, outError;\r
1837 \r
1838     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1839 \r
1840     if (appData.debugMode) {\r
1841         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1842         switch (ddww) {\r
1843           case TN_DO:\r
1844             ddwwStr = "DO";\r
1845             break;\r
1846           case TN_DONT:\r
1847             ddwwStr = "DONT";\r
1848             break;\r
1849           case TN_WILL:\r
1850             ddwwStr = "WILL";\r
1851             break;\r
1852           case TN_WONT:\r
1853             ddwwStr = "WONT";\r
1854             break;\r
1855           default:\r
1856             ddwwStr = buf1;\r
1857             sprintf(buf1, "%d", ddww);\r
1858             break;\r
1859         }\r
1860         switch (option) {\r
1861           case TN_ECHO:\r
1862             optionStr = "ECHO";\r
1863             break;\r
1864           default:\r
1865             optionStr = buf2;\r
1866             sprintf(buf2, "%d", option);\r
1867             break;\r
1868         }\r
1869         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1870     }\r
1871     msg[0] = TN_IAC;\r
1872     msg[1] = ddww;\r
1873     msg[2] = option;\r
1874     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1875     if (outCount < 3) {\r
1876         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1877     }\r
1878 }\r
1879 \r
1880 void\r
1881 DoEcho()\r
1882 {\r
1883     if (!appData.icsActive) return;\r
1884     TelnetRequest(TN_DO, TN_ECHO);\r
1885 }\r
1886 \r
1887 void\r
1888 DontEcho()\r
1889 {\r
1890     if (!appData.icsActive) return;\r
1891     TelnetRequest(TN_DONT, TN_ECHO);\r
1892 }\r
1893 \r
1894 void\r
1895 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
1896 {\r
1897     /* put the holdings sent to us by the server on the board holdings area */\r
1898     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
1899     char p;\r
1900     ChessSquare piece;\r
1901 \r
1902     if(gameInfo.holdingsWidth < 2)  return;\r
1903 \r
1904     if( (int)lowestPiece >= BlackPawn ) {\r
1905         holdingsColumn = 0;\r
1906         countsColumn = 1;\r
1907         holdingsStartRow = BOARD_HEIGHT-1;\r
1908         direction = -1;\r
1909     } else {\r
1910         holdingsColumn = BOARD_WIDTH-1;\r
1911         countsColumn = BOARD_WIDTH-2;\r
1912         holdingsStartRow = 0;\r
1913         direction = 1;\r
1914     }\r
1915 \r
1916     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
1917         board[i][holdingsColumn] = EmptySquare;\r
1918         board[i][countsColumn]   = (ChessSquare) 0;\r
1919     }\r
1920     while( (p=*holdings++) != NULLCHAR ) {\r
1921         piece = CharToPiece( ToUpper(p) );\r
1922         if(piece == EmptySquare) continue;\r
1923         /*j = (int) piece - (int) WhitePawn;*/\r
1924         j = PieceToNumber(piece);\r
1925         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1926         if(j < 0) continue;               /* should not happen */\r
1927         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );\r
1928         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
1929         board[holdingsStartRow+j*direction][countsColumn]++;\r
1930     }\r
1931 \r
1932 }\r
1933 \r
1934 \r
1935 void\r
1936 VariantSwitch(Board board, VariantClass newVariant)\r
1937 {\r
1938    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;\r
1939    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;\r
1940    Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;\r
1941 \r
1942    startedFromPositionFile = FALSE;\r
1943    if(gameInfo.variant == newVariant) return;\r
1944 \r
1945    /* [HGM] This routine is called each time an assignment is made to\r
1946     * gameInfo.variant during a game, to make sure the board sizes\r
1947     * are set to match the new variant. If that means adding or deleting\r
1948     * holdings, we shift the playing board accordingly\r
1949     * This kludge is needed because in ICS observe mode, we get boards\r
1950     * of an ongoing game without knowing the variant, and learn about the\r
1951     * latter only later. This can be because of the move list we requested,\r
1952     * in which case the game history is refilled from the beginning anyway,\r
1953     * but also when receiving holdings of a crazyhouse game. In the latter\r
1954     * case we want to add those holdings to the already received position.\r
1955     */\r
1956 \r
1957 \r
1958   if (appData.debugMode) {\r
1959     fprintf(debugFP, "Switch board from %s to %s\n",\r
1960                VariantName(gameInfo.variant), VariantName(newVariant));\r
1961     setbuf(debugFP, NULL);\r
1962   }\r
1963     shuffleOpenings = 0;       /* [HGM] shuffle */\r
1964     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
1965     switch(newVariant) {\r
1966             case VariantShogi:\r
1967               newWidth = 9;  newHeight = 9;\r
1968               gameInfo.holdingsSize = 7;\r
1969             case VariantBughouse:\r
1970             case VariantCrazyhouse:\r
1971               newHoldingsWidth = 2; break;\r
1972             default:\r
1973               newHoldingsWidth = gameInfo.holdingsSize = 0;\r
1974     }\r
1975 \r
1976     if(newWidth  != gameInfo.boardWidth  ||\r
1977        newHeight != gameInfo.boardHeight ||\r
1978        newHoldingsWidth != gameInfo.holdingsWidth ) {\r
1979 \r
1980         /* shift position to new playing area, if needed */\r
1981         if(newHoldingsWidth > gameInfo.holdingsWidth) {\r
1982            for(i=0; i<BOARD_HEIGHT; i++) \r
1983                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)\r
1984                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
1985                                                      board[i][j];\r
1986            for(i=0; i<newHeight; i++) {\r
1987                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;\r
1988                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;\r
1989            }\r
1990         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {\r
1991            for(i=0; i<BOARD_HEIGHT; i++)\r
1992                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
1993                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
1994                                                  board[i][j];\r
1995         }\r
1996 \r
1997         gameInfo.boardWidth  = newWidth;\r
1998         gameInfo.boardHeight = newHeight;\r
1999         gameInfo.holdingsWidth = newHoldingsWidth;\r
2000         gameInfo.variant = newVariant;\r
2001         InitDrawingSizes(-2, 0);\r
2002 \r
2003         /* [HGM] The following should definitely be solved in a better way */\r
2004 #if 0\r
2005         CopyBoard(board, tempBoard); /* save position in case it is board[0] */\r
2006         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];\r
2007         saveEP = epStatus[0];\r
2008 #endif\r
2009         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */\r
2010 #if 0\r
2011         epStatus[0] = saveEP;\r
2012         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];\r
2013         CopyBoard(tempBoard, board); /* restore position received from ICS   */\r
2014 #endif\r
2015     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }\r
2016 \r
2017     forwardMostMove = oldForwardMostMove;\r
2018     backwardMostMove = oldBackwardMostMove;\r
2019     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */\r
2020 }\r
2021 \r
2022 static int loggedOn = FALSE;\r
2023 \r
2024 /*-- Game start info cache: --*/\r
2025 int gs_gamenum;\r
2026 char gs_kind[MSG_SIZ];\r
2027 static char player1Name[128] = "";\r
2028 static char player2Name[128] = "";\r
2029 static int player1Rating = -1;\r
2030 static int player2Rating = -1;\r
2031 /*----------------------------*/\r
2032 \r
2033 ColorClass curColor = ColorNormal;\r
2034 int suppressKibitz = 0;\r
2035 \r
2036 void\r
2037 read_from_ics(isr, closure, data, count, error)\r
2038      InputSourceRef isr;\r
2039      VOIDSTAR closure;\r
2040      char *data;\r
2041      int count;\r
2042      int error;\r
2043 {\r
2044 #define BUF_SIZE 8192\r
2045 #define STARTED_NONE 0\r
2046 #define STARTED_MOVES 1\r
2047 #define STARTED_BOARD 2\r
2048 #define STARTED_OBSERVE 3\r
2049 #define STARTED_HOLDINGS 4\r
2050 #define STARTED_CHATTER 5\r
2051 #define STARTED_COMMENT 6\r
2052 #define STARTED_MOVES_NOHIDE 7\r
2053     \r
2054     static int started = STARTED_NONE;\r
2055     static char parse[20000];\r
2056     static int parse_pos = 0;\r
2057     static char buf[BUF_SIZE + 1];\r
2058     static int firstTime = TRUE, intfSet = FALSE;\r
2059     static ColorClass prevColor = ColorNormal;\r
2060     static int savingComment = FALSE;\r
2061     char str[500];\r
2062     int i, oldi;\r
2063     int buf_len;\r
2064     int next_out;\r
2065     int tkind;\r
2066     int backup;    /* [DM] For zippy color lines */\r
2067     char *p;\r
2068 \r
2069     if (appData.debugMode) {\r
2070       if (!error) {\r
2071         fprintf(debugFP, "<ICS: ");\r
2072         show_bytes(debugFP, data, count);\r
2073         fprintf(debugFP, "\n");\r
2074       }\r
2075     }\r
2076 \r
2077     if (appData.debugMode) { int f = forwardMostMove;\r
2078         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,\r
2079                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
2080     }\r
2081     if (count > 0) {\r
2082         /* If last read ended with a partial line that we couldn't parse,\r
2083            prepend it to the new read and try again. */\r
2084         if (leftover_len > 0) {\r
2085             for (i=0; i<leftover_len; i++)\r
2086               buf[i] = buf[leftover_start + i];\r
2087         }\r
2088 \r
2089         /* Copy in new characters, removing nulls and \r's */\r
2090         buf_len = leftover_len;\r
2091         for (i = 0; i < count; i++) {\r
2092             if (data[i] != NULLCHAR && data[i] != '\r')\r
2093               buf[buf_len++] = data[i];\r
2094             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && \r
2095                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') \r
2096                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous\r
2097         }\r
2098 \r
2099         buf[buf_len] = NULLCHAR;\r
2100         next_out = leftover_len;\r
2101         leftover_start = 0;\r
2102         \r
2103         i = 0;\r
2104         while (i < buf_len) {\r
2105             /* Deal with part of the TELNET option negotiation\r
2106                protocol.  We refuse to do anything beyond the\r
2107                defaults, except that we allow the WILL ECHO option,\r
2108                which ICS uses to turn off password echoing when we are\r
2109                directly connected to it.  We reject this option\r
2110                if localLineEditing mode is on (always on in xboard)\r
2111                and we are talking to port 23, which might be a real\r
2112                telnet server that will try to keep WILL ECHO on permanently.\r
2113              */\r
2114             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
2115                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
2116                 unsigned char option;\r
2117                 oldi = i;\r
2118                 switch ((unsigned char) buf[++i]) {\r
2119                   case TN_WILL:\r
2120                     if (appData.debugMode)\r
2121                       fprintf(debugFP, "\n<WILL ");\r
2122                     switch (option = (unsigned char) buf[++i]) {\r
2123                       case TN_ECHO:\r
2124                         if (appData.debugMode)\r
2125                           fprintf(debugFP, "ECHO ");\r
2126                         /* Reply only if this is a change, according\r
2127                            to the protocol rules. */\r
2128                         if (remoteEchoOption) break;\r
2129                         if (appData.localLineEditing &&\r
2130                             atoi(appData.icsPort) == TN_PORT) {\r
2131                             TelnetRequest(TN_DONT, TN_ECHO);\r
2132                         } else {\r
2133                             EchoOff();\r
2134                             TelnetRequest(TN_DO, TN_ECHO);\r
2135                             remoteEchoOption = TRUE;\r
2136                         }\r
2137                         break;\r
2138                       default:\r
2139                         if (appData.debugMode)\r
2140                           fprintf(debugFP, "%d ", option);\r
2141                         /* Whatever this is, we don't want it. */\r
2142                         TelnetRequest(TN_DONT, option);\r
2143                         break;\r
2144                     }\r
2145                     break;\r
2146                   case TN_WONT:\r
2147                     if (appData.debugMode)\r
2148                       fprintf(debugFP, "\n<WONT ");\r
2149                     switch (option = (unsigned char) buf[++i]) {\r
2150                       case TN_ECHO:\r
2151                         if (appData.debugMode)\r
2152                           fprintf(debugFP, "ECHO ");\r
2153                         /* Reply only if this is a change, according\r
2154                            to the protocol rules. */\r
2155                         if (!remoteEchoOption) break;\r
2156                         EchoOn();\r
2157                         TelnetRequest(TN_DONT, TN_ECHO);\r
2158                         remoteEchoOption = FALSE;\r
2159                         break;\r
2160                       default:\r
2161                         if (appData.debugMode)\r
2162                           fprintf(debugFP, "%d ", (unsigned char) option);\r
2163                         /* Whatever this is, it must already be turned\r
2164                            off, because we never agree to turn on\r
2165                            anything non-default, so according to the\r
2166                            protocol rules, we don't reply. */\r
2167                         break;\r
2168                     }\r
2169                     break;\r
2170                   case TN_DO:\r
2171                     if (appData.debugMode)\r
2172                       fprintf(debugFP, "\n<DO ");\r
2173                     switch (option = (unsigned char) buf[++i]) {\r
2174                       default:\r
2175                         /* Whatever this is, we refuse to do it. */\r
2176                         if (appData.debugMode)\r
2177                           fprintf(debugFP, "%d ", option);\r
2178                         TelnetRequest(TN_WONT, option);\r
2179                         break;\r
2180                     }\r
2181                     break;\r
2182                   case TN_DONT:\r
2183                     if (appData.debugMode)\r
2184                       fprintf(debugFP, "\n<DONT ");\r
2185                     switch (option = (unsigned char) buf[++i]) {\r
2186                       default:\r
2187                         if (appData.debugMode)\r
2188                           fprintf(debugFP, "%d ", option);\r
2189                         /* Whatever this is, we are already not doing\r
2190                            it, because we never agree to do anything\r
2191                            non-default, so according to the protocol\r
2192                            rules, we don't reply. */\r
2193                         break;\r
2194                     }\r
2195                     break;\r
2196                   case TN_IAC:\r
2197                     if (appData.debugMode)\r
2198                       fprintf(debugFP, "\n<IAC ");\r
2199                     /* Doubled IAC; pass it through */\r
2200                     i--;\r
2201                     break;\r
2202                   default:\r
2203                     if (appData.debugMode)\r
2204                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
2205                     /* Drop all other telnet commands on the floor */\r
2206                     break;\r
2207                 }\r
2208                 if (oldi > next_out)\r
2209                   SendToPlayer(&buf[next_out], oldi - next_out);\r
2210                 if (++i > next_out)\r
2211                   next_out = i;\r
2212                 continue;\r
2213             }\r
2214                 \r
2215             /* OK, this at least will *usually* work */\r
2216             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
2217                 loggedOn = TRUE;\r
2218             }\r
2219             \r
2220             if (loggedOn && !intfSet) {\r
2221                 if (ics_type == ICS_ICC) {\r
2222                   sprintf(str,\r
2223                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
2224                           programVersion);\r
2225 \r
2226                 } else if (ics_type == ICS_CHESSNET) {\r
2227                   sprintf(str, "/style 12\n");\r
2228                 } else {\r
2229                   strcpy(str, "alias $ @\n$set interface ");\r
2230                   strcat(str, programVersion);\r
2231                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
2232 #ifdef WIN32\r
2233                   strcat(str, "$iset nohighlight 1\n");\r
2234 #endif\r
2235                   strcat(str, "$iset lock 1\n$style 12\n");\r
2236                 }\r
2237                 SendToICS(str);\r
2238                 intfSet = TRUE;\r
2239             }\r
2240 \r
2241             if (started == STARTED_COMMENT) {\r
2242                 /* Accumulate characters in comment */\r
2243                 parse[parse_pos++] = buf[i];\r
2244                 if (buf[i] == '\n') {\r
2245                     parse[parse_pos] = NULLCHAR;\r
2246                     if(!suppressKibitz) // [HGM] kibitz\r
2247                         AppendComment(forwardMostMove, StripHighlight(parse));\r
2248                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window\r
2249                         int nrDigit = 0, nrAlph = 0, i;\r
2250                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input\r
2251                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }\r
2252                         parse[parse_pos] = NULLCHAR;\r
2253                         // try to be smart: if it does not look like search info, it should go to\r
2254                         // ICS interaction window after all, not to engine-output window.\r
2255                         for(i=0; i<parse_pos; i++) { // count letters and digits\r
2256                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');\r
2257                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');\r
2258                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');\r
2259                         }\r
2260                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info\r
2261                             OutputKibitz(suppressKibitz, parse);\r
2262                         } else {\r
2263                             char tmp[MSG_SIZ];\r
2264                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);\r
2265                             SendToPlayer(tmp, strlen(tmp));\r
2266                         }\r
2267                     }\r
2268                     started = STARTED_NONE;\r
2269                 } else {\r
2270                     /* Don't match patterns against characters in chatter */\r
2271                     i++;\r
2272                     continue;\r
2273                 }\r
2274             }\r
2275             if (started == STARTED_CHATTER) {\r
2276                 if (buf[i] != '\n') {\r
2277                     /* Don't match patterns against characters in chatter */\r
2278                     i++;\r
2279                     continue;\r
2280                 }\r
2281                 started = STARTED_NONE;\r
2282             }\r
2283 \r
2284             /* Kludge to deal with rcmd protocol */\r
2285             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2286                 DisplayFatalError(&buf[1], 0, 1);\r
2287                 continue;\r
2288             } else {\r
2289                 firstTime = FALSE;\r
2290             }\r
2291 \r
2292             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2293                 ics_type = ICS_ICC;\r
2294                 ics_prefix = "/";\r
2295                 if (appData.debugMode)\r
2296                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2297                 continue;\r
2298             }\r
2299             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2300                 ics_type = ICS_FICS;\r
2301                 ics_prefix = "$";\r
2302                 if (appData.debugMode)\r
2303                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2304                 continue;\r
2305             }\r
2306             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2307                 ics_type = ICS_CHESSNET;\r
2308                 ics_prefix = "/";\r
2309                 if (appData.debugMode)\r
2310                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2311                 continue;\r
2312             }\r
2313 \r
2314             if (!loggedOn &&\r
2315                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2316                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2317                  looking_at(buf, &i, "will be \"*\""))) {\r
2318               strcpy(ics_handle, star_match[0]);\r
2319               continue;\r
2320             }\r
2321 \r
2322             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2323               char buf[MSG_SIZ];\r
2324               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2325               DisplayIcsInteractionTitle(buf);\r
2326               have_set_title = TRUE;\r
2327             }\r
2328 \r
2329             /* skip finger notes */\r
2330             if (started == STARTED_NONE &&\r
2331                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2332                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2333                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2334               started = STARTED_CHATTER;\r
2335               i += 3;\r
2336               continue;\r
2337             }\r
2338 \r
2339             /* skip formula vars */\r
2340             if (started == STARTED_NONE &&\r
2341                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2342               started = STARTED_CHATTER;\r
2343               i += 3;\r
2344               continue;\r
2345             }\r
2346 \r
2347             oldi = i;\r
2348             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window\r
2349             if (appData.autoKibitz && started == STARTED_NONE && \r
2350                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze\r
2351                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {\r
2352                 if(looking_at(buf, &i, "* kibitzes: ") &&\r
2353                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || \r
2354                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent\r
2355                         suppressKibitz = TRUE;\r
2356                         if((StrStr(star_match[0], gameInfo.white) == star_match[0])\r
2357                                 && (gameMode == IcsPlayingWhite) ||\r
2358                            (StrStr(star_match[0], gameInfo.black) == star_match[0])\r
2359                                 && (gameMode == IcsPlayingBlack)   ) // opponent kibitz\r
2360                             started = STARTED_CHATTER; // own kibitz we simply discard\r
2361                         else {\r
2362                             started = STARTED_COMMENT; // make sure it will be collected in parse[]\r
2363                             parse_pos = 0; parse[0] = NULLCHAR;\r
2364                             savingComment = TRUE;\r
2365                             suppressKibitz = gameMode != IcsObserving ? 2 :\r
2366                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;\r
2367                         } \r
2368                         continue;\r
2369                 } else\r
2370                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz\r
2371                     started = STARTED_CHATTER;\r
2372                     suppressKibitz = TRUE;\r
2373                 }\r
2374             } // [HGM] kibitz: end of patch\r
2375 \r
2376             if (appData.zippyTalk || appData.zippyPlay) {\r
2377                 /* [DM] Backup address for color zippy lines */\r
2378                 backup = i;\r
2379 #if ZIPPY\r
2380        #ifdef WIN32\r
2381                if (loggedOn == TRUE)\r
2382                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||\r
2383                           (appData.zippyPlay && ZippyMatch(buf, &backup)));\r
2384        #else\r
2385                 if (ZippyControl(buf, &i) ||\r
2386                     ZippyConverse(buf, &i) ||\r
2387                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2388                       loggedOn = TRUE;\r
2389                       if (!appData.colorize) continue;\r
2390                 }\r
2391        #endif\r
2392 #endif\r
2393             } // [DM] 'else { ' deleted\r
2394                 if (/* Don't color "message" or "messages" output */\r
2395                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2396                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2397                     looking_at(buf, &i, "--* (*:*): ") ||\r
2398                     /* Regular tells and says */\r
2399                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2400                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2401                     looking_at(buf, &i, "* says: ") ||\r
2402                     /* Message notifications (same color as tells) */\r
2403                     looking_at(buf, &i, "* has left a message ") ||\r
2404                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2405                     /* Whispers and kibitzes */\r
2406                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2407                     looking_at(buf, &i, "* kibitzes: ") ||\r
2408                     /* Channel tells */\r
2409                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2410 \r
2411                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2412                       /* Avoid "tells you:" spoofs in channels */\r
2413                      tkind = 3;\r
2414                   }\r
2415                   if (star_match[0][0] == NULLCHAR ||\r
2416                       strchr(star_match[0], ' ') ||\r
2417                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2418                     /* Reject bogus matches */\r
2419                     i = oldi;\r
2420                   } else {\r
2421                     if (appData.colorize) {\r
2422                       if (oldi > next_out) {\r
2423                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2424                         next_out = oldi;\r
2425                       }\r
2426                       switch (tkind) {\r
2427                       case 1:\r
2428                         Colorize(ColorTell, FALSE);\r
2429                         curColor = ColorTell;\r
2430                         break;\r
2431                       case 2:\r
2432                         Colorize(ColorKibitz, FALSE);\r
2433                         curColor = ColorKibitz;\r
2434                         break;\r
2435                       case 3:\r
2436                         p = strrchr(star_match[1], '(');\r
2437                         if (p == NULL) {\r
2438                           p = star_match[1];\r
2439                         } else {\r
2440                           p++;\r
2441                         }\r
2442                         if (atoi(p) == 1) {\r
2443                           Colorize(ColorChannel1, FALSE);\r
2444                           curColor = ColorChannel1;\r
2445                         } else {\r
2446                           Colorize(ColorChannel, FALSE);\r
2447                           curColor = ColorChannel;\r
2448                         }\r
2449                         break;\r
2450                       case 5:\r
2451                         curColor = ColorNormal;\r
2452                         break;\r
2453                       }\r
2454                     }\r
2455                     if (started == STARTED_NONE && appData.autoComment &&\r
2456                         (gameMode == IcsObserving ||\r
2457                          gameMode == IcsPlayingWhite ||\r
2458                          gameMode == IcsPlayingBlack)) {\r
2459                       parse_pos = i - oldi;\r
2460                       memcpy(parse, &buf[oldi], parse_pos);\r
2461                       parse[parse_pos] = NULLCHAR;\r
2462                       started = STARTED_COMMENT;\r
2463                       savingComment = TRUE;\r
2464                     } else {\r
2465                       started = STARTED_CHATTER;\r
2466                       savingComment = FALSE;\r
2467                     }\r
2468                     loggedOn = TRUE;\r
2469                     continue;\r
2470                   }\r
2471                 }\r
2472 \r
2473                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2474                     looking_at(buf, &i, "* c-shouts: ")) {\r
2475                     if (appData.colorize) {\r
2476                         if (oldi > next_out) {\r
2477                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2478                             next_out = oldi;\r
2479                         }\r
2480                         Colorize(ColorSShout, FALSE);\r
2481                         curColor = ColorSShout;\r
2482                     }\r
2483                     loggedOn = TRUE;\r
2484                     started = STARTED_CHATTER;\r
2485                     continue;\r
2486                 }\r
2487 \r
2488                 if (looking_at(buf, &i, "--->")) {\r
2489                     loggedOn = TRUE;\r
2490                     continue;\r
2491                 }\r
2492 \r
2493                 if (looking_at(buf, &i, "* shouts: ") ||\r
2494                     looking_at(buf, &i, "--> ")) {\r
2495                     if (appData.colorize) {\r
2496                         if (oldi > next_out) {\r
2497                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2498                             next_out = oldi;\r
2499                         }\r
2500                         Colorize(ColorShout, FALSE);\r
2501                         curColor = ColorShout;\r
2502                     }\r
2503                     loggedOn = TRUE;\r
2504                     started = STARTED_CHATTER;\r
2505                     continue;\r
2506                 }\r
2507 \r
2508                 if (looking_at( buf, &i, "Challenge:")) {\r
2509                     if (appData.colorize) {\r
2510                         if (oldi > next_out) {\r
2511                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2512                             next_out = oldi;\r
2513                         }\r
2514                         Colorize(ColorChallenge, FALSE);\r
2515                         curColor = ColorChallenge;\r
2516                     }\r
2517                     loggedOn = TRUE;\r
2518                     continue;\r
2519                 }\r
2520 \r
2521                 if (looking_at(buf, &i, "* offers you") ||\r
2522                     looking_at(buf, &i, "* offers to be") ||\r
2523                     looking_at(buf, &i, "* would like to") ||\r
2524                     looking_at(buf, &i, "* requests to") ||\r
2525                     looking_at(buf, &i, "Your opponent offers") ||\r
2526                     looking_at(buf, &i, "Your opponent requests")) {\r
2527 \r
2528                     if (appData.colorize) {\r
2529                         if (oldi > next_out) {\r
2530                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2531                             next_out = oldi;\r
2532                         }\r
2533                         Colorize(ColorRequest, FALSE);\r
2534                         curColor = ColorRequest;\r
2535                     }\r
2536                     continue;\r
2537                 }\r
2538 \r
2539                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2540                     if (appData.colorize) {\r
2541                         if (oldi > next_out) {\r
2542                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2543                             next_out = oldi;\r
2544                         }\r
2545                         Colorize(ColorSeek, FALSE);\r
2546                         curColor = ColorSeek;\r
2547                     }\r
2548                     continue;\r
2549             }\r
2550 \r
2551             if (looking_at(buf, &i, "\\   ")) {\r
2552                 if (prevColor != ColorNormal) {\r
2553                     if (oldi > next_out) {\r
2554                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2555                         next_out = oldi;\r
2556                     }\r
2557                     Colorize(prevColor, TRUE);\r
2558                     curColor = prevColor;\r
2559                 }\r
2560                 if (savingComment) {\r
2561                     parse_pos = i - oldi;\r
2562                     memcpy(parse, &buf[oldi], parse_pos);\r
2563                     parse[parse_pos] = NULLCHAR;\r
2564                     started = STARTED_COMMENT;\r
2565                 } else {\r
2566                     started = STARTED_CHATTER;\r
2567                 }\r
2568                 continue;\r
2569             }\r
2570 \r
2571             if (looking_at(buf, &i, "Black Strength :") ||\r
2572                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2573                 looking_at(buf, &i, "<10>") ||\r
2574                 looking_at(buf, &i, "#@#")) {\r
2575                 /* Wrong board style */\r
2576                 loggedOn = TRUE;\r
2577                 SendToICS(ics_prefix);\r
2578                 SendToICS("set style 12\n");\r
2579                 SendToICS(ics_prefix);\r
2580                 SendToICS("refresh\n");\r
2581                 continue;\r
2582             }\r
2583             \r
2584             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2585                 ICSInitScript();\r
2586                 have_sent_ICS_logon = 1;\r
2587                 continue;\r
2588             }\r
2589               \r
2590             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2591                 (looking_at(buf, &i, "\n<12> ") ||\r
2592                  looking_at(buf, &i, "<12> "))) {\r
2593                 loggedOn = TRUE;\r
2594                 if (oldi > next_out) {\r
2595                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2596                 }\r
2597                 next_out = i;\r
2598                 started = STARTED_BOARD;\r
2599                 parse_pos = 0;\r
2600                 continue;\r
2601             }\r
2602 \r
2603             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2604                 looking_at(buf, &i, "<b1> ")) {\r
2605                 if (oldi > next_out) {\r
2606                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2607                 }\r
2608                 next_out = i;\r
2609                 started = STARTED_HOLDINGS;\r
2610                 parse_pos = 0;\r
2611                 continue;\r
2612             }\r
2613 \r
2614             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2615                 loggedOn = TRUE;\r
2616                 /* Header for a move list -- first line */\r
2617 \r
2618                 switch (ics_getting_history) {\r
2619                   case H_FALSE:\r
2620                     switch (gameMode) {\r
2621                       case IcsIdle:\r
2622                       case BeginningOfGame:\r
2623                         /* User typed "moves" or "oldmoves" while we\r
2624                            were idle.  Pretend we asked for these\r
2625                            moves and soak them up so user can step\r
2626                            through them and/or save them.\r
2627                            */\r
2628                         Reset(FALSE, TRUE);\r
2629                         gameMode = IcsObserving;\r
2630                         ModeHighlight();\r
2631                         ics_gamenum = -1;\r
2632                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2633                         break;\r
2634                       case EditGame: /*?*/\r
2635                       case EditPosition: /*?*/\r
2636                         /* Should above feature work in these modes too? */\r
2637                         /* For now it doesn't */\r
2638                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2639                         break;\r
2640                       default:\r
2641                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2642                         break;\r
2643                     }\r
2644                     break;\r
2645                   case H_REQUESTED:\r
2646                     /* Is this the right one? */\r
2647                     if (gameInfo.white && gameInfo.black &&\r
2648                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2649                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2650                         /* All is well */\r
2651                         ics_getting_history = H_GOT_REQ_HEADER;\r
2652                     }\r
2653                     break;\r
2654                   case H_GOT_REQ_HEADER:\r
2655                   case H_GOT_UNREQ_HEADER:\r
2656                   case H_GOT_UNWANTED_HEADER:\r
2657                   case H_GETTING_MOVES:\r
2658                     /* Should not happen */\r
2659                     DisplayError(_("Error gathering move list: two headers"), 0);\r
2660                     ics_getting_history = H_FALSE;\r
2661                     break;\r
2662                 }\r
2663 \r
2664                 /* Save player ratings into gameInfo if needed */\r
2665                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2666                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2667                     (gameInfo.whiteRating == -1 ||\r
2668                      gameInfo.blackRating == -1)) {\r
2669 \r
2670                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2671                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2672                     if (appData.debugMode)\r
2673                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), \r
2674                               gameInfo.whiteRating, gameInfo.blackRating);\r
2675                 }\r
2676                 continue;\r
2677             }\r
2678 \r
2679             if (looking_at(buf, &i,\r
2680               "* * match, initial time: * minute*, increment: * second")) {\r
2681                 /* Header for a move list -- second line */\r
2682                 /* Initial board will follow if this is a wild game */\r
2683                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2684                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2685                 gameInfo.event = StrSave(str);\r
2686                 /* [HGM] we switched variant. Translate boards if needed. */\r
2687                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2688                 continue;\r
2689             }\r
2690 \r
2691             if (looking_at(buf, &i, "Move  ")) {\r
2692                 /* Beginning of a move list */\r
2693                 switch (ics_getting_history) {\r
2694                   case H_FALSE:\r
2695                     /* Normally should not happen */\r
2696                     /* Maybe user hit reset while we were parsing */\r
2697                     break;\r
2698                   case H_REQUESTED:\r
2699                     /* Happens if we are ignoring a move list that is not\r
2700                      * the one we just requested.  Common if the user\r
2701                      * tries to observe two games without turning off\r
2702                      * getMoveList */\r
2703                     break;\r
2704                   case H_GETTING_MOVES:\r
2705                     /* Should not happen */\r
2706                     DisplayError(_("Error gathering move list: nested"), 0);\r
2707                     ics_getting_history = H_FALSE;\r
2708                     break;\r
2709                   case H_GOT_REQ_HEADER:\r
2710                     ics_getting_history = H_GETTING_MOVES;\r
2711                     started = STARTED_MOVES;\r
2712                     parse_pos = 0;\r
2713                     if (oldi > next_out) {\r
2714                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2715                     }\r
2716                     break;\r
2717                   case H_GOT_UNREQ_HEADER:\r
2718                     ics_getting_history = H_GETTING_MOVES;\r
2719                     started = STARTED_MOVES_NOHIDE;\r
2720                     parse_pos = 0;\r
2721                     break;\r
2722                   case H_GOT_UNWANTED_HEADER:\r
2723                     ics_getting_history = H_FALSE;\r
2724                     break;\r
2725                 }\r
2726                 continue;\r
2727             }                           \r
2728             \r
2729             if (looking_at(buf, &i, "% ") ||\r
2730                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2731                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book\r
2732                 savingComment = FALSE;\r
2733                 switch (started) {\r
2734                   case STARTED_MOVES:\r
2735                   case STARTED_MOVES_NOHIDE:\r
2736                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2737                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2738                     ParseGameHistory(parse);\r
2739 #if ZIPPY\r
2740                     if (appData.zippyPlay && first.initDone) {\r
2741                         FeedMovesToProgram(&first, forwardMostMove);\r
2742                         if (gameMode == IcsPlayingWhite) {\r
2743                             if (WhiteOnMove(forwardMostMove)) {\r
2744                                 if (first.sendTime) {\r
2745                                   if (first.useColors) {\r
2746                                     SendToProgram("black\n", &first); \r
2747                                   }\r
2748                                   SendTimeRemaining(&first, TRUE);\r
2749                                 }\r
2750 #if 0\r
2751                                 if (first.useColors) {\r
2752                                   SendToProgram("white\ngo\n", &first);\r
2753                                 } else {\r
2754                                   SendToProgram("go\n", &first);\r
2755                                 }\r
2756 #else\r
2757                                 if (first.useColors) {\r
2758                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent\r
2759                                 }\r
2760                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos\r
2761 #endif\r
2762                                 first.maybeThinking = TRUE;\r
2763                             } else {\r
2764                                 if (first.usePlayother) {\r
2765                                   if (first.sendTime) {\r
2766                                     SendTimeRemaining(&first, TRUE);\r
2767                                   }\r
2768                                   SendToProgram("playother\n", &first);\r
2769                                   firstMove = FALSE;\r
2770                                 } else {\r
2771                                   firstMove = TRUE;\r
2772                                 }\r
2773                             }\r
2774                         } else if (gameMode == IcsPlayingBlack) {\r
2775                             if (!WhiteOnMove(forwardMostMove)) {\r
2776                                 if (first.sendTime) {\r
2777                                   if (first.useColors) {\r
2778                                     SendToProgram("white\n", &first);\r
2779                                   }\r
2780                                   SendTimeRemaining(&first, FALSE);\r
2781                                 }\r
2782 #if 0\r
2783                                 if (first.useColors) {\r
2784                                   SendToProgram("black\ngo\n", &first);\r
2785                                 } else {\r
2786                                   SendToProgram("go\n", &first);\r
2787                                 }\r
2788 #else\r
2789                                 if (first.useColors) {\r
2790                                   SendToProgram("black\n", &first);\r
2791                                 }\r
2792                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);\r
2793 #endif\r
2794                                 first.maybeThinking = TRUE;\r
2795                             } else {\r
2796                                 if (first.usePlayother) {\r
2797                                   if (first.sendTime) {\r
2798                                     SendTimeRemaining(&first, FALSE);\r
2799                                   }\r
2800                                   SendToProgram("playother\n", &first);\r
2801                                   firstMove = FALSE;\r
2802                                 } else {\r
2803                                   firstMove = TRUE;\r
2804                                 }\r
2805                             }\r
2806                         }                       \r
2807                     }\r
2808 #endif\r
2809                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2810                         /* Moves came from oldmoves or moves command\r
2811                            while we weren't doing anything else.\r
2812                            */\r
2813                         currentMove = forwardMostMove;\r
2814                         ClearHighlights();/*!!could figure this out*/\r
2815                         flipView = appData.flipView;\r
2816                         DrawPosition(FALSE, boards[currentMove]);\r
2817                         DisplayBothClocks();\r
2818                         sprintf(str, "%s vs. %s",\r
2819                                 gameInfo.white, gameInfo.black);\r
2820                         DisplayTitle(str);\r
2821                         gameMode = IcsIdle;\r
2822                     } else {\r
2823                         /* Moves were history of an active game */\r
2824                         if (gameInfo.resultDetails != NULL) {\r
2825                             free(gameInfo.resultDetails);\r
2826                             gameInfo.resultDetails = NULL;\r
2827                         }\r
2828                     }\r
2829                     HistorySet(parseList, backwardMostMove,\r
2830                                forwardMostMove, currentMove-1);\r
2831                     DisplayMove(currentMove - 1);\r
2832                     if (started == STARTED_MOVES) next_out = i;\r
2833                     started = STARTED_NONE;\r
2834                     ics_getting_history = H_FALSE;\r
2835                     break;\r
2836 \r
2837                   case STARTED_OBSERVE:\r
2838                     started = STARTED_NONE;\r
2839                     SendToICS(ics_prefix);\r
2840                     SendToICS("refresh\n");\r
2841                     break;\r
2842 \r
2843                   default:\r
2844                     break;\r
2845                 }\r
2846                 if(bookHit) { // [HGM] book: simulate book reply\r
2847                     static char bookMove[MSG_SIZ]; // a bit generous?\r
2848 \r
2849                     programStats.depth = programStats.nodes = programStats.time = \r
2850                     programStats.score = programStats.got_only_move = 0;\r
2851                     sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
2852 \r
2853                     strcpy(bookMove, "move ");\r
2854                     strcat(bookMove, bookHit);\r
2855                     HandleMachineMove(bookMove, &first);\r
2856                 }\r
2857                 continue;\r
2858             }\r
2859             \r
2860             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2861                  started == STARTED_HOLDINGS ||\r
2862                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2863                 /* Accumulate characters in move list or board */\r
2864                 parse[parse_pos++] = buf[i];\r
2865             }\r
2866             \r
2867             /* Start of game messages.  Mostly we detect start of game\r
2868                when the first board image arrives.  On some versions\r
2869                of the ICS, though, we need to do a "refresh" after starting\r
2870                to observe in order to get the current board right away. */\r
2871             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2872                 started = STARTED_OBSERVE;\r
2873                 continue;\r
2874             }\r
2875 \r
2876             /* Handle auto-observe */\r
2877             if (appData.autoObserve &&\r
2878                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2879                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2880                 char *player;\r
2881                 /* Choose the player that was highlighted, if any. */\r
2882                 if (star_match[0][0] == '\033' ||\r
2883                     star_match[1][0] != '\033') {\r
2884                     player = star_match[0];\r
2885                 } else {\r
2886                     player = star_match[2];\r
2887                 }\r
2888                 sprintf(str, "%sobserve %s\n",\r
2889                         ics_prefix, StripHighlightAndTitle(player));\r
2890                 SendToICS(str);\r
2891 \r
2892                 /* Save ratings from notify string */\r
2893                 strcpy(player1Name, star_match[0]);\r
2894                 player1Rating = string_to_rating(star_match[1]);\r
2895                 strcpy(player2Name, star_match[2]);\r
2896                 player2Rating = string_to_rating(star_match[3]);\r
2897 \r
2898                 if (appData.debugMode)\r
2899                   fprintf(debugFP, \r
2900                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2901                           player1Name, player1Rating,\r
2902                           player2Name, player2Rating);\r
2903 \r
2904                 continue;\r
2905             }\r
2906 \r
2907             /* Deal with automatic examine mode after a game,\r
2908                and with IcsObserving -> IcsExamining transition */\r
2909             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2910                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2911 \r
2912                 int gamenum = atoi(star_match[0]);\r
2913                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2914                     gamenum == ics_gamenum) {\r
2915                     /* We were already playing or observing this game;\r
2916                        no need to refetch history */\r
2917                     gameMode = IcsExamining;\r
2918                     if (pausing) {\r
2919                         pauseExamForwardMostMove = forwardMostMove;\r
2920                     } else if (currentMove < forwardMostMove) {\r
2921                         ForwardInner(forwardMostMove);\r
2922                     }\r
2923                 } else {\r
2924                     /* I don't think this case really can happen */\r
2925                     SendToICS(ics_prefix);\r
2926                     SendToICS("refresh\n");\r
2927                 }\r
2928                 continue;\r
2929             }    \r
2930             \r
2931             /* Error messages */\r
2932             if (ics_user_moved) {\r
2933                 if (looking_at(buf, &i, "Illegal move") ||\r
2934                     looking_at(buf, &i, "Not a legal move") ||\r
2935                     looking_at(buf, &i, "Your king is in check") ||\r
2936                     looking_at(buf, &i, "It isn't your turn") ||\r
2937                     looking_at(buf, &i, "It is not your move")) {\r
2938                     /* Illegal move */\r
2939                     ics_user_moved = 0;\r
2940                     if (forwardMostMove > backwardMostMove) {\r
2941                         currentMove = --forwardMostMove;\r
2942                         DisplayMove(currentMove - 1); /* before DMError */\r
2943                         DisplayMoveError(_("Illegal move (rejected by ICS)"));\r
2944                         DrawPosition(FALSE, boards[currentMove]);\r
2945                         SwitchClocks();\r
2946                         DisplayBothClocks();\r
2947                     }\r
2948                     continue;\r
2949                 }\r
2950             }\r
2951 \r
2952             if (looking_at(buf, &i, "still have time") ||\r
2953                 looking_at(buf, &i, "not out of time") ||\r
2954                 looking_at(buf, &i, "either player is out of time") ||\r
2955                 looking_at(buf, &i, "has timeseal; checking")) {\r
2956                 /* We must have called his flag a little too soon */\r
2957                 whiteFlag = blackFlag = FALSE;\r
2958                 continue;\r
2959             }\r
2960 \r
2961             if (looking_at(buf, &i, "added * seconds to") ||\r
2962                 looking_at(buf, &i, "seconds were added to")) {\r
2963                 /* Update the clocks */\r
2964                 SendToICS(ics_prefix);\r
2965                 SendToICS("refresh\n");\r
2966                 continue;\r
2967             }\r
2968 \r
2969             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2970                 ics_clock_paused = TRUE;\r
2971                 StopClocks();\r
2972                 continue;\r
2973             }\r
2974 \r
2975             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2976                 ics_clock_paused = FALSE;\r
2977                 StartClocks();\r
2978                 continue;\r
2979             }\r
2980 \r
2981             /* Grab player ratings from the Creating: message.\r
2982                Note we have to check for the special case when\r
2983                the ICS inserts things like [white] or [black]. */\r
2984             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
2985                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
2986                 /* star_matches:\r
2987                    0    player 1 name (not necessarily white)\r
2988                    1    player 1 rating\r
2989                    2    empty, white, or black (IGNORED)\r
2990                    3    player 2 name (not necessarily black)\r
2991                    4    player 2 rating\r
2992                    \r
2993                    The names/ratings are sorted out when the game\r
2994                    actually starts (below).\r
2995                 */\r
2996                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
2997                 player1Rating = string_to_rating(star_match[1]);\r
2998                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
2999                 player2Rating = string_to_rating(star_match[4]);\r
3000 \r
3001                 if (appData.debugMode)\r
3002                   fprintf(debugFP, \r
3003                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
3004                           player1Name, player1Rating,\r
3005                           player2Name, player2Rating);\r
3006 \r
3007                 continue;\r
3008             }\r
3009             \r
3010             /* Improved generic start/end-of-game messages */\r
3011             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
3012                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
3013                 /* If tkind == 0: */\r
3014                 /* star_match[0] is the game number */\r
3015                 /*           [1] is the white player's name */\r
3016                 /*           [2] is the black player's name */\r
3017                 /* For end-of-game: */\r
3018                 /*           [3] is the reason for the game end */\r
3019                 /*           [4] is a PGN end game-token, preceded by " " */\r
3020                 /* For start-of-game: */\r
3021                 /*           [3] begins with "Creating" or "Continuing" */\r
3022                 /*           [4] is " *" or empty (don't care). */\r
3023                 int gamenum = atoi(star_match[0]);\r
3024                 char *whitename, *blackname, *why, *endtoken;\r
3025                 ChessMove endtype = (ChessMove) 0;\r
3026 \r
3027                 if (tkind == 0) {\r
3028                   whitename = star_match[1];\r
3029                   blackname = star_match[2];\r
3030                   why = star_match[3];\r
3031                   endtoken = star_match[4];\r
3032                 } else {\r
3033                   whitename = star_match[1];\r
3034                   blackname = star_match[3];\r
3035                   why = star_match[5];\r
3036                   endtoken = star_match[6];\r
3037                 }\r
3038 \r
3039                 /* Game start messages */\r
3040                 if (strncmp(why, "Creating ", 9) == 0 ||\r
3041                     strncmp(why, "Continuing ", 11) == 0) {\r
3042                     gs_gamenum = gamenum;\r
3043                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
3044 #if ZIPPY\r
3045                     if (appData.zippyPlay) {\r
3046                         ZippyGameStart(whitename, blackname);\r
3047                     }\r
3048 #endif /*ZIPPY*/\r
3049                     continue;\r
3050                 }\r
3051 \r
3052                 /* Game end messages */\r
3053                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
3054                     ics_gamenum != gamenum) {\r
3055                     continue;\r
3056                 }\r
3057                 while (endtoken[0] == ' ') endtoken++;\r
3058                 switch (endtoken[0]) {\r
3059                   case '*':\r
3060                   default:\r
3061                     endtype = GameUnfinished;\r
3062                     break;\r
3063                   case '0':\r
3064                     endtype = BlackWins;\r
3065                     break;\r
3066                   case '1':\r
3067                     if (endtoken[1] == '/')\r
3068                       endtype = GameIsDrawn;\r
3069                     else\r
3070                       endtype = WhiteWins;\r
3071                     break;\r
3072                 }\r
3073                 GameEnds(endtype, why, GE_ICS);\r
3074 #if ZIPPY\r
3075                 if (appData.zippyPlay && first.initDone) {\r
3076                     ZippyGameEnd(endtype, why);\r
3077                     if (first.pr == NULL) {\r
3078                       /* Start the next process early so that we'll\r
3079                          be ready for the next challenge */\r
3080                       StartChessProgram(&first);\r
3081                     }\r
3082                     /* Send "new" early, in case this command takes\r
3083                        a long time to finish, so that we'll be ready\r
3084                        for the next challenge. */\r
3085                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'\r
3086                     Reset(TRUE, TRUE);\r
3087                 }\r
3088 #endif /*ZIPPY*/\r
3089                 continue;\r
3090             }\r
3091 \r
3092             if (looking_at(buf, &i, "Removing game * from observation") ||\r
3093                 looking_at(buf, &i, "no longer observing game *") ||\r
3094                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
3095                 if (gameMode == IcsObserving &&\r
3096                     atoi(star_match[0]) == ics_gamenum)\r
3097                   {\r
3098                       /* icsEngineAnalyze */\r
3099                       if (appData.icsEngineAnalyze) {\r
3100                             ExitAnalyzeMode();\r
3101                             ModeHighlight();\r
3102                       }\r
3103                       StopClocks();\r
3104                       gameMode = IcsIdle;\r
3105                       ics_gamenum = -1;\r
3106                       ics_user_moved = FALSE;\r
3107                   }\r
3108                 continue;\r
3109             }\r
3110 \r
3111             if (looking_at(buf, &i, "no longer examining game *")) {\r
3112                 if (gameMode == IcsExamining &&\r
3113                     atoi(star_match[0]) == ics_gamenum)\r
3114                   {\r
3115                       gameMode = IcsIdle;\r
3116                       ics_gamenum = -1;\r
3117                       ics_user_moved = FALSE;\r
3118                   }\r
3119                 continue;\r
3120             }\r
3121 \r
3122             /* Advance leftover_start past any newlines we find,\r
3123                so only partial lines can get reparsed */\r
3124             if (looking_at(buf, &i, "\n")) {\r
3125                 prevColor = curColor;\r
3126                 if (curColor != ColorNormal) {\r
3127                     if (oldi > next_out) {\r
3128                         SendToPlayer(&buf[next_out], oldi - next_out);\r
3129                         next_out = oldi;\r
3130                     }\r
3131                     Colorize(ColorNormal, FALSE);\r
3132                     curColor = ColorNormal;\r
3133                 }\r
3134                 if (started == STARTED_BOARD) {\r
3135                     started = STARTED_NONE;\r
3136                     parse[parse_pos] = NULLCHAR;\r
3137                     ParseBoard12(parse);\r
3138                     ics_user_moved = 0;\r
3139 \r
3140                     /* Send premove here */\r
3141                     if (appData.premove) {\r
3142                       char str[MSG_SIZ];\r
3143                       if (currentMove == 0 &&\r
3144                           gameMode == IcsPlayingWhite &&\r
3145                           appData.premoveWhite) {\r
3146                         sprintf(str, "%s%s\n", ics_prefix,\r
3147                                 appData.premoveWhiteText);\r
3148                         if (appData.debugMode)\r
3149                           fprintf(debugFP, "Sending premove:\n");\r
3150                         SendToICS(str);\r
3151                       } else if (currentMove == 1 &&\r
3152                                  gameMode == IcsPlayingBlack &&\r
3153                                  appData.premoveBlack) {\r
3154                         sprintf(str, "%s%s\n", ics_prefix,\r
3155                                 appData.premoveBlackText);\r
3156                         if (appData.debugMode)\r
3157                           fprintf(debugFP, "Sending premove:\n");\r
3158                         SendToICS(str);\r
3159                       } else if (gotPremove) {\r
3160                         gotPremove = 0;\r
3161                         ClearPremoveHighlights();\r
3162                         if (appData.debugMode)\r
3163                           fprintf(debugFP, "Sending premove:\n");\r
3164                           UserMoveEvent(premoveFromX, premoveFromY, \r
3165                                         premoveToX, premoveToY, \r
3166                                         premovePromoChar);\r
3167                       }\r
3168                     }\r
3169 \r
3170                     /* Usually suppress following prompt */\r
3171                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
3172                         if (looking_at(buf, &i, "*% ")) {\r
3173                             savingComment = FALSE;\r
3174                         }\r
3175                     }\r
3176                     next_out = i;\r
3177                 } else if (started == STARTED_HOLDINGS) {\r
3178                     int gamenum;\r
3179                     char new_piece[MSG_SIZ];\r
3180                     started = STARTED_NONE;\r
3181                     parse[parse_pos] = NULLCHAR;\r
3182                     if (appData.debugMode)\r
3183                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
3184                                                         parse, currentMove);\r
3185                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
3186                         gamenum == ics_gamenum) {\r
3187                         if (gameInfo.variant == VariantNormal) {\r
3188                           /* [HGM] We seem to switch variant during a game!\r
3189                            * Presumably no holdings were displayed, so we have\r
3190                            * to move the position two files to the right to\r
3191                            * create room for them!\r
3192                            */\r
3193                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
3194                           /* Get a move list just to see the header, which\r
3195                              will tell us whether this is really bug or zh */\r
3196                           if (ics_getting_history == H_FALSE) {\r
3197                             ics_getting_history = H_REQUESTED;\r
3198                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3199                             SendToICS(str);\r
3200                           }\r
3201                         }\r
3202                         new_piece[0] = NULLCHAR;\r
3203                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
3204                                &gamenum, white_holding, black_holding,\r
3205                                new_piece);\r
3206                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
3207                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
3208                         /* [HGM] copy holdings to board holdings area */\r
3209                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
3210                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
3211 #if ZIPPY\r
3212                         if (appData.zippyPlay && first.initDone) {\r
3213                             ZippyHoldings(white_holding, black_holding,\r
3214                                           new_piece);\r
3215                         }\r
3216 #endif /*ZIPPY*/\r
3217                         if (tinyLayout || smallLayout) {\r
3218                             char wh[16], bh[16];\r
3219                             PackHolding(wh, white_holding);\r
3220                             PackHolding(bh, black_holding);\r
3221                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
3222                                     gameInfo.white, gameInfo.black);\r
3223                         } else {\r
3224                             sprintf(str, "%s [%s] vs. %s [%s]",\r
3225                                     gameInfo.white, white_holding,\r
3226                                     gameInfo.black, black_holding);\r
3227                         }\r
3228 \r
3229                         DrawPosition(FALSE, boards[currentMove]);\r
3230                         DisplayTitle(str);\r
3231                     }\r
3232                     /* Suppress following prompt */\r
3233                     if (looking_at(buf, &i, "*% ")) {\r
3234                         savingComment = FALSE;\r
3235                     }\r
3236                     next_out = i;\r
3237                 }\r
3238                 continue;\r
3239             }\r
3240 \r
3241             i++;                /* skip unparsed character and loop back */\r
3242         }\r
3243         \r
3244         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window\r
3245             started != STARTED_HOLDINGS && i > next_out) {\r
3246             SendToPlayer(&buf[next_out], i - next_out);\r
3247             next_out = i;\r
3248         }\r
3249         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above\r
3250         \r
3251         leftover_len = buf_len - leftover_start;\r
3252         /* if buffer ends with something we couldn't parse,\r
3253            reparse it after appending the next read */\r
3254         \r
3255     } else if (count == 0) {\r
3256         RemoveInputSource(isr);\r
3257         DisplayFatalError(_("Connection closed by ICS"), 0, 0);\r
3258     } else {\r
3259         DisplayFatalError(_("Error reading from ICS"), error, 1);\r
3260     }\r
3261 }\r
3262 \r
3263 \r
3264 /* Board style 12 looks like this:\r
3265    \r
3266    <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
3267    \r
3268  * The "<12> " is stripped before it gets to this routine.  The two\r
3269  * trailing 0's (flip state and clock ticking) are later addition, and\r
3270  * some chess servers may not have them, or may have only the first.\r
3271  * Additional trailing fields may be added in the future.  \r
3272  */\r
3273 \r
3274 #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
3275 \r
3276 #define RELATION_OBSERVING_PLAYED    0\r
3277 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
3278 #define RELATION_PLAYING_MYMOVE      1\r
3279 #define RELATION_PLAYING_NOTMYMOVE  -1\r
3280 #define RELATION_EXAMINING           2\r
3281 #define RELATION_ISOLATED_BOARD     -3\r
3282 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
3283 \r
3284 void\r
3285 ParseBoard12(string)\r
3286      char *string;\r
3287\r
3288     GameMode newGameMode;\r
3289     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;\r
3290     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;\r
3291     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
3292     char to_play, board_chars[200];\r
3293     char move_str[500], str[500], elapsed_time[500];\r
3294     char black[32], white[32];\r
3295     Board board;\r
3296     int prevMove = currentMove;\r
3297     int ticking = 2;\r
3298     ChessMove moveType;\r
3299     int fromX, fromY, toX, toY;\r
3300     char promoChar;\r
3301     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */\r
3302     char *bookHit = NULL; // [HGM] book\r
3303 \r
3304     fromX = fromY = toX = toY = -1;\r
3305     \r
3306     newGame = FALSE;\r
3307 \r
3308     if (appData.debugMode)\r
3309       fprintf(debugFP, _("Parsing board: %s\n"), string);\r
3310 \r
3311     move_str[0] = NULLCHAR;\r
3312     elapsed_time[0] = NULLCHAR;\r
3313     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */\r
3314         int  i = 0, j;\r
3315         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {\r
3316             if(string[i] == ' ') { ranks++; files = 0; }\r
3317             else files++;\r
3318             i++;\r
3319         }\r
3320         for(j = 0; j <i; j++) board_chars[j] = string[j];\r
3321         board_chars[i] = '\0';\r
3322         string += i + 1;\r
3323     }\r
3324     n = sscanf(string, PATTERN, &to_play, &double_push,\r
3325                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
3326                &gamenum, white, black, &relation, &basetime, &increment,\r
3327                &white_stren, &black_stren, &white_time, &black_time,\r
3328                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
3329                &ticking);\r
3330 \r
3331     if (n < 21) {\r
3332         sprintf(str, _("Failed to parse board string:\n\"%s\""), string);\r
3333         DisplayError(str, 0);\r
3334         return;\r
3335     }\r
3336 \r
3337     /* Convert the move number to internal form */\r
3338     moveNum = (moveNum - 1) * 2;\r
3339     if (to_play == 'B') moveNum++;\r
3340     if (moveNum >= MAX_MOVES) {\r
3341       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
3342                         0, 1);\r
3343       return;\r
3344     }\r
3345     \r
3346     switch (relation) {\r
3347       case RELATION_OBSERVING_PLAYED:\r
3348       case RELATION_OBSERVING_STATIC:\r
3349         if (gamenum == -1) {\r
3350             /* Old ICC buglet */\r
3351             relation = RELATION_OBSERVING_STATIC;\r
3352         }\r
3353         newGameMode = IcsObserving;\r
3354         break;\r
3355       case RELATION_PLAYING_MYMOVE:\r
3356       case RELATION_PLAYING_NOTMYMOVE:\r
3357         newGameMode =\r
3358           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3359             IcsPlayingWhite : IcsPlayingBlack;\r
3360         break;\r
3361       case RELATION_EXAMINING:\r
3362         newGameMode = IcsExamining;\r
3363         break;\r
3364       case RELATION_ISOLATED_BOARD:\r
3365       default:\r
3366         /* Just display this board.  If user was doing something else,\r
3367            we will forget about it until the next board comes. */ \r
3368         newGameMode = IcsIdle;\r
3369         break;\r
3370       case RELATION_STARTING_POSITION:\r
3371         newGameMode = gameMode;\r
3372         break;\r
3373     }\r
3374     \r
3375     /* Modify behavior for initial board display on move listing\r
3376        of wild games.\r
3377        */\r
3378     switch (ics_getting_history) {\r
3379       case H_FALSE:\r
3380       case H_REQUESTED:\r
3381         break;\r
3382       case H_GOT_REQ_HEADER:\r
3383       case H_GOT_UNREQ_HEADER:\r
3384         /* This is the initial position of the current game */\r
3385         gamenum = ics_gamenum;\r
3386         moveNum = 0;            /* old ICS bug workaround */\r
3387         if (to_play == 'B') {\r
3388           startedFromSetupPosition = TRUE;\r
3389           blackPlaysFirst = TRUE;\r
3390           moveNum = 1;\r
3391           if (forwardMostMove == 0) forwardMostMove = 1;\r
3392           if (backwardMostMove == 0) backwardMostMove = 1;\r
3393           if (currentMove == 0) currentMove = 1;\r
3394         }\r
3395         newGameMode = gameMode;\r
3396         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3397         break;\r
3398       case H_GOT_UNWANTED_HEADER:\r
3399         /* This is an initial board that we don't want */\r
3400         return;\r
3401       case H_GETTING_MOVES:\r
3402         /* Should not happen */\r
3403         DisplayError(_("Error gathering move list: extra board"), 0);\r
3404         ics_getting_history = H_FALSE;\r
3405         return;\r
3406     }\r
3407     \r
3408     /* Take action if this is the first board of a new game, or of a\r
3409        different game than is currently being displayed.  */\r
3410     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3411         relation == RELATION_ISOLATED_BOARD) {\r
3412         \r
3413         /* Forget the old game and get the history (if any) of the new one */\r
3414         if (gameMode != BeginningOfGame) {\r
3415           Reset(FALSE, TRUE);\r
3416         }\r
3417         newGame = TRUE;\r
3418         if (appData.autoRaiseBoard) BoardToTop();\r
3419         prevMove = -3;\r
3420         if (gamenum == -1) {\r
3421             newGameMode = IcsIdle;\r
3422         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3423                    appData.getMoveList) {\r
3424             /* Need to get game history */\r
3425             ics_getting_history = H_REQUESTED;\r
3426             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3427             SendToICS(str);\r
3428         }\r
3429         \r
3430         /* Initially flip the board to have black on the bottom if playing\r
3431            black or if the ICS flip flag is set, but let the user change\r
3432            it with the Flip View button. */\r
3433         flipView = appData.autoFlipView ? \r
3434           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3435           appData.flipView;\r
3436         \r
3437         /* Done with values from previous mode; copy in new ones */\r
3438         gameMode = newGameMode;\r
3439         ModeHighlight();\r
3440         ics_gamenum = gamenum;\r
3441         if (gamenum == gs_gamenum) {\r
3442             int klen = strlen(gs_kind);\r
3443             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3444             sprintf(str, "ICS %s", gs_kind);\r
3445             gameInfo.event = StrSave(str);\r
3446         } else {\r
3447             gameInfo.event = StrSave("ICS game");\r
3448         }\r
3449         gameInfo.site = StrSave(appData.icsHost);\r
3450         gameInfo.date = PGNDate();\r
3451         gameInfo.round = StrSave("-");\r
3452         gameInfo.white = StrSave(white);\r
3453         gameInfo.black = StrSave(black);\r
3454         timeControl = basetime * 60 * 1000;\r
3455         timeControl_2 = 0;\r
3456         timeIncrement = increment * 1000;\r
3457         movesPerSession = 0;\r
3458         gameInfo.timeControl = TimeControlTagValue();\r
3459         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3460   if (appData.debugMode) {\r
3461     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3462     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3463     setbuf(debugFP, NULL);\r
3464   }\r
3465 \r
3466         gameInfo.outOfBook = NULL;\r
3467         \r
3468         /* Do we have the ratings? */\r
3469         if (strcmp(player1Name, white) == 0 &&\r
3470             strcmp(player2Name, black) == 0) {\r
3471             if (appData.debugMode)\r
3472               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3473                       player1Rating, player2Rating);\r
3474             gameInfo.whiteRating = player1Rating;\r
3475             gameInfo.blackRating = player2Rating;\r
3476         } else if (strcmp(player2Name, white) == 0 &&\r
3477                    strcmp(player1Name, black) == 0) {\r
3478             if (appData.debugMode)\r
3479               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3480                       player2Rating, player1Rating);\r
3481             gameInfo.whiteRating = player2Rating;\r
3482             gameInfo.blackRating = player1Rating;\r
3483         }\r
3484         player1Name[0] = player2Name[0] = NULLCHAR;\r
3485 \r
3486         /* Silence shouts if requested */\r
3487         if (appData.quietPlay &&\r
3488             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3489             SendToICS(ics_prefix);\r
3490             SendToICS("set shout 0\n");\r
3491         }\r
3492     }\r
3493     \r
3494     /* Deal with midgame name changes */\r
3495     if (!newGame) {\r
3496         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3497             if (gameInfo.white) free(gameInfo.white);\r
3498             gameInfo.white = StrSave(white);\r
3499         }\r
3500         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3501             if (gameInfo.black) free(gameInfo.black);\r
3502             gameInfo.black = StrSave(black);\r
3503         }\r
3504     }\r
3505     \r
3506     /* Throw away game result if anything actually changes in examine mode */\r
3507     if (gameMode == IcsExamining && !newGame) {\r
3508         gameInfo.result = GameUnfinished;\r
3509         if (gameInfo.resultDetails != NULL) {\r
3510             free(gameInfo.resultDetails);\r
3511             gameInfo.resultDetails = NULL;\r
3512         }\r
3513     }\r
3514     \r
3515     /* In pausing && IcsExamining mode, we ignore boards coming\r
3516        in if they are in a different variation than we are. */\r
3517     if (pauseExamInvalid) return;\r
3518     if (pausing && gameMode == IcsExamining) {\r
3519         if (moveNum <= pauseExamForwardMostMove) {\r
3520             pauseExamInvalid = TRUE;\r
3521             forwardMostMove = pauseExamForwardMostMove;\r
3522             return;\r
3523         }\r
3524     }\r
3525     \r
3526   if (appData.debugMode) {\r
3527     fprintf(debugFP, "load %dx%d board\n", files, ranks);\r
3528   }\r
3529     /* Parse the board */\r
3530     for (k = 0; k < ranks; k++) {\r
3531       for (j = 0; j < files; j++)\r
3532         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);\r
3533       if(gameInfo.holdingsWidth > 1) {\r
3534            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3535            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3536       }\r
3537     }\r
3538     CopyBoard(boards[moveNum], board);\r
3539     if (moveNum == 0) {\r
3540         startedFromSetupPosition =\r
3541           !CompareBoards(board, initialPosition);\r
3542         if(startedFromSetupPosition)\r
3543             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */\r
3544     }\r
3545 \r
3546     /* [HGM] Set castling rights. Take the outermost Rooks,\r
3547        to make it also work for FRC opening positions. Note that board12\r
3548        is really defective for later FRC positions, as it has no way to\r
3549        indicate which Rook can castle if they are on the same side of King.\r
3550        For the initial position we grant rights to the outermost Rooks,\r
3551        and remember thos rights, and we then copy them on positions\r
3552        later in an FRC game. This means WB might not recognize castlings with\r
3553        Rooks that have moved back to their original position as illegal,\r
3554        but in ICS mode that is not its job anyway.\r
3555     */\r
3556     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)\r
3557     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;\r
3558 \r
3559         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3560             if(board[0][i] == WhiteRook) j = i;\r
3561         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3562         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3563             if(board[0][i] == WhiteRook) j = i;\r
3564         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3565         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3566             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3567         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3568         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3569             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3570         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3571 \r
3572         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }\r
3573         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3574             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;\r
3575         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3576             if(board[BOARD_HEIGHT-1][k] == bKing)\r
3577                 initialRights[5] = castlingRights[moveNum][5] = k;\r
3578     } else { int r;\r
3579         r = castlingRights[moveNum][0] = initialRights[0];\r
3580         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;\r
3581         r = castlingRights[moveNum][1] = initialRights[1];\r
3582         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;\r
3583         r = castlingRights[moveNum][3] = initialRights[3];\r
3584         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;\r
3585         r = castlingRights[moveNum][4] = initialRights[4];\r
3586         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;\r
3587         /* wildcastle kludge: always assume King has rights */\r
3588         r = castlingRights[moveNum][2] = initialRights[2];\r
3589         r = castlingRights[moveNum][5] = initialRights[5];\r
3590     }\r
3591     /* [HGM] e.p. rights. Assume that ICS sends file number here? */\r
3592     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;\r
3593 \r
3594     \r
3595     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3596         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3597         /* This was an initial position from a move list, not\r
3598            the current position */\r
3599         return;\r
3600     }\r
3601     \r
3602     /* Update currentMove and known move number limits */\r
3603     newMove = newGame || moveNum > forwardMostMove;\r
3604 \r
3605     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */\r
3606     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {\r
3607         takeback = forwardMostMove - moveNum;\r
3608         for (i = 0; i < takeback; i++) {\r
3609              if (appData.debugMode) fprintf(debugFP, "take back move\n");\r
3610              SendToProgram("undo\n", &first);\r
3611         }\r
3612     }\r
3613 \r
3614     if (newGame) {\r
3615         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3616         if (gameMode == IcsExamining && moveNum == 0) {\r
3617           /* Workaround for ICS limitation: we are not told the wild\r
3618              type when starting to examine a game.  But if we ask for\r
3619              the move list, the move list header will tell us */\r
3620             ics_getting_history = H_REQUESTED;\r
3621             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3622             SendToICS(str);\r
3623         }\r
3624     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3625                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3626         forwardMostMove = moveNum;\r
3627         if (!pausing || currentMove > forwardMostMove)\r
3628           currentMove = forwardMostMove;\r
3629     } else {\r
3630         /* New part of history that is not contiguous with old part */ \r
3631         if (pausing && gameMode == IcsExamining) {\r
3632             pauseExamInvalid = TRUE;\r
3633             forwardMostMove = pauseExamForwardMostMove;\r
3634             return;\r
3635         }\r
3636         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3637         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3638             ics_getting_history = H_REQUESTED;\r
3639             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3640             SendToICS(str);\r
3641         }\r
3642     }\r
3643     \r
3644     /* Update the clocks */\r
3645     if (strchr(elapsed_time, '.')) {\r
3646       /* Time is in ms */\r
3647       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3648       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3649     } else {\r
3650       /* Time is in seconds */\r
3651       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3652       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3653     }\r
3654       \r
3655 \r
3656 #if ZIPPY\r
3657     if (appData.zippyPlay && newGame &&\r
3658         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3659         gameMode != IcsExamining)\r
3660       ZippyFirstBoard(moveNum, basetime, increment);\r
3661 #endif\r
3662     \r
3663     /* Put the move on the move list, first converting\r
3664        to canonical algebraic form. */\r
3665     if (moveNum > 0) {\r
3666   if (appData.debugMode) {\r
3667     if (appData.debugMode) { int f = forwardMostMove;\r
3668         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,\r
3669                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
3670     }\r
3671     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3672     fprintf(debugFP, "moveNum = %d\n", moveNum);\r
3673     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3674     setbuf(debugFP, NULL);\r
3675   }\r
3676         if (moveNum <= backwardMostMove) {\r
3677             /* We don't know what the board looked like before\r
3678                this move.  Punt. */\r
3679             strcpy(parseList[moveNum - 1], move_str);\r
3680             strcat(parseList[moveNum - 1], " ");\r
3681             strcat(parseList[moveNum - 1], elapsed_time);\r
3682             moveList[moveNum - 1][0] = NULLCHAR;\r
3683         } else if (strcmp(move_str, "none") == 0) {\r
3684             // [HGM] long SAN: swapped order; test for 'none' before parsing move\r
3685             /* Again, we don't know what the board looked like;\r
3686                this is really the start of the game. */\r
3687             parseList[moveNum - 1][0] = NULLCHAR;\r
3688             moveList[moveNum - 1][0] = NULLCHAR;\r
3689             backwardMostMove = moveNum;\r
3690             startedFromSetupPosition = TRUE;\r
3691             fromX = fromY = toX = toY = -1;\r
3692         } else {\r
3693           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. \r
3694           //                 So we parse the long-algebraic move string in stead of the SAN move\r
3695           int valid; char buf[MSG_SIZ], *prom;\r
3696 \r
3697           // str looks something like "Q/a1-a2"; kill the slash\r
3698           if(str[1] == '/') \r
3699                 sprintf(buf, "%c%s", str[0], str+2);\r
3700           else  strcpy(buf, str); // might be castling\r
3701           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) \r
3702                 strcat(buf, prom); // long move lacks promo specification!\r
3703           if(!appData.testLegality) {\r
3704                 if(appData.debugMode) \r
3705                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);\r
3706                 strcpy(move_str, buf);\r
3707           }\r
3708           valid = ParseOneMove(move_str, moveNum - 1, &moveType,\r
3709                                 &fromX, &fromY, &toX, &toY, &promoChar)\r
3710                || ParseOneMove(buf, moveNum - 1, &moveType,\r
3711                                 &fromX, &fromY, &toX, &toY, &promoChar);\r
3712           // end of long SAN patch\r
3713           if (valid) {\r
3714             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3715                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3716                                      fromY, fromX, toY, toX, promoChar,\r
3717                                      parseList[moveNum-1]);\r
3718             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3719                              castlingRights[moveNum]) ) {\r
3720               case MT_NONE:\r
3721               case MT_STALEMATE:\r
3722               default:\r
3723                 break;\r
3724               case MT_CHECK:\r
3725                 if(gameInfo.variant != VariantShogi)\r
3726                     strcat(parseList[moveNum - 1], "+");\r
3727                 break;\r
3728               case MT_CHECKMATE:\r
3729                 strcat(parseList[moveNum - 1], "#");\r
3730                 break;\r
3731             }\r
3732             strcat(parseList[moveNum - 1], " ");\r
3733             strcat(parseList[moveNum - 1], elapsed_time);\r
3734             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3735             strcpy(moveList[moveNum - 1], currentMoveString);\r
3736             strcat(moveList[moveNum - 1], "\n");\r
3737           } else {\r
3738             /* Move from ICS was illegal!?  Punt. */\r
3739   if (appData.debugMode) {\r
3740     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3741     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3742   }\r
3743 #if 0\r
3744             if (appData.testLegality && appData.debugMode) {\r
3745                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3746                 DisplayError(str, 0);\r
3747             }\r
3748 #endif\r
3749             strcpy(parseList[moveNum - 1], move_str);\r
3750             strcat(parseList[moveNum - 1], " ");\r
3751             strcat(parseList[moveNum - 1], elapsed_time);\r
3752             moveList[moveNum - 1][0] = NULLCHAR;\r
3753             fromX = fromY = toX = toY = -1;\r
3754           }\r
3755         }\r
3756   if (appData.debugMode) {\r
3757     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3758     setbuf(debugFP, NULL);\r
3759   }\r
3760 \r
3761 #if ZIPPY\r
3762         /* Send move to chess program (BEFORE animating it). */\r
3763         if (appData.zippyPlay && !newGame && newMove && \r
3764            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3765 \r
3766             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3767                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3768                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3769                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),\r
3770                             move_str);\r
3771                     DisplayError(str, 0);\r
3772                 } else {\r
3773                     if (first.sendTime) {\r
3774                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3775                     }\r
3776                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book\r
3777                     if (firstMove && !bookHit) {\r
3778                         firstMove = FALSE;\r
3779                         if (first.useColors) {\r
3780                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3781                                         "white\ngo\n" :\r
3782                                         "black\ngo\n", &first);\r
3783                         } else {\r
3784                           SendToProgram("go\n", &first);\r
3785                         }\r
3786                         first.maybeThinking = TRUE;\r
3787                     }\r
3788                 }\r
3789             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3790               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3791                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);\r
3792                 DisplayError(str, 0);\r
3793               } else {\r
3794                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!\r
3795                 SendMoveToProgram(moveNum - 1, &first);\r
3796               }\r
3797             }\r
3798         }\r
3799 #endif\r
3800     }\r
3801 \r
3802     if (moveNum > 0 && !gotPremove) {\r
3803         /* If move comes from a remote source, animate it.  If it\r
3804            isn't remote, it will have already been animated. */\r
3805         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3806             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3807         }\r
3808         if (!pausing && appData.highlightLastMove) {\r
3809             SetHighlights(fromX, fromY, toX, toY);\r
3810         }\r
3811     }\r
3812     \r
3813     /* Start the clocks */\r
3814     whiteFlag = blackFlag = FALSE;\r
3815     appData.clockMode = !(basetime == 0 && increment == 0);\r
3816     if (ticking == 0) {\r
3817       ics_clock_paused = TRUE;\r
3818       StopClocks();\r
3819     } else if (ticking == 1) {\r
3820       ics_clock_paused = FALSE;\r
3821     }\r
3822     if (gameMode == IcsIdle ||\r
3823         relation == RELATION_OBSERVING_STATIC ||\r
3824         relation == RELATION_EXAMINING ||\r
3825         ics_clock_paused)\r
3826       DisplayBothClocks();\r
3827     else\r
3828       StartClocks();\r
3829     \r
3830     /* Display opponents and material strengths */\r
3831     if (gameInfo.variant != VariantBughouse &&\r
3832         gameInfo.variant != VariantCrazyhouse) {\r
3833         if (tinyLayout || smallLayout) {\r
3834             if(gameInfo.variant == VariantNormal)\r
3835                 sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3836                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3837                     basetime, increment);\r
3838             else\r
3839                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", \r
3840                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3841                     basetime, increment, (int) gameInfo.variant);\r
3842         } else {\r
3843             if(gameInfo.variant == VariantNormal)\r
3844                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3845                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3846                     basetime, increment);\r
3847             else\r
3848                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", \r
3849                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3850                     basetime, increment, VariantName(gameInfo.variant));\r
3851         }\r
3852         DisplayTitle(str);\r
3853   if (appData.debugMode) {\r
3854     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);\r
3855   }\r
3856     }\r
3857 \r
3858    \r
3859     /* Display the board */\r
3860     if (!pausing) {\r
3861       \r
3862       if (appData.premove)\r
3863           if (!gotPremove || \r
3864              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3865              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3866               ClearPremoveHighlights();\r
3867 \r
3868       DrawPosition(FALSE, boards[currentMove]);\r
3869       DisplayMove(moveNum - 1);\r
3870       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3871         RingBell();\r
3872     }\r
3873 \r
3874     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3875 #if ZIPPY\r
3876     if(bookHit) { // [HGM] book: simulate book reply\r
3877         static char bookMove[MSG_SIZ]; // a bit generous?\r
3878 \r
3879         programStats.depth = programStats.nodes = programStats.time = \r
3880         programStats.score = programStats.got_only_move = 0;\r
3881         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
3882 \r
3883         strcpy(bookMove, "move ");\r
3884         strcat(bookMove, bookHit);\r
3885         HandleMachineMove(bookMove, &first);\r
3886     }\r
3887 #endif\r
3888 }\r
3889 \r
3890 void\r
3891 GetMoveListEvent()\r
3892 {\r
3893     char buf[MSG_SIZ];\r
3894     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3895         ics_getting_history = H_REQUESTED;\r
3896         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3897         SendToICS(buf);\r
3898     }\r
3899 }\r
3900 \r
3901 void\r
3902 AnalysisPeriodicEvent(force)\r
3903      int force;\r
3904 {\r
3905     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3906          && !force) || !appData.periodicUpdates)\r
3907       return;\r
3908 \r
3909     /* Send . command to Crafty to collect stats */\r
3910     SendToProgram(".\n", &first);\r
3911 \r
3912     /* Don't send another until we get a response (this makes\r
3913        us stop sending to old Crafty's which don't understand\r
3914        the "." command (sending illegal cmds resets node count & time,\r
3915        which looks bad)) */\r
3916     programStats.ok_to_send = 0;\r
3917 }\r
3918 \r
3919 void\r
3920 SendMoveToProgram(moveNum, cps)\r
3921      int moveNum;\r
3922      ChessProgramState *cps;\r
3923 {\r
3924     char buf[MSG_SIZ];\r
3925 \r
3926     if (cps->useUsermove) {\r
3927       SendToProgram("usermove ", cps);\r
3928     }\r
3929     if (cps->useSAN) {\r
3930       char *space;\r
3931       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3932         int len = space - parseList[moveNum];\r
3933         memcpy(buf, parseList[moveNum], len);\r
3934         buf[len++] = '\n';\r
3935         buf[len] = NULLCHAR;\r
3936       } else {\r
3937         sprintf(buf, "%s\n", parseList[moveNum]);\r
3938       }\r
3939       SendToProgram(buf, cps);\r
3940     } else {\r
3941       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */\r
3942         AlphaRank(moveList[moveNum], 4);\r
3943         SendToProgram(moveList[moveNum], cps);\r
3944         AlphaRank(moveList[moveNum], 4); // and back\r
3945       } else\r
3946       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3947        * the engine. It would be nice to have a better way to identify castle \r
3948        * moves here. */\r
3949       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)\r
3950                                                                          && cps->useOOCastle) {\r
3951         int fromX = moveList[moveNum][0] - AAA; \r
3952         int fromY = moveList[moveNum][1] - ONE;\r
3953         int toX = moveList[moveNum][2] - AAA; \r
3954         int toY = moveList[moveNum][3] - ONE;\r
3955         if((boards[moveNum][fromY][fromX] == WhiteKing \r
3956             && boards[moveNum][toY][toX] == WhiteRook)\r
3957            || (boards[moveNum][fromY][fromX] == BlackKing \r
3958                && boards[moveNum][toY][toX] == BlackRook)) {\r
3959           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3960           else SendToProgram("O-O-O\n", cps);\r
3961         }\r
3962         else SendToProgram(moveList[moveNum], cps);\r
3963       }\r
3964       else SendToProgram(moveList[moveNum], cps);\r
3965       /* End of additions by Tord */\r
3966     }\r
3967 \r
3968     /* [HGM] setting up the opening has brought engine in force mode! */\r
3969     /*       Send 'go' if we are in a mode where machine should play. */\r
3970     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
3971         (gameMode == TwoMachinesPlay   ||\r
3972 #ifdef ZIPPY\r
3973          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||\r
3974 #endif\r
3975          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
3976         SendToProgram("go\n", cps);\r
3977   if (appData.debugMode) {\r
3978     fprintf(debugFP, "(extra)\n");\r
3979   }\r
3980     }\r
3981     setboardSpoiledMachineBlack = 0;\r
3982 }\r
3983 \r
3984 void\r
3985 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
3986      ChessMove moveType;\r
3987      int fromX, fromY, toX, toY;\r
3988 {\r
3989     char user_move[MSG_SIZ];\r
3990 \r
3991     switch (moveType) {\r
3992       default:\r
3993         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),\r
3994                 (int)moveType, fromX, fromY, toX, toY);\r
3995         DisplayError(user_move + strlen("say "), 0);\r
3996         break;\r
3997       case WhiteKingSideCastle:\r
3998       case BlackKingSideCastle:\r
3999       case WhiteQueenSideCastleWild:\r
4000       case BlackQueenSideCastleWild:\r
4001       /* PUSH Fabien */\r
4002       case WhiteHSideCastleFR:\r
4003       case BlackHSideCastleFR:\r
4004       /* POP Fabien */\r
4005         sprintf(user_move, "o-o\n");\r
4006         break;\r
4007       case WhiteQueenSideCastle:\r
4008       case BlackQueenSideCastle:\r
4009       case WhiteKingSideCastleWild:\r
4010       case BlackKingSideCastleWild:\r
4011       /* PUSH Fabien */\r
4012       case WhiteASideCastleFR:\r
4013       case BlackASideCastleFR:\r
4014       /* POP Fabien */\r
4015         sprintf(user_move, "o-o-o\n");\r
4016         break;\r
4017       case WhitePromotionQueen:\r
4018       case BlackPromotionQueen:\r
4019       case WhitePromotionRook:\r
4020       case BlackPromotionRook:\r
4021       case WhitePromotionBishop:\r
4022       case BlackPromotionBishop:\r
4023       case WhitePromotionKnight:\r
4024       case BlackPromotionKnight:\r
4025       case WhitePromotionKing:\r
4026       case BlackPromotionKing:\r
4027       case WhitePromotionChancellor:\r
4028       case BlackPromotionChancellor:\r
4029       case WhitePromotionArchbishop:\r
4030       case BlackPromotionArchbishop:\r
4031         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)\r
4032             sprintf(user_move, "%c%c%c%c=%c\n",\r
4033                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4034                 PieceToChar(WhiteFerz));\r
4035         else if(gameInfo.variant == VariantGreat)\r
4036             sprintf(user_move, "%c%c%c%c=%c\n",\r
4037                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4038                 PieceToChar(WhiteMan));\r
4039         else\r
4040             sprintf(user_move, "%c%c%c%c=%c\n",\r
4041                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4042                 PieceToChar(PromoPiece(moveType)));\r
4043         break;\r
4044       case WhiteDrop:\r
4045       case BlackDrop:\r
4046         sprintf(user_move, "%c@%c%c\n",\r
4047                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
4048                 AAA + toX, ONE + toY);\r
4049         break;\r
4050       case NormalMove:\r
4051       case WhiteCapturesEnPassant:\r
4052       case BlackCapturesEnPassant:\r
4053       case IllegalMove:  /* could be a variant we don't quite understand */\r
4054         sprintf(user_move, "%c%c%c%c\n",\r
4055                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
4056         break;\r
4057     }\r
4058     SendToICS(user_move);\r
4059 }\r
4060 \r
4061 void\r
4062 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
4063      int rf, ff, rt, ft;\r
4064      char promoChar;\r
4065      char move[7];\r
4066 {\r
4067     if (rf == DROP_RANK) {\r
4068         sprintf(move, "%c@%c%c\n",\r
4069                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
4070     } else {\r
4071         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
4072             sprintf(move, "%c%c%c%c\n",\r
4073                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
4074         } else {\r
4075             sprintf(move, "%c%c%c%c%c\n",\r
4076                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
4077         }\r
4078     }\r
4079 }\r
4080 \r
4081 void\r
4082 ProcessICSInitScript(f)\r
4083      FILE *f;\r
4084 {\r
4085     char buf[MSG_SIZ];\r
4086 \r
4087     while (fgets(buf, MSG_SIZ, f)) {\r
4088         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
4089     }\r
4090 \r
4091     fclose(f);\r
4092 }\r
4093 \r
4094 \r
4095 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
4096 void\r
4097 AlphaRank(char *move, int n)\r
4098 {\r
4099     char *p = move, c; int x, y;\r
4100 \r
4101     if (appData.debugMode) {\r
4102         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
4103     }\r
4104 \r
4105     if(move[1]=='*' && \r
4106        move[2]>='0' && move[2]<='9' &&\r
4107        move[3]>='a' && move[3]<='x'    ) {\r
4108         move[1] = '@';\r
4109         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4110         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4111     } else\r
4112     if(move[0]>='0' && move[0]<='9' &&\r
4113        move[1]>='a' && move[1]<='x' &&\r
4114        move[2]>='0' && move[2]<='9' &&\r
4115        move[3]>='a' && move[3]<='x'    ) {\r
4116         /* input move, Shogi -> normal */\r
4117         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;\r
4118         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\r
4119         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4120         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4121     } else\r
4122     if(move[1]=='@' &&\r
4123        move[3]>='0' && move[3]<='9' &&\r
4124        move[2]>='a' && move[2]<='x'    ) {\r
4125         move[1] = '*';\r
4126         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4127         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4128     } else\r
4129     if(\r
4130        move[0]>='a' && move[0]<='x' &&\r
4131        move[3]>='0' && move[3]<='9' &&\r
4132        move[2]>='a' && move[2]<='x'    ) {\r
4133          /* output move, normal -> Shogi */\r
4134         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
4135         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
4136         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4137         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4138         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
4139     }\r
4140     if (appData.debugMode) {\r
4141         fprintf(debugFP, "   out = '%s'\n", move);\r
4142     }\r
4143 }\r
4144 \r
4145 /* Parser for moves from gnuchess, ICS, or user typein box */\r
4146 Boolean\r
4147 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
4148      char *move;\r
4149      int moveNum;\r
4150      ChessMove *moveType;\r
4151      int *fromX, *fromY, *toX, *toY;\r
4152      char *promoChar;\r
4153 {       \r
4154     if (appData.debugMode) {\r
4155         fprintf(debugFP, "move to parse: %s\n", move);\r
4156     }\r
4157     *moveType = yylexstr(moveNum, move);\r
4158 \r
4159     switch (*moveType) {\r
4160       case WhitePromotionChancellor:\r
4161       case BlackPromotionChancellor:\r
4162       case WhitePromotionArchbishop:\r
4163       case BlackPromotionArchbishop:\r
4164       case WhitePromotionQueen:\r
4165       case BlackPromotionQueen:\r
4166       case WhitePromotionRook:\r
4167       case BlackPromotionRook:\r
4168       case WhitePromotionBishop:\r
4169       case BlackPromotionBishop:\r
4170       case WhitePromotionKnight:\r
4171       case BlackPromotionKnight:\r
4172       case WhitePromotionKing:\r
4173       case BlackPromotionKing:\r
4174       case NormalMove:\r
4175       case WhiteCapturesEnPassant:\r
4176       case BlackCapturesEnPassant:\r
4177       case WhiteKingSideCastle:\r
4178       case WhiteQueenSideCastle:\r
4179       case BlackKingSideCastle:\r
4180       case BlackQueenSideCastle:\r
4181       case WhiteKingSideCastleWild:\r
4182       case WhiteQueenSideCastleWild:\r
4183       case BlackKingSideCastleWild:\r
4184       case BlackQueenSideCastleWild:\r
4185       /* Code added by Tord: */\r
4186       case WhiteHSideCastleFR:\r
4187       case WhiteASideCastleFR:\r
4188       case BlackHSideCastleFR:\r
4189       case BlackASideCastleFR:\r
4190       /* End of code added by Tord */\r
4191       case IllegalMove:         /* bug or odd chess variant */\r
4192         *fromX = currentMoveString[0] - AAA;\r
4193         *fromY = currentMoveString[1] - ONE;\r
4194         *toX = currentMoveString[2] - AAA;\r
4195         *toY = currentMoveString[3] - ONE;\r
4196         *promoChar = currentMoveString[4];\r
4197         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
4198             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
4199     if (appData.debugMode) {\r
4200         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
4201     }\r
4202             *fromX = *fromY = *toX = *toY = 0;\r
4203             return FALSE;\r
4204         }\r
4205         if (appData.testLegality) {\r
4206           return (*moveType != IllegalMove);\r
4207         } else {\r
4208           return !(fromX == fromY && toX == toY);\r
4209         }\r
4210 \r
4211       case WhiteDrop:\r
4212       case BlackDrop:\r
4213         *fromX = *moveType == WhiteDrop ?\r
4214           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
4215           (int) CharToPiece(ToLower(currentMoveString[0]));\r
4216         *fromY = DROP_RANK;\r
4217         *toX = currentMoveString[2] - AAA;\r
4218         *toY = currentMoveString[3] - ONE;\r
4219         *promoChar = NULLCHAR;\r
4220         return TRUE;\r
4221 \r
4222       case AmbiguousMove:\r
4223       case ImpossibleMove:\r
4224       case (ChessMove) 0:       /* end of file */\r
4225       case ElapsedTime:\r
4226       case Comment:\r
4227       case PGNTag:\r
4228       case NAG:\r
4229       case WhiteWins:\r
4230       case BlackWins:\r
4231       case GameIsDrawn:\r
4232       default:\r
4233     if (appData.debugMode) {\r
4234         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
4235     }\r
4236         /* bug? */\r
4237         *fromX = *fromY = *toX = *toY = 0;\r
4238         *promoChar = NULLCHAR;\r
4239         return FALSE;\r
4240     }\r
4241 }\r
4242 \r
4243 /* [AS] FRC game initialization */\r
4244 static int FindEmptySquare( Board board, int n )\r
4245 {\r
4246     int i = 0;\r
4247 \r
4248     while( 1 ) {\r
4249         while( board[0][i] != EmptySquare ) i++;\r
4250         if( n == 0 )\r
4251             break;\r
4252         n--;\r
4253         i++;\r
4254     }\r
4255 \r
4256     return i;\r
4257 }\r
4258 \r
4259 #if 0\r
4260 static void ShuffleFRC( Board board )\r
4261 {\r
4262     int i;\r
4263 \r
4264     srand( time(0) );\r
4265     \r
4266     for( i=0; i<8; i++ ) {\r
4267         board[0][i] = EmptySquare;\r
4268     }\r
4269 \r
4270     board[0][(rand() % 4)*2  ] = WhiteBishop; /* On dark square */\r
4271     board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */\r
4272     board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;\r
4273     board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;\r
4274     board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;\r
4275     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4276     initialRights[1]  = initialRights[4]  =\r
4277     castlingRights[0][1] = castlingRights[0][4] = i;\r
4278     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4279     initialRights[2]  = initialRights[5]  =\r
4280     castlingRights[0][2] = castlingRights[0][5] = i;\r
4281     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4282     initialRights[0]  = initialRights[3]  =\r
4283     castlingRights[0][0] = castlingRights[0][3] = i;\r
4284 \r
4285     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4286         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4287     }\r
4288 }\r
4289 \r
4290 static unsigned char FRC_KnightTable[10] = {\r
4291     0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33\r
4292 };\r
4293 \r
4294 static void SetupFRC( Board board, int pos_index )\r
4295 {\r
4296     int i;\r
4297     unsigned char knights;\r
4298 \r
4299     /* Bring the position index into a safe range (just in case...) */\r
4300     if( pos_index < 0 ) pos_index = 0;\r
4301 \r
4302     pos_index %= 960;\r
4303 \r
4304     /* Clear the board */\r
4305     for( i=0; i<8; i++ ) {\r
4306         board[0][i] = EmptySquare;\r
4307     }\r
4308 \r
4309     /* Place bishops and queen */\r
4310     board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */\r
4311     pos_index /= 4;\r
4312     \r
4313     board[0][ (pos_index % 4)*2     ] = WhiteBishop; /* On dark square */\r
4314     pos_index /= 4;\r
4315 \r
4316     board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;\r
4317     pos_index /= 6;\r
4318 \r
4319     /* Place knigths */\r
4320     knights = FRC_KnightTable[ pos_index ];\r
4321 \r
4322     board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;\r
4323     board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;\r
4324 \r
4325     /* Place rooks and king */\r
4326     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4327     initialRights[1]  = initialRights[4]  =\r
4328     castlingRights[0][1] = castlingRights[0][4] = i;\r
4329     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4330     initialRights[2]  = initialRights[5]  =\r
4331     castlingRights[0][2] = castlingRights[0][5] = i;\r
4332     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4333     initialRights[0]  = initialRights[3]  =\r
4334     castlingRights[0][0] = castlingRights[0][3] = i;\r
4335 \r
4336     /* Mirror piece placement for black */\r
4337     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4338         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4339     }\r
4340 }\r
4341 #else\r
4342 // [HGM] shuffle: a more general way to suffle opening setups, applicable to arbitrry variants.\r
4343 // All positions will have equal probability, but the current method will not provide a unique\r
4344 // numbering scheme for arrays that contain 3 or more pieces of the same kind.\r
4345 #define DARK 1\r
4346 #define LITE 2\r
4347 #define ANY 3\r
4348 \r
4349 int squaresLeft[4];\r
4350 int piecesLeft[(int)BlackPawn];\r
4351 long long int seed, nrOfShuffles;\r
4352 \r
4353 void GetPositionNumber()\r
4354 {       // sets global variable seed\r
4355         int i;\r
4356 \r
4357         seed = appData.defaultFrcPosition;\r
4358         if(seed < 0) { // randomize based on time for negative FRC position numbers\r
4359                 srandom(time(0)); \r
4360                 for(i=0; i<50; i++) seed += random();\r
4361                 seed = random() ^ random() >> 8 ^ random() << 8;\r
4362                 if(seed<0) seed = -seed;\r
4363         }\r
4364 }\r
4365 \r
4366 int put(Board board, int pieceType, int rank, int n, int shade)\r
4367 // put the piece on the (n-1)-th empty squares of the given shade\r
4368 {\r
4369         int i;\r
4370 \r
4371         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
4372                 if( ((i-BOARD_LEFT)&1)+1 & shade && board[rank][i] == EmptySquare && n-- == 0) {\r
4373                         board[rank][i] = (ChessSquare) pieceType;\r
4374                         squaresLeft[(i-BOARD_LEFT&1) + 1]--;\r
4375                         squaresLeft[ANY]--;\r
4376                         piecesLeft[pieceType]--; \r
4377                         return i;\r
4378                 }\r
4379         }\r
4380         return -1;\r
4381 }\r
4382 \r
4383 \r
4384 void AddOnePiece(Board board, int pieceType, int rank, int shade)\r
4385 // calculate where the next piece goes, (any empty square), and put it there\r
4386 {\r
4387         int i;\r
4388 \r
4389         i = seed % squaresLeft[shade];\r
4390         nrOfShuffles *= squaresLeft[shade];\r
4391         seed /= squaresLeft[shade];\r
4392         put(board, pieceType, rank, i, shade);\r
4393 }\r
4394 \r
4395 void AddTwoPieces(Board board, int pieceType, int rank)\r
4396 // calculate where the next 2 identical pieces go, (any empty square), and put it there\r
4397 {\r
4398         int i, n=squaresLeft[ANY], j=n-1, k;\r
4399 \r
4400         k = n*(n-1)/2; // nr of possibilities, not counting permutations\r
4401         i = seed % k;  // pick one\r
4402         nrOfShuffles *= k;\r
4403         seed /= k;\r
4404         while(i >= j) i -= j--;\r
4405         j = n - 1 - j; i += j;\r
4406         put(board, pieceType, rank, j, ANY);\r
4407         put(board, pieceType, rank, i, ANY);\r
4408 }\r
4409 \r
4410 void SetUpShuffle(Board board, int number)\r
4411 {\r
4412         int i, p, first=1;\r
4413 \r
4414         GetPositionNumber(); nrOfShuffles = 1;\r
4415 \r
4416         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;\r
4417         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;\r
4418         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];\r
4419 \r
4420         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;\r
4421 \r
4422         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board\r
4423             p = (int) board[0][i];\r
4424             if(p < (int) BlackPawn) piecesLeft[p] ++;\r
4425             board[0][i] = EmptySquare;\r
4426         }\r
4427 \r
4428         if(PosFlags(0) & F_ALL_CASTLE_OK) {\r
4429             // shuffles restricted to allow normal castling put KRR first\r
4430             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle\r
4431                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4432             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles\r
4433                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4434             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling\r
4435                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);\r
4436             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling\r
4437                 put(board, WhiteRook, 0, 0, ANY);\r
4438             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle\r
4439         }\r
4440 \r
4441         if((BOARD_RGHT-BOARD_LEFT & 1) == 0)\r
4442             // only for even boards make effort to put pairs of colorbound pieces on opposite colors\r
4443             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {\r
4444                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;\r
4445                 while(piecesLeft[p] >= 2) {\r
4446                     AddOnePiece(board, p, 0, LITE);\r
4447                     AddOnePiece(board, p, 0, DARK);\r
4448                 }\r
4449                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)\r
4450             }\r
4451 \r
4452         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {\r
4453             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere\r
4454             // but we leave King and Rooks for last, to possibly obey FRC restriction\r
4455             if(p == (int)WhiteRook) continue;\r
4456             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations\r
4457             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece\r
4458         }\r
4459 \r
4460         // now everything is placed, except perhaps King (Unicorn) and Rooks\r
4461 \r
4462         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {\r
4463             // Last King gets castling rights\r
4464             while(piecesLeft[(int)WhiteUnicorn]) {\r
4465                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4466                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4467             }\r
4468 \r
4469             while(piecesLeft[(int)WhiteKing]) {\r
4470                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4471                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4472             }\r
4473 \r
4474 \r
4475         } else {\r
4476             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);\r
4477             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);\r
4478         }\r
4479 \r
4480         // Only Rooks can be left; simply place them all\r
4481         while(piecesLeft[(int)WhiteRook]) {\r
4482                 i = put(board, WhiteRook, 0, 0, ANY);\r
4483                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights\r
4484                         if(first) {\r
4485                                 first=0;\r
4486                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;\r
4487                         }\r
4488                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;\r
4489                 }\r
4490         }\r
4491         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white\r
4492             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;\r
4493         }\r
4494 \r
4495         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize\r
4496 }\r
4497 \r
4498 #endif\r
4499 \r
4500 int SetCharTable( char *table, const char * map )\r
4501 /* [HGM] moved here from winboard.c because of its general usefulness */\r
4502 /*       Basically a safe strcpy that uses the last character as King */\r
4503 {\r
4504     int result = FALSE; int NrPieces;\r
4505 \r
4506     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
4507                     && NrPieces >= 12 && !(NrPieces&1)) {\r
4508         int i; /* [HGM] Accept even length from 12 to 34 */\r
4509 \r
4510         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
4511         for( i=0; i<NrPieces/2-1; i++ ) {\r
4512             table[i] = map[i];\r
4513             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
4514         }\r
4515         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
4516         table[(int) BlackKing]  = map[NrPieces-1];\r
4517 \r
4518         result = TRUE;\r
4519     }\r
4520 \r
4521     return result;\r
4522 }\r
4523 \r
4524 void Prelude(Board board)\r
4525 {       // [HGM] superchess: random selection of exo-pieces\r
4526         int i, j, k; ChessSquare p; \r
4527         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };\r
4528 \r
4529         GetPositionNumber(); // use FRC position number\r
4530 \r
4531         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table\r
4532             SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4533             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) \r
4534                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;\r
4535         }\r
4536 \r
4537         j = seed%4;                 seed /= 4; \r
4538         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4539         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4540         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4541         j = seed%3 + (seed%3 >= j); seed /= 3; \r
4542         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4543         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4544         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4545         j = seed%3;                 seed /= 3; \r
4546         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4547         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4548         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4549         j = seed%2 + (seed%2 >= j); seed /= 2; \r
4550         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4551         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4552         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4553         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);\r
4554         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);\r
4555         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);\r
4556         put(board, exoPieces[0],    0, 0, ANY);\r
4557         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];\r
4558 }\r
4559 \r
4560 void\r
4561 InitPosition(redraw)\r
4562      int redraw;\r
4563 {\r
4564     ChessSquare (* pieces)[BOARD_SIZE];\r
4565     int i, j, pawnRow, overrule,\r
4566     oldx = gameInfo.boardWidth,\r
4567     oldy = gameInfo.boardHeight,\r
4568     oldh = gameInfo.holdingsWidth,\r
4569     oldv = gameInfo.variant;\r
4570 \r
4571     currentMove = forwardMostMove = backwardMostMove = 0;\r
4572     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request\r
4573 \r
4574     /* [AS] Initialize pv info list [HGM] and game status */\r
4575     {\r
4576         for( i=0; i<MAX_MOVES; i++ ) {\r
4577             pvInfoList[i].depth = 0;\r
4578             epStatus[i]=EP_NONE;\r
4579             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
4580         }\r
4581 \r
4582         initialRulePlies = 0; /* 50-move counter start */\r
4583 \r
4584         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4585         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4586     }\r
4587 \r
4588     \r
4589     /* [HGM] logic here is completely changed. In stead of full positions */\r
4590     /* the initialized data only consist of the two backranks. The switch */\r
4591     /* selects which one we will use, which is than copied to the Board   */\r
4592     /* initialPosition, which for the rest is initialized by Pawns and    */\r
4593     /* empty squares. This initial position is then copied to boards[0],  */\r
4594     /* possibly after shuffling, so that it remains available.            */\r
4595 \r
4596     gameInfo.holdingsWidth = 0; /* default board sizes */\r
4597     gameInfo.boardWidth    = 8;\r
4598     gameInfo.boardHeight   = 8;\r
4599     gameInfo.holdingsSize  = 0;\r
4600     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
4601     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
4602     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
4603 \r
4604     switch (gameInfo.variant) {\r
4605     case VariantFischeRandom:\r
4606       shuffleOpenings = TRUE;\r
4607     default:\r
4608       pieces = FIDEArray;\r
4609       break;\r
4610     case VariantShatranj:\r
4611       pieces = ShatranjArray;\r
4612       nrCastlingRights = 0;\r
4613       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
4614       break;\r
4615     case VariantTwoKings:\r
4616       pieces = twoKingsArray;\r
4617       nrCastlingRights = 8;                 /* add rights for second King */\r
4618       castlingRights[0][6] = initialRights[2] = 5;\r
4619       castlingRights[0][7] = initialRights[5] = 5;\r
4620       castlingRank[6] = 0;\r
4621       castlingRank[7] = BOARD_HEIGHT-1;\r
4622       break;\r
4623     case VariantCapaRandom:\r
4624       shuffleOpenings = TRUE;\r
4625     case VariantCapablanca:\r
4626       pieces = CapablancaArray;\r
4627       gameInfo.boardWidth = 10;\r
4628       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4629       break;\r
4630     case VariantGothic:\r
4631       pieces = GothicArray;\r
4632       gameInfo.boardWidth = 10;\r
4633       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4634       break;\r
4635     case VariantJanus:\r
4636       pieces = JanusArray;\r
4637       gameInfo.boardWidth = 10;\r
4638       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
4639       nrCastlingRights = 6;\r
4640         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4641         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4642         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH-1>>1;\r
4643         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4644         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4645         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH-1>>1;\r
4646       break;\r
4647     case VariantFalcon:\r
4648       pieces = FalconArray;\r
4649       gameInfo.boardWidth = 10;\r
4650       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4651       break;\r
4652     case VariantXiangqi:\r
4653       pieces = XiangqiArray;\r
4654       gameInfo.boardWidth  = 9;\r
4655       gameInfo.boardHeight = 10;\r
4656       nrCastlingRights = 0;\r
4657       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4658       break;\r
4659     case VariantShogi:\r
4660       pieces = ShogiArray;\r
4661       gameInfo.boardWidth  = 9;\r
4662       gameInfo.boardHeight = 9;\r
4663       gameInfo.holdingsSize = 7;\r
4664       nrCastlingRights = 0;\r
4665       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4666       break;\r
4667     case VariantCourier:\r
4668       pieces = CourierArray;\r
4669       gameInfo.boardWidth  = 12;\r
4670       nrCastlingRights = 0;\r
4671       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4672       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4673       break;\r
4674     case VariantKnightmate:\r
4675       pieces = KnightmateArray;\r
4676       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4677       break;\r
4678     case VariantFairy:\r
4679       pieces = fairyArray;\r
4680       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4681       break;\r
4682     case VariantGreat:\r
4683       pieces = GreatArray;\r
4684       gameInfo.boardWidth = 10;\r
4685       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");\r
4686       gameInfo.holdingsSize = 8;\r
4687       break;\r
4688     case VariantSuper:\r
4689       pieces = FIDEArray;\r
4690       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
4691       gameInfo.holdingsSize = 8;\r
4692       startedFromSetupPosition = TRUE;\r
4693       break;\r
4694     case VariantCrazyhouse:\r
4695     case VariantBughouse:\r
4696       pieces = FIDEArray;\r
4697       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4698       gameInfo.holdingsSize = 5;\r
4699       break;\r
4700     case VariantWildCastle:\r
4701       pieces = FIDEArray;\r
4702       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4703       shuffleOpenings = 1;\r
4704       break;\r
4705     case VariantNoCastle:\r
4706       pieces = FIDEArray;\r
4707       nrCastlingRights = 0;\r
4708       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4709       /* !!?unconstrained back-rank shuffle */\r
4710       shuffleOpenings = 1;\r
4711       break;\r
4712     }\r
4713 \r
4714     overrule = 0;\r
4715     if(appData.NrFiles >= 0) {\r
4716         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4717         gameInfo.boardWidth = appData.NrFiles;\r
4718     }\r
4719     if(appData.NrRanks >= 0) {\r
4720         gameInfo.boardHeight = appData.NrRanks;\r
4721     }\r
4722     if(appData.holdingsSize >= 0) {\r
4723         i = appData.holdingsSize;\r
4724         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4725         gameInfo.holdingsSize = i;\r
4726     }\r
4727     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4728     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4729         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);\r
4730 \r
4731     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4732     if(pawnRow < 1) pawnRow = 1;\r
4733 \r
4734     /* User pieceToChar list overrules defaults */\r
4735     if(appData.pieceToCharTable != NULL)\r
4736         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4737 \r
4738     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4739 \r
4740         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4741             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4742         for( i=0; i<BOARD_HEIGHT; i++ )\r
4743             initialPosition[i][j] = s;\r
4744 \r
4745         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4746         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4747         initialPosition[pawnRow][j] = WhitePawn;\r
4748         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4749         if(gameInfo.variant == VariantXiangqi) {\r
4750             if(j&1) {\r
4751                 initialPosition[pawnRow][j] = \r
4752                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4753                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4754                    initialPosition[2][j] = WhiteCannon;\r
4755                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4756                 }\r
4757             }\r
4758         }\r
4759         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4760     }\r
4761     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4762 \r
4763             j=BOARD_LEFT+1;\r
4764             initialPosition[1][j] = WhiteBishop;\r
4765             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4766             j=BOARD_RGHT-2;\r
4767             initialPosition[1][j] = WhiteRook;\r
4768             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4769     }\r
4770 \r
4771     if( nrCastlingRights == -1) {\r
4772         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4773         /*       This sets default castling rights from none to normal corners   */\r
4774         /* Variants with other castling rights must set them themselves above    */\r
4775         nrCastlingRights = 6;\r
4776        \r
4777         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4778         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4779         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4780         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4781         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4782         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4783      }\r
4784 \r
4785      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
4786      if(gameInfo.variant == VariantGreat) { // promotion commoners\r
4787         initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-1] = WhiteMan;\r
4788         initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-2] = 9;\r
4789         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;\r
4790         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;\r
4791      }\r
4792 #if 0\r
4793     if(gameInfo.variant == VariantFischeRandom) {\r
4794       if( appData.defaultFrcPosition < 0 ) {\r
4795         ShuffleFRC( initialPosition );\r
4796       }\r
4797       else {\r
4798         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4799       }\r
4800       startedFromSetupPosition = TRUE;\r
4801     } else \r
4802 #else\r
4803   if (appData.debugMode) {\r
4804     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
4805   }\r
4806     if(shuffleOpenings) {\r
4807         SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
4808         startedFromSetupPosition = TRUE;\r
4809     }\r
4810 #endif\r
4811     if(startedFromPositionFile) {\r
4812       /* [HGM] loadPos: use PositionFile for every new game */\r
4813       CopyBoard(initialPosition, filePosition);\r
4814       for(i=0; i<nrCastlingRights; i++)\r
4815           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4816       startedFromSetupPosition = TRUE;\r
4817     }\r
4818 \r
4819     CopyBoard(boards[0], initialPosition);\r
4820 \r
4821     if(oldx != gameInfo.boardWidth ||\r
4822        oldy != gameInfo.boardHeight ||\r
4823        oldh != gameInfo.holdingsWidth\r
4824 #ifdef GOTHIC\r
4825        || oldv == VariantGothic ||        // For licensing popups\r
4826        gameInfo.variant == VariantGothic\r
4827 #endif\r
4828 #ifdef FALCON\r
4829        || oldv == VariantFalcon ||\r
4830        gameInfo.variant == VariantFalcon\r
4831 #endif\r
4832                                          )\r
4833             InitDrawingSizes(-2 ,0);\r
4834 \r
4835     if (redraw)\r
4836       DrawPosition(TRUE, boards[currentMove]);\r
4837 }\r
4838 \r
4839 void\r
4840 SendBoard(cps, moveNum)\r
4841      ChessProgramState *cps;\r
4842      int moveNum;\r
4843 {\r
4844     char message[MSG_SIZ];\r
4845     \r
4846     if (cps->useSetboard) {\r
4847       char* fen = PositionToFEN(moveNum, cps->useFEN960);\r
4848       sprintf(message, "setboard %s\n", fen);\r
4849       SendToProgram(message, cps);\r
4850       free(fen);\r
4851 \r
4852     } else {\r
4853       ChessSquare *bp;\r
4854       int i, j;\r
4855       /* Kludge to set black to move, avoiding the troublesome and now\r
4856        * deprecated "black" command.\r
4857        */\r
4858       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4859 \r
4860       SendToProgram("edit\n", cps);\r
4861       SendToProgram("#\n", cps);\r
4862       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4863         bp = &boards[moveNum][i][BOARD_LEFT];\r
4864         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4865           if ((int) *bp < (int) BlackPawn) {\r
4866             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4867                     AAA + j, ONE + i);\r
4868             if(message[0] == '+' || message[0] == '~') {\r
4869                 sprintf(message, "%c%c%c+\n",\r
4870                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4871                         AAA + j, ONE + i);\r
4872             }\r
4873             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4874                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4875                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4876             }\r
4877             SendToProgram(message, cps);\r
4878           }\r
4879         }\r
4880       }\r
4881     \r
4882       SendToProgram("c\n", cps);\r
4883       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4884         bp = &boards[moveNum][i][BOARD_LEFT];\r
4885         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4886           if (((int) *bp != (int) EmptySquare)\r
4887               && ((int) *bp >= (int) BlackPawn)) {\r
4888             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4889                     AAA + j, ONE + i);\r
4890             if(message[0] == '+' || message[0] == '~') {\r
4891                 sprintf(message, "%c%c%c+\n",\r
4892                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4893                         AAA + j, ONE + i);\r
4894             }\r
4895             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4896                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4897                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4898             }\r
4899             SendToProgram(message, cps);\r
4900           }\r
4901         }\r
4902       }\r
4903     \r
4904       SendToProgram(".\n", cps);\r
4905     }\r
4906     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4907 }\r
4908 \r
4909 int\r
4910 IsPromotion(fromX, fromY, toX, toY)\r
4911      int fromX, fromY, toX, toY;\r
4912 {\r
4913     /* [HGM] add Shogi promotions */\r
4914     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4915     ChessSquare piece;\r
4916 \r
4917     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4918       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4919    /* [HGM] Note to self: line above also weeds out drops */\r
4920     piece = boards[currentMove][fromY][fromX];\r
4921     if(gameInfo.variant == VariantShogi) {\r
4922         promotionZoneSize = 3;\r
4923         highestPromotingPiece = (int)WhiteKing;\r
4924         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4925            and if in normal chess we then allow promotion to King, why not\r
4926            allow promotion of other piece in Shogi?                         */\r
4927     }\r
4928     if((int)piece >= BlackPawn) {\r
4929         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4930              return FALSE;\r
4931         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4932     } else {\r
4933         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4934            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4935     }\r
4936     return ( (int)piece <= highestPromotingPiece );\r
4937 }\r
4938 \r
4939 int\r
4940 InPalace(row, column)\r
4941      int row, column;\r
4942 {   /* [HGM] for Xiangqi */\r
4943     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4944          column < (BOARD_WIDTH + 4)/2 &&\r
4945          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4946     return FALSE;\r
4947 }\r
4948 \r
4949 int\r
4950 PieceForSquare (x, y)\r
4951      int x;\r
4952      int y;\r
4953 {\r
4954   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
4955      return -1;\r
4956   else\r
4957      return boards[currentMove][y][x];\r
4958 }\r
4959 \r
4960 int\r
4961 OKToStartUserMove(x, y)\r
4962      int x, y;\r
4963 {\r
4964     ChessSquare from_piece;\r
4965     int white_piece;\r
4966 \r
4967     if (matchMode) return FALSE;\r
4968     if (gameMode == EditPosition) return TRUE;\r
4969 \r
4970     if (x >= 0 && y >= 0)\r
4971       from_piece = boards[currentMove][y][x];\r
4972     else\r
4973       from_piece = EmptySquare;\r
4974 \r
4975     if (from_piece == EmptySquare) return FALSE;\r
4976 \r
4977     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4978       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4979 \r
4980     switch (gameMode) {\r
4981       case PlayFromGameFile:\r
4982       case AnalyzeFile:\r
4983       case TwoMachinesPlay:\r
4984       case EndOfGame:\r
4985         return FALSE;\r
4986 \r
4987       case IcsObserving:\r
4988       case IcsIdle:\r
4989         return FALSE;\r
4990 \r
4991       case MachinePlaysWhite:\r
4992       case IcsPlayingBlack:\r
4993         if (appData.zippyPlay) return FALSE;\r
4994         if (white_piece) {\r
4995             DisplayMoveError(_("You are playing Black"));\r
4996             return FALSE;\r
4997         }\r
4998         break;\r
4999 \r
5000       case MachinePlaysBlack:\r
5001       case IcsPlayingWhite:\r
5002         if (appData.zippyPlay) return FALSE;\r
5003         if (!white_piece) {\r
5004             DisplayMoveError(_("You are playing White"));\r
5005             return FALSE;\r
5006         }\r
5007         break;\r
5008 \r
5009       case EditGame:\r
5010         if (!white_piece && WhiteOnMove(currentMove)) {\r
5011             DisplayMoveError(_("It is White's turn"));\r
5012             return FALSE;\r
5013         }           \r
5014         if (white_piece && !WhiteOnMove(currentMove)) {\r
5015             DisplayMoveError(_("It is Black's turn"));\r
5016             return FALSE;\r
5017         }           \r
5018         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
5019             /* Editing correspondence game history */\r
5020             /* Could disallow this or prompt for confirmation */\r
5021             cmailOldMove = -1;\r
5022         }\r
5023         if (currentMove < forwardMostMove) {\r
5024             /* Discarding moves */\r
5025             /* Could prompt for confirmation here,\r
5026                but I don't think that's such a good idea */\r
5027             forwardMostMove = currentMove;\r
5028         }\r
5029         break;\r
5030 \r
5031       case BeginningOfGame:\r
5032         if (appData.icsActive) return FALSE;\r
5033         if (!appData.noChessProgram) {\r
5034             if (!white_piece) {\r
5035                 DisplayMoveError(_("You are playing White"));\r
5036                 return FALSE;\r
5037             }\r
5038         }\r
5039         break;\r
5040         \r
5041       case Training:\r
5042         if (!white_piece && WhiteOnMove(currentMove)) {\r
5043             DisplayMoveError(_("It is White's turn"));\r
5044             return FALSE;\r
5045         }           \r
5046         if (white_piece && !WhiteOnMove(currentMove)) {\r
5047             DisplayMoveError(_("It is Black's turn"));\r
5048             return FALSE;\r
5049         }           \r
5050         break;\r
5051 \r
5052       default:\r
5053       case IcsExamining:\r
5054         break;\r
5055     }\r
5056     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
5057         && gameMode != AnalyzeFile && gameMode != Training) {\r
5058         DisplayMoveError(_("Displayed position is not current"));\r
5059         return FALSE;\r
5060     }\r
5061     return TRUE;\r
5062 }\r
5063 \r
5064 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
5065 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
5066 int lastLoadGameUseList = FALSE;\r
5067 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
5068 ChessMove lastLoadGameStart = (ChessMove) 0;\r
5069 \r
5070 \r
5071 ChessMove\r
5072 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
5073      int fromX, fromY, toX, toY;\r
5074      int promoChar;\r
5075 {\r
5076     ChessMove moveType;\r
5077     ChessSquare pdown, pup;\r
5078 \r
5079     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
5080     if ((fromX == toX) && (fromY == toY)) {\r
5081         return ImpossibleMove;\r
5082     }\r
5083 \r
5084     /* [HGM] suppress all moves into holdings area and guard band */\r
5085     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
5086             return ImpossibleMove;\r
5087 \r
5088     /* [HGM] <sameColor> moved to here from winboard.c */\r
5089     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
5090     pdown = boards[currentMove][fromY][fromX];\r
5091     pup = boards[currentMove][toY][toX];\r
5092     if (    gameMode != EditPosition &&\r
5093             (WhitePawn <= pdown && pdown < BlackPawn &&\r
5094              WhitePawn <= pup && pup < BlackPawn  ||\r
5095              BlackPawn <= pdown && pdown < EmptySquare &&\r
5096              BlackPawn <= pup && pup < EmptySquare \r
5097             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
5098                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
5099                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
5100         )           )\r
5101          return ImpossibleMove;\r
5102 \r
5103     /* Check if the user is playing in turn.  This is complicated because we\r
5104        let the user "pick up" a piece before it is his turn.  So the piece he\r
5105        tried to pick up may have been captured by the time he puts it down!\r
5106        Therefore we use the color the user is supposed to be playing in this\r
5107        test, not the color of the piece that is currently on the starting\r
5108        square---except in EditGame mode, where the user is playing both\r
5109        sides; fortunately there the capture race can't happen.  (It can\r
5110        now happen in IcsExamining mode, but that's just too bad.  The user\r
5111        will get a somewhat confusing message in that case.)\r
5112        */\r
5113 \r
5114     switch (gameMode) {\r
5115       case PlayFromGameFile:\r
5116       case AnalyzeFile:\r
5117       case TwoMachinesPlay:\r
5118       case EndOfGame:\r
5119       case IcsObserving:\r
5120       case IcsIdle:\r
5121         /* We switched into a game mode where moves are not accepted,\r
5122            perhaps while the mouse button was down. */\r
5123         return ImpossibleMove;\r
5124 \r
5125       case MachinePlaysWhite:\r
5126         /* User is moving for Black */\r
5127         if (WhiteOnMove(currentMove)) {\r
5128             DisplayMoveError(_("It is White's turn"));\r
5129             return ImpossibleMove;\r
5130         }\r
5131         break;\r
5132 \r
5133       case MachinePlaysBlack:\r
5134         /* User is moving for White */\r
5135         if (!WhiteOnMove(currentMove)) {\r
5136             DisplayMoveError(_("It is Black's turn"));\r
5137             return ImpossibleMove;\r
5138         }\r
5139         break;\r
5140 \r
5141       case EditGame:\r
5142       case IcsExamining:\r
5143       case BeginningOfGame:\r
5144       case AnalyzeMode:\r
5145       case Training:\r
5146         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
5147             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
5148             /* User is moving for Black */\r
5149             if (WhiteOnMove(currentMove)) {\r
5150                 DisplayMoveError(_("It is White's turn"));\r
5151                 return ImpossibleMove;\r
5152             }\r
5153         } else {\r
5154             /* User is moving for White */\r
5155             if (!WhiteOnMove(currentMove)) {\r
5156                 DisplayMoveError(_("It is Black's turn"));\r
5157                 return ImpossibleMove;\r
5158             }\r
5159         }\r
5160         break;\r
5161 \r
5162       case IcsPlayingBlack:\r
5163         /* User is moving for Black */\r
5164         if (WhiteOnMove(currentMove)) {\r
5165             if (!appData.premove) {\r
5166                 DisplayMoveError(_("It is White's turn"));\r
5167             } else if (toX >= 0 && toY >= 0) {\r
5168                 premoveToX = toX;\r
5169                 premoveToY = toY;\r
5170                 premoveFromX = fromX;\r
5171                 premoveFromY = fromY;\r
5172                 premovePromoChar = promoChar;\r
5173                 gotPremove = 1;\r
5174                 if (appData.debugMode) \r
5175                     fprintf(debugFP, "Got premove: fromX %d,"\r
5176                             "fromY %d, toX %d, toY %d\n",\r
5177                             fromX, fromY, toX, toY);\r
5178             }\r
5179             return ImpossibleMove;\r
5180         }\r
5181         break;\r
5182 \r
5183       case IcsPlayingWhite:\r
5184         /* User is moving for White */\r
5185         if (!WhiteOnMove(currentMove)) {\r
5186             if (!appData.premove) {\r
5187                 DisplayMoveError(_("It is Black's turn"));\r
5188             } else if (toX >= 0 && toY >= 0) {\r
5189                 premoveToX = toX;\r
5190                 premoveToY = toY;\r
5191                 premoveFromX = fromX;\r
5192                 premoveFromY = fromY;\r
5193                 premovePromoChar = promoChar;\r
5194                 gotPremove = 1;\r
5195                 if (appData.debugMode) \r
5196                     fprintf(debugFP, "Got premove: fromX %d,"\r
5197                             "fromY %d, toX %d, toY %d\n",\r
5198                             fromX, fromY, toX, toY);\r
5199             }\r
5200             return ImpossibleMove;\r
5201         }\r
5202         break;\r
5203 \r
5204       default:\r
5205         break;\r
5206 \r
5207       case EditPosition:\r
5208         /* EditPosition, empty square, or different color piece;\r
5209            click-click move is possible */\r
5210         if (toX == -2 || toY == -2) {\r
5211             boards[0][fromY][fromX] = EmptySquare;\r
5212             return AmbiguousMove;\r
5213         } else if (toX >= 0 && toY >= 0) {\r
5214             boards[0][toY][toX] = boards[0][fromY][fromX];\r
5215             boards[0][fromY][fromX] = EmptySquare;\r
5216             return AmbiguousMove;\r
5217         }\r
5218         return ImpossibleMove;\r
5219     }\r
5220 \r
5221     /* [HGM] If move started in holdings, it means a drop */\r
5222     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
5223          if( pup != EmptySquare ) return ImpossibleMove;\r
5224          if(appData.testLegality) {\r
5225              /* it would be more logical if LegalityTest() also figured out\r
5226               * which drops are legal. For now we forbid pawns on back rank.\r
5227               * Shogi is on its own here...\r
5228               */\r
5229              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
5230                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
5231                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
5232          }\r
5233          return WhiteDrop; /* Not needed to specify white or black yet */\r
5234     }\r
5235 \r
5236     userOfferedDraw = FALSE;\r
5237         \r
5238     /* [HGM] always test for legality, to get promotion info */\r
5239     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
5240                           epStatus[currentMove], castlingRights[currentMove],\r
5241                                          fromY, fromX, toY, toX, promoChar);\r
5242 \r
5243     /* [HGM] but possibly ignore an IllegalMove result */\r
5244     if (appData.testLegality) {\r
5245         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
5246             DisplayMoveError(_("Illegal move"));\r
5247             return ImpossibleMove;\r
5248         }\r
5249     }\r
5250 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);\r
5251     return moveType;\r
5252     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
5253        function is made into one that returns an OK move type if FinishMove\r
5254        should be called. This to give the calling driver routine the\r
5255        opportunity to finish the userMove input with a promotion popup,\r
5256        without bothering the user with this for invalid or illegal moves */\r
5257 \r
5258 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
5259 }\r
5260 \r
5261 /* Common tail of UserMoveEvent and DropMenuEvent */\r
5262 int\r
5263 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
5264      ChessMove moveType;\r
5265      int fromX, fromY, toX, toY;\r
5266      /*char*/int promoChar;\r
5267 {\r
5268     char *bookHit = 0;\r
5269 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);\r
5270     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { \r
5271         // [HGM] superchess: suppress promotions to non-available piece\r
5272         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5273         if(WhiteOnMove(currentMove)) {\r
5274             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;\r
5275         } else {\r
5276             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;\r
5277         }\r
5278     }\r
5279 \r
5280     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion\r
5281        move type in caller when we know the move is a legal promotion */\r
5282     if(moveType == NormalMove && promoChar)\r
5283         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
5284 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);\r
5285     /* [HGM] convert drag-and-drop piece drops to standard form */\r
5286     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
5287          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
5288          fromX = boards[currentMove][fromY][fromX];\r
5289          fromY = DROP_RANK;\r
5290     }\r
5291 \r
5292     /* [HGM] <popupFix> The following if has been moved here from\r
5293        UserMoveEvent(). Because it seemed to belon here (why not allow\r
5294        piece drops in training games?), and because it can only be\r
5295        performed after it is known to what we promote. */\r
5296     if (gameMode == Training) {\r
5297       /* compare the move played on the board to the next move in the\r
5298        * game. If they match, display the move and the opponent's response. \r
5299        * If they don't match, display an error message.\r
5300        */\r
5301       int saveAnimate;\r
5302       Board testBoard;\r
5303       CopyBoard(testBoard, boards[currentMove]);\r
5304       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
5305 \r
5306       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
5307         ForwardInner(currentMove+1);\r
5308 \r
5309         /* Autoplay the opponent's response.\r
5310          * if appData.animate was TRUE when Training mode was entered,\r
5311          * the response will be animated.\r
5312          */\r
5313         saveAnimate = appData.animate;\r
5314         appData.animate = animateTraining;\r
5315         ForwardInner(currentMove+1);\r
5316         appData.animate = saveAnimate;\r
5317 \r
5318         /* check for the end of the game */\r
5319         if (currentMove >= forwardMostMove) {\r
5320           gameMode = PlayFromGameFile;\r
5321           ModeHighlight();\r
5322           SetTrainingModeOff();\r
5323           DisplayInformation(_("End of game"));\r
5324         }\r
5325       } else {\r
5326         DisplayError(_("Incorrect move"), 0);\r
5327       }\r
5328       return 1;\r
5329     }\r
5330 \r
5331   /* Ok, now we know that the move is good, so we can kill\r
5332      the previous line in Analysis Mode */\r
5333   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
5334     forwardMostMove = currentMove;\r
5335   }\r
5336 \r
5337   /* If we need the chess program but it's dead, restart it */\r
5338   ResurrectChessProgram();\r
5339 \r
5340   /* A user move restarts a paused game*/\r
5341   if (pausing)\r
5342     PauseEvent();\r
5343 \r
5344   thinkOutput[0] = NULLCHAR;\r
5345 \r
5346   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
5347 \r
5348     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) \r
5349                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
5350         // [HGM] superchess: take promotion piece out of holdings\r
5351         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5352         if(WhiteOnMove(forwardMostMove-1)) {\r
5353             if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])\r
5354                 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;\r
5355         } else {\r
5356             if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])\r
5357                 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;\r
5358         }\r
5359     }\r
5360 \r
5361   if (gameMode == BeginningOfGame) {\r
5362     if (appData.noChessProgram) {\r
5363       gameMode = EditGame;\r
5364       SetGameInfo();\r
5365     } else {\r
5366       char buf[MSG_SIZ];\r
5367       gameMode = MachinePlaysBlack;\r
5368       StartClocks();\r
5369       SetGameInfo();\r
5370       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
5371       DisplayTitle(buf);\r
5372       if (first.sendName) {\r
5373         sprintf(buf, "name %s\n", gameInfo.white);\r
5374         SendToProgram(buf, &first);\r
5375       }\r
5376       StartClocks();\r
5377     }\r
5378     ModeHighlight();\r
5379   }\r
5380 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);\r
5381   /* Relay move to ICS or chess engine */\r
5382   if (appData.icsActive) {\r
5383     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
5384         gameMode == IcsExamining) {\r
5385       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5386       ics_user_moved = 1;\r
5387     }\r
5388   } else {\r
5389     if (first.sendTime && (gameMode == BeginningOfGame ||\r
5390                            gameMode == MachinePlaysWhite ||\r
5391                            gameMode == MachinePlaysBlack)) {\r
5392       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
5393     }\r
5394     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
5395          // [HGM] book: if program might be playing, let it use book\r
5396         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);\r
5397         first.maybeThinking = TRUE;\r
5398     } else SendMoveToProgram(forwardMostMove-1, &first);\r
5399     if (currentMove == cmailOldMove + 1) {\r
5400       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
5401     }\r
5402   }\r
5403 \r
5404   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5405 \r
5406   switch (gameMode) {\r
5407   case EditGame:\r
5408     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
5409                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
5410     case MT_NONE:\r
5411     case MT_CHECK:\r
5412       break;\r
5413     case MT_CHECKMATE:\r
5414       if (WhiteOnMove(currentMove)) {\r
5415         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
5416       } else {\r
5417         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
5418       }\r
5419       break;\r
5420     case MT_STALEMATE:\r
5421       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
5422       break;\r
5423     }\r
5424     break;\r
5425     \r
5426   case MachinePlaysBlack:\r
5427   case MachinePlaysWhite:\r
5428     /* disable certain menu options while machine is thinking */\r
5429     SetMachineThinkingEnables();\r
5430     break;\r
5431 \r
5432   default:\r
5433     break;\r
5434   }\r
5435 \r
5436   if(bookHit) { // [HGM] book: simulate book reply\r
5437         static char bookMove[MSG_SIZ]; // a bit generous?\r
5438 \r
5439         programStats.depth = programStats.nodes = programStats.time = \r
5440         programStats.score = programStats.got_only_move = 0;\r
5441         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
5442 \r
5443         strcpy(bookMove, "move ");\r
5444         strcat(bookMove, bookHit);\r
5445         HandleMachineMove(bookMove, &first);\r
5446   }\r
5447   return 1;\r
5448 }\r
5449 \r
5450 void\r
5451 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
5452      int fromX, fromY, toX, toY;\r
5453      int promoChar;\r
5454 {\r
5455     /* [HGM] This routine was added to allow calling of its two logical\r
5456        parts from other modules in the old way. Before, UserMoveEvent()\r
5457        automatically called FinishMove() if the move was OK, and returned\r
5458        otherwise. I separated the two, in order to make it possible to\r
5459        slip a promotion popup in between. But that it always needs two\r
5460        calls, to the first part, (now called UserMoveTest() ), and to\r
5461        FinishMove if the first part succeeded. Calls that do not need\r
5462        to do anything in between, can call this routine the old way. \r
5463     */\r
5464     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
5465 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);\r
5466     if(moveType != ImpossibleMove)\r
5467         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
5468 }\r
5469 \r
5470 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
5471 {\r
5472     char * hint = lastHint;\r
5473     FrontEndProgramStats stats;\r
5474 \r
5475     stats.which = cps == &first ? 0 : 1;\r
5476     stats.depth = cpstats->depth;\r
5477     stats.nodes = cpstats->nodes;\r
5478     stats.score = cpstats->score;\r
5479     stats.time = cpstats->time;\r
5480     stats.pv = cpstats->movelist;\r
5481     stats.hint = lastHint;\r
5482     stats.an_move_index = 0;\r
5483     stats.an_move_count = 0;\r
5484 \r
5485     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
5486         stats.hint = cpstats->move_name;\r
5487         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
5488         stats.an_move_count = cpstats->nr_moves;\r
5489     }\r
5490 \r
5491     SetProgramStats( &stats );\r
5492 }\r
5493 \r
5494 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)\r
5495 {   // [HGM] book: this routine intercepts moves to simulate book replies\r
5496     char *bookHit = NULL;\r
5497 \r
5498     //first determine if the incoming move brings opponent into his book\r
5499     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
5500         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
5501     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");\r
5502     if(bookHit != NULL && !cps->bookSuspend) {\r
5503         // make sure opponent is not going to reply after receiving move to book position\r
5504         SendToProgram("force\n", cps);\r
5505         cps->bookSuspend = TRUE; // flag indicating it has to be restarted\r
5506     }\r
5507     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move\r
5508     // now arrange restart after book miss\r
5509     if(bookHit) {\r
5510         // after a book hit we never send 'go', and the code after the call to this routine\r
5511         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').\r
5512         char buf[MSG_SIZ];\r
5513         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(\r
5514         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it\r
5515         SendToProgram(buf, cps);\r
5516         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'\r
5517     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine\r
5518         SendToProgram("go\n", cps);\r
5519         cps->bookSuspend = FALSE; // after a 'go' we are never suspended\r
5520     } else { // 'go' might be sent based on 'firstMove' after this routine returns\r
5521         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return\r
5522             SendToProgram("go\n", cps); \r
5523         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss\r
5524     }\r
5525     return bookHit; // notify caller of hit, so it can take action to send move to opponent\r
5526 }\r
5527 \r
5528 char *savedMessage;\r
5529 ChessProgramState *savedState;\r
5530 void DeferredBookMove(void)\r
5531 {\r
5532         if(savedState->lastPing != savedState->lastPong)\r
5533                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
5534         else\r
5535         HandleMachineMove(savedMessage, savedState);\r
5536 }\r
5537 \r
5538 void\r
5539 HandleMachineMove(message, cps)\r
5540      char *message;\r
5541      ChessProgramState *cps;\r
5542 {\r
5543     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
5544     char realname[MSG_SIZ];\r
5545     int fromX, fromY, toX, toY;\r
5546     ChessMove moveType;\r
5547     char promoChar;\r
5548     char *p;\r
5549     int machineWhite;\r
5550     char *bookHit;\r
5551 \r
5552 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit\r
5553     /*\r
5554      * Kludge to ignore BEL characters\r
5555      */\r
5556     while (*message == '\007') message++;\r
5557 \r
5558     /*\r
5559      * [HGM] engine debug message: ignore lines starting with '#' character\r
5560      */\r
5561     if(cps->debug && *message == '#') return;\r
5562 \r
5563     /*\r
5564      * Look for book output\r
5565      */\r
5566     if (cps == &first && bookRequested) {\r
5567         if (message[0] == '\t' || message[0] == ' ') {\r
5568             /* Part of the book output is here; append it */\r
5569             strcat(bookOutput, message);\r
5570             strcat(bookOutput, "  \n");\r
5571             return;\r
5572         } else if (bookOutput[0] != NULLCHAR) {\r
5573             /* All of book output has arrived; display it */\r
5574             char *p = bookOutput;\r
5575             while (*p != NULLCHAR) {\r
5576                 if (*p == '\t') *p = ' ';\r
5577                 p++;\r
5578             }\r
5579             DisplayInformation(bookOutput);\r
5580             bookRequested = FALSE;\r
5581             /* Fall through to parse the current output */\r
5582         }\r
5583     }\r
5584 \r
5585     /*\r
5586      * Look for machine move.\r
5587      */\r
5588     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
5589         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
5590     {\r
5591         /* This method is only useful on engines that support ping */\r
5592         if (cps->lastPing != cps->lastPong) {\r
5593           if (gameMode == BeginningOfGame) {\r
5594             /* Extra move from before last new; ignore */\r
5595             if (appData.debugMode) {\r
5596                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5597             }\r
5598           } else {\r
5599             if (appData.debugMode) {\r
5600                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5601                         cps->which, gameMode);\r
5602             }\r
5603 \r
5604             SendToProgram("undo\n", cps);\r
5605           }\r
5606           return;\r
5607         }\r
5608 \r
5609         switch (gameMode) {\r
5610           case BeginningOfGame:\r
5611             /* Extra move from before last reset; ignore */\r
5612             if (appData.debugMode) {\r
5613                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5614             }\r
5615             return;\r
5616 \r
5617           case EndOfGame:\r
5618           case IcsIdle:\r
5619           default:\r
5620             /* Extra move after we tried to stop.  The mode test is\r
5621                not a reliable way of detecting this problem, but it's\r
5622                the best we can do on engines that don't support ping.\r
5623             */\r
5624             if (appData.debugMode) {\r
5625                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5626                         cps->which, gameMode);\r
5627             }\r
5628             SendToProgram("undo\n", cps);\r
5629             return;\r
5630 \r
5631           case MachinePlaysWhite:\r
5632           case IcsPlayingWhite:\r
5633             machineWhite = TRUE;\r
5634             break;\r
5635 \r
5636           case MachinePlaysBlack:\r
5637           case IcsPlayingBlack:\r
5638             machineWhite = FALSE;\r
5639             break;\r
5640 \r
5641           case TwoMachinesPlay:\r
5642             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5643             break;\r
5644         }\r
5645         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5646             if (appData.debugMode) {\r
5647                 fprintf(debugFP,\r
5648                         "Ignoring move out of turn by %s, gameMode %d"\r
5649                         ", forwardMost %d\n",\r
5650                         cps->which, gameMode, forwardMostMove);\r
5651             }\r
5652             return;\r
5653         }\r
5654 \r
5655     if (appData.debugMode) { int f = forwardMostMove;\r
5656         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5657                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5658     }\r
5659         if(cps->alphaRank) AlphaRank(machineMove, 4);\r
5660         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5661                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5662             /* Machine move could not be parsed; ignore it. */\r
5663             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),\r
5664                     machineMove, cps->which);\r
5665             DisplayError(buf1, 0);\r
5666             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",\r
5667                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5668             if (gameMode == TwoMachinesPlay) {\r
5669               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5670                        buf1, GE_XBOARD);\r
5671             }\r
5672             return;\r
5673         }\r
5674 \r
5675         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5676         /* So we have to redo legality test with true e.p. status here,  */\r
5677         /* to make sure an illegal e.p. capture does not slip through,   */\r
5678         /* to cause a forfeit on a justified illegal-move complaint      */\r
5679         /* of the opponent.                                              */\r
5680         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5681             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5682                                                               ) {\r
5683            ChessMove moveType;\r
5684            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5685                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5686                              fromY, fromX, toY, toX, promoChar);\r
5687             if (appData.debugMode) {\r
5688                 int i;\r
5689                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5690                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5691                 fprintf(debugFP, "castling rights\n");\r
5692             }\r
5693             if(moveType == IllegalMove) {\r
5694                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5695                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5696                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5697                            buf1, GE_XBOARD);\r
5698            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
5699            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5700               when they shouldn't (like TSCP-Gothic) */\r
5701            switch(moveType) {\r
5702              case WhiteASideCastleFR:\r
5703              case BlackASideCastleFR:\r
5704                toX+=2;\r
5705                currentMoveString[2]++;\r
5706                break;\r
5707              case WhiteHSideCastleFR:\r
5708              case BlackHSideCastleFR:\r
5709                toX--;\r
5710                currentMoveString[2]--;\r
5711                break;\r
5712            }\r
5713         }\r
5714         hintRequested = FALSE;\r
5715         lastHint[0] = NULLCHAR;\r
5716         bookRequested = FALSE;\r
5717         /* Program may be pondering now */\r
5718         cps->maybeThinking = TRUE;\r
5719         if (cps->sendTime == 2) cps->sendTime = 1;\r
5720         if (cps->offeredDraw) cps->offeredDraw--;\r
5721 \r
5722 #if ZIPPY\r
5723         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5724             first.initDone) {\r
5725           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5726           ics_user_moved = 1;\r
5727           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
5728                 char buf[3*MSG_SIZ];\r
5729 \r
5730                 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",\r
5731                         programStats.depth,\r
5732                         programStats.score / 100.,\r
5733                         programStats.time / 100.,\r
5734                         (double) programStats.nodes,\r
5735                         programStats.nodes / (10*abs(programStats.time) + 1.),\r
5736                         programStats.movelist);\r
5737                 SendToICS(buf);\r
5738           }\r
5739         }\r
5740 #endif\r
5741         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5742         strcpy(machineMove, currentMoveString);\r
5743         strcat(machineMove, "\n");\r
5744         strcpy(moveList[forwardMostMove], machineMove);\r
5745 \r
5746         /* [AS] Save move info and clear stats for next move */\r
5747         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5748         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5749         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5750         ClearProgramStats();\r
5751         thinkOutput[0] = NULLCHAR;\r
5752         hiddenThinkOutputState = 0;\r
5753 \r
5754         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5755 \r
5756         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5757         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5758             int count = 0;\r
5759 \r
5760             while( count < adjudicateLossPlies ) {\r
5761                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5762 \r
5763                 if( count & 1 ) {\r
5764                     score = -score; /* Flip score for winning side */\r
5765                 }\r
5766 \r
5767                 if( score > adjudicateLossThreshold ) {\r
5768                     break;\r
5769                 }\r
5770 \r
5771                 count++;\r
5772             }\r
5773 \r
5774             if( count >= adjudicateLossPlies ) {\r
5775                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5776 \r
5777                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5778                     "Xboard adjudication", \r
5779                     GE_XBOARD );\r
5780 \r
5781                 return;\r
5782             }\r
5783         }\r
5784 \r
5785         if( gameMode == TwoMachinesPlay ) {\r
5786           // [HGM] some adjudications useful with buggy engines\r
5787             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
5788           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {\r
5789 \r
5790             if(appData.testLegality)\r
5791             // don't wait for engine to announce game end if we can judge ourselves\r
5792             switch (MateTest(boards[forwardMostMove],\r
5793                                  PosFlags(forwardMostMove), epFile,\r
5794                                        castlingRights[forwardMostMove]) ) {\r
5795               case MT_NONE:\r
5796               case MT_CHECK:\r
5797               default:\r
5798                 break;\r
5799               case MT_STALEMATE:\r
5800                 epStatus[forwardMostMove] = EP_STALEMATE;\r
5801                 if(appData.checkMates) {\r
5802                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5803                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5804                     GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",\r
5805                         GE_XBOARD );\r
5806                 }\r
5807                 break;\r
5808               case MT_CHECKMATE:\r
5809                 epStatus[forwardMostMove] = EP_CHECKMATE;\r
5810                 if(appData.checkMates) {\r
5811                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5812                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5813                     GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5814                     "Xboard adjudication: Checkmate", \r
5815                     GE_XBOARD );\r
5816                 }\r
5817                 break;\r
5818             }\r
5819 \r
5820             if( appData.testLegality )\r
5821             {   /* [HGM] Some more adjudications for obstinate engines */\r
5822                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5823                     NrWQ=0, NrBQ=0, NrW=0, bishopsColor = 0,\r
5824                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;\r
5825                 static int moveCount = 6;\r
5826 \r
5827                 /* First absolutely insufficient mating material. Count what is on board. */\r
5828                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5829                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5830                     int m=i;\r
5831 \r
5832                     switch((int) p)\r
5833                     {   /* count B,N,R and other of each side */\r
5834                         case WhiteKnight:\r
5835                              NrWN++; break;\r
5836                         case WhiteBishop:\r
5837                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5838                              bishopsColor |= 1 << ((i^j)&1);\r
5839                              NrWB++; break;\r
5840                         case BlackKnight:\r
5841                              NrBN++; break;\r
5842                         case BlackBishop:\r
5843                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5844                              bishopsColor |= 1 << ((i^j)&1);\r
5845                              NrBB++; break;\r
5846                         case WhiteRook:\r
5847                              NrWR++; break;\r
5848                         case BlackRook:\r
5849                              NrBR++; break;\r
5850                         case WhiteQueen:\r
5851                              NrWQ++; break;\r
5852                         case BlackQueen:\r
5853                              NrBQ++; break;\r
5854                         case EmptySquare: \r
5855                              break;\r
5856                         case BlackPawn:\r
5857                              m = 7-i;\r
5858                         case WhitePawn:\r
5859                              PawnAdvance += m; NrPawns++;\r
5860                     }\r
5861                     NrPieces += (p != EmptySquare);\r
5862                     NrW += ((int)p < (int)BlackPawn);\r
5863                     if(gameInfo.variant == VariantXiangqi && \r
5864                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {\r
5865                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces\r
5866                         NrW -= ((int)p < (int)BlackPawn);\r
5867                     }\r
5868                 }\r
5869 \r
5870                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&\r
5871                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||\r
5872                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color\r
5873                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5874 \r
5875                      /* always flag draws, for judging claims */\r
5876                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5877 \r
5878                      if(appData.materialDraws) {\r
5879                          /* but only adjudicate them if adjudication enabled */\r
5880                          SendToProgram("force\n", cps->other); // suppress reply\r
5881                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */\r
5882                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5883                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5884                          return;\r
5885                      }\r
5886                 }\r
5887 \r
5888                 /* Shatranj baring rule */\r
5889                 if( gameInfo.variant == VariantShatranj && (NrW == 1 || NrPieces - NrW == 1) )\r
5890                 {    /* bare King */\r
5891 \r
5892                      if(--bare < 0 && appData.checkMates) {\r
5893                          /* but only adjudicate them if adjudication enabled */\r
5894                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5895                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5896                          GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
5897                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5898                          return;\r
5899                      }\r
5900                 } else bare = 1;\r
5901 \r
5902                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5903                 if(NrPieces == 4 && \r
5904                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5905                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5906                    || NrWN==2 || NrBN==2     /* KNNK */\r
5907                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5908                   ) ) {\r
5909                      if(--moveCount < 0 && appData.trivialDraws)\r
5910                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5911                           SendToProgram("force\n", cps->other); // suppress reply\r
5912                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5913                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5914                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5915                           return;\r
5916                      }\r
5917                 } else moveCount = 6;\r
5918             }\r
5919           }\r
5920 #if 1\r
5921     if (appData.debugMode) { int i;\r
5922       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5923               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5924               appData.drawRepeats);\r
5925       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5926            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5927 \r
5928     }\r
5929 #endif\r
5930                 /* Check for rep-draws */\r
5931                 count = 0;\r
5932                 for(k = forwardMostMove-2;\r
5933                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5934                         epStatus[k] < EP_UNKNOWN &&\r
5935                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5936                     k-=2)\r
5937                 {   int rights=0;\r
5938 #if 0\r
5939     if (appData.debugMode) {\r
5940       fprintf(debugFP, " loop\n");\r
5941     }\r
5942 #endif\r
5943                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5944 #if 0\r
5945     if (appData.debugMode) {\r
5946       fprintf(debugFP, "match\n");\r
5947     }\r
5948 #endif\r
5949                         /* compare castling rights */\r
5950                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5951                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5952                                 rights++; /* King lost rights, while rook still had them */\r
5953                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5954                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5955                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5956                                    rights++; /* but at least one rook lost them */\r
5957                         }\r
5958                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5959                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5960                                 rights++; \r
5961                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5962                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5963                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5964                                    rights++;\r
5965                         }\r
5966 #if 0\r
5967     if (appData.debugMode) {\r
5968       for(i=0; i<nrCastlingRights; i++)\r
5969       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5970     }\r
5971 \r
5972     if (appData.debugMode) {\r
5973       fprintf(debugFP, " %d %d\n", rights, k);\r
5974     }\r
5975 #endif\r
5976                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5977                             && appData.drawRepeats > 1) {\r
5978                              /* adjudicate after user-specified nr of repeats */\r
5979                              SendToProgram("force\n", cps->other); // suppress reply\r
5980                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5981                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5982                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { \r
5983                                 // [HGM] xiangqi: check for forbidden perpetuals\r
5984                                 int m, ourPerpetual = 1, hisPerpetual = 1;\r
5985                                 for(m=forwardMostMove; m>k; m-=2) {\r
5986                                     if(MateTest(boards[m], PosFlags(m), \r
5987                                                         EP_NONE, castlingRights[m]) != MT_CHECK)\r
5988                                         ourPerpetual = 0; // the current mover did not always check\r
5989                                     if(MateTest(boards[m-1], PosFlags(m-1), \r
5990                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)\r
5991                                         hisPerpetual = 0; // the opponent did not always check\r
5992                                 }\r
5993                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
5994                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5995                                            "Xboard adjudication: perpetual checking", GE_XBOARD );\r
5996                                     return;\r
5997                                 }\r
5998                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet\r
5999                                     break; // (or we would have caught him before). Abort repetition-checking loop.\r
6000                                 // if neither of us is checking all the time, or both are, it is draw\r
6001                                 // (illegal-chase forfeits not implemented yet!)\r
6002                              }\r
6003                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
6004                              return;\r
6005                         }\r
6006                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
6007                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
6008                     }\r
6009                 }\r
6010 \r
6011                 /* Now we test for 50-move draws. Determine ply count */\r
6012                 count = forwardMostMove;\r
6013                 /* look for last irreversble move */\r
6014                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
6015                     count--;\r
6016                 /* if we hit starting position, add initial plies */\r
6017                 if( count == backwardMostMove )\r
6018                     count -= initialRulePlies;\r
6019                 count = forwardMostMove - count; \r
6020                 if( count >= 100)\r
6021                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
6022                          /* this is used to judge if draw claims are legal */\r
6023                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
6024                          SendToProgram("force\n", cps->other); // suppress reply\r
6025                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6026                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6027                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
6028                          return;\r
6029                 }\r
6030 \r
6031                 /* if draw offer is pending, treat it as a draw claim\r
6032                  * when draw condition present, to allow engines a way to\r
6033                  * claim draws before making their move to avoid a race\r
6034                  * condition occurring after their move\r
6035                  */\r
6036                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
6037                          char *p = NULL;\r
6038                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
6039                              p = "Draw claim: 50-move rule";\r
6040                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
6041                              p = "Draw claim: 3-fold repetition";\r
6042                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
6043                              p = "Draw claim: insufficient mating material";\r
6044                          if( p != NULL ) {\r
6045                              SendToProgram("force\n", cps->other); // suppress reply\r
6046                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6047                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
6048                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6049                              return;\r
6050                          }\r
6051                 }\r
6052 \r
6053 \r
6054                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
6055                     SendToProgram("force\n", cps->other); // suppress reply\r
6056                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6057                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6058 \r
6059                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
6060 \r
6061                     return;\r
6062                 }\r
6063         }\r
6064 \r
6065         bookHit = NULL;\r
6066         if (gameMode == TwoMachinesPlay) {\r
6067             /* [HGM] relaying draw offers moved to after reception of move */\r
6068             /* and interpreting offer as claim if it brings draw condition */\r
6069             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
6070                 SendToProgram("draw\n", cps->other);\r
6071             }\r
6072             if (cps->other->sendTime) {\r
6073                 SendTimeRemaining(cps->other,\r
6074                                   cps->other->twoMachinesColor[0] == 'w');\r
6075             }\r
6076             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);\r
6077             if (firstMove && !bookHit) {\r
6078                 firstMove = FALSE;\r
6079                 if (cps->other->useColors) {\r
6080                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
6081                 }\r
6082                 SendToProgram("go\n", cps->other);\r
6083             }\r
6084             cps->other->maybeThinking = TRUE;\r
6085         }\r
6086 \r
6087         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6088         \r
6089         if (!pausing && appData.ringBellAfterMoves) {\r
6090             RingBell();\r
6091         }\r
6092 \r
6093         /* \r
6094          * Reenable menu items that were disabled while\r
6095          * machine was thinking\r
6096          */\r
6097         if (gameMode != TwoMachinesPlay)\r
6098             SetUserThinkingEnables();\r
6099 \r
6100         // [HGM] book: after book hit opponent has received move and is now in force mode\r
6101         // force the book reply into it, and then fake that it outputted this move by jumping\r
6102         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move\r
6103         if(bookHit) {\r
6104                 static char bookMove[MSG_SIZ]; // a bit generous?\r
6105 \r
6106                 strcpy(bookMove, "move ");\r
6107                 strcat(bookMove, bookHit);\r
6108                 message = bookMove;\r
6109                 cps = cps->other;\r
6110                 programStats.depth = programStats.nodes = programStats.time = \r
6111                 programStats.score = programStats.got_only_move = 0;\r
6112                 sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
6113 \r
6114                 if(cps->lastPing != cps->lastPong) {\r
6115                     savedMessage = message; // args for deferred call\r
6116                     savedState = cps;\r
6117                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
6118                     return;\r
6119                 }\r
6120                 goto FakeBookMove;\r
6121         }\r
6122 \r
6123         return;\r
6124     }\r
6125 \r
6126     /* Set special modes for chess engines.  Later something general\r
6127      *  could be added here; for now there is just one kludge feature,\r
6128      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
6129      *  when "xboard" is given as an interactive command.\r
6130      */\r
6131     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
6132         cps->useSigint = FALSE;\r
6133         cps->useSigterm = FALSE;\r
6134     }\r
6135 \r
6136     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
6137      * want this, I was asked to put it in, and obliged.\r
6138      */\r
6139     if (!strncmp(message, "setboard ", 9)) {\r
6140         Board initial_position; int i;\r
6141 \r
6142         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
6143 \r
6144         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
6145             DisplayError(_("Bad FEN received from engine"), 0);\r
6146             return ;\r
6147         } else {\r
6148            Reset(FALSE, FALSE);\r
6149            CopyBoard(boards[0], initial_position);\r
6150            initialRulePlies = FENrulePlies;\r
6151            epStatus[0] = FENepStatus;\r
6152            for( i=0; i<nrCastlingRights; i++ )\r
6153                 castlingRights[0][i] = FENcastlingRights[i];\r
6154            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
6155            else gameMode = MachinePlaysBlack;                 \r
6156            DrawPosition(FALSE, boards[currentMove]);\r
6157         }\r
6158         return;\r
6159     }\r
6160 \r
6161     /*\r
6162      * Look for communication commands\r
6163      */\r
6164     if (!strncmp(message, "telluser ", 9)) {\r
6165         DisplayNote(message + 9);\r
6166         return;\r
6167     }\r
6168     if (!strncmp(message, "tellusererror ", 14)) {\r
6169         DisplayError(message + 14, 0);\r
6170         return;\r
6171     }\r
6172     if (!strncmp(message, "tellopponent ", 13)) {\r
6173       if (appData.icsActive) {\r
6174         if (loggedOn) {\r
6175           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
6176           SendToICS(buf1);\r
6177         }\r
6178       } else {\r
6179         DisplayNote(message + 13);\r
6180       }\r
6181       return;\r
6182     }\r
6183     if (!strncmp(message, "tellothers ", 11)) {\r
6184       if (appData.icsActive) {\r
6185         if (loggedOn) {\r
6186           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
6187           SendToICS(buf1);\r
6188         }\r
6189       }\r
6190       return;\r
6191     }\r
6192     if (!strncmp(message, "tellall ", 8)) {\r
6193       if (appData.icsActive) {\r
6194         if (loggedOn) {\r
6195           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
6196           SendToICS(buf1);\r
6197         }\r
6198       } else {\r
6199         DisplayNote(message + 8);\r
6200       }\r
6201       return;\r
6202     }\r
6203     if (strncmp(message, "warning", 7) == 0) {\r
6204         /* Undocumented feature, use tellusererror in new code */\r
6205         DisplayError(message, 0);\r
6206         return;\r
6207     }\r
6208     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
6209         strcpy(realname, cps->tidy);\r
6210         strcat(realname, " query");\r
6211         AskQuestion(realname, buf2, buf1, cps->pr);\r
6212         return;\r
6213     }\r
6214     /* Commands from the engine directly to ICS.  We don't allow these to be \r
6215      *  sent until we are logged on. Crafty kibitzes have been known to \r
6216      *  interfere with the login process.\r
6217      */\r
6218     if (loggedOn) {\r
6219         if (!strncmp(message, "tellics ", 8)) {\r
6220             SendToICS(message + 8);\r
6221             SendToICS("\n");\r
6222             return;\r
6223         }\r
6224         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
6225             SendToICS(ics_prefix);\r
6226             SendToICS(message + 15);\r
6227             SendToICS("\n");\r
6228             return;\r
6229         }\r
6230         /* The following are for backward compatibility only */\r
6231         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
6232             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
6233             SendToICS(ics_prefix);\r
6234             SendToICS(message);\r
6235             SendToICS("\n");\r
6236             return;\r
6237         }\r
6238     }\r
6239     if (strncmp(message, "feature ", 8) == 0) {\r
6240       ParseFeatures(message+8, cps);\r
6241     }\r
6242     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
6243         return;\r
6244     }\r
6245     /*\r
6246      * If the move is illegal, cancel it and redraw the board.\r
6247      * Also deal with other error cases.  Matching is rather loose\r
6248      * here to accommodate engines written before the spec.\r
6249      */\r
6250     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
6251         strncmp(message, "Error", 5) == 0) {\r
6252         if (StrStr(message, "name") || \r
6253             StrStr(message, "rating") || StrStr(message, "?") ||\r
6254             StrStr(message, "result") || StrStr(message, "board") ||\r
6255             StrStr(message, "bk") || StrStr(message, "computer") ||\r
6256             StrStr(message, "variant") || StrStr(message, "hint") ||\r
6257             StrStr(message, "random") || StrStr(message, "depth") ||\r
6258             StrStr(message, "accepted")) {\r
6259             return;\r
6260         }\r
6261         if (StrStr(message, "protover")) {\r
6262           /* Program is responding to input, so it's apparently done\r
6263              initializing, and this error message indicates it is\r
6264              protocol version 1.  So we don't need to wait any longer\r
6265              for it to initialize and send feature commands. */\r
6266           FeatureDone(cps, 1);\r
6267           cps->protocolVersion = 1;\r
6268           return;\r
6269         }\r
6270         cps->maybeThinking = FALSE;\r
6271 \r
6272         if (StrStr(message, "draw")) {\r
6273             /* Program doesn't have "draw" command */\r
6274             cps->sendDrawOffers = 0;\r
6275             return;\r
6276         }\r
6277         if (cps->sendTime != 1 &&\r
6278             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
6279           /* Program apparently doesn't have "time" or "otim" command */\r
6280           cps->sendTime = 0;\r
6281           return;\r
6282         }\r
6283         if (StrStr(message, "analyze")) {\r
6284             cps->analysisSupport = FALSE;\r
6285             cps->analyzing = FALSE;\r
6286             Reset(FALSE, TRUE);\r
6287             sprintf(buf2, _("%s does not support analysis"), cps->tidy);\r
6288             DisplayError(buf2, 0);\r
6289             return;\r
6290         }\r
6291         if (StrStr(message, "(no matching move)st")) {\r
6292           /* Special kludge for GNU Chess 4 only */\r
6293           cps->stKludge = TRUE;\r
6294           SendTimeControl(cps, movesPerSession, timeControl,\r
6295                           timeIncrement, appData.searchDepth,\r
6296                           searchTime);\r
6297           return;\r
6298         }\r
6299         if (StrStr(message, "(no matching move)sd")) {\r
6300           /* Special kludge for GNU Chess 4 only */\r
6301           cps->sdKludge = TRUE;\r
6302           SendTimeControl(cps, movesPerSession, timeControl,\r
6303                           timeIncrement, appData.searchDepth,\r
6304                           searchTime);\r
6305           return;\r
6306         }\r
6307         if (!StrStr(message, "llegal")) {\r
6308             return;\r
6309         }\r
6310         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6311             gameMode == IcsIdle) return;\r
6312         if (forwardMostMove <= backwardMostMove) return;\r
6313 #if 0\r
6314         /* Following removed: it caused a bug where a real illegal move\r
6315            message in analyze mored would be ignored. */\r
6316         if (cps == &first && programStats.ok_to_send == 0) {\r
6317             /* Bogus message from Crafty responding to "."  This filtering\r
6318                can miss some of the bad messages, but fortunately the bug \r
6319                is fixed in current Crafty versions, so it doesn't matter. */\r
6320             return;\r
6321         }\r
6322 #endif\r
6323         if (pausing) PauseEvent();\r
6324         if (gameMode == PlayFromGameFile) {\r
6325             /* Stop reading this game file */\r
6326             gameMode = EditGame;\r
6327             ModeHighlight();\r
6328         }\r
6329         currentMove = --forwardMostMove;\r
6330         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
6331         SwitchClocks();\r
6332         DisplayBothClocks();\r
6333         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),\r
6334                 parseList[currentMove], cps->which);\r
6335         DisplayMoveError(buf1);\r
6336         DrawPosition(FALSE, boards[currentMove]);\r
6337 \r
6338         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
6339         /* only passes fully legal moves                            */\r
6340         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
6341             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
6342                                 "False illegal-move claim", GE_XBOARD );\r
6343         }\r
6344         return;\r
6345     }\r
6346     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
6347         /* Program has a broken "time" command that\r
6348            outputs a string not ending in newline.\r
6349            Don't use it. */\r
6350         cps->sendTime = 0;\r
6351     }\r
6352     \r
6353     /*\r
6354      * If chess program startup fails, exit with an error message.\r
6355      * Attempts to recover here are futile.\r
6356      */\r
6357     if ((StrStr(message, "unknown host") != NULL)\r
6358         || (StrStr(message, "No remote directory") != NULL)\r
6359         || (StrStr(message, "not found") != NULL)\r
6360         || (StrStr(message, "No such file") != NULL)\r
6361         || (StrStr(message, "can't alloc") != NULL)\r
6362         || (StrStr(message, "Permission denied") != NULL)) {\r
6363 \r
6364         cps->maybeThinking = FALSE;\r
6365         sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),\r
6366                 cps->which, cps->program, cps->host, message);\r
6367         RemoveInputSource(cps->isr);\r
6368         DisplayFatalError(buf1, 0, 1);\r
6369         return;\r
6370     }\r
6371     \r
6372     /* \r
6373      * Look for hint output\r
6374      */\r
6375     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
6376         if (cps == &first && hintRequested) {\r
6377             hintRequested = FALSE;\r
6378             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
6379                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
6380                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
6381                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
6382                                     fromY, fromX, toY, toX, promoChar, buf1);\r
6383                 sprintf(buf2, _("Hint: %s"), buf1);\r
6384                 DisplayInformation(buf2);\r
6385             } else {\r
6386                 /* Hint move could not be parsed!? */\r
6387                 sprintf(buf2,\r
6388                         _("Illegal hint move \"%s\"\nfrom %s chess program"),\r
6389                         buf1, cps->which);\r
6390                 DisplayError(buf2, 0);\r
6391             }\r
6392         } else {\r
6393             strcpy(lastHint, buf1);\r
6394         }\r
6395         return;\r
6396     }\r
6397 \r
6398     /*\r
6399      * Ignore other messages if game is not in progress\r
6400      */\r
6401     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6402         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
6403 \r
6404     /*\r
6405      * look for win, lose, draw, or draw offer\r
6406      */\r
6407     if (strncmp(message, "1-0", 3) == 0) {\r
6408         char *p, *q, *r = "";\r
6409         p = strchr(message, '{');\r
6410         if (p) {\r
6411             q = strchr(p, '}');\r
6412             if (q) {\r
6413                 *q = NULLCHAR;\r
6414                 r = p + 1;\r
6415             }\r
6416         }\r
6417         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
6418         return;\r
6419     } else if (strncmp(message, "0-1", 3) == 0) {\r
6420         char *p, *q, *r = "";\r
6421         p = strchr(message, '{');\r
6422         if (p) {\r
6423             q = strchr(p, '}');\r
6424             if (q) {\r
6425                 *q = NULLCHAR;\r
6426                 r = p + 1;\r
6427             }\r
6428         }\r
6429         /* Kludge for Arasan 4.1 bug */\r
6430         if (strcmp(r, "Black resigns") == 0) {\r
6431             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
6432             return;\r
6433         }\r
6434         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
6435         return;\r
6436     } else if (strncmp(message, "1/2", 3) == 0) {\r
6437         char *p, *q, *r = "";\r
6438         p = strchr(message, '{');\r
6439         if (p) {\r
6440             q = strchr(p, '}');\r
6441             if (q) {\r
6442                 *q = NULLCHAR;\r
6443                 r = p + 1;\r
6444             }\r
6445         }\r
6446             \r
6447         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
6448         return;\r
6449 \r
6450     } else if (strncmp(message, "White resign", 12) == 0) {\r
6451         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6452         return;\r
6453     } else if (strncmp(message, "Black resign", 12) == 0) {\r
6454         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6455         return;\r
6456     } else if (strncmp(message, "White matches", 13) == 0 ||\r
6457                strncmp(message, "Black matches", 13) == 0   ) {\r
6458         /* [HGM] ignore GNUShogi noises */\r
6459         return;\r
6460     } else if (strncmp(message, "White", 5) == 0 &&\r
6461                message[5] != '(' &&\r
6462                StrStr(message, "Black") == NULL) {\r
6463         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6464         return;\r
6465     } else if (strncmp(message, "Black", 5) == 0 &&\r
6466                message[5] != '(') {\r
6467         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6468         return;\r
6469     } else if (strcmp(message, "resign") == 0 ||\r
6470                strcmp(message, "computer resigns") == 0) {\r
6471         switch (gameMode) {\r
6472           case MachinePlaysBlack:\r
6473           case IcsPlayingBlack:\r
6474             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
6475             break;\r
6476           case MachinePlaysWhite:\r
6477           case IcsPlayingWhite:\r
6478             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
6479             break;\r
6480           case TwoMachinesPlay:\r
6481             if (cps->twoMachinesColor[0] == 'w')\r
6482               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6483             else\r
6484               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6485             break;\r
6486           default:\r
6487             /* can't happen */\r
6488             break;\r
6489         }\r
6490         return;\r
6491     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
6492         switch (gameMode) {\r
6493           case MachinePlaysBlack:\r
6494           case IcsPlayingBlack:\r
6495             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6496             break;\r
6497           case MachinePlaysWhite:\r
6498           case IcsPlayingWhite:\r
6499             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
6500             break;\r
6501           case TwoMachinesPlay:\r
6502             if (cps->twoMachinesColor[0] == 'w')\r
6503               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6504             else\r
6505               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6506             break;\r
6507           default:\r
6508             /* can't happen */\r
6509             break;\r
6510         }\r
6511         return;\r
6512     } else if (strncmp(message, "computer mates", 14) == 0) {\r
6513         switch (gameMode) {\r
6514           case MachinePlaysBlack:\r
6515           case IcsPlayingBlack:\r
6516             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
6517             break;\r
6518           case MachinePlaysWhite:\r
6519           case IcsPlayingWhite:\r
6520             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6521             break;\r
6522           case TwoMachinesPlay:\r
6523             if (cps->twoMachinesColor[0] == 'w')\r
6524               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6525             else\r
6526               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6527             break;\r
6528           default:\r
6529             /* can't happen */\r
6530             break;\r
6531         }\r
6532         return;\r
6533     } else if (strncmp(message, "checkmate", 9) == 0) {\r
6534         if (WhiteOnMove(forwardMostMove)) {\r
6535             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6536         } else {\r
6537             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6538         }\r
6539         return;\r
6540     } else if (strstr(message, "Draw") != NULL ||\r
6541                strstr(message, "game is a draw") != NULL) {\r
6542         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
6543         return;\r
6544     } else if (strstr(message, "offer") != NULL &&\r
6545                strstr(message, "draw") != NULL) {\r
6546 #if ZIPPY\r
6547         if (appData.zippyPlay && first.initDone) {\r
6548             /* Relay offer to ICS */\r
6549             SendToICS(ics_prefix);\r
6550             SendToICS("draw\n");\r
6551         }\r
6552 #endif\r
6553         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
6554         if (gameMode == TwoMachinesPlay) {\r
6555             if (cps->other->offeredDraw) {\r
6556                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6557             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
6558             /* until after we also have move, to see if it is really claim */\r
6559             }\r
6560 #if 0\r
6561               else {\r
6562                 if (cps->other->sendDrawOffers) {\r
6563                     SendToProgram("draw\n", cps->other);\r
6564                 }\r
6565             }\r
6566 #endif\r
6567         } else if (gameMode == MachinePlaysWhite ||\r
6568                    gameMode == MachinePlaysBlack) {\r
6569           if (userOfferedDraw) {\r
6570             DisplayInformation(_("Machine accepts your draw offer"));\r
6571             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6572           } else {\r
6573             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));\r
6574           }\r
6575         }\r
6576     }\r
6577 \r
6578     \r
6579     /*\r
6580      * Look for thinking output\r
6581      */\r
6582     if ( appData.showThinking // [HGM] thinking: test all options that cause this output\r
6583           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
6584                                 ) {\r
6585         int plylev, mvleft, mvtot, curscore, time;\r
6586         char mvname[MOVE_LEN];\r
6587         u64 nodes; // [DM]\r
6588         char plyext;\r
6589         int ignore = FALSE;\r
6590         int prefixHint = FALSE;\r
6591         mvname[0] = NULLCHAR;\r
6592 \r
6593         switch (gameMode) {\r
6594           case MachinePlaysBlack:\r
6595           case IcsPlayingBlack:\r
6596             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6597             break;\r
6598           case MachinePlaysWhite:\r
6599           case IcsPlayingWhite:\r
6600             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6601             break;\r
6602           case AnalyzeMode:\r
6603           case AnalyzeFile:\r
6604             break;\r
6605           case IcsObserving: /* [DM] icsEngineAnalyze */\r
6606             if (!appData.icsEngineAnalyze) ignore = TRUE;\r
6607             break;\r
6608           case TwoMachinesPlay:\r
6609             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
6610                 ignore = TRUE;\r
6611             }\r
6612             break;\r
6613           default:\r
6614             ignore = TRUE;\r
6615             break;\r
6616         }\r
6617 \r
6618         if (!ignore) {\r
6619             buf1[0] = NULLCHAR;\r
6620             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6621                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
6622 \r
6623                 if (plyext != ' ' && plyext != '\t') {\r
6624                     time *= 100;\r
6625                 }\r
6626 \r
6627                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6628                 if( cps->scoreIsAbsolute && \r
6629                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
6630                 {\r
6631                     curscore = -curscore;\r
6632                 }\r
6633 \r
6634 \r
6635                 programStats.depth = plylev;\r
6636                 programStats.nodes = nodes;\r
6637                 programStats.time = time;\r
6638                 programStats.score = curscore;\r
6639                 programStats.got_only_move = 0;\r
6640 \r
6641                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
6642                         int ticklen;\r
6643 \r
6644                         if(cps->nps == 0) ticklen = 10*time;       // use engine reported time\r
6645                         else ticklen = (1000. * nodes) / cps->nps; // convert node count to time\r
6646                         if(WhiteOnMove(forwardMostMove)) \r
6647                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
6648                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
6649                 }\r
6650 \r
6651                 /* Buffer overflow protection */\r
6652                 if (buf1[0] != NULLCHAR) {\r
6653                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
6654                         && appData.debugMode) {\r
6655                         fprintf(debugFP,\r
6656                                 "PV is too long; using the first %d bytes.\n",\r
6657                                 sizeof(programStats.movelist) - 1);\r
6658                     }\r
6659 \r
6660                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
6661                 } else {\r
6662                     sprintf(programStats.movelist, " no PV\n");\r
6663                 }\r
6664 \r
6665                 if (programStats.seen_stat) {\r
6666                     programStats.ok_to_send = 1;\r
6667                 }\r
6668 \r
6669                 if (strchr(programStats.movelist, '(') != NULL) {\r
6670                     programStats.line_is_book = 1;\r
6671                     programStats.nr_moves = 0;\r
6672                     programStats.moves_left = 0;\r
6673                 } else {\r
6674                     programStats.line_is_book = 0;\r
6675                 }\r
6676 \r
6677                 SendProgramStatsToFrontend( cps, &programStats );\r
6678 \r
6679                 /* \r
6680                     [AS] Protect the thinkOutput buffer from overflow... this\r
6681                     is only useful if buf1 hasn't overflowed first!\r
6682                 */\r
6683                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
6684                         plylev, \r
6685                         (gameMode == TwoMachinesPlay ?\r
6686                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
6687                         ((double) curscore) / 100.0,\r
6688                         prefixHint ? lastHint : "",\r
6689                         prefixHint ? " " : "" );\r
6690 \r
6691                 if( buf1[0] != NULLCHAR ) {\r
6692                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
6693 \r
6694                     if( strlen(buf1) > max_len ) {\r
6695                         if( appData.debugMode) {\r
6696                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
6697                         }\r
6698                         buf1[max_len+1] = '\0';\r
6699                     }\r
6700 \r
6701                     strcat( thinkOutput, buf1 );\r
6702                 }\r
6703 \r
6704                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode\r
6705                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6706                     DisplayMove(currentMove - 1);\r
6707                     DisplayAnalysis();\r
6708                 }\r
6709                 return;\r
6710 \r
6711             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6712                 /* crafty (9.25+) says "(only move) <move>"\r
6713                  * if there is only 1 legal move\r
6714                  */\r
6715                 sscanf(p, "(only move) %s", buf1);\r
6716                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6717                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6718                 programStats.depth = 1;\r
6719                 programStats.nr_moves = 1;\r
6720                 programStats.moves_left = 1;\r
6721                 programStats.nodes = 1;\r
6722                 programStats.time = 1;\r
6723                 programStats.got_only_move = 1;\r
6724 \r
6725                 /* Not really, but we also use this member to\r
6726                    mean "line isn't going to change" (Crafty\r
6727                    isn't searching, so stats won't change) */\r
6728                 programStats.line_is_book = 1;\r
6729 \r
6730                 SendProgramStatsToFrontend( cps, &programStats );\r
6731                 \r
6732                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || \r
6733                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6734                     DisplayMove(currentMove - 1);\r
6735                     DisplayAnalysis();\r
6736                 }\r
6737                 return;\r
6738             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",\r
6739                               &time, &nodes, &plylev, &mvleft,\r
6740                               &mvtot, mvname) >= 5) {\r
6741                 /* The stat01: line is from Crafty (9.29+) in response\r
6742                    to the "." command */\r
6743                 programStats.seen_stat = 1;\r
6744                 cps->maybeThinking = TRUE;\r
6745 \r
6746                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6747                   return;\r
6748 \r
6749                 programStats.depth = plylev;\r
6750                 programStats.time = time;\r
6751                 programStats.nodes = nodes;\r
6752                 programStats.moves_left = mvleft;\r
6753                 programStats.nr_moves = mvtot;\r
6754                 strcpy(programStats.move_name, mvname);\r
6755                 programStats.ok_to_send = 1;\r
6756                 programStats.movelist[0] = '\0';\r
6757 \r
6758                 SendProgramStatsToFrontend( cps, &programStats );\r
6759 \r
6760                 DisplayAnalysis();\r
6761                 return;\r
6762 \r
6763             } else if (strncmp(message,"++",2) == 0) {\r
6764                 /* Crafty 9.29+ outputs this */\r
6765                 programStats.got_fail = 2;\r
6766                 return;\r
6767 \r
6768             } else if (strncmp(message,"--",2) == 0) {\r
6769                 /* Crafty 9.29+ outputs this */\r
6770                 programStats.got_fail = 1;\r
6771                 return;\r
6772 \r
6773             } else if (thinkOutput[0] != NULLCHAR &&\r
6774                        strncmp(message, "    ", 4) == 0) {\r
6775                 unsigned message_len;\r
6776 \r
6777                 p = message;\r
6778                 while (*p && *p == ' ') p++;\r
6779 \r
6780                 message_len = strlen( p );\r
6781 \r
6782                 /* [AS] Avoid buffer overflow */\r
6783                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6784                     strcat(thinkOutput, " ");\r
6785                     strcat(thinkOutput, p);\r
6786                 }\r
6787 \r
6788                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6789                     strcat(programStats.movelist, " ");\r
6790                     strcat(programStats.movelist, p);\r
6791                 }\r
6792 \r
6793                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||\r
6794                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6795                     DisplayMove(currentMove - 1);\r
6796                     DisplayAnalysis();\r
6797                 }\r
6798                 return;\r
6799             }\r
6800         }\r
6801         else {\r
6802             buf1[0] = NULLCHAR;\r
6803 \r
6804             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
6805                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6806             {\r
6807                 ChessProgramStats cpstats;\r
6808 \r
6809                 if (plyext != ' ' && plyext != '\t') {\r
6810                     time *= 100;\r
6811                 }\r
6812 \r
6813                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6814                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6815                     curscore = -curscore;\r
6816                 }\r
6817 \r
6818                 cpstats.depth = plylev;\r
6819                 cpstats.nodes = nodes;\r
6820                 cpstats.time = time;\r
6821                 cpstats.score = curscore;\r
6822                 cpstats.got_only_move = 0;\r
6823                 cpstats.movelist[0] = '\0';\r
6824 \r
6825                 if (buf1[0] != NULLCHAR) {\r
6826                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6827                 }\r
6828 \r
6829                 cpstats.ok_to_send = 0;\r
6830                 cpstats.line_is_book = 0;\r
6831                 cpstats.nr_moves = 0;\r
6832                 cpstats.moves_left = 0;\r
6833 \r
6834                 SendProgramStatsToFrontend( cps, &cpstats );\r
6835             }\r
6836         }\r
6837     }\r
6838 }\r
6839 \r
6840 \r
6841 /* Parse a game score from the character string "game", and\r
6842    record it as the history of the current game.  The game\r
6843    score is NOT assumed to start from the standard position. \r
6844    The display is not updated in any way.\r
6845    */\r
6846 void\r
6847 ParseGameHistory(game)\r
6848      char *game;\r
6849 {\r
6850     ChessMove moveType;\r
6851     int fromX, fromY, toX, toY, boardIndex;\r
6852     char promoChar;\r
6853     char *p, *q;\r
6854     char buf[MSG_SIZ];\r
6855 \r
6856     if (appData.debugMode)\r
6857       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6858 \r
6859     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6860     gameInfo.site = StrSave(appData.icsHost);\r
6861     gameInfo.date = PGNDate();\r
6862     gameInfo.round = StrSave("-");\r
6863 \r
6864     /* Parse out names of players */\r
6865     while (*game == ' ') game++;\r
6866     p = buf;\r
6867     while (*game != ' ') *p++ = *game++;\r
6868     *p = NULLCHAR;\r
6869     gameInfo.white = StrSave(buf);\r
6870     while (*game == ' ') game++;\r
6871     p = buf;\r
6872     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6873     *p = NULLCHAR;\r
6874     gameInfo.black = StrSave(buf);\r
6875 \r
6876     /* Parse moves */\r
6877     boardIndex = blackPlaysFirst ? 1 : 0;\r
6878     yynewstr(game);\r
6879     for (;;) {\r
6880         yyboardindex = boardIndex;\r
6881         moveType = (ChessMove) yylex();\r
6882         switch (moveType) {\r
6883           case IllegalMove:             /* maybe suicide chess, etc. */\r
6884   if (appData.debugMode) {\r
6885     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6886     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6887     setbuf(debugFP, NULL);\r
6888   }\r
6889           case WhitePromotionChancellor:\r
6890           case BlackPromotionChancellor:\r
6891           case WhitePromotionArchbishop:\r
6892           case BlackPromotionArchbishop:\r
6893           case WhitePromotionQueen:\r
6894           case BlackPromotionQueen:\r
6895           case WhitePromotionRook:\r
6896           case BlackPromotionRook:\r
6897           case WhitePromotionBishop:\r
6898           case BlackPromotionBishop:\r
6899           case WhitePromotionKnight:\r
6900           case BlackPromotionKnight:\r
6901           case WhitePromotionKing:\r
6902           case BlackPromotionKing:\r
6903           case NormalMove:\r
6904           case WhiteCapturesEnPassant:\r
6905           case BlackCapturesEnPassant:\r
6906           case WhiteKingSideCastle:\r
6907           case WhiteQueenSideCastle:\r
6908           case BlackKingSideCastle:\r
6909           case BlackQueenSideCastle:\r
6910           case WhiteKingSideCastleWild:\r
6911           case WhiteQueenSideCastleWild:\r
6912           case BlackKingSideCastleWild:\r
6913           case BlackQueenSideCastleWild:\r
6914           /* PUSH Fabien */\r
6915           case WhiteHSideCastleFR:\r
6916           case WhiteASideCastleFR:\r
6917           case BlackHSideCastleFR:\r
6918           case BlackASideCastleFR:\r
6919           /* POP Fabien */\r
6920             fromX = currentMoveString[0] - AAA;\r
6921             fromY = currentMoveString[1] - ONE;\r
6922             toX = currentMoveString[2] - AAA;\r
6923             toY = currentMoveString[3] - ONE;\r
6924             promoChar = currentMoveString[4];\r
6925             break;\r
6926           case WhiteDrop:\r
6927           case BlackDrop:\r
6928             fromX = moveType == WhiteDrop ?\r
6929               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6930             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6931             fromY = DROP_RANK;\r
6932             toX = currentMoveString[2] - AAA;\r
6933             toY = currentMoveString[3] - ONE;\r
6934             promoChar = NULLCHAR;\r
6935             break;\r
6936           case AmbiguousMove:\r
6937             /* bug? */\r
6938             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);\r
6939   if (appData.debugMode) {\r
6940     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
6941     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6942     setbuf(debugFP, NULL);\r
6943   }\r
6944             DisplayError(buf, 0);\r
6945             return;\r
6946           case ImpossibleMove:\r
6947             /* bug? */\r
6948             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);\r
6949   if (appData.debugMode) {\r
6950     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
6951     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6952     setbuf(debugFP, NULL);\r
6953   }\r
6954             DisplayError(buf, 0);\r
6955             return;\r
6956           case (ChessMove) 0:   /* end of file */\r
6957             if (boardIndex < backwardMostMove) {\r
6958                 /* Oops, gap.  How did that happen? */\r
6959                 DisplayError(_("Gap in move list"), 0);\r
6960                 return;\r
6961             }\r
6962             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
6963             if (boardIndex > forwardMostMove) {\r
6964                 forwardMostMove = boardIndex;\r
6965             }\r
6966             return;\r
6967           case ElapsedTime:\r
6968             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
6969                 strcat(parseList[boardIndex-1], " ");\r
6970                 strcat(parseList[boardIndex-1], yy_text);\r
6971             }\r
6972             continue;\r
6973           case Comment:\r
6974           case PGNTag:\r
6975           case NAG:\r
6976           default:\r
6977             /* ignore */\r
6978             continue;\r
6979           case WhiteWins:\r
6980           case BlackWins:\r
6981           case GameIsDrawn:\r
6982           case GameUnfinished:\r
6983             if (gameMode == IcsExamining) {\r
6984                 if (boardIndex < backwardMostMove) {\r
6985                     /* Oops, gap.  How did that happen? */\r
6986                     return;\r
6987                 }\r
6988                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6989                 return;\r
6990             }\r
6991             gameInfo.result = moveType;\r
6992             p = strchr(yy_text, '{');\r
6993             if (p == NULL) p = strchr(yy_text, '(');\r
6994             if (p == NULL) {\r
6995                 p = yy_text;\r
6996                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
6997             } else {\r
6998                 q = strchr(p, *p == '{' ? '}' : ')');\r
6999                 if (q != NULL) *q = NULLCHAR;\r
7000                 p++;\r
7001             }\r
7002             gameInfo.resultDetails = StrSave(p);\r
7003             continue;\r
7004         }\r
7005         if (boardIndex >= forwardMostMove &&\r
7006             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
7007             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
7008             return;\r
7009         }\r
7010         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
7011                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
7012                                  parseList[boardIndex]);\r
7013         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
7014         /* currentMoveString is set as a side-effect of yylex */\r
7015         strcpy(moveList[boardIndex], currentMoveString);\r
7016         strcat(moveList[boardIndex], "\n");\r
7017         boardIndex++;\r
7018         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
7019         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
7020                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
7021           case MT_NONE:\r
7022           case MT_STALEMATE:\r
7023           default:\r
7024             break;\r
7025           case MT_CHECK:\r
7026             if(gameInfo.variant != VariantShogi)\r
7027                 strcat(parseList[boardIndex - 1], "+");\r
7028             break;\r
7029           case MT_CHECKMATE:\r
7030             strcat(parseList[boardIndex - 1], "#");\r
7031             break;\r
7032         }\r
7033     }\r
7034 }\r
7035 \r
7036 \r
7037 /* Apply a move to the given board  */\r
7038 void\r
7039 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
7040      int fromX, fromY, toX, toY;\r
7041      int promoChar;\r
7042      Board board;\r
7043 {\r
7044   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
7045 \r
7046     /* [HGM] compute & store e.p. status and castling rights for new position */\r
7047     /* if we are updating a board for which those exist (i.e. in boards[])    */\r
7048     if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)\r
7049     { int i, j;\r
7050 \r
7051       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
7052       oldEP = epStatus[p-1];\r
7053       epStatus[p] = EP_NONE;\r
7054 \r
7055       if( board[toY][toX] != EmptySquare ) \r
7056            epStatus[p] = EP_CAPTURE;  \r
7057 \r
7058       if( board[fromY][fromX] == WhitePawn ) {\r
7059            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7060                epStatus[p] = EP_PAWN_MOVE;\r
7061            if( toY-fromY==2) {\r
7062                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
7063                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7064                       epStatus[p] = toX | berolina;\r
7065                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
7066                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7067                       epStatus[p] = toX;\r
7068            }\r
7069       } else \r
7070       if( board[fromY][fromX] == BlackPawn ) {\r
7071            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7072                epStatus[p] = EP_PAWN_MOVE; \r
7073            if( toY-fromY== -2) {\r
7074                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
7075                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7076                       epStatus[p] = toX | berolina;\r
7077                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
7078                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7079                       epStatus[p] = toX;\r
7080            }\r
7081        }\r
7082 \r
7083        for(i=0; i<nrCastlingRights; i++) {\r
7084            castlingRights[p][i] = castlingRights[p-1][i];\r
7085            if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||\r
7086               castlingRights[p][i] == toX   && castlingRank[i] == toY   \r
7087              ) castlingRights[p][i] = -1; // revoke for moved or captured piece\r
7088        }\r
7089 \r
7090     }\r
7091 \r
7092   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
7093   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
7094        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
7095          \r
7096   if (fromX == toX && fromY == toY) return;\r
7097 \r
7098   if (fromY == DROP_RANK) {\r
7099         /* must be first */\r
7100         piece = board[toY][toX] = (ChessSquare) fromX;\r
7101   } else {\r
7102      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
7103      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
7104      if(gameInfo.variant == VariantKnightmate)\r
7105          king += (int) WhiteUnicorn - (int) WhiteKing;\r
7106 \r
7107     /* Code added by Tord: */\r
7108     /* FRC castling assumed when king captures friendly rook. */\r
7109     if (board[fromY][fromX] == WhiteKing &&\r
7110              board[toY][toX] == WhiteRook) {\r
7111       board[fromY][fromX] = EmptySquare;\r
7112       board[toY][toX] = EmptySquare;\r
7113       if(toX > fromX) {\r
7114         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
7115       } else {\r
7116         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
7117       }\r
7118     } else if (board[fromY][fromX] == BlackKing &&\r
7119                board[toY][toX] == BlackRook) {\r
7120       board[fromY][fromX] = EmptySquare;\r
7121       board[toY][toX] = EmptySquare;\r
7122       if(toX > fromX) {\r
7123         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
7124       } else {\r
7125         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
7126       }\r
7127     /* End of code added by Tord */\r
7128 \r
7129     } else if (board[fromY][fromX] == king\r
7130         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7131         && toY == fromY && toX > fromX+1) {\r
7132         board[fromY][fromX] = EmptySquare;\r
7133         board[toY][toX] = king;\r
7134         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7135         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7136     } else if (board[fromY][fromX] == king\r
7137         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7138                && toY == fromY && toX < fromX-1) {\r
7139         board[fromY][fromX] = EmptySquare;\r
7140         board[toY][toX] = king;\r
7141         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7142         board[fromY][BOARD_LEFT] = EmptySquare;\r
7143     } else if (board[fromY][fromX] == WhitePawn\r
7144                && toY == BOARD_HEIGHT-1\r
7145                && gameInfo.variant != VariantXiangqi\r
7146                ) {\r
7147         /* white pawn promotion */\r
7148         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
7149         if (board[toY][toX] == EmptySquare) {\r
7150             board[toY][toX] = WhiteQueen;\r
7151         }\r
7152         if(gameInfo.variant==VariantBughouse ||\r
7153            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7154             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7155         board[fromY][fromX] = EmptySquare;\r
7156     } else if ((fromY == BOARD_HEIGHT-4)\r
7157                && (toX != fromX)\r
7158                && gameInfo.variant != VariantXiangqi\r
7159                && gameInfo.variant != VariantBerolina\r
7160                && (board[fromY][fromX] == WhitePawn)\r
7161                && (board[toY][toX] == EmptySquare)) {\r
7162         board[fromY][fromX] = EmptySquare;\r
7163         board[toY][toX] = WhitePawn;\r
7164         captured = board[toY - 1][toX];\r
7165         board[toY - 1][toX] = EmptySquare;\r
7166     } else if ((fromY == BOARD_HEIGHT-4)\r
7167                && (toX == fromX)\r
7168                && gameInfo.variant == VariantBerolina\r
7169                && (board[fromY][fromX] == WhitePawn)\r
7170                && (board[toY][toX] == EmptySquare)) {\r
7171         board[fromY][fromX] = EmptySquare;\r
7172         board[toY][toX] = WhitePawn;\r
7173         if(oldEP & EP_BEROLIN_A) {\r
7174                 captured = board[fromY][fromX-1];\r
7175                 board[fromY][fromX-1] = EmptySquare;\r
7176         }else{  captured = board[fromY][fromX+1];\r
7177                 board[fromY][fromX+1] = EmptySquare;\r
7178         }\r
7179     } else if (board[fromY][fromX] == king\r
7180         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7181                && toY == fromY && toX > fromX+1) {\r
7182         board[fromY][fromX] = EmptySquare;\r
7183         board[toY][toX] = king;\r
7184         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7185         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7186     } else if (board[fromY][fromX] == king\r
7187         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7188                && toY == fromY && toX < fromX-1) {\r
7189         board[fromY][fromX] = EmptySquare;\r
7190         board[toY][toX] = king;\r
7191         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7192         board[fromY][BOARD_LEFT] = EmptySquare;\r
7193     } else if (fromY == 7 && fromX == 3\r
7194                && board[fromY][fromX] == BlackKing\r
7195                && toY == 7 && toX == 5) {\r
7196         board[fromY][fromX] = EmptySquare;\r
7197         board[toY][toX] = BlackKing;\r
7198         board[fromY][7] = EmptySquare;\r
7199         board[toY][4] = BlackRook;\r
7200     } else if (fromY == 7 && fromX == 3\r
7201                && board[fromY][fromX] == BlackKing\r
7202                && toY == 7 && toX == 1) {\r
7203         board[fromY][fromX] = EmptySquare;\r
7204         board[toY][toX] = BlackKing;\r
7205         board[fromY][0] = EmptySquare;\r
7206         board[toY][2] = BlackRook;\r
7207     } else if (board[fromY][fromX] == BlackPawn\r
7208                && toY == 0\r
7209                && gameInfo.variant != VariantXiangqi\r
7210                ) {\r
7211         /* black pawn promotion */\r
7212         board[0][toX] = CharToPiece(ToLower(promoChar));\r
7213         if (board[0][toX] == EmptySquare) {\r
7214             board[0][toX] = BlackQueen;\r
7215         }\r
7216         if(gameInfo.variant==VariantBughouse ||\r
7217            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7218             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7219         board[fromY][fromX] = EmptySquare;\r
7220     } else if ((fromY == 3)\r
7221                && (toX != fromX)\r
7222                && gameInfo.variant != VariantXiangqi\r
7223                && gameInfo.variant != VariantBerolina\r
7224                && (board[fromY][fromX] == BlackPawn)\r
7225                && (board[toY][toX] == EmptySquare)) {\r
7226         board[fromY][fromX] = EmptySquare;\r
7227         board[toY][toX] = BlackPawn;\r
7228         captured = board[toY + 1][toX];\r
7229         board[toY + 1][toX] = EmptySquare;\r
7230     } else if ((fromY == 3)\r
7231                && (toX == fromX)\r
7232                && gameInfo.variant == VariantBerolina\r
7233                && (board[fromY][fromX] == BlackPawn)\r
7234                && (board[toY][toX] == EmptySquare)) {\r
7235         board[fromY][fromX] = EmptySquare;\r
7236         board[toY][toX] = BlackPawn;\r
7237         if(oldEP & EP_BEROLIN_A) {\r
7238                 captured = board[fromY][fromX-1];\r
7239                 board[fromY][fromX-1] = EmptySquare;\r
7240         }else{  captured = board[fromY][fromX+1];\r
7241                 board[fromY][fromX+1] = EmptySquare;\r
7242         }\r
7243     } else {\r
7244         board[toY][toX] = board[fromY][fromX];\r
7245         board[fromY][fromX] = EmptySquare;\r
7246     }\r
7247 \r
7248     /* [HGM] now we promote for Shogi, if needed */\r
7249     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
7250         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7251   }\r
7252 \r
7253     if (gameInfo.holdingsWidth != 0) {\r
7254 \r
7255       /* !!A lot more code needs to be written to support holdings  */\r
7256       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
7257       /* penultimate board files, so they are automaticlly stored   */\r
7258       /* in the game history.                                       */\r
7259       if (fromY == DROP_RANK) {\r
7260         /* Delete from holdings, by decreasing count */\r
7261         /* and erasing image if necessary            */\r
7262         p = (int) fromX;\r
7263         if(p < (int) BlackPawn) { /* white drop */\r
7264              p -= (int)WhitePawn;\r
7265              if(p >= gameInfo.holdingsSize) p = 0;\r
7266              if(--board[p][BOARD_WIDTH-2] == 0)\r
7267                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
7268         } else {                  /* black drop */\r
7269              p -= (int)BlackPawn;\r
7270              if(p >= gameInfo.holdingsSize) p = 0;\r
7271              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
7272                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
7273         }\r
7274       }\r
7275       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
7276           && gameInfo.variant != VariantBughouse        ) {\r
7277         /* [HGM] holdings: Add to holdings, if holdings exist */\r
7278         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { \r
7279                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
7280                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
7281         }\r
7282         p = (int) captured;\r
7283         if (p >= (int) BlackPawn) {\r
7284           p -= (int)BlackPawn;\r
7285           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7286                   /* in Shogi restore piece to its original  first */\r
7287                   captured = (ChessSquare) (DEMOTED captured);\r
7288                   p = DEMOTED p;\r
7289           }\r
7290           p = PieceToNumber((ChessSquare)p);\r
7291           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
7292           board[p][BOARD_WIDTH-2]++;\r
7293           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;\r
7294         } else {\r
7295           p -= (int)WhitePawn;\r
7296           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7297                   captured = (ChessSquare) (DEMOTED captured);\r
7298                   p = DEMOTED p;\r
7299           }\r
7300           p = PieceToNumber((ChessSquare)p);\r
7301           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
7302           board[BOARD_HEIGHT-1-p][1]++;\r
7303           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;\r
7304         }\r
7305       }\r
7306 \r
7307     } else if (gameInfo.variant == VariantAtomic) {\r
7308       if (captured != EmptySquare) {\r
7309         int y, x;\r
7310         for (y = toY-1; y <= toY+1; y++) {\r
7311           for (x = toX-1; x <= toX+1; x++) {\r
7312             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
7313                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
7314               board[y][x] = EmptySquare;\r
7315             }\r
7316           }\r
7317         }\r
7318         board[toY][toX] = EmptySquare;\r
7319       }\r
7320     }\r
7321     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
7322         /* [HGM] Shogi promotions */\r
7323         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7324     }\r
7325 \r
7326 }\r
7327 \r
7328 /* Updates forwardMostMove */\r
7329 void\r
7330 MakeMove(fromX, fromY, toX, toY, promoChar)\r
7331      int fromX, fromY, toX, toY;\r
7332      int promoChar;\r
7333 {\r
7334     forwardMostMove++;\r
7335 \r
7336     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting */\r
7337         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
7338         piece = boards[forwardMostMove-1][fromY][fromX];\r
7339         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
7340         if(gameInfo.variant == VariantKnightmate)\r
7341             king += (int) WhiteUnicorn - (int) WhiteKing;\r
7342         if(forwardMostMove == 1) {\r
7343             if(blackPlaysFirst) \r
7344                 fprintf(serverMoves, "%s;", second.tidy);\r
7345             fprintf(serverMoves, "%s;", first.tidy);\r
7346             if(!blackPlaysFirst) \r
7347                 fprintf(serverMoves, "%s;", second.tidy);\r
7348         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
7349         lastLoadFlag = loadFlag;\r
7350         // print base move\r
7351         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
7352         // print castling suffix\r
7353         if( toY == fromY && piece == king ) {\r
7354             if(toX-fromX > 1)\r
7355                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
7356             if(fromX-toX >1)\r
7357                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
7358         }\r
7359         // e.p. suffix\r
7360         if( (boards[forwardMostMove-1][fromY][fromX] == WhitePawn ||\r
7361              boards[forwardMostMove-1][fromY][fromX] == BlackPawn   ) &&\r
7362              boards[forwardMostMove-1][toY][toX] == EmptySquare\r
7363              && fromX != toX )\r
7364                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
7365         // promotion suffix\r
7366         if(promoChar != NULLCHAR)\r
7367                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
7368         if(!loadFlag) {\r
7369             fprintf(serverMoves, "/%d/%d",\r
7370                pvInfoList[forwardMostMove-1].depth, pvInfoList[forwardMostMove-1].score);\r
7371             if(forwardMostMove & 1) timeLeft = whiteTimeRemaining/1000;\r
7372             else                    timeLeft = blackTimeRemaining/1000;\r
7373             fprintf(serverMoves, "/%d", timeLeft);\r
7374         }\r
7375         fflush(serverMoves);\r
7376     }\r
7377 \r
7378     if (forwardMostMove >= MAX_MOVES) {\r
7379       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
7380                         0, 1);\r
7381       return;\r
7382     }\r
7383     SwitchClocks();\r
7384     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
7385     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
7386     if (commentList[forwardMostMove] != NULL) {\r
7387         free(commentList[forwardMostMove]);\r
7388         commentList[forwardMostMove] = NULL;\r
7389     }\r
7390     CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);\r
7391     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);\r
7392     gameInfo.result = GameUnfinished;\r
7393     if (gameInfo.resultDetails != NULL) {\r
7394         free(gameInfo.resultDetails);\r
7395         gameInfo.resultDetails = NULL;\r
7396     }\r
7397     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
7398                               moveList[forwardMostMove - 1]);\r
7399     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
7400                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
7401                              fromY, fromX, toY, toX, promoChar,\r
7402                              parseList[forwardMostMove - 1]);\r
7403     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
7404                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
7405                             castlingRights[forwardMostMove]) ) {\r
7406       case MT_NONE:\r
7407       case MT_STALEMATE:\r
7408       default:\r
7409         break;\r
7410       case MT_CHECK:\r
7411         if(gameInfo.variant != VariantShogi)\r
7412             strcat(parseList[forwardMostMove - 1], "+");\r
7413         break;\r
7414       case MT_CHECKMATE:\r
7415         strcat(parseList[forwardMostMove - 1], "#");\r
7416         break;\r
7417     }\r
7418     if (appData.debugMode) {\r
7419         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
7420     }\r
7421 \r
7422 }\r
7423 \r
7424 /* Updates currentMove if not pausing */\r
7425 void\r
7426 ShowMove(fromX, fromY, toX, toY)\r
7427 {\r
7428     int instant = (gameMode == PlayFromGameFile) ?\r
7429         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
7430     if(appData.noGUI) return;\r
7431     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
7432         if (!instant) {\r
7433             if (forwardMostMove == currentMove + 1) {\r
7434                 AnimateMove(boards[forwardMostMove - 1],\r
7435                             fromX, fromY, toX, toY);\r
7436             }\r
7437             if (appData.highlightLastMove) {\r
7438                 SetHighlights(fromX, fromY, toX, toY);\r
7439             }\r
7440         }\r
7441         currentMove = forwardMostMove;\r
7442     }\r
7443 \r
7444     if (instant) return;\r
7445 \r
7446     DisplayMove(currentMove - 1);\r
7447     DrawPosition(FALSE, boards[currentMove]);\r
7448     DisplayBothClocks();\r
7449     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
7450 }\r
7451 \r
7452 void SendEgtPath(ChessProgramState *cps)\r
7453 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */\r
7454         char buf[MSG_SIZ], name[MSG_SIZ], *p;\r
7455 \r
7456         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;\r
7457 \r
7458         while(*p) {\r
7459             char c, *q = name+1, *r, *s;\r
7460 \r
7461             name[0] = ','; // extract next format name from feature and copy with prefixed ','\r
7462             while(*p && *p != ',') *q++ = *p++;\r
7463             *q++ = ':'; *q = 0;\r
7464             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && \r
7465                 strcmp(name, ",nalimov:") == 0 ) {\r
7466                 // take nalimov path from the menu-changeable option first, if it is defined\r
7467                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);\r
7468                 SendToProgram(buf,cps);     // send egtbpath command for nalimov\r
7469             } else\r
7470             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||\r
7471                 (s = StrStr(appData.egtFormats, name)) != NULL) {\r
7472                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma\r
7473                 s = r = StrStr(s, ":") + 1; // beginning of path info\r
7474                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string\r
7475                 c = *r; *r = 0;             // temporarily null-terminate path info\r
7476                     *--q = 0;               // strip of trailig ':' from name\r
7477                     sprintf(buf, "egtbpath %s %s\n", name+1, s);\r
7478                 *r = c;\r
7479                 SendToProgram(buf,cps);     // send egtbpath command for this format\r
7480             }\r
7481             if(*p == ',') p++; // read away comma to position for next format name\r
7482         }\r
7483 }\r
7484 \r
7485 void\r
7486 InitChessProgram(cps, setup)\r
7487      ChessProgramState *cps;\r
7488      int setup; /* [HGM] needed to setup FRC opening position */\r
7489 {\r
7490     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
7491     if (appData.noChessProgram) return;\r
7492     hintRequested = FALSE;\r
7493     bookRequested = FALSE;\r
7494 \r
7495     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */\r
7496     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */\r
7497     if(cps->memSize) { /* [HGM] memory */\r
7498         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);\r
7499         SendToProgram(buf, cps);\r
7500     }\r
7501     SendEgtPath(cps); /* [HGM] EGT */\r
7502     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */\r
7503         sprintf(buf, "cores %d\n", appData.smpCores);\r
7504         SendToProgram(buf, cps);\r
7505     }\r
7506 \r
7507     SendToProgram(cps->initString, cps);\r
7508     if (gameInfo.variant != VariantNormal &&\r
7509         gameInfo.variant != VariantLoadable\r
7510         /* [HGM] also send variant if board size non-standard */\r
7511         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
7512                                             ) {\r
7513       char *v = VariantName(gameInfo.variant);\r
7514       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
7515         /* [HGM] in protocol 1 we have to assume all variants valid */\r
7516         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);\r
7517         DisplayFatalError(buf, 0, 1);\r
7518         return;\r
7519       }\r
7520 \r
7521       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
7522       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7523       if( gameInfo.variant == VariantXiangqi )\r
7524            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
7525       if( gameInfo.variant == VariantShogi )\r
7526            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
7527       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
7528            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
7529       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
7530                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
7531            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7532       if( gameInfo.variant == VariantCourier )\r
7533            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7534       if( gameInfo.variant == VariantSuper )\r
7535            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7536       if( gameInfo.variant == VariantGreat )\r
7537            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7538 \r
7539       if(overruled) {\r
7540            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
7541                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
7542            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
7543            if(StrStr(cps->variants, b) == NULL) { \r
7544                // specific sized variant not known, check if general sizing allowed\r
7545                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
7546                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
7547                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
7548                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
7549                        DisplayFatalError(buf, 0, 1);\r
7550                        return;\r
7551                    }\r
7552                    /* [HGM] here we really should compare with the maximum supported board size */\r
7553                }\r
7554            }\r
7555       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
7556       sprintf(buf, "variant %s\n", b);\r
7557       SendToProgram(buf, cps);\r
7558     }\r
7559     currentlyInitializedVariant = gameInfo.variant;\r
7560 \r
7561     /* [HGM] send opening position in FRC to first engine */\r
7562     if(setup) {\r
7563           SendToProgram("force\n", cps);\r
7564           SendBoard(cps, 0);\r
7565           /* engine is now in force mode! Set flag to wake it up after first move. */\r
7566           setboardSpoiledMachineBlack = 1;\r
7567     }\r
7568 \r
7569     if (cps->sendICS) {\r
7570       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
7571       SendToProgram(buf, cps);\r
7572     }\r
7573     cps->maybeThinking = FALSE;\r
7574     cps->offeredDraw = 0;\r
7575     if (!appData.icsActive) {\r
7576         SendTimeControl(cps, movesPerSession, timeControl,\r
7577                         timeIncrement, appData.searchDepth,\r
7578                         searchTime);\r
7579     }\r
7580     if (appData.showThinking \r
7581         // [HGM] thinking: four options require thinking output to be sent\r
7582         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
7583                                 ) {\r
7584         SendToProgram("post\n", cps);\r
7585     }\r
7586     SendToProgram("hard\n", cps);\r
7587     if (!appData.ponderNextMove) {\r
7588         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
7589            it without being sure what state we are in first.  "hard"\r
7590            is not a toggle, so that one is OK.\r
7591          */\r
7592         SendToProgram("easy\n", cps);\r
7593     }\r
7594     if (cps->usePing) {\r
7595       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
7596       SendToProgram(buf, cps);\r
7597     }\r
7598     cps->initDone = TRUE;\r
7599 }   \r
7600 \r
7601 \r
7602 void\r
7603 StartChessProgram(cps)\r
7604      ChessProgramState *cps;\r
7605 {\r
7606     char buf[MSG_SIZ];\r
7607     int err;\r
7608 \r
7609     if (appData.noChessProgram) return;\r
7610     cps->initDone = FALSE;\r
7611 \r
7612     if (strcmp(cps->host, "localhost") == 0) {\r
7613         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
7614     } else if (*appData.remoteShell == NULLCHAR) {\r
7615         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
7616     } else {\r
7617         if (*appData.remoteUser == NULLCHAR) {\r
7618             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
7619                     cps->program);\r
7620         } else {\r
7621             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
7622                     cps->host, appData.remoteUser, cps->program);\r
7623         }\r
7624         err = StartChildProcess(buf, "", &cps->pr);\r
7625     }\r
7626     \r
7627     if (err != 0) {\r
7628         sprintf(buf, _("Startup failure on '%s'"), cps->program);\r
7629         DisplayFatalError(buf, err, 1);\r
7630         cps->pr = NoProc;\r
7631         cps->isr = NULL;\r
7632         return;\r
7633     }\r
7634     \r
7635     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
7636     if (cps->protocolVersion > 1) {\r
7637       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
7638       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options\r
7639       cps->comboCnt = 0;  //                and values of combo boxes\r
7640       SendToProgram(buf, cps);\r
7641     } else {\r
7642       SendToProgram("xboard\n", cps);\r
7643     }\r
7644 }\r
7645 \r
7646 \r
7647 void\r
7648 TwoMachinesEventIfReady P((void))\r
7649 {\r
7650   if (first.lastPing != first.lastPong) {\r
7651     DisplayMessage("", _("Waiting for first chess program"));\r
7652     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7653     return;\r
7654   }\r
7655   if (second.lastPing != second.lastPong) {\r
7656     DisplayMessage("", _("Waiting for second chess program"));\r
7657     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7658     return;\r
7659   }\r
7660   ThawUI();\r
7661   TwoMachinesEvent();\r
7662 }\r
7663 \r
7664 void\r
7665 NextMatchGame P((void))\r
7666 {\r
7667     int index; /* [HGM] autoinc: step lod index during match */\r
7668     Reset(FALSE, TRUE);\r
7669     if (*appData.loadGameFile != NULLCHAR) {\r
7670         index = appData.loadGameIndex;\r
7671         if(index < 0) { // [HGM] autoinc\r
7672             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7673             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7674         } \r
7675         LoadGameFromFile(appData.loadGameFile,\r
7676                          index,\r
7677                          appData.loadGameFile, FALSE);\r
7678     } else if (*appData.loadPositionFile != NULLCHAR) {\r
7679         index = appData.loadPositionIndex;\r
7680         if(index < 0) { // [HGM] autoinc\r
7681             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7682             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7683         } \r
7684         LoadPositionFromFile(appData.loadPositionFile,\r
7685                              index,\r
7686                              appData.loadPositionFile);\r
7687     }\r
7688     TwoMachinesEventIfReady();\r
7689 }\r
7690 \r
7691 void UserAdjudicationEvent( int result )\r
7692 {\r
7693     ChessMove gameResult = GameIsDrawn;\r
7694 \r
7695     if( result > 0 ) {\r
7696         gameResult = WhiteWins;\r
7697     }\r
7698     else if( result < 0 ) {\r
7699         gameResult = BlackWins;\r
7700     }\r
7701 \r
7702     if( gameMode == TwoMachinesPlay ) {\r
7703         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
7704     }\r
7705 }\r
7706 \r
7707 \r
7708 void\r
7709 GameEnds(result, resultDetails, whosays)\r
7710      ChessMove result;\r
7711      char *resultDetails;\r
7712      int whosays;\r
7713 {\r
7714     GameMode nextGameMode;\r
7715     int isIcsGame;\r
7716     char buf[MSG_SIZ];\r
7717 \r
7718     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
7719     endingGame = 1;\r
7720 \r
7721     if (appData.debugMode) {\r
7722       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
7723               result, resultDetails ? resultDetails : "(null)", whosays);\r
7724     }\r
7725 \r
7726     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
7727         /* If we are playing on ICS, the server decides when the\r
7728            game is over, but the engine can offer to draw, claim \r
7729            a draw, or resign. \r
7730          */\r
7731 #if ZIPPY\r
7732         if (appData.zippyPlay && first.initDone) {\r
7733             if (result == GameIsDrawn) {\r
7734                 /* In case draw still needs to be claimed */\r
7735                 SendToICS(ics_prefix);\r
7736                 SendToICS("draw\n");\r
7737             } else if (StrCaseStr(resultDetails, "resign")) {\r
7738                 SendToICS(ics_prefix);\r
7739                 SendToICS("resign\n");\r
7740             }\r
7741         }\r
7742 #endif\r
7743         endingGame = 0; /* [HGM] crash */\r
7744         return;\r
7745     }\r
7746 \r
7747     /* If we're loading the game from a file, stop */\r
7748     if (whosays == GE_FILE) {\r
7749       (void) StopLoadGameTimer();\r
7750       gameFileFP = NULL;\r
7751     }\r
7752 \r
7753     /* Cancel draw offers */\r
7754     first.offeredDraw = second.offeredDraw = 0;\r
7755 \r
7756     /* If this is an ICS game, only ICS can really say it's done;\r
7757        if not, anyone can. */\r
7758     isIcsGame = (gameMode == IcsPlayingWhite || \r
7759                  gameMode == IcsPlayingBlack || \r
7760                  gameMode == IcsObserving    || \r
7761                  gameMode == IcsExamining);\r
7762 \r
7763     if (!isIcsGame || whosays == GE_ICS) {\r
7764         /* OK -- not an ICS game, or ICS said it was done */\r
7765         StopClocks();\r
7766         if (!isIcsGame && !appData.noChessProgram) \r
7767           SetUserThinkingEnables();\r
7768     \r
7769         /* [HGM] if a machine claims the game end we verify this claim */\r
7770         if(gameMode == TwoMachinesPlay && appData.testClaims) {\r
7771             if(appData.testLegality && whosays >= GE_ENGINE1 ) {\r
7772                 char claimer;\r
7773 \r
7774                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
7775                                             first.twoMachinesColor[0] :\r
7776                                             second.twoMachinesColor[0] ;\r
7777                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) &&\r
7778                     (result == WhiteWins && claimer == 'w' ||\r
7779                      result == BlackWins && claimer == 'b'   ) ) {\r
7780                 if (appData.debugMode) {\r
7781                      fprintf(debugFP, "result=%d sp=%d move=%d\n",\r
7782                         result, epStatus[forwardMostMove], forwardMostMove);\r
7783                 }\r
7784                       /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7785                       if(epStatus[forwardMostMove] != EP_CHECKMATE &&\r
7786                          result != (WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins)) {\r
7787                               sprintf(buf, "False win claim: '%s'", resultDetails);\r
7788                               result = claimer == 'w' ? BlackWins : WhiteWins;\r
7789                               resultDetails = buf;\r
7790                       }\r
7791                 } else\r
7792                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
7793                     && (forwardMostMove <= backwardMostMove ||\r
7794                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
7795                         (claimer=='b')==(forwardMostMove&1))\r
7796                                                                                   ) {\r
7797                       /* [HGM] verify: draws that were not flagged are false claims */\r
7798                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
7799                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7800                       resultDetails = buf;\r
7801                 }\r
7802                 /* (Claiming a loss is accepted no questions asked!) */\r
7803             }\r
7804             /* [HGM] bare: don't allow bare King to win */\r
7805             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)\r
7806                          && result != GameIsDrawn)\r
7807             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
7808                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
7809                         int p = (int)boards[forwardMostMove][i][j] - color;\r
7810                         if(p >= 0 && p <= (int)WhiteKing) k++;\r
7811                 }\r
7812                 if (appData.debugMode) {\r
7813                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",\r
7814                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);\r
7815                 }\r
7816                 if(k <= 1) {\r
7817                         result = GameIsDrawn;\r
7818                         sprintf(buf, "%s but bare king", resultDetails);\r
7819                         resultDetails = buf;\r
7820                 }\r
7821             }\r
7822         }\r
7823 \r
7824 \r
7825         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
7826             if(result==WhiteWins) c = '+';\r
7827             if(result==BlackWins) c = '-';\r
7828             if(resultDetails != NULL)\r
7829                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
7830         }\r
7831         if (resultDetails != NULL) {\r
7832             gameInfo.result = result;\r
7833             gameInfo.resultDetails = StrSave(resultDetails);\r
7834 \r
7835             /* display last move only if game was not loaded from file */\r
7836             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7837                 DisplayMove(currentMove - 1);\r
7838     \r
7839             if (forwardMostMove != 0) {\r
7840                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7841                     if (*appData.saveGameFile != NULLCHAR) {\r
7842                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7843                     } else if (appData.autoSaveGames) {\r
7844                         AutoSaveGame();\r
7845                     }\r
7846                     if (*appData.savePositionFile != NULLCHAR) {\r
7847                         SavePositionToFile(appData.savePositionFile);\r
7848                     }\r
7849                 }\r
7850             }\r
7851 \r
7852             /* Tell program how game ended in case it is learning */\r
7853             /* [HGM] Moved this to after saving the PGN, just in case */\r
7854             /* engine died and we got here through time loss. In that */\r
7855             /* case we will get a fatal error writing the pipe, which */\r
7856             /* would otherwise lose us the PGN.                       */\r
7857             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7858             /* output during GameEnds should never be fatal anymore   */\r
7859             if (gameMode == MachinePlaysWhite ||\r
7860                 gameMode == MachinePlaysBlack ||\r
7861                 gameMode == TwoMachinesPlay ||\r
7862                 gameMode == IcsPlayingWhite ||\r
7863                 gameMode == IcsPlayingBlack ||\r
7864                 gameMode == BeginningOfGame) {\r
7865                 char buf[MSG_SIZ];\r
7866                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7867                         resultDetails);\r
7868                 if (first.pr != NoProc) {\r
7869                     SendToProgram(buf, &first);\r
7870                 }\r
7871                 if (second.pr != NoProc &&\r
7872                     gameMode == TwoMachinesPlay) {\r
7873                     SendToProgram(buf, &second);\r
7874                 }\r
7875             }\r
7876         }\r
7877 \r
7878         if (appData.icsActive) {\r
7879             if (appData.quietPlay &&\r
7880                 (gameMode == IcsPlayingWhite ||\r
7881                  gameMode == IcsPlayingBlack)) {\r
7882                 SendToICS(ics_prefix);\r
7883                 SendToICS("set shout 1\n");\r
7884             }\r
7885             nextGameMode = IcsIdle;\r
7886             ics_user_moved = FALSE;\r
7887             /* clean up premove.  It's ugly when the game has ended and the\r
7888              * premove highlights are still on the board.\r
7889              */\r
7890             if (gotPremove) {\r
7891               gotPremove = FALSE;\r
7892               ClearPremoveHighlights();\r
7893               DrawPosition(FALSE, boards[currentMove]);\r
7894             }\r
7895             if (whosays == GE_ICS) {\r
7896                 switch (result) {\r
7897                 case WhiteWins:\r
7898                     if (gameMode == IcsPlayingWhite)\r
7899                         PlayIcsWinSound();\r
7900                     else if(gameMode == IcsPlayingBlack)\r
7901                         PlayIcsLossSound();\r
7902                     break;\r
7903                 case BlackWins:\r
7904                     if (gameMode == IcsPlayingBlack)\r
7905                         PlayIcsWinSound();\r
7906                     else if(gameMode == IcsPlayingWhite)\r
7907                         PlayIcsLossSound();\r
7908                     break;\r
7909                 case GameIsDrawn:\r
7910                     PlayIcsDrawSound();\r
7911                     break;\r
7912                 default:\r
7913                     PlayIcsUnfinishedSound();\r
7914                 }\r
7915             }\r
7916         } else if (gameMode == EditGame ||\r
7917                    gameMode == PlayFromGameFile || \r
7918                    gameMode == AnalyzeMode || \r
7919                    gameMode == AnalyzeFile) {\r
7920             nextGameMode = gameMode;\r
7921         } else {\r
7922             nextGameMode = EndOfGame;\r
7923         }\r
7924         pausing = FALSE;\r
7925         ModeHighlight();\r
7926     } else {\r
7927         nextGameMode = gameMode;\r
7928     }\r
7929 \r
7930     if (appData.noChessProgram) {\r
7931         gameMode = nextGameMode;\r
7932         ModeHighlight();\r
7933         endingGame = 0; /* [HGM] crash */\r
7934         return;\r
7935     }\r
7936 \r
7937     if (first.reuse) {\r
7938         /* Put first chess program into idle state */\r
7939         if (first.pr != NoProc &&\r
7940             (gameMode == MachinePlaysWhite ||\r
7941              gameMode == MachinePlaysBlack ||\r
7942              gameMode == TwoMachinesPlay ||\r
7943              gameMode == IcsPlayingWhite ||\r
7944              gameMode == IcsPlayingBlack ||\r
7945              gameMode == BeginningOfGame)) {\r
7946             SendToProgram("force\n", &first);\r
7947             if (first.usePing) {\r
7948               char buf[MSG_SIZ];\r
7949               sprintf(buf, "ping %d\n", ++first.lastPing);\r
7950               SendToProgram(buf, &first);\r
7951             }\r
7952         }\r
7953     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7954         /* Kill off first chess program */\r
7955         if (first.isr != NULL)\r
7956           RemoveInputSource(first.isr);\r
7957         first.isr = NULL;\r
7958     \r
7959         if (first.pr != NoProc) {\r
7960             ExitAnalyzeMode();\r
7961             DoSleep( appData.delayBeforeQuit );\r
7962             SendToProgram("quit\n", &first);\r
7963             DoSleep( appData.delayAfterQuit );\r
7964             DestroyChildProcess(first.pr, first.useSigterm);\r
7965         }\r
7966         first.pr = NoProc;\r
7967     }\r
7968     if (second.reuse) {\r
7969         /* Put second chess program into idle state */\r
7970         if (second.pr != NoProc &&\r
7971             gameMode == TwoMachinesPlay) {\r
7972             SendToProgram("force\n", &second);\r
7973             if (second.usePing) {\r
7974               char buf[MSG_SIZ];\r
7975               sprintf(buf, "ping %d\n", ++second.lastPing);\r
7976               SendToProgram(buf, &second);\r
7977             }\r
7978         }\r
7979     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7980         /* Kill off second chess program */\r
7981         if (second.isr != NULL)\r
7982           RemoveInputSource(second.isr);\r
7983         second.isr = NULL;\r
7984     \r
7985         if (second.pr != NoProc) {\r
7986             DoSleep( appData.delayBeforeQuit );\r
7987             SendToProgram("quit\n", &second);\r
7988             DoSleep( appData.delayAfterQuit );\r
7989             DestroyChildProcess(second.pr, second.useSigterm);\r
7990         }\r
7991         second.pr = NoProc;\r
7992     }\r
7993 \r
7994     if (matchMode && gameMode == TwoMachinesPlay) {\r
7995         switch (result) {\r
7996         case WhiteWins:\r
7997           if (first.twoMachinesColor[0] == 'w') {\r
7998             first.matchWins++;\r
7999           } else {\r
8000             second.matchWins++;\r
8001           }\r
8002           break;\r
8003         case BlackWins:\r
8004           if (first.twoMachinesColor[0] == 'b') {\r
8005             first.matchWins++;\r
8006           } else {\r
8007             second.matchWins++;\r
8008           }\r
8009           break;\r
8010         default:\r
8011           break;\r
8012         }\r
8013         if (matchGame < appData.matchGames) {\r
8014             char *tmp;\r
8015             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */\r
8016                 tmp = first.twoMachinesColor;\r
8017                 first.twoMachinesColor = second.twoMachinesColor;\r
8018                 second.twoMachinesColor = tmp;\r
8019             }\r
8020             gameMode = nextGameMode;\r
8021             matchGame++;\r
8022             if(appData.matchPause>10000 || appData.matchPause<10)\r
8023                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
8024             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
8025             endingGame = 0; /* [HGM] crash */\r
8026             return;\r
8027         } else {\r
8028             char buf[MSG_SIZ];\r
8029             gameMode = nextGameMode;\r
8030             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),\r
8031                     first.tidy, second.tidy,\r
8032                     first.matchWins, second.matchWins,\r
8033                     appData.matchGames - (first.matchWins + second.matchWins));\r
8034             DisplayFatalError(buf, 0, 0);\r
8035         }\r
8036     }\r
8037     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
8038         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
8039       ExitAnalyzeMode();\r
8040     gameMode = nextGameMode;\r
8041     ModeHighlight();\r
8042     endingGame = 0;  /* [HGM] crash */\r
8043 }\r
8044 \r
8045 /* Assumes program was just initialized (initString sent).\r
8046    Leaves program in force mode. */\r
8047 void\r
8048 FeedMovesToProgram(cps, upto) \r
8049      ChessProgramState *cps;\r
8050      int upto;\r
8051 {\r
8052     int i;\r
8053     \r
8054     if (appData.debugMode)\r
8055       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
8056               startedFromSetupPosition ? "position and " : "",\r
8057               backwardMostMove, upto, cps->which);\r
8058     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
8059         // [HGM] variantswitch: make engine aware of new variant\r
8060         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
8061                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
8062         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
8063         SendToProgram(buf, cps);\r
8064         currentlyInitializedVariant = gameInfo.variant;\r
8065     }\r
8066     SendToProgram("force\n", cps);\r
8067     if (startedFromSetupPosition) {\r
8068         SendBoard(cps, backwardMostMove);\r
8069     if (appData.debugMode) {\r
8070         fprintf(debugFP, "feedMoves\n");\r
8071     }\r
8072     }\r
8073     for (i = backwardMostMove; i < upto; i++) {\r
8074         SendMoveToProgram(i, cps);\r
8075     }\r
8076 }\r
8077 \r
8078 \r
8079 void\r
8080 ResurrectChessProgram()\r
8081 {\r
8082      /* The chess program may have exited.\r
8083         If so, restart it and feed it all the moves made so far. */\r
8084 \r
8085     if (appData.noChessProgram || first.pr != NoProc) return;\r
8086     \r
8087     StartChessProgram(&first);\r
8088     InitChessProgram(&first, FALSE);\r
8089     FeedMovesToProgram(&first, currentMove);\r
8090 \r
8091     if (!first.sendTime) {\r
8092         /* can't tell gnuchess what its clock should read,\r
8093            so we bow to its notion. */\r
8094         ResetClocks();\r
8095         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
8096         timeRemaining[1][currentMove] = blackTimeRemaining;\r
8097     }\r
8098 \r
8099     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||\r
8100                 appData.icsEngineAnalyze) && first.analysisSupport) {\r
8101       SendToProgram("analyze\n", &first);\r
8102       first.analyzing = TRUE;\r
8103     }\r
8104 }\r
8105 \r
8106 /*\r
8107  * Button procedures\r
8108  */\r
8109 void\r
8110 Reset(redraw, init)\r
8111      int redraw, init;\r
8112 {\r
8113     int i;\r
8114 \r
8115     if (appData.debugMode) {\r
8116         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
8117                 redraw, init, gameMode);\r
8118     }\r
8119     pausing = pauseExamInvalid = FALSE;\r
8120     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
8121     firstMove = TRUE;\r
8122     whiteFlag = blackFlag = FALSE;\r
8123     userOfferedDraw = FALSE;\r
8124     hintRequested = bookRequested = FALSE;\r
8125     first.maybeThinking = FALSE;\r
8126     second.maybeThinking = FALSE;\r
8127     first.bookSuspend = FALSE; // [HGM] book\r
8128     second.bookSuspend = FALSE;\r
8129     thinkOutput[0] = NULLCHAR;\r
8130     lastHint[0] = NULLCHAR;\r
8131     ClearGameInfo(&gameInfo);\r
8132     gameInfo.variant = StringToVariant(appData.variant);\r
8133     ics_user_moved = ics_clock_paused = FALSE;\r
8134     ics_getting_history = H_FALSE;\r
8135     ics_gamenum = -1;\r
8136     white_holding[0] = black_holding[0] = NULLCHAR;\r
8137     ClearProgramStats();\r
8138     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode\r
8139     \r
8140     ResetFrontEnd();\r
8141     ClearHighlights();\r
8142     flipView = appData.flipView;\r
8143     ClearPremoveHighlights();\r
8144     gotPremove = FALSE;\r
8145     alarmSounded = FALSE;\r
8146 \r
8147     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8148     if(appData.serverMovesName != NULL) {\r
8149         /* [HGM] prepare to make moves file for broadcasting */\r
8150         clock_t t = clock();\r
8151         if(serverMoves != NULL) fclose(serverMoves);\r
8152         serverMoves = fopen(appData.serverMovesName, "r");\r
8153         if(serverMoves != NULL) {\r
8154             fclose(serverMoves);\r
8155             /* delay 15 sec before overwriting, so all clients can see end */\r
8156             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
8157         }\r
8158         serverMoves = fopen(appData.serverMovesName, "w");\r
8159     }\r
8160 \r
8161     ExitAnalyzeMode();\r
8162     gameMode = BeginningOfGame;\r
8163     ModeHighlight();\r
8164     if(appData.icsActive) gameInfo.variant = VariantNormal;\r
8165     InitPosition(redraw);\r
8166     for (i = 0; i < MAX_MOVES; i++) {\r
8167         if (commentList[i] != NULL) {\r
8168             free(commentList[i]);\r
8169             commentList[i] = NULL;\r
8170         }\r
8171     }\r
8172     ResetClocks();\r
8173     timeRemaining[0][0] = whiteTimeRemaining;\r
8174     timeRemaining[1][0] = blackTimeRemaining;\r
8175     if (first.pr == NULL) {\r
8176         StartChessProgram(&first);\r
8177     }\r
8178     if (init) {\r
8179             InitChessProgram(&first, startedFromSetupPosition);\r
8180     }\r
8181     DisplayTitle("");\r
8182     DisplayMessage("", "");\r
8183     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8184 }\r
8185 \r
8186 void\r
8187 AutoPlayGameLoop()\r
8188 {\r
8189     for (;;) {\r
8190         if (!AutoPlayOneMove())\r
8191           return;\r
8192         if (matchMode || appData.timeDelay == 0)\r
8193           continue;\r
8194         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
8195           return;\r
8196         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
8197         break;\r
8198     }\r
8199 }\r
8200 \r
8201 \r
8202 int\r
8203 AutoPlayOneMove()\r
8204 {\r
8205     int fromX, fromY, toX, toY;\r
8206 \r
8207     if (appData.debugMode) {\r
8208       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
8209     }\r
8210 \r
8211     if (gameMode != PlayFromGameFile)\r
8212       return FALSE;\r
8213 \r
8214     if (currentMove >= forwardMostMove) {\r
8215       gameMode = EditGame;\r
8216       ModeHighlight();\r
8217 \r
8218       /* [AS] Clear current move marker at the end of a game */\r
8219       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
8220 \r
8221       return FALSE;\r
8222     }\r
8223     \r
8224     toX = moveList[currentMove][2] - AAA;\r
8225     toY = moveList[currentMove][3] - ONE;\r
8226 \r
8227     if (moveList[currentMove][1] == '@') {\r
8228         if (appData.highlightLastMove) {\r
8229             SetHighlights(-1, -1, toX, toY);\r
8230         }\r
8231     } else {\r
8232         fromX = moveList[currentMove][0] - AAA;\r
8233         fromY = moveList[currentMove][1] - ONE;\r
8234 \r
8235         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
8236 \r
8237         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
8238 \r
8239         if (appData.highlightLastMove) {\r
8240             SetHighlights(fromX, fromY, toX, toY);\r
8241         }\r
8242     }\r
8243     DisplayMove(currentMove);\r
8244     SendMoveToProgram(currentMove++, &first);\r
8245     DisplayBothClocks();\r
8246     DrawPosition(FALSE, boards[currentMove]);\r
8247     // [HGM] PV info: always display, routine tests if empty\r
8248     DisplayComment(currentMove - 1, commentList[currentMove]);\r
8249     return TRUE;\r
8250 }\r
8251 \r
8252 \r
8253 int\r
8254 LoadGameOneMove(readAhead)\r
8255      ChessMove readAhead;\r
8256 {\r
8257     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
8258     char promoChar = NULLCHAR;\r
8259     ChessMove moveType;\r
8260     char move[MSG_SIZ];\r
8261     char *p, *q;\r
8262     \r
8263     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
8264         gameMode != AnalyzeMode && gameMode != Training) {\r
8265         gameFileFP = NULL;\r
8266         return FALSE;\r
8267     }\r
8268     \r
8269     yyboardindex = forwardMostMove;\r
8270     if (readAhead != (ChessMove)0) {\r
8271       moveType = readAhead;\r
8272     } else {\r
8273       if (gameFileFP == NULL)\r
8274           return FALSE;\r
8275       moveType = (ChessMove) yylex();\r
8276     }\r
8277     \r
8278     done = FALSE;\r
8279     switch (moveType) {\r
8280       case Comment:\r
8281         if (appData.debugMode) \r
8282           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8283         p = yy_text;\r
8284         if (*p == '{' || *p == '[' || *p == '(') {\r
8285             p[strlen(p) - 1] = NULLCHAR;\r
8286             p++;\r
8287         }\r
8288 \r
8289         /* append the comment but don't display it */\r
8290         while (*p == '\n') p++;\r
8291         AppendComment(currentMove, p);\r
8292         return TRUE;\r
8293 \r
8294       case WhiteCapturesEnPassant:\r
8295       case BlackCapturesEnPassant:\r
8296       case WhitePromotionChancellor:\r
8297       case BlackPromotionChancellor:\r
8298       case WhitePromotionArchbishop:\r
8299       case BlackPromotionArchbishop:\r
8300       case WhitePromotionCentaur:\r
8301       case BlackPromotionCentaur:\r
8302       case WhitePromotionQueen:\r
8303       case BlackPromotionQueen:\r
8304       case WhitePromotionRook:\r
8305       case BlackPromotionRook:\r
8306       case WhitePromotionBishop:\r
8307       case BlackPromotionBishop:\r
8308       case WhitePromotionKnight:\r
8309       case BlackPromotionKnight:\r
8310       case WhitePromotionKing:\r
8311       case BlackPromotionKing:\r
8312       case NormalMove:\r
8313       case WhiteKingSideCastle:\r
8314       case WhiteQueenSideCastle:\r
8315       case BlackKingSideCastle:\r
8316       case BlackQueenSideCastle:\r
8317       case WhiteKingSideCastleWild:\r
8318       case WhiteQueenSideCastleWild:\r
8319       case BlackKingSideCastleWild:\r
8320       case BlackQueenSideCastleWild:\r
8321       /* PUSH Fabien */\r
8322       case WhiteHSideCastleFR:\r
8323       case WhiteASideCastleFR:\r
8324       case BlackHSideCastleFR:\r
8325       case BlackASideCastleFR:\r
8326       /* POP Fabien */\r
8327         if (appData.debugMode)\r
8328           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8329         fromX = currentMoveString[0] - AAA;\r
8330         fromY = currentMoveString[1] - ONE;\r
8331         toX = currentMoveString[2] - AAA;\r
8332         toY = currentMoveString[3] - ONE;\r
8333         promoChar = currentMoveString[4];\r
8334         break;\r
8335 \r
8336       case WhiteDrop:\r
8337       case BlackDrop:\r
8338         if (appData.debugMode)\r
8339           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8340         fromX = moveType == WhiteDrop ?\r
8341           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
8342         (int) CharToPiece(ToLower(currentMoveString[0]));\r
8343         fromY = DROP_RANK;\r
8344         toX = currentMoveString[2] - AAA;\r
8345         toY = currentMoveString[3] - ONE;\r
8346         break;\r
8347 \r
8348       case WhiteWins:\r
8349       case BlackWins:\r
8350       case GameIsDrawn:\r
8351       case GameUnfinished:\r
8352         if (appData.debugMode)\r
8353           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
8354         p = strchr(yy_text, '{');\r
8355         if (p == NULL) p = strchr(yy_text, '(');\r
8356         if (p == NULL) {\r
8357             p = yy_text;\r
8358             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
8359         } else {\r
8360             q = strchr(p, *p == '{' ? '}' : ')');\r
8361             if (q != NULL) *q = NULLCHAR;\r
8362             p++;\r
8363         }\r
8364         GameEnds(moveType, p, GE_FILE);\r
8365         done = TRUE;\r
8366         if (cmailMsgLoaded) {\r
8367             ClearHighlights();\r
8368             flipView = WhiteOnMove(currentMove);\r
8369             if (moveType == GameUnfinished) flipView = !flipView;\r
8370             if (appData.debugMode)\r
8371               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
8372         }\r
8373         break;\r
8374 \r
8375       case (ChessMove) 0:       /* end of file */\r
8376         if (appData.debugMode)\r
8377           fprintf(debugFP, "Parser hit end of file\n");\r
8378         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8379                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8380           case MT_NONE:\r
8381           case MT_CHECK:\r
8382             break;\r
8383           case MT_CHECKMATE:\r
8384             if (WhiteOnMove(currentMove)) {\r
8385                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8386             } else {\r
8387                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8388             }\r
8389             break;\r
8390           case MT_STALEMATE:\r
8391             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8392             break;\r
8393         }\r
8394         done = TRUE;\r
8395         break;\r
8396 \r
8397       case MoveNumberOne:\r
8398         if (lastLoadGameStart == GNUChessGame) {\r
8399             /* GNUChessGames have numbers, but they aren't move numbers */\r
8400             if (appData.debugMode)\r
8401               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8402                       yy_text, (int) moveType);\r
8403             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8404         }\r
8405         /* else fall thru */\r
8406 \r
8407       case XBoardGame:\r
8408       case GNUChessGame:\r
8409       case PGNTag:\r
8410         /* Reached start of next game in file */\r
8411         if (appData.debugMode)\r
8412           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
8413         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8414                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8415           case MT_NONE:\r
8416           case MT_CHECK:\r
8417             break;\r
8418           case MT_CHECKMATE:\r
8419             if (WhiteOnMove(currentMove)) {\r
8420                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8421             } else {\r
8422                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8423             }\r
8424             break;\r
8425           case MT_STALEMATE:\r
8426             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8427             break;\r
8428         }\r
8429         done = TRUE;\r
8430         break;\r
8431 \r
8432       case PositionDiagram:     /* should not happen; ignore */\r
8433       case ElapsedTime:         /* ignore */\r
8434       case NAG:                 /* ignore */\r
8435         if (appData.debugMode)\r
8436           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8437                   yy_text, (int) moveType);\r
8438         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8439 \r
8440       case IllegalMove:\r
8441         if (appData.testLegality) {\r
8442             if (appData.debugMode)\r
8443               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
8444             sprintf(move, _("Illegal move: %d.%s%s"),\r
8445                     (forwardMostMove / 2) + 1,\r
8446                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8447             DisplayError(move, 0);\r
8448             done = TRUE;\r
8449         } else {\r
8450             if (appData.debugMode)\r
8451               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
8452                       yy_text, currentMoveString);\r
8453             fromX = currentMoveString[0] - AAA;\r
8454             fromY = currentMoveString[1] - ONE;\r
8455             toX = currentMoveString[2] - AAA;\r
8456             toY = currentMoveString[3] - ONE;\r
8457             promoChar = currentMoveString[4];\r
8458         }\r
8459         break;\r
8460 \r
8461       case AmbiguousMove:\r
8462         if (appData.debugMode)\r
8463           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
8464         sprintf(move, _("Ambiguous move: %d.%s%s"),\r
8465                 (forwardMostMove / 2) + 1,\r
8466                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8467         DisplayError(move, 0);\r
8468         done = TRUE;\r
8469         break;\r
8470 \r
8471       default:\r
8472       case ImpossibleMove:\r
8473         if (appData.debugMode)\r
8474           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);\r
8475         sprintf(move, _("Illegal move: %d.%s%s"),\r
8476                 (forwardMostMove / 2) + 1,\r
8477                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8478         DisplayError(move, 0);\r
8479         done = TRUE;\r
8480         break;\r
8481     }\r
8482 \r
8483     if (done) {\r
8484         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
8485             DrawPosition(FALSE, boards[currentMove]);\r
8486             DisplayBothClocks();\r
8487             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
8488               DisplayComment(currentMove - 1, commentList[currentMove]);\r
8489         }\r
8490         (void) StopLoadGameTimer();\r
8491         gameFileFP = NULL;\r
8492         cmailOldMove = forwardMostMove;\r
8493         return FALSE;\r
8494     } else {\r
8495         /* currentMoveString is set as a side-effect of yylex */\r
8496         strcat(currentMoveString, "\n");\r
8497         strcpy(moveList[forwardMostMove], currentMoveString);\r
8498         \r
8499         thinkOutput[0] = NULLCHAR;\r
8500         MakeMove(fromX, fromY, toX, toY, promoChar);\r
8501         currentMove = forwardMostMove;\r
8502         return TRUE;\r
8503     }\r
8504 }\r
8505 \r
8506 /* Load the nth game from the given file */\r
8507 int\r
8508 LoadGameFromFile(filename, n, title, useList)\r
8509      char *filename;\r
8510      int n;\r
8511      char *title;\r
8512      /*Boolean*/ int useList;\r
8513 {\r
8514     FILE *f;\r
8515     char buf[MSG_SIZ];\r
8516 \r
8517     if (strcmp(filename, "-") == 0) {\r
8518         f = stdin;\r
8519         title = "stdin";\r
8520     } else {\r
8521         f = fopen(filename, "rb");\r
8522         if (f == NULL) {\r
8523             sprintf(buf, _("Can't open \"%s\""), filename);\r
8524             DisplayError(buf, errno);\r
8525             return FALSE;\r
8526         }\r
8527     }\r
8528     if (fseek(f, 0, 0) == -1) {\r
8529         /* f is not seekable; probably a pipe */\r
8530         useList = FALSE;\r
8531     }\r
8532     if (useList && n == 0) {\r
8533         int error = GameListBuild(f);\r
8534         if (error) {\r
8535             DisplayError(_("Cannot build game list"), error);\r
8536         } else if (!ListEmpty(&gameList) &&\r
8537                    ((ListGame *) gameList.tailPred)->number > 1) {\r
8538             GameListPopUp(f, title);\r
8539             return TRUE;\r
8540         }\r
8541         GameListDestroy();\r
8542         n = 1;\r
8543     }\r
8544     if (n == 0) n = 1;\r
8545     return LoadGame(f, n, title, FALSE);\r
8546 }\r
8547 \r
8548 \r
8549 void\r
8550 MakeRegisteredMove()\r
8551 {\r
8552     int fromX, fromY, toX, toY;\r
8553     char promoChar;\r
8554     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8555         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
8556           case CMAIL_MOVE:\r
8557           case CMAIL_DRAW:\r
8558             if (appData.debugMode)\r
8559               fprintf(debugFP, "Restoring %s for game %d\n",\r
8560                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8561     \r
8562             thinkOutput[0] = NULLCHAR;\r
8563             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
8564             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
8565             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
8566             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
8567             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
8568             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
8569             MakeMove(fromX, fromY, toX, toY, promoChar);\r
8570             ShowMove(fromX, fromY, toX, toY);\r
8571               \r
8572             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8573                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8574               case MT_NONE:\r
8575               case MT_CHECK:\r
8576                 break;\r
8577                 \r
8578               case MT_CHECKMATE:\r
8579                 if (WhiteOnMove(currentMove)) {\r
8580                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
8581                 } else {\r
8582                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
8583                 }\r
8584                 break;\r
8585                 \r
8586               case MT_STALEMATE:\r
8587                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
8588                 break;\r
8589             }\r
8590 \r
8591             break;\r
8592             \r
8593           case CMAIL_RESIGN:\r
8594             if (WhiteOnMove(currentMove)) {\r
8595                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
8596             } else {\r
8597                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
8598             }\r
8599             break;\r
8600             \r
8601           case CMAIL_ACCEPT:\r
8602             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
8603             break;\r
8604               \r
8605           default:\r
8606             break;\r
8607         }\r
8608     }\r
8609 \r
8610     return;\r
8611 }\r
8612 \r
8613 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
8614 int\r
8615 CmailLoadGame(f, gameNumber, title, useList)\r
8616      FILE *f;\r
8617      int gameNumber;\r
8618      char *title;\r
8619      int useList;\r
8620 {\r
8621     int retVal;\r
8622 \r
8623     if (gameNumber > nCmailGames) {\r
8624         DisplayError(_("No more games in this message"), 0);\r
8625         return FALSE;\r
8626     }\r
8627     if (f == lastLoadGameFP) {\r
8628         int offset = gameNumber - lastLoadGameNumber;\r
8629         if (offset == 0) {\r
8630             cmailMsg[0] = NULLCHAR;\r
8631             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8632                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8633                 nCmailMovesRegistered--;\r
8634             }\r
8635             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
8636             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
8637                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
8638             }\r
8639         } else {\r
8640             if (! RegisterMove()) return FALSE;\r
8641         }\r
8642     }\r
8643 \r
8644     retVal = LoadGame(f, gameNumber, title, useList);\r
8645 \r
8646     /* Make move registered during previous look at this game, if any */\r
8647     MakeRegisteredMove();\r
8648 \r
8649     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
8650         commentList[currentMove]\r
8651           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
8652         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8653     }\r
8654 \r
8655     return retVal;\r
8656 }\r
8657 \r
8658 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
8659 int\r
8660 ReloadGame(offset)\r
8661      int offset;\r
8662 {\r
8663     int gameNumber = lastLoadGameNumber + offset;\r
8664     if (lastLoadGameFP == NULL) {\r
8665         DisplayError(_("No game has been loaded yet"), 0);\r
8666         return FALSE;\r
8667     }\r
8668     if (gameNumber <= 0) {\r
8669         DisplayError(_("Can't back up any further"), 0);\r
8670         return FALSE;\r
8671     }\r
8672     if (cmailMsgLoaded) {\r
8673         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
8674                              lastLoadGameTitle, lastLoadGameUseList);\r
8675     } else {\r
8676         return LoadGame(lastLoadGameFP, gameNumber,\r
8677                         lastLoadGameTitle, lastLoadGameUseList);\r
8678     }\r
8679 }\r
8680 \r
8681 \r
8682 \r
8683 /* Load the nth game from open file f */\r
8684 int\r
8685 LoadGame(f, gameNumber, title, useList)\r
8686      FILE *f;\r
8687      int gameNumber;\r
8688      char *title;\r
8689      int useList;\r
8690 {\r
8691     ChessMove cm;\r
8692     char buf[MSG_SIZ];\r
8693     int gn = gameNumber;\r
8694     ListGame *lg = NULL;\r
8695     int numPGNTags = 0;\r
8696     int err;\r
8697     GameMode oldGameMode;\r
8698     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
8699 \r
8700     if (appData.debugMode) \r
8701         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
8702 \r
8703     if (gameMode == Training )\r
8704         SetTrainingModeOff();\r
8705 \r
8706     oldGameMode = gameMode;\r
8707     if (gameMode != BeginningOfGame) {\r
8708       Reset(FALSE, TRUE);\r
8709     }\r
8710 \r
8711     gameFileFP = f;\r
8712     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
8713         fclose(lastLoadGameFP);\r
8714     }\r
8715 \r
8716     if (useList) {\r
8717         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
8718         \r
8719         if (lg) {\r
8720             fseek(f, lg->offset, 0);\r
8721             GameListHighlight(gameNumber);\r
8722             gn = 1;\r
8723         }\r
8724         else {\r
8725             DisplayError(_("Game number out of range"), 0);\r
8726             return FALSE;\r
8727         }\r
8728     } else {\r
8729         GameListDestroy();\r
8730         if (fseek(f, 0, 0) == -1) {\r
8731             if (f == lastLoadGameFP ?\r
8732                 gameNumber == lastLoadGameNumber + 1 :\r
8733                 gameNumber == 1) {\r
8734                 gn = 1;\r
8735             } else {\r
8736                 DisplayError(_("Can't seek on game file"), 0);\r
8737                 return FALSE;\r
8738             }\r
8739         }\r
8740     }\r
8741     lastLoadGameFP = f;\r
8742     lastLoadGameNumber = gameNumber;\r
8743     strcpy(lastLoadGameTitle, title);\r
8744     lastLoadGameUseList = useList;\r
8745 \r
8746     yynewfile(f);\r
8747 \r
8748     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
8749         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
8750                 lg->gameInfo.black);\r
8751             DisplayTitle(buf);\r
8752     } else if (*title != NULLCHAR) {\r
8753         if (gameNumber > 1) {\r
8754             sprintf(buf, "%s %d", title, gameNumber);\r
8755             DisplayTitle(buf);\r
8756         } else {\r
8757             DisplayTitle(title);\r
8758         }\r
8759     }\r
8760 \r
8761     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
8762         gameMode = PlayFromGameFile;\r
8763         ModeHighlight();\r
8764     }\r
8765 \r
8766     currentMove = forwardMostMove = backwardMostMove = 0;\r
8767     CopyBoard(boards[0], initialPosition);\r
8768     StopClocks();\r
8769 \r
8770     /*\r
8771      * Skip the first gn-1 games in the file.\r
8772      * Also skip over anything that precedes an identifiable \r
8773      * start of game marker, to avoid being confused by \r
8774      * garbage at the start of the file.  Currently \r
8775      * recognized start of game markers are the move number "1",\r
8776      * the pattern "gnuchess .* game", the pattern\r
8777      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
8778      * A game that starts with one of the latter two patterns\r
8779      * will also have a move number 1, possibly\r
8780      * following a position diagram.\r
8781      * 5-4-02: Let's try being more lenient and allowing a game to\r
8782      * start with an unnumbered move.  Does that break anything?\r
8783      */\r
8784     cm = lastLoadGameStart = (ChessMove) 0;\r
8785     while (gn > 0) {\r
8786         yyboardindex = forwardMostMove;\r
8787         cm = (ChessMove) yylex();\r
8788         switch (cm) {\r
8789           case (ChessMove) 0:\r
8790             if (cmailMsgLoaded) {\r
8791                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
8792             } else {\r
8793                 Reset(TRUE, TRUE);\r
8794                 DisplayError(_("Game not found in file"), 0);\r
8795             }\r
8796             return FALSE;\r
8797 \r
8798           case GNUChessGame:\r
8799           case XBoardGame:\r
8800             gn--;\r
8801             lastLoadGameStart = cm;\r
8802             break;\r
8803             \r
8804           case MoveNumberOne:\r
8805             switch (lastLoadGameStart) {\r
8806               case GNUChessGame:\r
8807               case XBoardGame:\r
8808               case PGNTag:\r
8809                 break;\r
8810               case MoveNumberOne:\r
8811               case (ChessMove) 0:\r
8812                 gn--;           /* count this game */\r
8813                 lastLoadGameStart = cm;\r
8814                 break;\r
8815               default:\r
8816                 /* impossible */\r
8817                 break;\r
8818             }\r
8819             break;\r
8820 \r
8821           case PGNTag:\r
8822             switch (lastLoadGameStart) {\r
8823               case GNUChessGame:\r
8824               case PGNTag:\r
8825               case MoveNumberOne:\r
8826               case (ChessMove) 0:\r
8827                 gn--;           /* count this game */\r
8828                 lastLoadGameStart = cm;\r
8829                 break;\r
8830               case XBoardGame:\r
8831                 lastLoadGameStart = cm; /* game counted already */\r
8832                 break;\r
8833               default:\r
8834                 /* impossible */\r
8835                 break;\r
8836             }\r
8837             if (gn > 0) {\r
8838                 do {\r
8839                     yyboardindex = forwardMostMove;\r
8840                     cm = (ChessMove) yylex();\r
8841                 } while (cm == PGNTag || cm == Comment);\r
8842             }\r
8843             break;\r
8844 \r
8845           case WhiteWins:\r
8846           case BlackWins:\r
8847           case GameIsDrawn:\r
8848             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8849                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8850                     != CMAIL_OLD_RESULT) {\r
8851                     nCmailResults ++ ;\r
8852                     cmailResult[  CMAIL_MAX_GAMES\r
8853                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8854                 }\r
8855             }\r
8856             break;\r
8857 \r
8858           case NormalMove:\r
8859             /* Only a NormalMove can be at the start of a game\r
8860              * without a position diagram. */\r
8861             if (lastLoadGameStart == (ChessMove) 0) {\r
8862               gn--;\r
8863               lastLoadGameStart = MoveNumberOne;\r
8864             }\r
8865             break;\r
8866 \r
8867           default:\r
8868             break;\r
8869         }\r
8870     }\r
8871     \r
8872     if (appData.debugMode)\r
8873       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8874 \r
8875     if (cm == XBoardGame) {\r
8876         /* Skip any header junk before position diagram and/or move 1 */\r
8877         for (;;) {\r
8878             yyboardindex = forwardMostMove;\r
8879             cm = (ChessMove) yylex();\r
8880 \r
8881             if (cm == (ChessMove) 0 ||\r
8882                 cm == GNUChessGame || cm == XBoardGame) {\r
8883                 /* Empty game; pretend end-of-file and handle later */\r
8884                 cm = (ChessMove) 0;\r
8885                 break;\r
8886             }\r
8887 \r
8888             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8889                 cm == PGNTag || cm == Comment)\r
8890               break;\r
8891         }\r
8892     } else if (cm == GNUChessGame) {\r
8893         if (gameInfo.event != NULL) {\r
8894             free(gameInfo.event);\r
8895         }\r
8896         gameInfo.event = StrSave(yy_text);\r
8897     }   \r
8898 \r
8899     startedFromSetupPosition = FALSE;\r
8900     while (cm == PGNTag) {\r
8901         if (appData.debugMode) \r
8902           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8903         err = ParsePGNTag(yy_text, &gameInfo);\r
8904         if (!err) numPGNTags++;\r
8905 \r
8906         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8907         if(gameInfo.variant != oldVariant) {\r
8908             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8909             InitPosition(TRUE);\r
8910             oldVariant = gameInfo.variant;\r
8911             if (appData.debugMode) \r
8912               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8913         }\r
8914 \r
8915 \r
8916         if (gameInfo.fen != NULL) {\r
8917           Board initial_position;\r
8918           startedFromSetupPosition = TRUE;\r
8919           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8920             Reset(TRUE, TRUE);\r
8921             DisplayError(_("Bad FEN position in file"), 0);\r
8922             return FALSE;\r
8923           }\r
8924           CopyBoard(boards[0], initial_position);\r
8925           if (blackPlaysFirst) {\r
8926             currentMove = forwardMostMove = backwardMostMove = 1;\r
8927             CopyBoard(boards[1], initial_position);\r
8928             strcpy(moveList[0], "");\r
8929             strcpy(parseList[0], "");\r
8930             timeRemaining[0][1] = whiteTimeRemaining;\r
8931             timeRemaining[1][1] = blackTimeRemaining;\r
8932             if (commentList[0] != NULL) {\r
8933               commentList[1] = commentList[0];\r
8934               commentList[0] = NULL;\r
8935             }\r
8936           } else {\r
8937             currentMove = forwardMostMove = backwardMostMove = 0;\r
8938           }\r
8939           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */\r
8940           {   int i;\r
8941               initialRulePlies = FENrulePlies;\r
8942               epStatus[forwardMostMove] = FENepStatus;\r
8943               for( i=0; i< nrCastlingRights; i++ )\r
8944                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
8945           }\r
8946           yyboardindex = forwardMostMove;\r
8947           free(gameInfo.fen);\r
8948           gameInfo.fen = NULL;\r
8949         }\r
8950 \r
8951         yyboardindex = forwardMostMove;\r
8952         cm = (ChessMove) yylex();\r
8953 \r
8954         /* Handle comments interspersed among the tags */\r
8955         while (cm == Comment) {\r
8956             char *p;\r
8957             if (appData.debugMode) \r
8958               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8959             p = yy_text;\r
8960             if (*p == '{' || *p == '[' || *p == '(') {\r
8961                 p[strlen(p) - 1] = NULLCHAR;\r
8962                 p++;\r
8963             }\r
8964             while (*p == '\n') p++;\r
8965             AppendComment(currentMove, p);\r
8966             yyboardindex = forwardMostMove;\r
8967             cm = (ChessMove) yylex();\r
8968         }\r
8969     }\r
8970 \r
8971     /* don't rely on existence of Event tag since if game was\r
8972      * pasted from clipboard the Event tag may not exist\r
8973      */\r
8974     if (numPGNTags > 0){\r
8975         char *tags;\r
8976         if (gameInfo.variant == VariantNormal) {\r
8977           gameInfo.variant = StringToVariant(gameInfo.event);\r
8978         }\r
8979         if (!matchMode) {\r
8980           if( appData.autoDisplayTags ) {\r
8981             tags = PGNTags(&gameInfo);\r
8982             TagsPopUp(tags, CmailMsg());\r
8983             free(tags);\r
8984           }\r
8985         }\r
8986     } else {\r
8987         /* Make something up, but don't display it now */\r
8988         SetGameInfo();\r
8989         TagsPopDown();\r
8990     }\r
8991 \r
8992     if (cm == PositionDiagram) {\r
8993         int i, j;\r
8994         char *p;\r
8995         Board initial_position;\r
8996 \r
8997         if (appData.debugMode)\r
8998           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
8999 \r
9000         if (!startedFromSetupPosition) {\r
9001             p = yy_text;\r
9002             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
9003               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
9004                 switch (*p) {\r
9005                   case '[':\r
9006                   case '-':\r
9007                   case ' ':\r
9008                   case '\t':\r
9009                   case '\n':\r
9010                   case '\r':\r
9011                     break;\r
9012                   default:\r
9013                     initial_position[i][j++] = CharToPiece(*p);\r
9014                     break;\r
9015                 }\r
9016             while (*p == ' ' || *p == '\t' ||\r
9017                    *p == '\n' || *p == '\r') p++;\r
9018         \r
9019             if (strncmp(p, "black", strlen("black"))==0)\r
9020               blackPlaysFirst = TRUE;\r
9021             else\r
9022               blackPlaysFirst = FALSE;\r
9023             startedFromSetupPosition = TRUE;\r
9024         \r
9025             CopyBoard(boards[0], initial_position);\r
9026             if (blackPlaysFirst) {\r
9027                 currentMove = forwardMostMove = backwardMostMove = 1;\r
9028                 CopyBoard(boards[1], initial_position);\r
9029                 strcpy(moveList[0], "");\r
9030                 strcpy(parseList[0], "");\r
9031                 timeRemaining[0][1] = whiteTimeRemaining;\r
9032                 timeRemaining[1][1] = blackTimeRemaining;\r
9033                 if (commentList[0] != NULL) {\r
9034                     commentList[1] = commentList[0];\r
9035                     commentList[0] = NULL;\r
9036                 }\r
9037             } else {\r
9038                 currentMove = forwardMostMove = backwardMostMove = 0;\r
9039             }\r
9040         }\r
9041         yyboardindex = forwardMostMove;\r
9042         cm = (ChessMove) yylex();\r
9043     }\r
9044 \r
9045     if (first.pr == NoProc) {\r
9046         StartChessProgram(&first);\r
9047     }\r
9048     InitChessProgram(&first, FALSE);\r
9049     SendToProgram("force\n", &first);\r
9050     if (startedFromSetupPosition) {\r
9051         SendBoard(&first, forwardMostMove);\r
9052     if (appData.debugMode) {\r
9053         fprintf(debugFP, "Load Game\n");\r
9054     }\r
9055         DisplayBothClocks();\r
9056     }      \r
9057 \r
9058     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
9059     loadFlag = appData.suppressLoadMoves;\r
9060 \r
9061     while (cm == Comment) {\r
9062         char *p;\r
9063         if (appData.debugMode) \r
9064           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
9065         p = yy_text;\r
9066         if (*p == '{' || *p == '[' || *p == '(') {\r
9067             p[strlen(p) - 1] = NULLCHAR;\r
9068             p++;\r
9069         }\r
9070         while (*p == '\n') p++;\r
9071         AppendComment(currentMove, p);\r
9072         yyboardindex = forwardMostMove;\r
9073         cm = (ChessMove) yylex();\r
9074     }\r
9075 \r
9076     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
9077         cm == WhiteWins || cm == BlackWins ||\r
9078         cm == GameIsDrawn || cm == GameUnfinished) {\r
9079         DisplayMessage("", _("No moves in game"));\r
9080         if (cmailMsgLoaded) {\r
9081             if (appData.debugMode)\r
9082               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
9083             ClearHighlights();\r
9084             flipView = FALSE;\r
9085         }\r
9086         DrawPosition(FALSE, boards[currentMove]);\r
9087         DisplayBothClocks();\r
9088         gameMode = EditGame;\r
9089         ModeHighlight();\r
9090         gameFileFP = NULL;\r
9091         cmailOldMove = 0;\r
9092         return TRUE;\r
9093     }\r
9094 \r
9095     // [HGM] PV info: routine tests if comment empty\r
9096     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
9097         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9098     }\r
9099     if (!matchMode && appData.timeDelay != 0) \r
9100       DrawPosition(FALSE, boards[currentMove]);\r
9101 \r
9102     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
9103       programStats.ok_to_send = 1;\r
9104     }\r
9105 \r
9106     /* if the first token after the PGN tags is a move\r
9107      * and not move number 1, retrieve it from the parser \r
9108      */\r
9109     if (cm != MoveNumberOne)\r
9110         LoadGameOneMove(cm);\r
9111 \r
9112     /* load the remaining moves from the file */\r
9113     while (LoadGameOneMove((ChessMove)0)) {\r
9114       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9115       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9116     }\r
9117 \r
9118     /* rewind to the start of the game */\r
9119     currentMove = backwardMostMove;\r
9120 \r
9121     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9122 \r
9123     if (oldGameMode == AnalyzeFile ||\r
9124         oldGameMode == AnalyzeMode) {\r
9125       AnalyzeFileEvent();\r
9126     }\r
9127 \r
9128     if (matchMode || appData.timeDelay == 0) {\r
9129       ToEndEvent();\r
9130       gameMode = EditGame;\r
9131       ModeHighlight();\r
9132     } else if (appData.timeDelay > 0) {\r
9133       AutoPlayGameLoop();\r
9134     }\r
9135 \r
9136     if (appData.debugMode) \r
9137         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
9138 \r
9139     loadFlag = 0; /* [HGM] true game starts */\r
9140     return TRUE;\r
9141 }\r
9142 \r
9143 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
9144 int\r
9145 ReloadPosition(offset)\r
9146      int offset;\r
9147 {\r
9148     int positionNumber = lastLoadPositionNumber + offset;\r
9149     if (lastLoadPositionFP == NULL) {\r
9150         DisplayError(_("No position has been loaded yet"), 0);\r
9151         return FALSE;\r
9152     }\r
9153     if (positionNumber <= 0) {\r
9154         DisplayError(_("Can't back up any further"), 0);\r
9155         return FALSE;\r
9156     }\r
9157     return LoadPosition(lastLoadPositionFP, positionNumber,\r
9158                         lastLoadPositionTitle);\r
9159 }\r
9160 \r
9161 /* Load the nth position from the given file */\r
9162 int\r
9163 LoadPositionFromFile(filename, n, title)\r
9164      char *filename;\r
9165      int n;\r
9166      char *title;\r
9167 {\r
9168     FILE *f;\r
9169     char buf[MSG_SIZ];\r
9170 \r
9171     if (strcmp(filename, "-") == 0) {\r
9172         return LoadPosition(stdin, n, "stdin");\r
9173     } else {\r
9174         f = fopen(filename, "rb");\r
9175         if (f == NULL) {\r
9176             sprintf(buf, _("Can't open \"%s\""), filename);\r
9177             DisplayError(buf, errno);\r
9178             return FALSE;\r
9179         } else {\r
9180             return LoadPosition(f, n, title);\r
9181         }\r
9182     }\r
9183 }\r
9184 \r
9185 /* Load the nth position from the given open file, and close it */\r
9186 int\r
9187 LoadPosition(f, positionNumber, title)\r
9188      FILE *f;\r
9189      int positionNumber;\r
9190      char *title;\r
9191 {\r
9192     char *p, line[MSG_SIZ];\r
9193     Board initial_position;\r
9194     int i, j, fenMode, pn;\r
9195     \r
9196     if (gameMode == Training )\r
9197         SetTrainingModeOff();\r
9198 \r
9199     if (gameMode != BeginningOfGame) {\r
9200         Reset(FALSE, TRUE);\r
9201     }\r
9202     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
9203         fclose(lastLoadPositionFP);\r
9204     }\r
9205     if (positionNumber == 0) positionNumber = 1;\r
9206     lastLoadPositionFP = f;\r
9207     lastLoadPositionNumber = positionNumber;\r
9208     strcpy(lastLoadPositionTitle, title);\r
9209     if (first.pr == NoProc) {\r
9210       StartChessProgram(&first);\r
9211       InitChessProgram(&first, FALSE);\r
9212     }    \r
9213     pn = positionNumber;\r
9214     if (positionNumber < 0) {\r
9215         /* Negative position number means to seek to that byte offset */\r
9216         if (fseek(f, -positionNumber, 0) == -1) {\r
9217             DisplayError(_("Can't seek on position file"), 0);\r
9218             return FALSE;\r
9219         };\r
9220         pn = 1;\r
9221     } else {\r
9222         if (fseek(f, 0, 0) == -1) {\r
9223             if (f == lastLoadPositionFP ?\r
9224                 positionNumber == lastLoadPositionNumber + 1 :\r
9225                 positionNumber == 1) {\r
9226                 pn = 1;\r
9227             } else {\r
9228                 DisplayError(_("Can't seek on position file"), 0);\r
9229                 return FALSE;\r
9230             }\r
9231         }\r
9232     }\r
9233     /* See if this file is FEN or old-style xboard */\r
9234     if (fgets(line, MSG_SIZ, f) == NULL) {\r
9235         DisplayError(_("Position not found in file"), 0);\r
9236         return FALSE;\r
9237     }\r
9238 #if 0\r
9239     switch (line[0]) {\r
9240       case '#':  case 'x':\r
9241       default:\r
9242         fenMode = FALSE;\r
9243         break;\r
9244       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
9245       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
9246       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
9247       case '7':  case '8':  case '9':\r
9248       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
9249       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
9250       case 'C':  case 'W':             case 'c':  case 'w': \r
9251         fenMode = TRUE;\r
9252         break;\r
9253     }\r
9254 #else\r
9255     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
9256     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
9257 #endif\r
9258 \r
9259     if (pn >= 2) {\r
9260         if (fenMode || line[0] == '#') pn--;\r
9261         while (pn > 0) {\r
9262             /* skip positions before number pn */\r
9263             if (fgets(line, MSG_SIZ, f) == NULL) {\r
9264                 Reset(TRUE, TRUE);\r
9265                 DisplayError(_("Position not found in file"), 0);\r
9266                 return FALSE;\r
9267             }\r
9268             if (fenMode || line[0] == '#') pn--;\r
9269         }\r
9270     }\r
9271 \r
9272     if (fenMode) {\r
9273         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
9274             DisplayError(_("Bad FEN position in file"), 0);\r
9275             return FALSE;\r
9276         }\r
9277     } else {\r
9278         (void) fgets(line, MSG_SIZ, f);\r
9279         (void) fgets(line, MSG_SIZ, f);\r
9280     \r
9281         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
9282             (void) fgets(line, MSG_SIZ, f);\r
9283             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
9284                 if (*p == ' ')\r
9285                   continue;\r
9286                 initial_position[i][j++] = CharToPiece(*p);\r
9287             }\r
9288         }\r
9289     \r
9290         blackPlaysFirst = FALSE;\r
9291         if (!feof(f)) {\r
9292             (void) fgets(line, MSG_SIZ, f);\r
9293             if (strncmp(line, "black", strlen("black"))==0)\r
9294               blackPlaysFirst = TRUE;\r
9295         }\r
9296     }\r
9297     startedFromSetupPosition = TRUE;\r
9298     \r
9299     SendToProgram("force\n", &first);\r
9300     CopyBoard(boards[0], initial_position);\r
9301     if (blackPlaysFirst) {\r
9302         currentMove = forwardMostMove = backwardMostMove = 1;\r
9303         strcpy(moveList[0], "");\r
9304         strcpy(parseList[0], "");\r
9305         CopyBoard(boards[1], initial_position);\r
9306         DisplayMessage("", _("Black to play"));\r
9307     } else {\r
9308         currentMove = forwardMostMove = backwardMostMove = 0;\r
9309         DisplayMessage("", _("White to play"));\r
9310     }\r
9311           /* [HGM] copy FEN attributes as well */\r
9312           {   int i;\r
9313               initialRulePlies = FENrulePlies;\r
9314               epStatus[forwardMostMove] = FENepStatus;\r
9315               for( i=0; i< nrCastlingRights; i++ )\r
9316                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
9317           }\r
9318     SendBoard(&first, forwardMostMove);\r
9319     if (appData.debugMode) {\r
9320 int i, j;\r
9321   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}\r
9322   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");\r
9323         fprintf(debugFP, "Load Position\n");\r
9324     }\r
9325 \r
9326     if (positionNumber > 1) {\r
9327         sprintf(line, "%s %d", title, positionNumber);\r
9328         DisplayTitle(line);\r
9329     } else {\r
9330         DisplayTitle(title);\r
9331     }\r
9332     gameMode = EditGame;\r
9333     ModeHighlight();\r
9334     ResetClocks();\r
9335     timeRemaining[0][1] = whiteTimeRemaining;\r
9336     timeRemaining[1][1] = blackTimeRemaining;\r
9337     DrawPosition(FALSE, boards[currentMove]);\r
9338    \r
9339     return TRUE;\r
9340 }\r
9341 \r
9342 \r
9343 void\r
9344 CopyPlayerNameIntoFileName(dest, src)\r
9345      char **dest, *src;\r
9346 {\r
9347     while (*src != NULLCHAR && *src != ',') {\r
9348         if (*src == ' ') {\r
9349             *(*dest)++ = '_';\r
9350             src++;\r
9351         } else {\r
9352             *(*dest)++ = *src++;\r
9353         }\r
9354     }\r
9355 }\r
9356 \r
9357 char *DefaultFileName(ext)\r
9358      char *ext;\r
9359 {\r
9360     static char def[MSG_SIZ];\r
9361     char *p;\r
9362 \r
9363     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
9364         p = def;\r
9365         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
9366         *p++ = '-';\r
9367         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
9368         *p++ = '.';\r
9369         strcpy(p, ext);\r
9370     } else {\r
9371         def[0] = NULLCHAR;\r
9372     }\r
9373     return def;\r
9374 }\r
9375 \r
9376 /* Save the current game to the given file */\r
9377 int\r
9378 SaveGameToFile(filename, append)\r
9379      char *filename;\r
9380      int append;\r
9381 {\r
9382     FILE *f;\r
9383     char buf[MSG_SIZ];\r
9384 \r
9385     if (strcmp(filename, "-") == 0) {\r
9386         return SaveGame(stdout, 0, NULL);\r
9387     } else {\r
9388         f = fopen(filename, append ? "a" : "w");\r
9389         if (f == NULL) {\r
9390             sprintf(buf, _("Can't open \"%s\""), filename);\r
9391             DisplayError(buf, errno);\r
9392             return FALSE;\r
9393         } else {\r
9394             return SaveGame(f, 0, NULL);\r
9395         }\r
9396     }\r
9397 }\r
9398 \r
9399 char *\r
9400 SavePart(str)\r
9401      char *str;\r
9402 {\r
9403     static char buf[MSG_SIZ];\r
9404     char *p;\r
9405     \r
9406     p = strchr(str, ' ');\r
9407     if (p == NULL) return str;\r
9408     strncpy(buf, str, p - str);\r
9409     buf[p - str] = NULLCHAR;\r
9410     return buf;\r
9411 }\r
9412 \r
9413 #define PGN_MAX_LINE 75\r
9414 \r
9415 #define PGN_SIDE_WHITE  0\r
9416 #define PGN_SIDE_BLACK  1\r
9417 \r
9418 /* [AS] */\r
9419 static int FindFirstMoveOutOfBook( int side )\r
9420 {\r
9421     int result = -1;\r
9422 \r
9423     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
9424         int index = backwardMostMove;\r
9425         int has_book_hit = 0;\r
9426 \r
9427         if( (index % 2) != side ) {\r
9428             index++;\r
9429         }\r
9430 \r
9431         while( index < forwardMostMove ) {\r
9432             /* Check to see if engine is in book */\r
9433             int depth = pvInfoList[index].depth;\r
9434             int score = pvInfoList[index].score;\r
9435             int in_book = 0;\r
9436 \r
9437             if( depth <= 2 ) {\r
9438                 in_book = 1;\r
9439             }\r
9440             else if( score == 0 && depth == 63 ) {\r
9441                 in_book = 1; /* Zappa */\r
9442             }\r
9443             else if( score == 2 && depth == 99 ) {\r
9444                 in_book = 1; /* Abrok */\r
9445             }\r
9446 \r
9447             has_book_hit += in_book;\r
9448 \r
9449             if( ! in_book ) {\r
9450                 result = index;\r
9451 \r
9452                 break;\r
9453             }\r
9454 \r
9455             index += 2;\r
9456         }\r
9457     }\r
9458 \r
9459     return result;\r
9460 }\r
9461 \r
9462 /* [AS] */\r
9463 void GetOutOfBookInfo( char * buf )\r
9464 {\r
9465     int oob[2];\r
9466     int i;\r
9467     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9468 \r
9469     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
9470     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
9471 \r
9472     *buf = '\0';\r
9473 \r
9474     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
9475         for( i=0; i<2; i++ ) {\r
9476             int idx = oob[i];\r
9477 \r
9478             if( idx >= 0 ) {\r
9479                 if( i > 0 && oob[0] >= 0 ) {\r
9480                     strcat( buf, "   " );\r
9481                 }\r
9482 \r
9483                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
9484                 sprintf( buf+strlen(buf), "%s%.2f", \r
9485                     pvInfoList[idx].score >= 0 ? "+" : "",\r
9486                     pvInfoList[idx].score / 100.0 );\r
9487             }\r
9488         }\r
9489     }\r
9490 }\r
9491 \r
9492 /* Save game in PGN style and close the file */\r
9493 int\r
9494 SaveGamePGN(f)\r
9495      FILE *f;\r
9496 {\r
9497     int i, offset, linelen, newblock;\r
9498     time_t tm;\r
9499     char *movetext;\r
9500     char numtext[32];\r
9501     int movelen, numlen, blank;\r
9502     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
9503 \r
9504     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9505     \r
9506     tm = time((time_t *) NULL);\r
9507     \r
9508     PrintPGNTags(f, &gameInfo);\r
9509     \r
9510     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9511         char *fen = PositionToFEN(backwardMostMove, 1);\r
9512         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
9513         fprintf(f, "\n{--------------\n");\r
9514         PrintPosition(f, backwardMostMove);\r
9515         fprintf(f, "--------------}\n");\r
9516         free(fen);\r
9517     }\r
9518     else {\r
9519         /* [AS] Out of book annotation */\r
9520         if( appData.saveOutOfBookInfo ) {\r
9521             char buf[64];\r
9522 \r
9523             GetOutOfBookInfo( buf );\r
9524 \r
9525             if( buf[0] != '\0' ) {\r
9526                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
9527             }\r
9528         }\r
9529 \r
9530         fprintf(f, "\n");\r
9531     }\r
9532 \r
9533     i = backwardMostMove;\r
9534     linelen = 0;\r
9535     newblock = TRUE;\r
9536 \r
9537     while (i < forwardMostMove) {\r
9538         /* Print comments preceding this move */\r
9539         if (commentList[i] != NULL) {\r
9540             if (linelen > 0) fprintf(f, "\n");\r
9541             fprintf(f, "{\n%s}\n", commentList[i]);\r
9542             linelen = 0;\r
9543             newblock = TRUE;\r
9544         }\r
9545 \r
9546         /* Format move number */\r
9547         if ((i % 2) == 0) {\r
9548             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
9549         } else {\r
9550             if (newblock) {\r
9551                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
9552             } else {\r
9553                 numtext[0] = NULLCHAR;\r
9554             }\r
9555         }\r
9556         numlen = strlen(numtext);\r
9557         newblock = FALSE;\r
9558 \r
9559         /* Print move number */\r
9560         blank = linelen > 0 && numlen > 0;\r
9561         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
9562             fprintf(f, "\n");\r
9563             linelen = 0;\r
9564             blank = 0;\r
9565         }\r
9566         if (blank) {\r
9567             fprintf(f, " ");\r
9568             linelen++;\r
9569         }\r
9570         fprintf(f, numtext);\r
9571         linelen += numlen;\r
9572 \r
9573         /* Get move */\r
9574         movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */\r
9575 \r
9576         /* Print move */\r
9577         blank = linelen > 0 && movelen > 0;\r
9578         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9579             fprintf(f, "\n");\r
9580             linelen = 0;\r
9581             blank = 0;\r
9582         }\r
9583         if (blank) {\r
9584             fprintf(f, " ");\r
9585             linelen++;\r
9586         }\r
9587         fprintf(f, parseList[i]);\r
9588         linelen += movelen;\r
9589 \r
9590         /* [AS] Add PV info if present */\r
9591         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9592             /* [HGM] add time */\r
9593             char buf[MSG_SIZ]; int seconds = 0;\r
9594 \r
9595 #if 0\r
9596             if(i >= backwardMostMove) {\r
9597                 if(WhiteOnMove(i))\r
9598                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]\r
9599                                   + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;\r
9600                 else\r
9601                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]\r
9602                                   + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;\r
9603             }\r
9604             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest\r
9605 #else\r
9606             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time\r
9607 #endif\r
9608     if (appData.debugMode,0) {\r
9609         fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",\r
9610                 timeRemaining[0][i+1], timeRemaining[0][i],\r
9611                      timeRemaining[1][i+1], timeRemaining[1][i], seconds\r
9612         );\r
9613     }\r
9614 \r
9615             if( seconds <= 0) buf[0] = 0; else\r
9616             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {\r
9617                 seconds = (seconds + 4)/10; // round to full seconds\r
9618                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else\r
9619                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
9620             }\r
9621 \r
9622             sprintf( move_buffer, "{%s%.2f/%d%s}", \r
9623                 pvInfoList[i].score >= 0 ? "+" : "",\r
9624                 pvInfoList[i].score / 100.0,\r
9625                 pvInfoList[i].depth,\r
9626                 buf );\r
9627 \r
9628             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */\r
9629 \r
9630             /* Print score/depth */\r
9631             blank = linelen > 0 && movelen > 0;\r
9632             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9633                 fprintf(f, "\n");\r
9634                 linelen = 0;\r
9635                 blank = 0;\r
9636             }\r
9637             if (blank) {\r
9638                 fprintf(f, " ");\r
9639                 linelen++;\r
9640             }\r
9641             fprintf(f, move_buffer);\r
9642             linelen += movelen;\r
9643         }\r
9644 \r
9645         i++;\r
9646     }\r
9647     \r
9648     /* Start a new line */\r
9649     if (linelen > 0) fprintf(f, "\n");\r
9650 \r
9651     /* Print comments after last move */\r
9652     if (commentList[i] != NULL) {\r
9653         fprintf(f, "{\n%s}\n", commentList[i]);\r
9654     }\r
9655 \r
9656     /* Print result */\r
9657     if (gameInfo.resultDetails != NULL &&\r
9658         gameInfo.resultDetails[0] != NULLCHAR) {\r
9659         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
9660                 PGNResult(gameInfo.result));\r
9661     } else {\r
9662         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9663     }\r
9664 \r
9665     fclose(f);\r
9666     return TRUE;\r
9667 }\r
9668 \r
9669 /* Save game in old style and close the file */\r
9670 int\r
9671 SaveGameOldStyle(f)\r
9672      FILE *f;\r
9673 {\r
9674     int i, offset;\r
9675     time_t tm;\r
9676     \r
9677     tm = time((time_t *) NULL);\r
9678     \r
9679     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
9680     PrintOpponents(f);\r
9681     \r
9682     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9683         fprintf(f, "\n[--------------\n");\r
9684         PrintPosition(f, backwardMostMove);\r
9685         fprintf(f, "--------------]\n");\r
9686     } else {\r
9687         fprintf(f, "\n");\r
9688     }\r
9689 \r
9690     i = backwardMostMove;\r
9691     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9692 \r
9693     while (i < forwardMostMove) {\r
9694         if (commentList[i] != NULL) {\r
9695             fprintf(f, "[%s]\n", commentList[i]);\r
9696         }\r
9697 \r
9698         if ((i % 2) == 1) {\r
9699             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
9700             i++;\r
9701         } else {\r
9702             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
9703             i++;\r
9704             if (commentList[i] != NULL) {\r
9705                 fprintf(f, "\n");\r
9706                 continue;\r
9707             }\r
9708             if (i >= forwardMostMove) {\r
9709                 fprintf(f, "\n");\r
9710                 break;\r
9711             }\r
9712             fprintf(f, "%s\n", parseList[i]);\r
9713             i++;\r
9714         }\r
9715     }\r
9716     \r
9717     if (commentList[i] != NULL) {\r
9718         fprintf(f, "[%s]\n", commentList[i]);\r
9719     }\r
9720 \r
9721     /* This isn't really the old style, but it's close enough */\r
9722     if (gameInfo.resultDetails != NULL &&\r
9723         gameInfo.resultDetails[0] != NULLCHAR) {\r
9724         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
9725                 gameInfo.resultDetails);\r
9726     } else {\r
9727         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9728     }\r
9729 \r
9730     fclose(f);\r
9731     return TRUE;\r
9732 }\r
9733 \r
9734 /* Save the current game to open file f and close the file */\r
9735 int\r
9736 SaveGame(f, dummy, dummy2)\r
9737      FILE *f;\r
9738      int dummy;\r
9739      char *dummy2;\r
9740 {\r
9741     if (gameMode == EditPosition) EditPositionDone();\r
9742     if (appData.oldSaveStyle)\r
9743       return SaveGameOldStyle(f);\r
9744     else\r
9745       return SaveGamePGN(f);\r
9746 }\r
9747 \r
9748 /* Save the current position to the given file */\r
9749 int\r
9750 SavePositionToFile(filename)\r
9751      char *filename;\r
9752 {\r
9753     FILE *f;\r
9754     char buf[MSG_SIZ];\r
9755 \r
9756     if (strcmp(filename, "-") == 0) {\r
9757         return SavePosition(stdout, 0, NULL);\r
9758     } else {\r
9759         f = fopen(filename, "a");\r
9760         if (f == NULL) {\r
9761             sprintf(buf, _("Can't open \"%s\""), filename);\r
9762             DisplayError(buf, errno);\r
9763             return FALSE;\r
9764         } else {\r
9765             SavePosition(f, 0, NULL);\r
9766             return TRUE;\r
9767         }\r
9768     }\r
9769 }\r
9770 \r
9771 /* Save the current position to the given open file and close the file */\r
9772 int\r
9773 SavePosition(f, dummy, dummy2)\r
9774      FILE *f;\r
9775      int dummy;\r
9776      char *dummy2;\r
9777 {\r
9778     time_t tm;\r
9779     char *fen;\r
9780     \r
9781     if (appData.oldSaveStyle) {\r
9782         tm = time((time_t *) NULL);\r
9783     \r
9784         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
9785         PrintOpponents(f);\r
9786         fprintf(f, "[--------------\n");\r
9787         PrintPosition(f, currentMove);\r
9788         fprintf(f, "--------------]\n");\r
9789     } else {\r
9790         fen = PositionToFEN(currentMove, 1);\r
9791         fprintf(f, "%s\n", fen);\r
9792         free(fen);\r
9793     }\r
9794     fclose(f);\r
9795     return TRUE;\r
9796 }\r
9797 \r
9798 void\r
9799 ReloadCmailMsgEvent(unregister)\r
9800      int unregister;\r
9801 {\r
9802 #if !WIN32\r
9803     static char *inFilename = NULL;\r
9804     static char *outFilename;\r
9805     int i;\r
9806     struct stat inbuf, outbuf;\r
9807     int status;\r
9808     \r
9809     /* Any registered moves are unregistered if unregister is set, */\r
9810     /* i.e. invoked by the signal handler */\r
9811     if (unregister) {\r
9812         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9813             cmailMoveRegistered[i] = FALSE;\r
9814             if (cmailCommentList[i] != NULL) {\r
9815                 free(cmailCommentList[i]);\r
9816                 cmailCommentList[i] = NULL;\r
9817             }\r
9818         }\r
9819         nCmailMovesRegistered = 0;\r
9820     }\r
9821 \r
9822     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9823         cmailResult[i] = CMAIL_NOT_RESULT;\r
9824     }\r
9825     nCmailResults = 0;\r
9826 \r
9827     if (inFilename == NULL) {\r
9828         /* Because the filenames are static they only get malloced once  */\r
9829         /* and they never get freed                                      */\r
9830         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
9831         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
9832 \r
9833         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
9834         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
9835     }\r
9836     \r
9837     status = stat(outFilename, &outbuf);\r
9838     if (status < 0) {\r
9839         cmailMailedMove = FALSE;\r
9840     } else {\r
9841         status = stat(inFilename, &inbuf);\r
9842         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
9843     }\r
9844     \r
9845     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
9846        counts the games, notes how each one terminated, etc.\r
9847        \r
9848        It would be nice to remove this kludge and instead gather all\r
9849        the information while building the game list.  (And to keep it\r
9850        in the game list nodes instead of having a bunch of fixed-size\r
9851        parallel arrays.)  Note this will require getting each game's\r
9852        termination from the PGN tags, as the game list builder does\r
9853        not process the game moves.  --mann\r
9854        */\r
9855     cmailMsgLoaded = TRUE;\r
9856     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
9857     \r
9858     /* Load first game in the file or popup game menu */\r
9859     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
9860 \r
9861 #endif /* !WIN32 */\r
9862     return;\r
9863 }\r
9864 \r
9865 int\r
9866 RegisterMove()\r
9867 {\r
9868     FILE *f;\r
9869     char string[MSG_SIZ];\r
9870 \r
9871     if (   cmailMailedMove\r
9872         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9873         return TRUE;            /* Allow free viewing  */\r
9874     }\r
9875 \r
9876     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9877     /* with the move registered when the conditions for registering no   */\r
9878     /* longer hold                                                       */\r
9879     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9880         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9881         nCmailMovesRegistered --;\r
9882 \r
9883         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9884           {\r
9885               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9886               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9887           }\r
9888     }\r
9889 \r
9890     if (cmailOldMove == -1) {\r
9891         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);\r
9892         return FALSE;\r
9893     }\r
9894 \r
9895     if (currentMove > cmailOldMove + 1) {\r
9896         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);\r
9897         return FALSE;\r
9898     }\r
9899 \r
9900     if (currentMove < cmailOldMove) {\r
9901         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);\r
9902         return FALSE;\r
9903     }\r
9904 \r
9905     if (forwardMostMove > currentMove) {\r
9906         /* Silently truncate extra moves */\r
9907         TruncateGame();\r
9908     }\r
9909 \r
9910     if (   (currentMove == cmailOldMove + 1)\r
9911         || (   (currentMove == cmailOldMove)\r
9912             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9913                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9914         if (gameInfo.result != GameUnfinished) {\r
9915             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9916         }\r
9917 \r
9918         if (commentList[currentMove] != NULL) {\r
9919             cmailCommentList[lastLoadGameNumber - 1]\r
9920               = StrSave(commentList[currentMove]);\r
9921         }\r
9922         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9923 \r
9924         if (appData.debugMode)\r
9925           fprintf(debugFP, "Saving %s for game %d\n",\r
9926                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9927 \r
9928         sprintf(string,\r
9929                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
9930         \r
9931         f = fopen(string, "w");\r
9932         if (appData.oldSaveStyle) {\r
9933             SaveGameOldStyle(f); /* also closes the file */\r
9934             \r
9935             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
9936             f = fopen(string, "w");\r
9937             SavePosition(f, 0, NULL); /* also closes the file */\r
9938         } else {\r
9939             fprintf(f, "{--------------\n");\r
9940             PrintPosition(f, currentMove);\r
9941             fprintf(f, "--------------}\n\n");\r
9942             \r
9943             SaveGame(f, 0, NULL); /* also closes the file*/\r
9944         }\r
9945         \r
9946         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
9947         nCmailMovesRegistered ++;\r
9948     } else if (nCmailGames == 1) {\r
9949         DisplayError(_("You have not made a move yet"), 0);\r
9950         return FALSE;\r
9951     }\r
9952 \r
9953     return TRUE;\r
9954 }\r
9955 \r
9956 void\r
9957 MailMoveEvent()\r
9958 {\r
9959 #if !WIN32\r
9960     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
9961     FILE *commandOutput;\r
9962     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
9963     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
9964     int nBuffers;\r
9965     int i;\r
9966     int archived;\r
9967     char *arcDir;\r
9968 \r
9969     if (! cmailMsgLoaded) {\r
9970         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);\r
9971         return;\r
9972     }\r
9973 \r
9974     if (nCmailGames == nCmailResults) {\r
9975         DisplayError(_("No unfinished games"), 0);\r
9976         return;\r
9977     }\r
9978 \r
9979 #if CMAIL_PROHIBIT_REMAIL\r
9980     if (cmailMailedMove) {\r
9981         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
9982         DisplayError(msg, 0);\r
9983         return;\r
9984     }\r
9985 #endif\r
9986 \r
9987     if (! (cmailMailedMove || RegisterMove())) return;\r
9988     \r
9989     if (   cmailMailedMove\r
9990         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
9991         sprintf(string, partCommandString,\r
9992                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
9993         commandOutput = popen(string, "r");\r
9994 \r
9995         if (commandOutput == NULL) {\r
9996             DisplayError(_("Failed to invoke cmail"), 0);\r
9997         } else {\r
9998             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
9999                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
10000             }\r
10001             if (nBuffers > 1) {\r
10002                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
10003                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
10004                 nBytes = MSG_SIZ - 1;\r
10005             } else {\r
10006                 (void) memcpy(msg, buffer, nBytes);\r
10007             }\r
10008             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
10009 \r
10010             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
10011                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
10012 \r
10013                 archived = TRUE;\r
10014                 for (i = 0; i < nCmailGames; i ++) {\r
10015                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
10016                         archived = FALSE;\r
10017                     }\r
10018                 }\r
10019                 if (   archived\r
10020                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
10021                         != NULL)) {\r
10022                     sprintf(buffer, "%s/%s.%s.archive",\r
10023                             arcDir,\r
10024                             appData.cmailGameName,\r
10025                             gameInfo.date);\r
10026                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
10027                     cmailMsgLoaded = FALSE;\r
10028                 }\r
10029             }\r
10030 \r
10031             DisplayInformation(msg);\r
10032             pclose(commandOutput);\r
10033         }\r
10034     } else {\r
10035         if ((*cmailMsg) != '\0') {\r
10036             DisplayInformation(cmailMsg);\r
10037         }\r
10038     }\r
10039 \r
10040     return;\r
10041 #endif /* !WIN32 */\r
10042 }\r
10043 \r
10044 char *\r
10045 CmailMsg()\r
10046 {\r
10047 #if WIN32\r
10048     return NULL;\r
10049 #else\r
10050     int  prependComma = 0;\r
10051     char number[5];\r
10052     char string[MSG_SIZ];       /* Space for game-list */\r
10053     int  i;\r
10054     \r
10055     if (!cmailMsgLoaded) return "";\r
10056 \r
10057     if (cmailMailedMove) {\r
10058         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));\r
10059     } else {\r
10060         /* Create a list of games left */\r
10061         sprintf(string, "[");\r
10062         for (i = 0; i < nCmailGames; i ++) {\r
10063             if (! (   cmailMoveRegistered[i]\r
10064                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
10065                 if (prependComma) {\r
10066                     sprintf(number, ",%d", i + 1);\r
10067                 } else {\r
10068                     sprintf(number, "%d", i + 1);\r
10069                     prependComma = 1;\r
10070                 }\r
10071                 \r
10072                 strcat(string, number);\r
10073             }\r
10074         }\r
10075         strcat(string, "]");\r
10076 \r
10077         if (nCmailMovesRegistered + nCmailResults == 0) {\r
10078             switch (nCmailGames) {\r
10079               case 1:\r
10080                 sprintf(cmailMsg,\r
10081                         _("Still need to make move for game\n"));\r
10082                 break;\r
10083                 \r
10084               case 2:\r
10085                 sprintf(cmailMsg,\r
10086                         _("Still need to make moves for both games\n"));\r
10087                 break;\r
10088                 \r
10089               default:\r
10090                 sprintf(cmailMsg,\r
10091                         _("Still need to make moves for all %d games\n"),\r
10092                         nCmailGames);\r
10093                 break;\r
10094             }\r
10095         } else {\r
10096             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
10097               case 1:\r
10098                 sprintf(cmailMsg,\r
10099                         _("Still need to make a move for game %s\n"),\r
10100                         string);\r
10101                 break;\r
10102                 \r
10103               case 0:\r
10104                 if (nCmailResults == nCmailGames) {\r
10105                     sprintf(cmailMsg, _("No unfinished games\n"));\r
10106                 } else {\r
10107                     sprintf(cmailMsg, _("Ready to send mail\n"));\r
10108                 }\r
10109                 break;\r
10110                 \r
10111               default:\r
10112                 sprintf(cmailMsg,\r
10113                         _("Still need to make moves for games %s\n"),\r
10114                         string);\r
10115             }\r
10116         }\r
10117     }\r
10118     return cmailMsg;\r
10119 #endif /* WIN32 */\r
10120 }\r
10121 \r
10122 void\r
10123 ResetGameEvent()\r
10124 {\r
10125     if (gameMode == Training)\r
10126       SetTrainingModeOff();\r
10127 \r
10128     Reset(TRUE, TRUE);\r
10129     cmailMsgLoaded = FALSE;\r
10130     if (appData.icsActive) {\r
10131       SendToICS(ics_prefix);\r
10132       SendToICS("refresh\n");\r
10133     }\r
10134 }\r
10135 \r
10136 void\r
10137 ExitEvent(status)\r
10138      int status;\r
10139 {\r
10140     exiting++;\r
10141     if (exiting > 2) {\r
10142       /* Give up on clean exit */\r
10143       exit(status);\r
10144     }\r
10145     if (exiting > 1) {\r
10146       /* Keep trying for clean exit */\r
10147       return;\r
10148     }\r
10149 \r
10150     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
10151 \r
10152     if (telnetISR != NULL) {\r
10153       RemoveInputSource(telnetISR);\r
10154     }\r
10155     if (icsPR != NoProc) {\r
10156       DestroyChildProcess(icsPR, TRUE);\r
10157     }\r
10158 #if 0\r
10159     /* Save game if resource set and not already saved by GameEnds() */\r
10160     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
10161                              && forwardMostMove > 0) {\r
10162       if (*appData.saveGameFile != NULLCHAR) {\r
10163         SaveGameToFile(appData.saveGameFile, TRUE);\r
10164       } else if (appData.autoSaveGames) {\r
10165         AutoSaveGame();\r
10166       }\r
10167       if (*appData.savePositionFile != NULLCHAR) {\r
10168         SavePositionToFile(appData.savePositionFile);\r
10169       }\r
10170     }\r
10171     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10172 #else\r
10173     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
10174     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);\r
10175 #endif\r
10176     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
10177     /* make sure this other one finishes before killing it!                  */\r
10178     if(endingGame) { int count = 0;\r
10179         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
10180         while(endingGame && count++ < 10) DoSleep(1);\r
10181         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
10182     }\r
10183 \r
10184     /* Kill off chess programs */\r
10185     if (first.pr != NoProc) {\r
10186         ExitAnalyzeMode();\r
10187         \r
10188         DoSleep( appData.delayBeforeQuit );\r
10189         SendToProgram("quit\n", &first);\r
10190         DoSleep( appData.delayAfterQuit );\r
10191         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
10192     }\r
10193     if (second.pr != NoProc) {\r
10194         DoSleep( appData.delayBeforeQuit );\r
10195         SendToProgram("quit\n", &second);\r
10196         DoSleep( appData.delayAfterQuit );\r
10197         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
10198     }\r
10199     if (first.isr != NULL) {\r
10200         RemoveInputSource(first.isr);\r
10201     }\r
10202     if (second.isr != NULL) {\r
10203         RemoveInputSource(second.isr);\r
10204     }\r
10205 \r
10206     ShutDownFrontEnd();\r
10207     exit(status);\r
10208 }\r
10209 \r
10210 void\r
10211 PauseEvent()\r
10212 {\r
10213     if (appData.debugMode)\r
10214         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
10215     if (pausing) {\r
10216         pausing = FALSE;\r
10217         ModeHighlight();\r
10218         if (gameMode == MachinePlaysWhite ||\r
10219             gameMode == MachinePlaysBlack) {\r
10220             StartClocks();\r
10221         } else {\r
10222             DisplayBothClocks();\r
10223         }\r
10224         if (gameMode == PlayFromGameFile) {\r
10225             if (appData.timeDelay >= 0) \r
10226                 AutoPlayGameLoop();\r
10227         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
10228             Reset(FALSE, TRUE);\r
10229             SendToICS(ics_prefix);\r
10230             SendToICS("refresh\n");\r
10231         } else if (currentMove < forwardMostMove) {\r
10232             ForwardInner(forwardMostMove);\r
10233         }\r
10234         pauseExamInvalid = FALSE;\r
10235     } else {\r
10236         switch (gameMode) {\r
10237           default:\r
10238             return;\r
10239           case IcsExamining:\r
10240             pauseExamForwardMostMove = forwardMostMove;\r
10241             pauseExamInvalid = FALSE;\r
10242             /* fall through */\r
10243           case IcsObserving:\r
10244           case IcsPlayingWhite:\r
10245           case IcsPlayingBlack:\r
10246             pausing = TRUE;\r
10247             ModeHighlight();\r
10248             return;\r
10249           case PlayFromGameFile:\r
10250             (void) StopLoadGameTimer();\r
10251             pausing = TRUE;\r
10252             ModeHighlight();\r
10253             break;\r
10254           case BeginningOfGame:\r
10255             if (appData.icsActive) return;\r
10256             /* else fall through */\r
10257           case MachinePlaysWhite:\r
10258           case MachinePlaysBlack:\r
10259           case TwoMachinesPlay:\r
10260             if (forwardMostMove == 0)\r
10261               return;           /* don't pause if no one has moved */\r
10262             if ((gameMode == MachinePlaysWhite &&\r
10263                  !WhiteOnMove(forwardMostMove)) ||\r
10264                 (gameMode == MachinePlaysBlack &&\r
10265                  WhiteOnMove(forwardMostMove))) {\r
10266                 StopClocks();\r
10267             }\r
10268             pausing = TRUE;\r
10269             ModeHighlight();\r
10270             break;\r
10271         }\r
10272     }\r
10273 }\r
10274 \r
10275 void\r
10276 EditCommentEvent()\r
10277 {\r
10278     char title[MSG_SIZ];\r
10279 \r
10280     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
10281         strcpy(title, _("Edit comment"));\r
10282     } else {\r
10283         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,\r
10284                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
10285                 parseList[currentMove - 1]);\r
10286     }\r
10287 \r
10288     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
10289 }\r
10290 \r
10291 \r
10292 void\r
10293 EditTagsEvent()\r
10294 {\r
10295     char *tags = PGNTags(&gameInfo);\r
10296     EditTagsPopUp(tags);\r
10297     free(tags);\r
10298 }\r
10299 \r
10300 void\r
10301 AnalyzeModeEvent()\r
10302 {\r
10303     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
10304       return;\r
10305 \r
10306     if (gameMode != AnalyzeFile) {\r
10307         if (!appData.icsEngineAnalyze) {\r
10308                EditGameEvent();\r
10309                if (gameMode != EditGame) return;\r
10310         }\r
10311         ResurrectChessProgram();\r
10312         SendToProgram("analyze\n", &first);\r
10313         first.analyzing = TRUE;\r
10314         /*first.maybeThinking = TRUE;*/\r
10315         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10316         AnalysisPopUp(_("Analysis"),\r
10317                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10318     }\r
10319     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;\r
10320     pausing = FALSE;\r
10321     ModeHighlight();\r
10322     SetGameInfo();\r
10323 \r
10324     StartAnalysisClock();\r
10325     GetTimeMark(&lastNodeCountTime);\r
10326     lastNodeCount = 0;\r
10327 }\r
10328 \r
10329 void\r
10330 AnalyzeFileEvent()\r
10331 {\r
10332     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
10333       return;\r
10334 \r
10335     if (gameMode != AnalyzeMode) {\r
10336         EditGameEvent();\r
10337         if (gameMode != EditGame) return;\r
10338         ResurrectChessProgram();\r
10339         SendToProgram("analyze\n", &first);\r
10340         first.analyzing = TRUE;\r
10341         /*first.maybeThinking = TRUE;*/\r
10342         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10343         AnalysisPopUp(_("Analysis"),\r
10344                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10345     }\r
10346     gameMode = AnalyzeFile;\r
10347     pausing = FALSE;\r
10348     ModeHighlight();\r
10349     SetGameInfo();\r
10350 \r
10351     StartAnalysisClock();\r
10352     GetTimeMark(&lastNodeCountTime);\r
10353     lastNodeCount = 0;\r
10354 }\r
10355 \r
10356 void\r
10357 MachineWhiteEvent()\r
10358 {\r
10359     char buf[MSG_SIZ];\r
10360     char *bookHit = NULL;\r
10361 \r
10362     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
10363       return;\r
10364 \r
10365 \r
10366     if (gameMode == PlayFromGameFile || \r
10367         gameMode == TwoMachinesPlay  || \r
10368         gameMode == Training         || \r
10369         gameMode == AnalyzeMode      || \r
10370         gameMode == EndOfGame)\r
10371         EditGameEvent();\r
10372 \r
10373     if (gameMode == EditPosition) \r
10374         EditPositionDone();\r
10375 \r
10376     if (!WhiteOnMove(currentMove)) {\r
10377         DisplayError(_("It is not White's turn"), 0);\r
10378         return;\r
10379     }\r
10380   \r
10381     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10382       ExitAnalyzeMode();\r
10383 \r
10384     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10385         gameMode == AnalyzeFile)\r
10386         TruncateGame();\r
10387 \r
10388     ResurrectChessProgram();    /* in case it isn't running */\r
10389     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */\r
10390         gameMode = MachinePlaysWhite;\r
10391         ResetClocks();\r
10392     } else\r
10393     gameMode = MachinePlaysWhite;\r
10394     pausing = FALSE;\r
10395     ModeHighlight();\r
10396     SetGameInfo();\r
10397     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10398     DisplayTitle(buf);\r
10399     if (first.sendName) {\r
10400       sprintf(buf, "name %s\n", gameInfo.black);\r
10401       SendToProgram(buf, &first);\r
10402     }\r
10403     if (first.sendTime) {\r
10404       if (first.useColors) {\r
10405         SendToProgram("black\n", &first); /*gnu kludge*/\r
10406       }\r
10407       SendTimeRemaining(&first, TRUE);\r
10408     }\r
10409     if (first.useColors) {\r
10410       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately\r
10411     }\r
10412     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10413     SetMachineThinkingEnables();\r
10414     first.maybeThinking = TRUE;\r
10415     StartClocks();\r
10416 \r
10417     if (appData.autoFlipView && !flipView) {\r
10418       flipView = !flipView;\r
10419       DrawPosition(FALSE, NULL);\r
10420       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10421     }\r
10422 \r
10423     if(bookHit) { // [HGM] book: simulate book reply\r
10424         static char bookMove[MSG_SIZ]; // a bit generous?\r
10425 \r
10426         programStats.depth = programStats.nodes = programStats.time = \r
10427         programStats.score = programStats.got_only_move = 0;\r
10428         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10429 \r
10430         strcpy(bookMove, "move ");\r
10431         strcat(bookMove, bookHit);\r
10432         HandleMachineMove(bookMove, &first);\r
10433     }\r
10434 }\r
10435 \r
10436 void\r
10437 MachineBlackEvent()\r
10438 {\r
10439     char buf[MSG_SIZ];\r
10440    char *bookHit = NULL;\r
10441 \r
10442     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
10443         return;\r
10444 \r
10445 \r
10446     if (gameMode == PlayFromGameFile || \r
10447         gameMode == TwoMachinesPlay  || \r
10448         gameMode == Training         || \r
10449         gameMode == AnalyzeMode      || \r
10450         gameMode == EndOfGame)\r
10451         EditGameEvent();\r
10452 \r
10453     if (gameMode == EditPosition) \r
10454         EditPositionDone();\r
10455 \r
10456     if (WhiteOnMove(currentMove)) {\r
10457         DisplayError(_("It is not Black's turn"), 0);\r
10458         return;\r
10459     }\r
10460     \r
10461     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10462       ExitAnalyzeMode();\r
10463 \r
10464     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10465         gameMode == AnalyzeFile)\r
10466         TruncateGame();\r
10467 \r
10468     ResurrectChessProgram();    /* in case it isn't running */\r
10469     gameMode = MachinePlaysBlack;\r
10470     pausing = FALSE;\r
10471     ModeHighlight();\r
10472     SetGameInfo();\r
10473     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10474     DisplayTitle(buf);\r
10475     if (first.sendName) {\r
10476       sprintf(buf, "name %s\n", gameInfo.white);\r
10477       SendToProgram(buf, &first);\r
10478     }\r
10479     if (first.sendTime) {\r
10480       if (first.useColors) {\r
10481         SendToProgram("white\n", &first); /*gnu kludge*/\r
10482       }\r
10483       SendTimeRemaining(&first, FALSE);\r
10484     }\r
10485     if (first.useColors) {\r
10486       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately\r
10487     }\r
10488     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10489     SetMachineThinkingEnables();\r
10490     first.maybeThinking = TRUE;\r
10491     StartClocks();\r
10492 \r
10493     if (appData.autoFlipView && flipView) {\r
10494       flipView = !flipView;\r
10495       DrawPosition(FALSE, NULL);\r
10496       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10497     }\r
10498     if(bookHit) { // [HGM] book: simulate book reply\r
10499         static char bookMove[MSG_SIZ]; // a bit generous?\r
10500 \r
10501         programStats.depth = programStats.nodes = programStats.time = \r
10502         programStats.score = programStats.got_only_move = 0;\r
10503         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10504 \r
10505         strcpy(bookMove, "move ");\r
10506         strcat(bookMove, bookHit);\r
10507         HandleMachineMove(bookMove, &first);\r
10508     }\r
10509 }\r
10510 \r
10511 \r
10512 void\r
10513 DisplayTwoMachinesTitle()\r
10514 {\r
10515     char buf[MSG_SIZ];\r
10516     if (appData.matchGames > 0) {\r
10517         if (first.twoMachinesColor[0] == 'w') {\r
10518             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10519                     gameInfo.white, gameInfo.black,\r
10520                     first.matchWins, second.matchWins,\r
10521                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10522         } else {\r
10523             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10524                     gameInfo.white, gameInfo.black,\r
10525                     second.matchWins, first.matchWins,\r
10526                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10527         }\r
10528     } else {\r
10529         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10530     }\r
10531     DisplayTitle(buf);\r
10532 }\r
10533 \r
10534 void\r
10535 TwoMachinesEvent P((void))\r
10536 {\r
10537     int i;\r
10538     char buf[MSG_SIZ];\r
10539     ChessProgramState *onmove;\r
10540     char *bookHit = NULL;\r
10541     \r
10542     if (appData.noChessProgram) return;\r
10543 \r
10544     switch (gameMode) {\r
10545       case TwoMachinesPlay:\r
10546         return;\r
10547       case MachinePlaysWhite:\r
10548       case MachinePlaysBlack:\r
10549         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10550             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
10551             return;\r
10552         }\r
10553         /* fall through */\r
10554       case BeginningOfGame:\r
10555       case PlayFromGameFile:\r
10556       case EndOfGame:\r
10557         EditGameEvent();\r
10558         if (gameMode != EditGame) return;\r
10559         break;\r
10560       case EditPosition:\r
10561         EditPositionDone();\r
10562         break;\r
10563       case AnalyzeMode:\r
10564       case AnalyzeFile:\r
10565         ExitAnalyzeMode();\r
10566         break;\r
10567       case EditGame:\r
10568       default:\r
10569         break;\r
10570     }\r
10571 \r
10572     forwardMostMove = currentMove;\r
10573     ResurrectChessProgram();    /* in case first program isn't running */\r
10574 \r
10575     if (second.pr == NULL) {\r
10576         StartChessProgram(&second);\r
10577         if (second.protocolVersion == 1) {\r
10578           TwoMachinesEventIfReady();\r
10579         } else {\r
10580           /* kludge: allow timeout for initial "feature" command */\r
10581           FreezeUI();\r
10582           DisplayMessage("", _("Starting second chess program"));\r
10583           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
10584         }\r
10585         return;\r
10586     }\r
10587     DisplayMessage("", "");\r
10588     InitChessProgram(&second, FALSE);\r
10589     SendToProgram("force\n", &second);\r
10590     if (startedFromSetupPosition) {\r
10591         SendBoard(&second, backwardMostMove);\r
10592     if (appData.debugMode) {\r
10593         fprintf(debugFP, "Two Machines\n");\r
10594     }\r
10595     }\r
10596     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
10597         SendMoveToProgram(i, &second);\r
10598     }\r
10599 \r
10600     gameMode = TwoMachinesPlay;\r
10601     pausing = FALSE;\r
10602     ModeHighlight();\r
10603     SetGameInfo();\r
10604     DisplayTwoMachinesTitle();\r
10605     firstMove = TRUE;\r
10606     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
10607         onmove = &first;\r
10608     } else {\r
10609         onmove = &second;\r
10610     }\r
10611 \r
10612     SendToProgram(first.computerString, &first);\r
10613     if (first.sendName) {\r
10614       sprintf(buf, "name %s\n", second.tidy);\r
10615       SendToProgram(buf, &first);\r
10616     }\r
10617     SendToProgram(second.computerString, &second);\r
10618     if (second.sendName) {\r
10619       sprintf(buf, "name %s\n", first.tidy);\r
10620       SendToProgram(buf, &second);\r
10621     }\r
10622 \r
10623     ResetClocks();\r
10624     if (!first.sendTime || !second.sendTime) {\r
10625         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10626         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10627     }\r
10628     if (onmove->sendTime) {\r
10629       if (onmove->useColors) {\r
10630         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
10631       }\r
10632       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
10633     }\r
10634     if (onmove->useColors) {\r
10635       SendToProgram(onmove->twoMachinesColor, onmove);\r
10636     }\r
10637     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move\r
10638 //    SendToProgram("go\n", onmove);\r
10639     onmove->maybeThinking = TRUE;\r
10640     SetMachineThinkingEnables();\r
10641 \r
10642     StartClocks();\r
10643 \r
10644     if(bookHit) { // [HGM] book: simulate book reply\r
10645         static char bookMove[MSG_SIZ]; // a bit generous?\r
10646 \r
10647         programStats.depth = programStats.nodes = programStats.time = \r
10648         programStats.score = programStats.got_only_move = 0;\r
10649         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10650 \r
10651         strcpy(bookMove, "move ");\r
10652         strcat(bookMove, bookHit);\r
10653         HandleMachineMove(bookMove, &first);\r
10654     }\r
10655 }\r
10656 \r
10657 void\r
10658 TrainingEvent()\r
10659 {\r
10660     if (gameMode == Training) {\r
10661       SetTrainingModeOff();\r
10662       gameMode = PlayFromGameFile;\r
10663       DisplayMessage("", _("Training mode off"));\r
10664     } else {\r
10665       gameMode = Training;\r
10666       animateTraining = appData.animate;\r
10667 \r
10668       /* make sure we are not already at the end of the game */\r
10669       if (currentMove < forwardMostMove) {\r
10670         SetTrainingModeOn();\r
10671         DisplayMessage("", _("Training mode on"));\r
10672       } else {\r
10673         gameMode = PlayFromGameFile;\r
10674         DisplayError(_("Already at end of game"), 0);\r
10675       }\r
10676     }\r
10677     ModeHighlight();\r
10678 }\r
10679 \r
10680 void\r
10681 IcsClientEvent()\r
10682 {\r
10683     if (!appData.icsActive) return;\r
10684     switch (gameMode) {\r
10685       case IcsPlayingWhite:\r
10686       case IcsPlayingBlack:\r
10687       case IcsObserving:\r
10688       case IcsIdle:\r
10689       case BeginningOfGame:\r
10690       case IcsExamining:\r
10691         return;\r
10692 \r
10693       case EditGame:\r
10694         break;\r
10695 \r
10696       case EditPosition:\r
10697         EditPositionDone();\r
10698         break;\r
10699 \r
10700       case AnalyzeMode:\r
10701       case AnalyzeFile:\r
10702         ExitAnalyzeMode();\r
10703         break;\r
10704         \r
10705       default:\r
10706         EditGameEvent();\r
10707         break;\r
10708     }\r
10709 \r
10710     gameMode = IcsIdle;\r
10711     ModeHighlight();\r
10712     return;\r
10713 }\r
10714 \r
10715 \r
10716 void\r
10717 EditGameEvent()\r
10718 {\r
10719     int i;\r
10720 \r
10721     switch (gameMode) {\r
10722       case Training:\r
10723         SetTrainingModeOff();\r
10724         break;\r
10725       case MachinePlaysWhite:\r
10726       case MachinePlaysBlack:\r
10727       case BeginningOfGame:\r
10728         SendToProgram("force\n", &first);\r
10729         SetUserThinkingEnables();\r
10730         break;\r
10731       case PlayFromGameFile:\r
10732         (void) StopLoadGameTimer();\r
10733         if (gameFileFP != NULL) {\r
10734             gameFileFP = NULL;\r
10735         }\r
10736         break;\r
10737       case EditPosition:\r
10738         EditPositionDone();\r
10739         break;\r
10740       case AnalyzeMode:\r
10741       case AnalyzeFile:\r
10742         ExitAnalyzeMode();\r
10743         SendToProgram("force\n", &first);\r
10744         break;\r
10745       case TwoMachinesPlay:\r
10746         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10747         ResurrectChessProgram();\r
10748         SetUserThinkingEnables();\r
10749         break;\r
10750       case EndOfGame:\r
10751         ResurrectChessProgram();\r
10752         break;\r
10753       case IcsPlayingBlack:\r
10754       case IcsPlayingWhite:\r
10755         DisplayError(_("Warning: You are still playing a game"), 0);\r
10756         break;\r
10757       case IcsObserving:\r
10758         DisplayError(_("Warning: You are still observing a game"), 0);\r
10759         break;\r
10760       case IcsExamining:\r
10761         DisplayError(_("Warning: You are still examining a game"), 0);\r
10762         break;\r
10763       case IcsIdle:\r
10764         break;\r
10765       case EditGame:\r
10766       default:\r
10767         return;\r
10768     }\r
10769     \r
10770     pausing = FALSE;\r
10771     StopClocks();\r
10772     first.offeredDraw = second.offeredDraw = 0;\r
10773 \r
10774     if (gameMode == PlayFromGameFile) {\r
10775         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10776         blackTimeRemaining = timeRemaining[1][currentMove];\r
10777         DisplayTitle("");\r
10778     }\r
10779 \r
10780     if (gameMode == MachinePlaysWhite ||\r
10781         gameMode == MachinePlaysBlack ||\r
10782         gameMode == TwoMachinesPlay ||\r
10783         gameMode == EndOfGame) {\r
10784         i = forwardMostMove;\r
10785         while (i > currentMove) {\r
10786             SendToProgram("undo\n", &first);\r
10787             i--;\r
10788         }\r
10789         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10790         blackTimeRemaining = timeRemaining[1][currentMove];\r
10791         DisplayBothClocks();\r
10792         if (whiteFlag || blackFlag) {\r
10793             whiteFlag = blackFlag = 0;\r
10794         }\r
10795         DisplayTitle("");\r
10796     }           \r
10797     \r
10798     gameMode = EditGame;\r
10799     ModeHighlight();\r
10800     SetGameInfo();\r
10801 }\r
10802 \r
10803 \r
10804 void\r
10805 EditPositionEvent()\r
10806 {\r
10807     if (gameMode == EditPosition) {\r
10808         EditGameEvent();\r
10809         return;\r
10810     }\r
10811     \r
10812     EditGameEvent();\r
10813     if (gameMode != EditGame) return;\r
10814     \r
10815     gameMode = EditPosition;\r
10816     ModeHighlight();\r
10817     SetGameInfo();\r
10818     if (currentMove > 0)\r
10819       CopyBoard(boards[0], boards[currentMove]);\r
10820     \r
10821     blackPlaysFirst = !WhiteOnMove(currentMove);\r
10822     ResetClocks();\r
10823     currentMove = forwardMostMove = backwardMostMove = 0;\r
10824     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10825     DisplayMove(-1);\r
10826 }\r
10827 \r
10828 void\r
10829 ExitAnalyzeMode()\r
10830 {\r
10831     /* [DM] icsEngineAnalyze - possible call from other functions */\r
10832     if (appData.icsEngineAnalyze) {\r
10833         appData.icsEngineAnalyze = FALSE;\r
10834 \r
10835         DisplayMessage("",_("Close ICS engine analyze..."));\r
10836     }\r
10837     if (first.analysisSupport && first.analyzing) {\r
10838       SendToProgram("exit\n", &first);\r
10839       first.analyzing = FALSE;\r
10840     }\r
10841     AnalysisPopDown();\r
10842     thinkOutput[0] = NULLCHAR;\r
10843 }\r
10844 \r
10845 void\r
10846 EditPositionDone()\r
10847 {\r
10848     startedFromSetupPosition = TRUE;\r
10849     InitChessProgram(&first, FALSE);\r
10850     SendToProgram("force\n", &first);\r
10851     if (blackPlaysFirst) {\r
10852         strcpy(moveList[0], "");\r
10853         strcpy(parseList[0], "");\r
10854         currentMove = forwardMostMove = backwardMostMove = 1;\r
10855         CopyBoard(boards[1], boards[0]);\r
10856         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */\r
10857         { int i;\r
10858           epStatus[1] = epStatus[0];\r
10859           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];\r
10860         }\r
10861     } else {\r
10862         currentMove = forwardMostMove = backwardMostMove = 0;\r
10863     }\r
10864     SendBoard(&first, forwardMostMove);\r
10865     if (appData.debugMode) {\r
10866         fprintf(debugFP, "EditPosDone\n");\r
10867     }\r
10868     DisplayTitle("");\r
10869     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10870     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10871     gameMode = EditGame;\r
10872     ModeHighlight();\r
10873     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10874     ClearHighlights(); /* [AS] */\r
10875 }\r
10876 \r
10877 /* Pause for `ms' milliseconds */\r
10878 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10879 void\r
10880 TimeDelay(ms)\r
10881      long ms;\r
10882 {\r
10883     TimeMark m1, m2;\r
10884 \r
10885     GetTimeMark(&m1);\r
10886     do {\r
10887         GetTimeMark(&m2);\r
10888     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
10889 }\r
10890 \r
10891 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10892 void\r
10893 SendMultiLineToICS(buf)\r
10894      char *buf;\r
10895 {\r
10896     char temp[MSG_SIZ+1], *p;\r
10897     int len;\r
10898 \r
10899     len = strlen(buf);\r
10900     if (len > MSG_SIZ)\r
10901       len = MSG_SIZ;\r
10902   \r
10903     strncpy(temp, buf, len);\r
10904     temp[len] = 0;\r
10905 \r
10906     p = temp;\r
10907     while (*p) {\r
10908         if (*p == '\n' || *p == '\r')\r
10909           *p = ' ';\r
10910         ++p;\r
10911     }\r
10912 \r
10913     strcat(temp, "\n");\r
10914     SendToICS(temp);\r
10915     SendToPlayer(temp, strlen(temp));\r
10916 }\r
10917 \r
10918 void\r
10919 SetWhiteToPlayEvent()\r
10920 {\r
10921     if (gameMode == EditPosition) {\r
10922         blackPlaysFirst = FALSE;\r
10923         DisplayBothClocks();    /* works because currentMove is 0 */\r
10924     } else if (gameMode == IcsExamining) {\r
10925         SendToICS(ics_prefix);\r
10926         SendToICS("tomove white\n");\r
10927     }\r
10928 }\r
10929 \r
10930 void\r
10931 SetBlackToPlayEvent()\r
10932 {\r
10933     if (gameMode == EditPosition) {\r
10934         blackPlaysFirst = TRUE;\r
10935         currentMove = 1;        /* kludge */\r
10936         DisplayBothClocks();\r
10937         currentMove = 0;\r
10938     } else if (gameMode == IcsExamining) {\r
10939         SendToICS(ics_prefix);\r
10940         SendToICS("tomove black\n");\r
10941     }\r
10942 }\r
10943 \r
10944 void\r
10945 EditPositionMenuEvent(selection, x, y)\r
10946      ChessSquare selection;\r
10947      int x, y;\r
10948 {\r
10949     char buf[MSG_SIZ];\r
10950     ChessSquare piece = boards[0][y][x];\r
10951 \r
10952     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
10953 \r
10954     switch (selection) {\r
10955       case ClearBoard:\r
10956         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
10957             SendToICS(ics_prefix);\r
10958             SendToICS("bsetup clear\n");\r
10959         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
10960             SendToICS(ics_prefix);\r
10961             SendToICS("clearboard\n");\r
10962         } else {\r
10963             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;\r
10964                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */\r
10965                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
10966                     if (gameMode == IcsExamining) {\r
10967                         if (boards[currentMove][y][x] != EmptySquare) {\r
10968                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
10969                                     AAA + x, ONE + y);\r
10970                             SendToICS(buf);\r
10971                         }\r
10972                     } else {\r
10973                         boards[0][y][x] = p;\r
10974                     }\r
10975                 }\r
10976             }\r
10977         }\r
10978         if (gameMode == EditPosition) {\r
10979             DrawPosition(FALSE, boards[0]);\r
10980         }\r
10981         break;\r
10982 \r
10983       case WhitePlay:\r
10984         SetWhiteToPlayEvent();\r
10985         break;\r
10986 \r
10987       case BlackPlay:\r
10988         SetBlackToPlayEvent();\r
10989         break;\r
10990 \r
10991       case EmptySquare:\r
10992         if (gameMode == IcsExamining) {\r
10993             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
10994             SendToICS(buf);\r
10995         } else {\r
10996             boards[0][y][x] = EmptySquare;\r
10997             DrawPosition(FALSE, boards[0]);\r
10998         }\r
10999         break;\r
11000 \r
11001       case PromotePiece:\r
11002         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
11003            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
11004             selection = (ChessSquare) (PROMOTED piece);\r
11005         } else if(piece == EmptySquare) selection = WhiteSilver;\r
11006         else selection = (ChessSquare)((int)piece - 1);\r
11007         goto defaultlabel;\r
11008 \r
11009       case DemotePiece:\r
11010         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
11011            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
11012             selection = (ChessSquare) (DEMOTED piece);\r
11013         } else if(piece == EmptySquare) selection = BlackSilver;\r
11014         else selection = (ChessSquare)((int)piece + 1);       \r
11015         goto defaultlabel;\r
11016 \r
11017       case WhiteQueen:\r
11018       case BlackQueen:\r
11019         if(gameInfo.variant == VariantShatranj ||\r
11020            gameInfo.variant == VariantXiangqi  ||\r
11021            gameInfo.variant == VariantCourier    )\r
11022             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
11023         goto defaultlabel;\r
11024 \r
11025       case WhiteKing:\r
11026       case BlackKing:\r
11027         if(gameInfo.variant == VariantXiangqi)\r
11028             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
11029         if(gameInfo.variant == VariantKnightmate)\r
11030             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
11031       default:\r
11032         defaultlabel:\r
11033         if (gameMode == IcsExamining) {\r
11034             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
11035                     PieceToChar(selection), AAA + x, ONE + y);\r
11036             SendToICS(buf);\r
11037         } else {\r
11038             boards[0][y][x] = selection;\r
11039             DrawPosition(FALSE, boards[0]);\r
11040         }\r
11041         break;\r
11042     }\r
11043 }\r
11044 \r
11045 \r
11046 void\r
11047 DropMenuEvent(selection, x, y)\r
11048      ChessSquare selection;\r
11049      int x, y;\r
11050 {\r
11051     ChessMove moveType;\r
11052 \r
11053     switch (gameMode) {\r
11054       case IcsPlayingWhite:\r
11055       case MachinePlaysBlack:\r
11056         if (!WhiteOnMove(currentMove)) {\r
11057             DisplayMoveError(_("It is Black's turn"));\r
11058             return;\r
11059         }\r
11060         moveType = WhiteDrop;\r
11061         break;\r
11062       case IcsPlayingBlack:\r
11063       case MachinePlaysWhite:\r
11064         if (WhiteOnMove(currentMove)) {\r
11065             DisplayMoveError(_("It is White's turn"));\r
11066             return;\r
11067         }\r
11068         moveType = BlackDrop;\r
11069         break;\r
11070       case EditGame:\r
11071         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
11072         break;\r
11073       default:\r
11074         return;\r
11075     }\r
11076 \r
11077     if (moveType == BlackDrop && selection < BlackPawn) {\r
11078       selection = (ChessSquare) ((int) selection\r
11079                                  + (int) BlackPawn - (int) WhitePawn);\r
11080     }\r
11081     if (boards[currentMove][y][x] != EmptySquare) {\r
11082         DisplayMoveError(_("That square is occupied"));\r
11083         return;\r
11084     }\r
11085 \r
11086     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
11087 }\r
11088 \r
11089 void\r
11090 AcceptEvent()\r
11091 {\r
11092     /* Accept a pending offer of any kind from opponent */\r
11093     \r
11094     if (appData.icsActive) {\r
11095         SendToICS(ics_prefix);\r
11096         SendToICS("accept\n");\r
11097     } else if (cmailMsgLoaded) {\r
11098         if (currentMove == cmailOldMove &&\r
11099             commentList[cmailOldMove] != NULL &&\r
11100             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11101                    "Black offers a draw" : "White offers a draw")) {\r
11102             TruncateGame();\r
11103             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11104             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11105         } else {\r
11106             DisplayError(_("There is no pending offer on this move"), 0);\r
11107             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11108         }\r
11109     } else {\r
11110         /* Not used for offers from chess program */\r
11111     }\r
11112 }\r
11113 \r
11114 void\r
11115 DeclineEvent()\r
11116 {\r
11117     /* Decline a pending offer of any kind from opponent */\r
11118     \r
11119     if (appData.icsActive) {\r
11120         SendToICS(ics_prefix);\r
11121         SendToICS("decline\n");\r
11122     } else if (cmailMsgLoaded) {\r
11123         if (currentMove == cmailOldMove &&\r
11124             commentList[cmailOldMove] != NULL &&\r
11125             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11126                    "Black offers a draw" : "White offers a draw")) {\r
11127 #ifdef NOTDEF\r
11128             AppendComment(cmailOldMove, "Draw declined");\r
11129             DisplayComment(cmailOldMove - 1, "Draw declined");\r
11130 #endif /*NOTDEF*/\r
11131         } else {\r
11132             DisplayError(_("There is no pending offer on this move"), 0);\r
11133         }\r
11134     } else {\r
11135         /* Not used for offers from chess program */\r
11136     }\r
11137 }\r
11138 \r
11139 void\r
11140 RematchEvent()\r
11141 {\r
11142     /* Issue ICS rematch command */\r
11143     if (appData.icsActive) {\r
11144         SendToICS(ics_prefix);\r
11145         SendToICS("rematch\n");\r
11146     }\r
11147 }\r
11148 \r
11149 void\r
11150 CallFlagEvent()\r
11151 {\r
11152     /* Call your opponent's flag (claim a win on time) */\r
11153     if (appData.icsActive) {\r
11154         SendToICS(ics_prefix);\r
11155         SendToICS("flag\n");\r
11156     } else {\r
11157         switch (gameMode) {\r
11158           default:\r
11159             return;\r
11160           case MachinePlaysWhite:\r
11161             if (whiteFlag) {\r
11162                 if (blackFlag)\r
11163                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11164                            GE_PLAYER);\r
11165                 else\r
11166                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
11167             } else {\r
11168                 DisplayError(_("Your opponent is not out of time"), 0);\r
11169             }\r
11170             break;\r
11171           case MachinePlaysBlack:\r
11172             if (blackFlag) {\r
11173                 if (whiteFlag)\r
11174                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11175                            GE_PLAYER);\r
11176                 else\r
11177                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
11178             } else {\r
11179                 DisplayError(_("Your opponent is not out of time"), 0);\r
11180             }\r
11181             break;\r
11182         }\r
11183     }\r
11184 }\r
11185 \r
11186 void\r
11187 DrawEvent()\r
11188 {\r
11189     /* Offer draw or accept pending draw offer from opponent */\r
11190     \r
11191     if (appData.icsActive) {\r
11192         /* Note: tournament rules require draw offers to be\r
11193            made after you make your move but before you punch\r
11194            your clock.  Currently ICS doesn't let you do that;\r
11195            instead, you immediately punch your clock after making\r
11196            a move, but you can offer a draw at any time. */\r
11197         \r
11198         SendToICS(ics_prefix);\r
11199         SendToICS("draw\n");\r
11200     } else if (cmailMsgLoaded) {\r
11201         if (currentMove == cmailOldMove &&\r
11202             commentList[cmailOldMove] != NULL &&\r
11203             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11204                    "Black offers a draw" : "White offers a draw")) {\r
11205             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11206             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11207         } else if (currentMove == cmailOldMove + 1) {\r
11208             char *offer = WhiteOnMove(cmailOldMove) ?\r
11209               "White offers a draw" : "Black offers a draw";\r
11210             AppendComment(currentMove, offer);\r
11211             DisplayComment(currentMove - 1, offer);\r
11212             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
11213         } else {\r
11214             DisplayError(_("You must make your move before offering a draw"), 0);\r
11215             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11216         }\r
11217     } else if (first.offeredDraw) {\r
11218         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
11219     } else {\r
11220         if (first.sendDrawOffers) {\r
11221             SendToProgram("draw\n", &first);\r
11222             userOfferedDraw = TRUE;\r
11223         }\r
11224     }\r
11225 }\r
11226 \r
11227 void\r
11228 AdjournEvent()\r
11229 {\r
11230     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
11231     \r
11232     if (appData.icsActive) {\r
11233         SendToICS(ics_prefix);\r
11234         SendToICS("adjourn\n");\r
11235     } else {\r
11236         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
11237     }\r
11238 }\r
11239 \r
11240 \r
11241 void\r
11242 AbortEvent()\r
11243 {\r
11244     /* Offer Abort or accept pending Abort offer from opponent */\r
11245     \r
11246     if (appData.icsActive) {\r
11247         SendToICS(ics_prefix);\r
11248         SendToICS("abort\n");\r
11249     } else {\r
11250         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
11251     }\r
11252 }\r
11253 \r
11254 void\r
11255 ResignEvent()\r
11256 {\r
11257     /* Resign.  You can do this even if it's not your turn. */\r
11258     \r
11259     if (appData.icsActive) {\r
11260         SendToICS(ics_prefix);\r
11261         SendToICS("resign\n");\r
11262     } else {\r
11263         switch (gameMode) {\r
11264           case MachinePlaysWhite:\r
11265             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11266             break;\r
11267           case MachinePlaysBlack:\r
11268             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11269             break;\r
11270           case EditGame:\r
11271             if (cmailMsgLoaded) {\r
11272                 TruncateGame();\r
11273                 if (WhiteOnMove(cmailOldMove)) {\r
11274                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11275                 } else {\r
11276                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11277                 }\r
11278                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
11279             }\r
11280             break;\r
11281           default:\r
11282             break;\r
11283         }\r
11284     }\r
11285 }\r
11286 \r
11287 \r
11288 void\r
11289 StopObservingEvent()\r
11290 {\r
11291     /* Stop observing current games */\r
11292     SendToICS(ics_prefix);\r
11293     SendToICS("unobserve\n");\r
11294 }\r
11295 \r
11296 void\r
11297 StopExaminingEvent()\r
11298 {\r
11299     /* Stop observing current game */\r
11300     SendToICS(ics_prefix);\r
11301     SendToICS("unexamine\n");\r
11302 }\r
11303 \r
11304 void\r
11305 ForwardInner(target)\r
11306      int target;\r
11307 {\r
11308     int limit;\r
11309 \r
11310     if (appData.debugMode)\r
11311         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
11312                 target, currentMove, forwardMostMove);\r
11313 \r
11314     if (gameMode == EditPosition)\r
11315       return;\r
11316 \r
11317     if (gameMode == PlayFromGameFile && !pausing)\r
11318       PauseEvent();\r
11319     \r
11320     if (gameMode == IcsExamining && pausing)\r
11321       limit = pauseExamForwardMostMove;\r
11322     else\r
11323       limit = forwardMostMove;\r
11324     \r
11325     if (target > limit) target = limit;\r
11326 \r
11327     if (target > 0 && moveList[target - 1][0]) {\r
11328         int fromX, fromY, toX, toY;\r
11329         toX = moveList[target - 1][2] - AAA;\r
11330         toY = moveList[target - 1][3] - ONE;\r
11331         if (moveList[target - 1][1] == '@') {\r
11332             if (appData.highlightLastMove) {\r
11333                 SetHighlights(-1, -1, toX, toY);\r
11334             }\r
11335         } else {\r
11336             fromX = moveList[target - 1][0] - AAA;\r
11337             fromY = moveList[target - 1][1] - ONE;\r
11338             if (target == currentMove + 1) {\r
11339                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
11340             }\r
11341             if (appData.highlightLastMove) {\r
11342                 SetHighlights(fromX, fromY, toX, toY);\r
11343             }\r
11344         }\r
11345     }\r
11346     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
11347         gameMode == Training || gameMode == PlayFromGameFile || \r
11348         gameMode == AnalyzeFile) {\r
11349         while (currentMove < target) {\r
11350             SendMoveToProgram(currentMove++, &first);\r
11351         }\r
11352     } else {\r
11353         currentMove = target;\r
11354     }\r
11355     \r
11356     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11357         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11358         blackTimeRemaining = timeRemaining[1][currentMove];\r
11359     }\r
11360     DisplayBothClocks();\r
11361     DisplayMove(currentMove - 1);\r
11362     DrawPosition(FALSE, boards[currentMove]);\r
11363     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11364     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
11365         DisplayComment(currentMove - 1, commentList[currentMove]);\r
11366     }\r
11367 }\r
11368 \r
11369 \r
11370 void\r
11371 ForwardEvent()\r
11372 {\r
11373     if (gameMode == IcsExamining && !pausing) {\r
11374         SendToICS(ics_prefix);\r
11375         SendToICS("forward\n");\r
11376     } else {\r
11377         ForwardInner(currentMove + 1);\r
11378     }\r
11379 }\r
11380 \r
11381 void\r
11382 ToEndEvent()\r
11383 {\r
11384     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11385         /* to optimze, we temporarily turn off analysis mode while we feed\r
11386          * the remaining moves to the engine. Otherwise we get analysis output\r
11387          * after each move.\r
11388          */ \r
11389         if (first.analysisSupport) {\r
11390           SendToProgram("exit\nforce\n", &first);\r
11391           first.analyzing = FALSE;\r
11392         }\r
11393     }\r
11394         \r
11395     if (gameMode == IcsExamining && !pausing) {\r
11396         SendToICS(ics_prefix);\r
11397         SendToICS("forward 999999\n");\r
11398     } else {\r
11399         ForwardInner(forwardMostMove);\r
11400     }\r
11401 \r
11402     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11403         /* we have fed all the moves, so reactivate analysis mode */\r
11404         SendToProgram("analyze\n", &first);\r
11405         first.analyzing = TRUE;\r
11406         /*first.maybeThinking = TRUE;*/\r
11407         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11408     }\r
11409 }\r
11410 \r
11411 void\r
11412 BackwardInner(target)\r
11413      int target;\r
11414 {\r
11415     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
11416 \r
11417     if (appData.debugMode)\r
11418         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
11419                 target, currentMove, forwardMostMove);\r
11420 \r
11421     if (gameMode == EditPosition) return;\r
11422     if (currentMove <= backwardMostMove) {\r
11423         ClearHighlights();\r
11424         DrawPosition(full_redraw, boards[currentMove]);\r
11425         return;\r
11426     }\r
11427     if (gameMode == PlayFromGameFile && !pausing)\r
11428       PauseEvent();\r
11429     \r
11430     if (moveList[target][0]) {\r
11431         int fromX, fromY, toX, toY;\r
11432         toX = moveList[target][2] - AAA;\r
11433         toY = moveList[target][3] - ONE;\r
11434         if (moveList[target][1] == '@') {\r
11435             if (appData.highlightLastMove) {\r
11436                 SetHighlights(-1, -1, toX, toY);\r
11437             }\r
11438         } else {\r
11439             fromX = moveList[target][0] - AAA;\r
11440             fromY = moveList[target][1] - ONE;\r
11441             if (target == currentMove - 1) {\r
11442                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
11443             }\r
11444             if (appData.highlightLastMove) {\r
11445                 SetHighlights(fromX, fromY, toX, toY);\r
11446             }\r
11447         }\r
11448     }\r
11449     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
11450         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
11451         while (currentMove > target) {\r
11452             SendToProgram("undo\n", &first);\r
11453             currentMove--;\r
11454         }\r
11455     } else {\r
11456         currentMove = target;\r
11457     }\r
11458     \r
11459     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11460         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11461         blackTimeRemaining = timeRemaining[1][currentMove];\r
11462     }\r
11463     DisplayBothClocks();\r
11464     DisplayMove(currentMove - 1);\r
11465     DrawPosition(full_redraw, boards[currentMove]);\r
11466     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11467     // [HGM] PV info: routine tests if comment empty\r
11468     DisplayComment(currentMove - 1, commentList[currentMove]);\r
11469 }\r
11470 \r
11471 void\r
11472 BackwardEvent()\r
11473 {\r
11474     if (gameMode == IcsExamining && !pausing) {\r
11475         SendToICS(ics_prefix);\r
11476         SendToICS("backward\n");\r
11477     } else {\r
11478         BackwardInner(currentMove - 1);\r
11479     }\r
11480 }\r
11481 \r
11482 void\r
11483 ToStartEvent()\r
11484 {\r
11485     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11486         /* to optimze, we temporarily turn off analysis mode while we undo\r
11487          * all the moves. Otherwise we get analysis output after each undo.\r
11488          */ \r
11489         if (first.analysisSupport) {\r
11490           SendToProgram("exit\nforce\n", &first);\r
11491           first.analyzing = FALSE;\r
11492         }\r
11493     }\r
11494 \r
11495     if (gameMode == IcsExamining && !pausing) {\r
11496         SendToICS(ics_prefix);\r
11497         SendToICS("backward 999999\n");\r
11498     } else {\r
11499         BackwardInner(backwardMostMove);\r
11500     }\r
11501 \r
11502     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11503         /* we have fed all the moves, so reactivate analysis mode */\r
11504         SendToProgram("analyze\n", &first);\r
11505         first.analyzing = TRUE;\r
11506         /*first.maybeThinking = TRUE;*/\r
11507         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11508     }\r
11509 }\r
11510 \r
11511 void\r
11512 ToNrEvent(int to)\r
11513 {\r
11514   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
11515   if (to >= forwardMostMove) to = forwardMostMove;\r
11516   if (to <= backwardMostMove) to = backwardMostMove;\r
11517   if (to < currentMove) {\r
11518     BackwardInner(to);\r
11519   } else {\r
11520     ForwardInner(to);\r
11521   }\r
11522 }\r
11523 \r
11524 void\r
11525 RevertEvent()\r
11526 {\r
11527     if (gameMode != IcsExamining) {\r
11528         DisplayError(_("You are not examining a game"), 0);\r
11529         return;\r
11530     }\r
11531     if (pausing) {\r
11532         DisplayError(_("You can't revert while pausing"), 0);\r
11533         return;\r
11534     }\r
11535     SendToICS(ics_prefix);\r
11536     SendToICS("revert\n");\r
11537 }\r
11538 \r
11539 void\r
11540 RetractMoveEvent()\r
11541 {\r
11542     switch (gameMode) {\r
11543       case MachinePlaysWhite:\r
11544       case MachinePlaysBlack:\r
11545         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
11546             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
11547             return;\r
11548         }\r
11549         if (forwardMostMove < 2) return;\r
11550         currentMove = forwardMostMove = forwardMostMove - 2;\r
11551         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11552         blackTimeRemaining = timeRemaining[1][currentMove];\r
11553         DisplayBothClocks();\r
11554         DisplayMove(currentMove - 1);\r
11555         ClearHighlights();/*!! could figure this out*/\r
11556         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
11557         SendToProgram("remove\n", &first);\r
11558         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
11559         break;\r
11560 \r
11561       case BeginningOfGame:\r
11562       default:\r
11563         break;\r
11564 \r
11565       case IcsPlayingWhite:\r
11566       case IcsPlayingBlack:\r
11567         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
11568             SendToICS(ics_prefix);\r
11569             SendToICS("takeback 2\n");\r
11570         } else {\r
11571             SendToICS(ics_prefix);\r
11572             SendToICS("takeback 1\n");\r
11573         }\r
11574         break;\r
11575     }\r
11576 }\r
11577 \r
11578 void\r
11579 MoveNowEvent()\r
11580 {\r
11581     ChessProgramState *cps;\r
11582 \r
11583     switch (gameMode) {\r
11584       case MachinePlaysWhite:\r
11585         if (!WhiteOnMove(forwardMostMove)) {\r
11586             DisplayError(_("It is your turn"), 0);\r
11587             return;\r
11588         }\r
11589         cps = &first;\r
11590         break;\r
11591       case MachinePlaysBlack:\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 TwoMachinesPlay:\r
11599         if (WhiteOnMove(forwardMostMove) ==\r
11600             (first.twoMachinesColor[0] == 'w')) {\r
11601             cps = &first;\r
11602         } else {\r
11603             cps = &second;\r
11604         }\r
11605         break;\r
11606       case BeginningOfGame:\r
11607       default:\r
11608         return;\r
11609     }\r
11610     SendToProgram("?\n", cps);\r
11611 }\r
11612 \r
11613 void\r
11614 TruncateGameEvent()\r
11615 {\r
11616     EditGameEvent();\r
11617     if (gameMode != EditGame) return;\r
11618     TruncateGame();\r
11619 }\r
11620 \r
11621 void\r
11622 TruncateGame()\r
11623 {\r
11624     if (forwardMostMove > currentMove) {\r
11625         if (gameInfo.resultDetails != NULL) {\r
11626             free(gameInfo.resultDetails);\r
11627             gameInfo.resultDetails = NULL;\r
11628             gameInfo.result = GameUnfinished;\r
11629         }\r
11630         forwardMostMove = currentMove;\r
11631         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
11632                    currentMove-1);\r
11633     }\r
11634 }\r
11635 \r
11636 void\r
11637 HintEvent()\r
11638 {\r
11639     if (appData.noChessProgram) return;\r
11640     switch (gameMode) {\r
11641       case MachinePlaysWhite:\r
11642         if (WhiteOnMove(forwardMostMove)) {\r
11643             DisplayError(_("Wait until your turn"), 0);\r
11644             return;\r
11645         }\r
11646         break;\r
11647       case BeginningOfGame:\r
11648       case MachinePlaysBlack:\r
11649         if (!WhiteOnMove(forwardMostMove)) {\r
11650             DisplayError(_("Wait until your turn"), 0);\r
11651             return;\r
11652         }\r
11653         break;\r
11654       default:\r
11655         DisplayError(_("No hint available"), 0);\r
11656         return;\r
11657     }\r
11658     SendToProgram("hint\n", &first);\r
11659     hintRequested = TRUE;\r
11660 }\r
11661 \r
11662 void\r
11663 BookEvent()\r
11664 {\r
11665     if (appData.noChessProgram) return;\r
11666     switch (gameMode) {\r
11667       case MachinePlaysWhite:\r
11668         if (WhiteOnMove(forwardMostMove)) {\r
11669             DisplayError(_("Wait until your turn"), 0);\r
11670             return;\r
11671         }\r
11672         break;\r
11673       case BeginningOfGame:\r
11674       case MachinePlaysBlack:\r
11675         if (!WhiteOnMove(forwardMostMove)) {\r
11676             DisplayError(_("Wait until your turn"), 0);\r
11677             return;\r
11678         }\r
11679         break;\r
11680       case EditPosition:\r
11681         EditPositionDone();\r
11682         break;\r
11683       case TwoMachinesPlay:\r
11684         return;\r
11685       default:\r
11686         break;\r
11687     }\r
11688     SendToProgram("bk\n", &first);\r
11689     bookOutput[0] = NULLCHAR;\r
11690     bookRequested = TRUE;\r
11691 }\r
11692 \r
11693 void\r
11694 AboutGameEvent()\r
11695 {\r
11696     char *tags = PGNTags(&gameInfo);\r
11697     TagsPopUp(tags, CmailMsg());\r
11698     free(tags);\r
11699 }\r
11700 \r
11701 /* end button procedures */\r
11702 \r
11703 void\r
11704 PrintPosition(fp, move)\r
11705      FILE *fp;\r
11706      int move;\r
11707 {\r
11708     int i, j;\r
11709     \r
11710     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11711         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11712             char c = PieceToChar(boards[move][i][j]);\r
11713             fputc(c == 'x' ? '.' : c, fp);\r
11714             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
11715         }\r
11716     }\r
11717     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
11718       fprintf(fp, "white to play\n");\r
11719     else\r
11720       fprintf(fp, "black to play\n");\r
11721 }\r
11722 \r
11723 void\r
11724 PrintOpponents(fp)\r
11725      FILE *fp;\r
11726 {\r
11727     if (gameInfo.white != NULL) {\r
11728         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
11729     } else {\r
11730         fprintf(fp, "\n");\r
11731     }\r
11732 }\r
11733 \r
11734 /* Find last component of program's own name, using some heuristics */\r
11735 void\r
11736 TidyProgramName(prog, host, buf)\r
11737      char *prog, *host, buf[MSG_SIZ];\r
11738 {\r
11739     char *p, *q;\r
11740     int local = (strcmp(host, "localhost") == 0);\r
11741     while (!local && (p = strchr(prog, ';')) != NULL) {\r
11742         p++;\r
11743         while (*p == ' ') p++;\r
11744         prog = p;\r
11745     }\r
11746     if (*prog == '"' || *prog == '\'') {\r
11747         q = strchr(prog + 1, *prog);\r
11748     } else {\r
11749         q = strchr(prog, ' ');\r
11750     }\r
11751     if (q == NULL) q = prog + strlen(prog);\r
11752     p = q;\r
11753     while (p >= prog && *p != '/' && *p != '\\') p--;\r
11754     p++;\r
11755     if(p == prog && *p == '"') p++;\r
11756     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
11757     memcpy(buf, p, q - p);\r
11758     buf[q - p] = NULLCHAR;\r
11759     if (!local) {\r
11760         strcat(buf, "@");\r
11761         strcat(buf, host);\r
11762     }\r
11763 }\r
11764 \r
11765 char *\r
11766 TimeControlTagValue()\r
11767 {\r
11768     char buf[MSG_SIZ];\r
11769     if (!appData.clockMode) {\r
11770         strcpy(buf, "-");\r
11771     } else if (movesPerSession > 0) {\r
11772         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
11773     } else if (timeIncrement == 0) {\r
11774         sprintf(buf, "%ld", timeControl/1000);\r
11775     } else {\r
11776         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
11777     }\r
11778     return StrSave(buf);\r
11779 }\r
11780 \r
11781 void\r
11782 SetGameInfo()\r
11783 {\r
11784     /* This routine is used only for certain modes */\r
11785     VariantClass v = gameInfo.variant;\r
11786     ClearGameInfo(&gameInfo);\r
11787     gameInfo.variant = v;\r
11788 \r
11789     switch (gameMode) {\r
11790       case MachinePlaysWhite:\r
11791         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11792         gameInfo.site = StrSave(HostName());\r
11793         gameInfo.date = PGNDate();\r
11794         gameInfo.round = StrSave("-");\r
11795         gameInfo.white = StrSave(first.tidy);\r
11796         gameInfo.black = StrSave(UserName());\r
11797         gameInfo.timeControl = TimeControlTagValue();\r
11798         break;\r
11799 \r
11800       case MachinePlaysBlack:\r
11801         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11802         gameInfo.site = StrSave(HostName());\r
11803         gameInfo.date = PGNDate();\r
11804         gameInfo.round = StrSave("-");\r
11805         gameInfo.white = StrSave(UserName());\r
11806         gameInfo.black = StrSave(first.tidy);\r
11807         gameInfo.timeControl = TimeControlTagValue();\r
11808         break;\r
11809 \r
11810       case TwoMachinesPlay:\r
11811         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11812         gameInfo.site = StrSave(HostName());\r
11813         gameInfo.date = PGNDate();\r
11814         if (matchGame > 0) {\r
11815             char buf[MSG_SIZ];\r
11816             sprintf(buf, "%d", matchGame);\r
11817             gameInfo.round = StrSave(buf);\r
11818         } else {\r
11819             gameInfo.round = StrSave("-");\r
11820         }\r
11821         if (first.twoMachinesColor[0] == 'w') {\r
11822             gameInfo.white = StrSave(first.tidy);\r
11823             gameInfo.black = StrSave(second.tidy);\r
11824         } else {\r
11825             gameInfo.white = StrSave(second.tidy);\r
11826             gameInfo.black = StrSave(first.tidy);\r
11827         }\r
11828         gameInfo.timeControl = TimeControlTagValue();\r
11829         break;\r
11830 \r
11831       case EditGame:\r
11832         gameInfo.event = StrSave("Edited game");\r
11833         gameInfo.site = StrSave(HostName());\r
11834         gameInfo.date = PGNDate();\r
11835         gameInfo.round = StrSave("-");\r
11836         gameInfo.white = StrSave("-");\r
11837         gameInfo.black = StrSave("-");\r
11838         break;\r
11839 \r
11840       case EditPosition:\r
11841         gameInfo.event = StrSave("Edited position");\r
11842         gameInfo.site = StrSave(HostName());\r
11843         gameInfo.date = PGNDate();\r
11844         gameInfo.round = StrSave("-");\r
11845         gameInfo.white = StrSave("-");\r
11846         gameInfo.black = StrSave("-");\r
11847         break;\r
11848 \r
11849       case IcsPlayingWhite:\r
11850       case IcsPlayingBlack:\r
11851       case IcsObserving:\r
11852       case IcsExamining:\r
11853         break;\r
11854 \r
11855       case PlayFromGameFile:\r
11856         gameInfo.event = StrSave("Game from non-PGN file");\r
11857         gameInfo.site = StrSave(HostName());\r
11858         gameInfo.date = PGNDate();\r
11859         gameInfo.round = StrSave("-");\r
11860         gameInfo.white = StrSave("?");\r
11861         gameInfo.black = StrSave("?");\r
11862         break;\r
11863 \r
11864       default:\r
11865         break;\r
11866     }\r
11867 }\r
11868 \r
11869 void\r
11870 ReplaceComment(index, text)\r
11871      int index;\r
11872      char *text;\r
11873 {\r
11874     int len;\r
11875 \r
11876     while (*text == '\n') text++;\r
11877     len = strlen(text);\r
11878     while (len > 0 && text[len - 1] == '\n') len--;\r
11879 \r
11880     if (commentList[index] != NULL)\r
11881       free(commentList[index]);\r
11882 \r
11883     if (len == 0) {\r
11884         commentList[index] = NULL;\r
11885         return;\r
11886     }\r
11887     commentList[index] = (char *) malloc(len + 2);\r
11888     strncpy(commentList[index], text, len);\r
11889     commentList[index][len] = '\n';\r
11890     commentList[index][len + 1] = NULLCHAR;\r
11891 }\r
11892 \r
11893 void\r
11894 CrushCRs(text)\r
11895      char *text;\r
11896 {\r
11897   char *p = text;\r
11898   char *q = text;\r
11899   char ch;\r
11900 \r
11901   do {\r
11902     ch = *p++;\r
11903     if (ch == '\r') continue;\r
11904     *q++ = ch;\r
11905   } while (ch != '\0');\r
11906 }\r
11907 \r
11908 void\r
11909 AppendComment(index, text)\r
11910      int index;\r
11911      char *text;\r
11912 {\r
11913     int oldlen, len;\r
11914     char *old;\r
11915 \r
11916     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11917 \r
11918     CrushCRs(text);\r
11919     while (*text == '\n') text++;\r
11920     len = strlen(text);\r
11921     while (len > 0 && text[len - 1] == '\n') len--;\r
11922 \r
11923     if (len == 0) return;\r
11924 \r
11925     if (commentList[index] != NULL) {\r
11926         old = commentList[index];\r
11927         oldlen = strlen(old);\r
11928         commentList[index] = (char *) malloc(oldlen + len + 2);\r
11929         strcpy(commentList[index], old);\r
11930         free(old);\r
11931         strncpy(&commentList[index][oldlen], text, len);\r
11932         commentList[index][oldlen + len] = '\n';\r
11933         commentList[index][oldlen + len + 1] = NULLCHAR;\r
11934     } else {\r
11935         commentList[index] = (char *) malloc(len + 2);\r
11936         strncpy(commentList[index], text, len);\r
11937         commentList[index][len] = '\n';\r
11938         commentList[index][len + 1] = NULLCHAR;\r
11939     }\r
11940 }\r
11941 \r
11942 static char * FindStr( char * text, char * sub_text )\r
11943 {\r
11944     char * result = strstr( text, sub_text );\r
11945 \r
11946     if( result != NULL ) {\r
11947         result += strlen( sub_text );\r
11948     }\r
11949 \r
11950     return result;\r
11951 }\r
11952 \r
11953 /* [AS] Try to extract PV info from PGN comment */\r
11954 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
11955 char *GetInfoFromComment( int index, char * text )\r
11956 {\r
11957     char * sep = text;\r
11958 \r
11959     if( text != NULL && index > 0 ) {\r
11960         int score = 0;\r
11961         int depth = 0;\r
11962         int time = -1, sec = 0, deci;\r
11963         char * s_eval = FindStr( text, "[%eval " );\r
11964         char * s_emt = FindStr( text, "[%emt " );\r
11965 \r
11966         if( s_eval != NULL || s_emt != NULL ) {\r
11967             /* New style */\r
11968             char delim;\r
11969 \r
11970             if( s_eval != NULL ) {\r
11971                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
11972                     return text;\r
11973                 }\r
11974 \r
11975                 if( delim != ']' ) {\r
11976                     return text;\r
11977                 }\r
11978             }\r
11979 \r
11980             if( s_emt != NULL ) {\r
11981             }\r
11982         }\r
11983         else {\r
11984             /* We expect something like: [+|-]nnn.nn/dd */\r
11985             int score_lo = 0;\r
11986 \r
11987             sep = strchr( text, '/' );\r
11988             if( sep == NULL || sep < (text+4) ) {\r
11989                 return text;\r
11990             }\r
11991 \r
11992             time = -1; sec = -1; deci = -1;\r
11993             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
11994                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&\r
11995                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
11996                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
11997                 return text;\r
11998             }\r
11999 \r
12000             if( score_lo < 0 || score_lo >= 100 ) {\r
12001                 return text;\r
12002             }\r
12003 \r
12004             if(sec >= 0) time = 600*time + 10*sec; else\r
12005             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec\r
12006 \r
12007             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
12008 \r
12009             /* [HGM] PV time: now locate end of PV info */\r
12010             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
12011             if(time >= 0)\r
12012             while( *++sep >= '0' && *sep <= '9'); // strip time\r
12013             if(sec >= 0)\r
12014             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
12015             if(deci >= 0)\r
12016             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds\r
12017             while(*sep == ' ') sep++;\r
12018         }\r
12019 \r
12020         if( depth <= 0 ) {\r
12021             return text;\r
12022         }\r
12023 \r
12024         if( time < 0 ) {\r
12025             time = -1;\r
12026         }\r
12027 \r
12028         pvInfoList[index-1].depth = depth;\r
12029         pvInfoList[index-1].score = score;\r
12030         pvInfoList[index-1].time  = 10*time; // centi-sec\r
12031     }\r
12032     return sep;\r
12033 }\r
12034 \r
12035 void\r
12036 SendToProgram(message, cps)\r
12037      char *message;\r
12038      ChessProgramState *cps;\r
12039 {\r
12040     int count, outCount, error;\r
12041     char buf[MSG_SIZ];\r
12042 \r
12043     if (cps->pr == NULL) return;\r
12044     Attention(cps);\r
12045     \r
12046     if (appData.debugMode) {\r
12047         TimeMark now;\r
12048         GetTimeMark(&now);\r
12049         fprintf(debugFP, "%ld >%-6s: %s", \r
12050                 SubtractTimeMarks(&now, &programStartTime),\r
12051                 cps->which, message);\r
12052     }\r
12053     \r
12054     count = strlen(message);\r
12055     outCount = OutputToProcess(cps->pr, message, count, &error);\r
12056     if (outCount < count && !exiting \r
12057                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
12058         sprintf(buf, _("Error writing to %s chess program"), cps->which);\r
12059         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12060             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12061                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12062                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
12063             } else {\r
12064                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12065             }\r
12066             gameInfo.resultDetails = buf;\r
12067         }\r
12068         DisplayFatalError(buf, error, 1);\r
12069     }\r
12070 }\r
12071 \r
12072 void\r
12073 ReceiveFromProgram(isr, closure, message, count, error)\r
12074      InputSourceRef isr;\r
12075      VOIDSTAR closure;\r
12076      char *message;\r
12077      int count;\r
12078      int error;\r
12079 {\r
12080     char *end_str;\r
12081     char buf[MSG_SIZ];\r
12082     ChessProgramState *cps = (ChessProgramState *)closure;\r
12083 \r
12084     if (isr != cps->isr) return; /* Killed intentionally */\r
12085     if (count <= 0) {\r
12086         if (count == 0) {\r
12087             sprintf(buf,\r
12088                     _("Error: %s chess program (%s) exited unexpectedly"),\r
12089                     cps->which, cps->program);\r
12090         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12091                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12092                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12093                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);\r
12094                 } else {\r
12095                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12096                 }\r
12097                 gameInfo.resultDetails = buf;\r
12098             }\r
12099             RemoveInputSource(cps->isr);\r
12100             DisplayFatalError(buf, 0, 1);\r
12101         } else {\r
12102             sprintf(buf,\r
12103                     _("Error reading from %s chess program (%s)"),\r
12104                     cps->which, cps->program);\r
12105             RemoveInputSource(cps->isr);\r
12106 \r
12107             /* [AS] Program is misbehaving badly... kill it */\r
12108             if( count == -2 ) {\r
12109                 DestroyChildProcess( cps->pr, 9 );\r
12110                 cps->pr = NoProc;\r
12111             }\r
12112 \r
12113             DisplayFatalError(buf, error, 1);\r
12114         }\r
12115         return;\r
12116     }\r
12117     \r
12118     if ((end_str = strchr(message, '\r')) != NULL)\r
12119       *end_str = NULLCHAR;\r
12120     if ((end_str = strchr(message, '\n')) != NULL)\r
12121       *end_str = NULLCHAR;\r
12122     \r
12123     if (appData.debugMode) {\r
12124         TimeMark now; int print = 1;\r
12125         char *quote = ""; char c; int i;\r
12126 \r
12127         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
12128                 char start = message[0];\r
12129                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
12130                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
12131                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&\r
12132                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
12133                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
12134                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&\r
12135                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
12136                         { quote = "# "; print = (appData.engineComments == 2); }\r
12137                 message[0] = start; // restore original message\r
12138         }\r
12139         if(print) {\r
12140                 GetTimeMark(&now);\r
12141                 fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
12142                         SubtractTimeMarks(&now, &programStartTime), cps->which, \r
12143                         quote,\r
12144                         message);\r
12145         }\r
12146     }\r
12147 \r
12148     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */\r
12149     if (appData.icsEngineAnalyze) {\r
12150         if (strstr(message, "whisper") != NULL ||\r
12151              strstr(message, "kibitz") != NULL || \r
12152             strstr(message, "tellics") != NULL) return;\r
12153     }\r
12154 \r
12155     HandleMachineMove(message, cps);\r
12156 }\r
12157 \r
12158 \r
12159 void\r
12160 SendTimeControl(cps, mps, tc, inc, sd, st)\r
12161      ChessProgramState *cps;\r
12162      int mps, inc, sd, st;\r
12163      long tc;\r
12164 {\r
12165     char buf[MSG_SIZ];\r
12166     int seconds;\r
12167 \r
12168     if( timeControl_2 > 0 ) {\r
12169         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
12170             tc = timeControl_2;\r
12171         }\r
12172     }\r
12173     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
12174     inc /= cps->timeOdds;\r
12175     st  /= cps->timeOdds;\r
12176 \r
12177     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
12178 \r
12179     if (st > 0) {\r
12180       /* Set exact time per move, normally using st command */\r
12181       if (cps->stKludge) {\r
12182         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
12183         seconds = st % 60;\r
12184         if (seconds == 0) {\r
12185           sprintf(buf, "level 1 %d\n", st/60);\r
12186         } else {\r
12187           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
12188         }\r
12189       } else {\r
12190         sprintf(buf, "st %d\n", st);\r
12191       }\r
12192     } else {\r
12193       /* Set conventional or incremental time control, using level command */\r
12194       if (seconds == 0) {\r
12195         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
12196            Fixed in later versions, but still avoid :seconds\r
12197            when seconds is 0. */\r
12198         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
12199       } else {\r
12200         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
12201                 seconds, inc/1000);\r
12202       }\r
12203     }\r
12204     SendToProgram(buf, cps);\r
12205 \r
12206     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
12207     /* Orthogonally, limit search to given depth */\r
12208     if (sd > 0) {\r
12209       if (cps->sdKludge) {\r
12210         sprintf(buf, "depth\n%d\n", sd);\r
12211       } else {\r
12212         sprintf(buf, "sd %d\n", sd);\r
12213       }\r
12214       SendToProgram(buf, cps);\r
12215     }\r
12216 \r
12217     if(cps->nps > 0) { /* [HGM] nps */\r
12218         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
12219         else {\r
12220                 sprintf(buf, "nps %d\n", cps->nps);\r
12221               SendToProgram(buf, cps);\r
12222         }\r
12223     }\r
12224 }\r
12225 \r
12226 ChessProgramState *WhitePlayer()\r
12227 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
12228 {\r
12229     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || \r
12230        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)\r
12231         return &second;\r
12232     return &first;\r
12233 }\r
12234 \r
12235 void\r
12236 SendTimeRemaining(cps, machineWhite)\r
12237      ChessProgramState *cps;\r
12238      int /*boolean*/ machineWhite;\r
12239 {\r
12240     char message[MSG_SIZ];\r
12241     long time, otime;\r
12242 \r
12243     /* Note: this routine must be called when the clocks are stopped\r
12244        or when they have *just* been set or switched; otherwise\r
12245        it will be off by the time since the current tick started.\r
12246     */\r
12247     if (machineWhite) {\r
12248         time = whiteTimeRemaining / 10;\r
12249         otime = blackTimeRemaining / 10;\r
12250     } else {\r
12251         time = blackTimeRemaining / 10;\r
12252         otime = whiteTimeRemaining / 10;\r
12253     }\r
12254     /* [HGM] translate opponent's time by time-odds factor */\r
12255     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
12256     if (appData.debugMode) {\r
12257         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
12258     }\r
12259 \r
12260     if (time <= 0) time = 1;\r
12261     if (otime <= 0) otime = 1;\r
12262     \r
12263     sprintf(message, "time %ld\n", time);\r
12264     SendToProgram(message, cps);\r
12265 \r
12266     sprintf(message, "otim %ld\n", otime);\r
12267     SendToProgram(message, cps);\r
12268 }\r
12269 \r
12270 int\r
12271 BoolFeature(p, name, loc, cps)\r
12272      char **p;\r
12273      char *name;\r
12274      int *loc;\r
12275      ChessProgramState *cps;\r
12276 {\r
12277   char buf[MSG_SIZ];\r
12278   int len = strlen(name);\r
12279   int val;\r
12280   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12281     (*p) += len + 1;\r
12282     sscanf(*p, "%d", &val);\r
12283     *loc = (val != 0);\r
12284     while (**p && **p != ' ') (*p)++;\r
12285     sprintf(buf, "accepted %s\n", name);\r
12286     SendToProgram(buf, cps);\r
12287     return TRUE;\r
12288   }\r
12289   return FALSE;\r
12290 }\r
12291 \r
12292 int\r
12293 IntFeature(p, name, loc, cps)\r
12294      char **p;\r
12295      char *name;\r
12296      int *loc;\r
12297      ChessProgramState *cps;\r
12298 {\r
12299   char buf[MSG_SIZ];\r
12300   int len = strlen(name);\r
12301   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12302     (*p) += len + 1;\r
12303     sscanf(*p, "%d", loc);\r
12304     while (**p && **p != ' ') (*p)++;\r
12305     sprintf(buf, "accepted %s\n", name);\r
12306     SendToProgram(buf, cps);\r
12307     return TRUE;\r
12308   }\r
12309   return FALSE;\r
12310 }\r
12311 \r
12312 int\r
12313 StringFeature(p, name, loc, cps)\r
12314      char **p;\r
12315      char *name;\r
12316      char loc[];\r
12317      ChessProgramState *cps;\r
12318 {\r
12319   char buf[MSG_SIZ];\r
12320   int len = strlen(name);\r
12321   if (strncmp((*p), name, len) == 0\r
12322       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
12323     (*p) += len + 2;\r
12324     sscanf(*p, "%[^\"]", loc);\r
12325     while (**p && **p != '\"') (*p)++;\r
12326     if (**p == '\"') (*p)++;\r
12327     sprintf(buf, "accepted %s\n", name);\r
12328     SendToProgram(buf, cps);\r
12329     return TRUE;\r
12330   }\r
12331   return FALSE;\r
12332 }\r
12333 \r
12334 int \r
12335 ParseOption(Option *opt, ChessProgramState *cps)\r
12336 // [HGM] options: process the string that defines an engine option, and determine\r
12337 // name, type, default value, and allowed value range\r
12338 {\r
12339         char *p, *q, buf[MSG_SIZ];\r
12340         int n, min = (-1)<<31, max = 1<<31, def;\r
12341 \r
12342         if(p = strstr(opt->name, " -spin ")) {\r
12343             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;\r
12344             if(max < min) max = min; // enforce consistency\r
12345             if(def < min) def = min;\r
12346             if(def > max) def = max;\r
12347             opt->value = def;\r
12348             opt->min = min;\r
12349             opt->max = max;\r
12350             opt->type = Spin;\r
12351         } else if(p = strstr(opt->name, " -string ")) {\r
12352             opt->textValue = p+9;\r
12353             opt->type = TextBox;\r
12354         } else if(p = strstr(opt->name, " -check ")) {\r
12355             if(sscanf(p, " -check %d", &def) < 1) return FALSE;\r
12356             opt->value = (def != 0);\r
12357             opt->type = CheckBox;\r
12358         } else if(p = strstr(opt->name, " -combo ")) {\r
12359             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type\r
12360             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices\r
12361             opt->value = n = 0;\r
12362             while(q = StrStr(q, " /// ")) {\r
12363                 n++; *q = 0;    // count choices, and null-terminate each of them\r
12364                 q += 5;\r
12365                 if(*q == '*') { // remember default, which is marked with * prefix\r
12366                     q++;\r
12367                     opt->value = n;\r
12368                 }\r
12369                 cps->comboList[cps->comboCnt++] = q;\r
12370             }\r
12371             cps->comboList[cps->comboCnt++] = NULL;\r
12372             opt->max = n + 1;\r
12373             opt->type = ComboBox;\r
12374         } else if(p = strstr(opt->name, " -button")) {\r
12375             opt->type = Button;\r
12376         } else if(p = strstr(opt->name, " -save")) {\r
12377             opt->type = SaveButton;\r
12378         } else return FALSE;\r
12379         *p = 0; // terminate option name\r
12380         return TRUE;\r
12381 }\r
12382 \r
12383 void\r
12384 FeatureDone(cps, val)\r
12385      ChessProgramState* cps;\r
12386      int val;\r
12387 {\r
12388   DelayedEventCallback cb = GetDelayedEvent();\r
12389   if ((cb == InitBackEnd3 && cps == &first) ||\r
12390       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
12391     CancelDelayedEvent();\r
12392     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
12393   }\r
12394   cps->initDone = val;\r
12395 }\r
12396 \r
12397 /* Parse feature command from engine */\r
12398 void\r
12399 ParseFeatures(args, cps)\r
12400      char* args;\r
12401      ChessProgramState *cps;  \r
12402 {\r
12403   char *p = args;\r
12404   char *q;\r
12405   int val;\r
12406   char buf[MSG_SIZ];\r
12407 \r
12408   for (;;) {\r
12409     while (*p == ' ') p++;\r
12410     if (*p == NULLCHAR) return;\r
12411 \r
12412     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
12413     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
12414     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
12415     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
12416     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
12417     if (BoolFeature(&p, "reuse", &val, cps)) {\r
12418       /* Engine can disable reuse, but can't enable it if user said no */\r
12419       if (!val) cps->reuse = FALSE;\r
12420       continue;\r
12421     }\r
12422     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
12423     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
12424       if (gameMode == TwoMachinesPlay) {\r
12425         DisplayTwoMachinesTitle();\r
12426       } else {\r
12427         DisplayTitle("");\r
12428       }\r
12429       continue;\r
12430     }\r
12431     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
12432     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
12433     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
12434     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
12435     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
12436     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
12437     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
12438     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
12439     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
12440     if (IntFeature(&p, "done", &val, cps)) {\r
12441       FeatureDone(cps, val);\r
12442       continue;\r
12443     }\r
12444     /* Added by Tord: */\r
12445     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
12446     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
12447     /* End of additions by Tord */\r
12448 \r
12449     /* [HGM] added features: */\r
12450     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
12451     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
12452     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
12453     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
12454     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12455     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
12456     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {\r
12457         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature\r
12458         if(cps->nrOptions >= MAX_OPTIONS) {\r
12459             cps->nrOptions--;\r
12460             sprintf(buf, "%s engine has too many options\n", cps->which);\r
12461             DisplayError(buf, 0);\r
12462         }\r
12463         continue;\r
12464     }\r
12465     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12466     /* End of additions by HGM */\r
12467 \r
12468     /* unknown feature: complain and skip */\r
12469     q = p;\r
12470     while (*q && *q != '=') q++;\r
12471     sprintf(buf, "rejected %.*s\n", q-p, p);\r
12472     SendToProgram(buf, cps);\r
12473     p = q;\r
12474     if (*p == '=') {\r
12475       p++;\r
12476       if (*p == '\"') {\r
12477         p++;\r
12478         while (*p && *p != '\"') p++;\r
12479         if (*p == '\"') p++;\r
12480       } else {\r
12481         while (*p && *p != ' ') p++;\r
12482       }\r
12483     }\r
12484   }\r
12485 \r
12486 }\r
12487 \r
12488 void\r
12489 PeriodicUpdatesEvent(newState)\r
12490      int newState;\r
12491 {\r
12492     if (newState == appData.periodicUpdates)\r
12493       return;\r
12494 \r
12495     appData.periodicUpdates=newState;\r
12496 \r
12497     /* Display type changes, so update it now */\r
12498     DisplayAnalysis();\r
12499 \r
12500     /* Get the ball rolling again... */\r
12501     if (newState) {\r
12502         AnalysisPeriodicEvent(1);\r
12503         StartAnalysisClock();\r
12504     }\r
12505 }\r
12506 \r
12507 void\r
12508 PonderNextMoveEvent(newState)\r
12509      int newState;\r
12510 {\r
12511     if (newState == appData.ponderNextMove) return;\r
12512     if (gameMode == EditPosition) EditPositionDone();\r
12513     if (newState) {\r
12514         SendToProgram("hard\n", &first);\r
12515         if (gameMode == TwoMachinesPlay) {\r
12516             SendToProgram("hard\n", &second);\r
12517         }\r
12518     } else {\r
12519         SendToProgram("easy\n", &first);\r
12520         thinkOutput[0] = NULLCHAR;\r
12521         if (gameMode == TwoMachinesPlay) {\r
12522             SendToProgram("easy\n", &second);\r
12523         }\r
12524     }\r
12525     appData.ponderNextMove = newState;\r
12526 }\r
12527 \r
12528 void\r
12529 NewSettingEvent(option, command, value)\r
12530      char *command;\r
12531      int option, value;\r
12532 {\r
12533     char buf[MSG_SIZ];\r
12534 \r
12535     if (gameMode == EditPosition) EditPositionDone();\r
12536     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);\r
12537     SendToProgram(buf, &first);\r
12538     if (gameMode == TwoMachinesPlay) {\r
12539         SendToProgram(buf, &second);\r
12540     }\r
12541 }\r
12542 \r
12543 void\r
12544 ShowThinkingEvent()\r
12545 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup\r
12546 {\r
12547     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated\r
12548     int newState = appData.showThinking\r
12549         // [HGM] thinking: other features now need thinking output as well\r
12550         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();\r
12551     \r
12552     if (oldState == newState) return;\r
12553     oldState = newState;\r
12554     if (gameMode == EditPosition) EditPositionDone();\r
12555     if (oldState) {\r
12556         SendToProgram("post\n", &first);\r
12557         if (gameMode == TwoMachinesPlay) {\r
12558             SendToProgram("post\n", &second);\r
12559         }\r
12560     } else {\r
12561         SendToProgram("nopost\n", &first);\r
12562         thinkOutput[0] = NULLCHAR;\r
12563         if (gameMode == TwoMachinesPlay) {\r
12564             SendToProgram("nopost\n", &second);\r
12565         }\r
12566     }\r
12567 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!\r
12568 }\r
12569 \r
12570 void\r
12571 AskQuestionEvent(title, question, replyPrefix, which)\r
12572      char *title; char *question; char *replyPrefix; char *which;\r
12573 {\r
12574   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
12575   if (pr == NoProc) return;\r
12576   AskQuestion(title, question, replyPrefix, pr);\r
12577 }\r
12578 \r
12579 void\r
12580 DisplayMove(moveNumber)\r
12581      int moveNumber;\r
12582 {\r
12583     char message[MSG_SIZ];\r
12584     char res[MSG_SIZ];\r
12585     char cpThinkOutput[MSG_SIZ];\r
12586 \r
12587     if (moveNumber == forwardMostMove - 1 || \r
12588         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12589 \r
12590         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
12591 \r
12592         if (strchr(cpThinkOutput, '\n')) {\r
12593             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
12594         }\r
12595     } else {\r
12596         *cpThinkOutput = NULLCHAR;\r
12597     }\r
12598 \r
12599     /* [AS] Hide thinking from human user */\r
12600     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
12601         *cpThinkOutput = NULLCHAR;\r
12602         if( thinkOutput[0] != NULLCHAR ) {\r
12603             int i;\r
12604 \r
12605             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
12606                 cpThinkOutput[i] = '.';\r
12607             }\r
12608             cpThinkOutput[i] = NULLCHAR;\r
12609             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
12610         }\r
12611     }\r
12612 \r
12613     if (moveNumber == forwardMostMove - 1 &&\r
12614         gameInfo.resultDetails != NULL) {\r
12615         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
12616             sprintf(res, " %s", PGNResult(gameInfo.result));\r
12617         } else {\r
12618             sprintf(res, " {%s} %s",\r
12619                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
12620         }\r
12621     } else {\r
12622         res[0] = NULLCHAR;\r
12623     }\r
12624     \r
12625     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12626         DisplayMessage(res, cpThinkOutput);\r
12627     } else {\r
12628         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
12629                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
12630                 parseList[moveNumber], res);\r
12631         DisplayMessage(message, cpThinkOutput);\r
12632     }\r
12633 }\r
12634 \r
12635 void\r
12636 DisplayAnalysisText(text)\r
12637      char *text;\r
12638 {\r
12639     char buf[MSG_SIZ];\r
12640 \r
12641     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile \r
12642                || appData.icsEngineAnalyze) {\r
12643         sprintf(buf, "Analysis (%s)", first.tidy);\r
12644         AnalysisPopUp(buf, text);\r
12645     }\r
12646 }\r
12647 \r
12648 static int\r
12649 only_one_move(str)\r
12650      char *str;\r
12651 {\r
12652     while (*str && isspace(*str)) ++str;\r
12653     while (*str && !isspace(*str)) ++str;\r
12654     if (!*str) return 1;\r
12655     while (*str && isspace(*str)) ++str;\r
12656     if (!*str) return 1;\r
12657     return 0;\r
12658 }\r
12659 \r
12660 void\r
12661 DisplayAnalysis()\r
12662 {\r
12663     char buf[MSG_SIZ];\r
12664     char lst[MSG_SIZ / 2];\r
12665     double nps;\r
12666     static char *xtra[] = { "", " (--)", " (++)" };\r
12667     int h, m, s, cs;\r
12668   \r
12669     if (programStats.time == 0) {\r
12670         programStats.time = 1;\r
12671     }\r
12672   \r
12673     if (programStats.got_only_move) {\r
12674         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
12675     } else {\r
12676         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
12677 \r
12678         nps = (u64ToDouble(programStats.nodes) /\r
12679              ((double)programStats.time /100.0));\r
12680 \r
12681         cs = programStats.time % 100;\r
12682         s = programStats.time / 100;\r
12683         h = (s / (60*60));\r
12684         s = s - h*60*60;\r
12685         m = (s/60);\r
12686         s = s - m*60;\r
12687 \r
12688         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
12689           if (programStats.move_name[0] != NULLCHAR) {\r
12690             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12691                     programStats.depth,\r
12692                     programStats.nr_moves-programStats.moves_left,\r
12693                     programStats.nr_moves, programStats.move_name,\r
12694                     ((float)programStats.score)/100.0, lst,\r
12695                     only_one_move(lst)?\r
12696                     xtra[programStats.got_fail] : "",\r
12697                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12698           } else {\r
12699             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12700                     programStats.depth,\r
12701                     programStats.nr_moves-programStats.moves_left,\r
12702                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
12703                     lst,\r
12704                     only_one_move(lst)?\r
12705                     xtra[programStats.got_fail] : "",\r
12706                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12707           }\r
12708         } else {\r
12709             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12710                     programStats.depth,\r
12711                     ((float)programStats.score)/100.0,\r
12712                     lst,\r
12713                     only_one_move(lst)?\r
12714                     xtra[programStats.got_fail] : "",\r
12715                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12716         }\r
12717     }\r
12718     DisplayAnalysisText(buf);\r
12719 }\r
12720 \r
12721 void\r
12722 DisplayComment(moveNumber, text)\r
12723      int moveNumber;\r
12724      char *text;\r
12725 {\r
12726     char title[MSG_SIZ];\r
12727     char buf[8000]; // comment can be long!\r
12728     int score, depth;\r
12729 \r
12730     if( appData.autoDisplayComment ) {\r
12731         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12732             strcpy(title, "Comment");\r
12733         } else {\r
12734             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
12735                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
12736                     parseList[moveNumber]);\r
12737         }\r
12738     } else title[0] = 0;\r
12739 \r
12740     // [HGM] PV info: display PV info together with (or as) comment\r
12741     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
12742         if(text == NULL) text = "";                                           \r
12743         score = pvInfoList[moveNumber].score;\r
12744         sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
12745                               depth, (pvInfoList[moveNumber].time+50)/100, text);\r
12746         CommentPopUp(title, buf);\r
12747     } else\r
12748     if (text != NULL)\r
12749         CommentPopUp(title, text);\r
12750 }\r
12751 \r
12752 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
12753  * might be busy thinking or pondering.  It can be omitted if your\r
12754  * gnuchess is configured to stop thinking immediately on any user\r
12755  * input.  However, that gnuchess feature depends on the FIONREAD\r
12756  * ioctl, which does not work properly on some flavors of Unix.\r
12757  */\r
12758 void\r
12759 Attention(cps)\r
12760      ChessProgramState *cps;\r
12761 {\r
12762 #if ATTENTION\r
12763     if (!cps->useSigint) return;\r
12764     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
12765     switch (gameMode) {\r
12766       case MachinePlaysWhite:\r
12767       case MachinePlaysBlack:\r
12768       case TwoMachinesPlay:\r
12769       case IcsPlayingWhite:\r
12770       case IcsPlayingBlack:\r
12771       case AnalyzeMode:\r
12772       case AnalyzeFile:\r
12773         /* Skip if we know it isn't thinking */\r
12774         if (!cps->maybeThinking) return;\r
12775         if (appData.debugMode)\r
12776           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
12777         InterruptChildProcess(cps->pr);\r
12778         cps->maybeThinking = FALSE;\r
12779         break;\r
12780       default:\r
12781         break;\r
12782     }\r
12783 #endif /*ATTENTION*/\r
12784 }\r
12785 \r
12786 int\r
12787 CheckFlags()\r
12788 {\r
12789     if (whiteTimeRemaining <= 0) {\r
12790         if (!whiteFlag) {\r
12791             whiteFlag = TRUE;\r
12792             if (appData.icsActive) {\r
12793                 if (appData.autoCallFlag &&\r
12794                     gameMode == IcsPlayingBlack && !blackFlag) {\r
12795                   SendToICS(ics_prefix);\r
12796                   SendToICS("flag\n");\r
12797                 }\r
12798             } else {\r
12799                 if (blackFlag) {\r
12800                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12801                 } else {\r
12802                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));\r
12803                     if (appData.autoCallFlag) {\r
12804                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
12805                         return TRUE;\r
12806                     }\r
12807                 }\r
12808             }\r
12809         }\r
12810     }\r
12811     if (blackTimeRemaining <= 0) {\r
12812         if (!blackFlag) {\r
12813             blackFlag = TRUE;\r
12814             if (appData.icsActive) {\r
12815                 if (appData.autoCallFlag &&\r
12816                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
12817                   SendToICS(ics_prefix);\r
12818                   SendToICS("flag\n");\r
12819                 }\r
12820             } else {\r
12821                 if (whiteFlag) {\r
12822                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12823                 } else {\r
12824                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));\r
12825                     if (appData.autoCallFlag) {\r
12826                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
12827                         return TRUE;\r
12828                     }\r
12829                 }\r
12830             }\r
12831         }\r
12832     }\r
12833     return FALSE;\r
12834 }\r
12835 \r
12836 void\r
12837 CheckTimeControl()\r
12838 {\r
12839     if (!appData.clockMode || appData.icsActive ||\r
12840         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
12841 \r
12842     /*\r
12843      * add time to clocks when time control is achieved ([HGM] now also used for increment)\r
12844      */\r
12845     if ( !WhiteOnMove(forwardMostMove) )\r
12846         /* White made time control */\r
12847         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12848         /* [HGM] time odds: correct new time quota for time odds! */\r
12849                                             / WhitePlayer()->timeOdds;\r
12850       else\r
12851         /* Black made time control */\r
12852         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12853                                             / WhitePlayer()->other->timeOdds;\r
12854 }\r
12855 \r
12856 void\r
12857 DisplayBothClocks()\r
12858 {\r
12859     int wom = gameMode == EditPosition ?\r
12860       !blackPlaysFirst : WhiteOnMove(currentMove);\r
12861     DisplayWhiteClock(whiteTimeRemaining, wom);\r
12862     DisplayBlackClock(blackTimeRemaining, !wom);\r
12863 }\r
12864 \r
12865 \r
12866 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
12867    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
12868    to use other calls if you don't.  Clocks will be less accurate if\r
12869    you have neither ftime nor gettimeofday.\r
12870 */\r
12871 \r
12872 /* Get the current time as a TimeMark */\r
12873 void\r
12874 GetTimeMark(tm)\r
12875      TimeMark *tm;\r
12876 {\r
12877 #if HAVE_GETTIMEOFDAY\r
12878 \r
12879     struct timeval timeVal;\r
12880     struct timezone timeZone;\r
12881 \r
12882     gettimeofday(&timeVal, &timeZone);\r
12883     tm->sec = (long) timeVal.tv_sec; \r
12884     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
12885 \r
12886 #else /*!HAVE_GETTIMEOFDAY*/\r
12887 #if HAVE_FTIME\r
12888 \r
12889 #include <sys/timeb.h>\r
12890     struct timeb timeB;\r
12891 \r
12892     ftime(&timeB);\r
12893     tm->sec = (long) timeB.time;\r
12894     tm->ms = (int) timeB.millitm;\r
12895 \r
12896 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
12897     tm->sec = (long) time(NULL);\r
12898     tm->ms = 0;\r
12899 #endif\r
12900 #endif\r
12901 }\r
12902 \r
12903 /* Return the difference in milliseconds between two\r
12904    time marks.  We assume the difference will fit in a long!\r
12905 */\r
12906 long\r
12907 SubtractTimeMarks(tm2, tm1)\r
12908      TimeMark *tm2, *tm1;\r
12909 {\r
12910     return 1000L*(tm2->sec - tm1->sec) +\r
12911            (long) (tm2->ms - tm1->ms);\r
12912 }\r
12913 \r
12914 \r
12915 /*\r
12916  * Code to manage the game clocks.\r
12917  *\r
12918  * In tournament play, black starts the clock and then white makes a move.\r
12919  * We give the human user a slight advantage if he is playing white---the\r
12920  * clocks don't run until he makes his first move, so it takes zero time.\r
12921  * Also, we don't account for network lag, so we could get out of sync\r
12922  * with GNU Chess's clock -- but then, referees are always right.  \r
12923  */\r
12924 \r
12925 static TimeMark tickStartTM;\r
12926 static long intendedTickLength;\r
12927 \r
12928 long\r
12929 NextTickLength(timeRemaining)\r
12930      long timeRemaining;\r
12931 {\r
12932     long nominalTickLength, nextTickLength;\r
12933 \r
12934     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
12935       nominalTickLength = 100L;\r
12936     else\r
12937       nominalTickLength = 1000L;\r
12938     nextTickLength = timeRemaining % nominalTickLength;\r
12939     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
12940 \r
12941     return nextTickLength;\r
12942 }\r
12943 \r
12944 /* Adjust clock one minute up or down */\r
12945 void\r
12946 AdjustClock(Boolean which, int dir)\r
12947 {\r
12948     if(which) blackTimeRemaining += 60000*dir;\r
12949     else      whiteTimeRemaining += 60000*dir;\r
12950     DisplayBothClocks();\r
12951 }\r
12952 \r
12953 /* Stop clocks and reset to a fresh time control */\r
12954 void\r
12955 ResetClocks() \r
12956 {\r
12957     (void) StopClockTimer();\r
12958     if (appData.icsActive) {\r
12959         whiteTimeRemaining = blackTimeRemaining = 0;\r
12960     } else { /* [HGM] correct new time quote for time odds */\r
12961         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
12962         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
12963     }\r
12964     if (whiteFlag || blackFlag) {\r
12965         DisplayTitle("");\r
12966         whiteFlag = blackFlag = FALSE;\r
12967     }\r
12968     DisplayBothClocks();\r
12969 }\r
12970 \r
12971 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
12972 \r
12973 /* Decrement running clock by amount of time that has passed */\r
12974 void\r
12975 DecrementClocks()\r
12976 {\r
12977     long timeRemaining;\r
12978     long lastTickLength, fudge;\r
12979     TimeMark now;\r
12980 \r
12981     if (!appData.clockMode) return;\r
12982     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
12983         \r
12984     GetTimeMark(&now);\r
12985 \r
12986     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
12987 \r
12988     /* Fudge if we woke up a little too soon */\r
12989     fudge = intendedTickLength - lastTickLength;\r
12990     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
12991 \r
12992     if (WhiteOnMove(forwardMostMove)) {\r
12993         if(whiteNPS >= 0) lastTickLength = 0;\r
12994         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
12995         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
12996                           WhiteOnMove(currentMove));\r
12997     } else {\r
12998         if(blackNPS >= 0) lastTickLength = 0;\r
12999         timeRemaining = blackTimeRemaining -= lastTickLength;\r
13000         DisplayBlackClock(blackTimeRemaining - fudge,\r
13001                           !WhiteOnMove(currentMove));\r
13002     }\r
13003 \r
13004     if (CheckFlags()) return;\r
13005         \r
13006     tickStartTM = now;\r
13007     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
13008     StartClockTimer(intendedTickLength);\r
13009 \r
13010     /* if the time remaining has fallen below the alarm threshold, sound the\r
13011      * alarm. if the alarm has sounded and (due to a takeback or time control\r
13012      * with increment) the time remaining has increased to a level above the\r
13013      * threshold, reset the alarm so it can sound again. \r
13014      */\r
13015     \r
13016     if (appData.icsActive && appData.icsAlarm) {\r
13017 \r
13018         /* make sure we are dealing with the user's clock */\r
13019         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
13020                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
13021            )) return;\r
13022 \r
13023         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
13024             alarmSounded = FALSE;\r
13025         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
13026             PlayAlarmSound();\r
13027             alarmSounded = TRUE;\r
13028         }\r
13029     }\r
13030 }\r
13031 \r
13032 \r
13033 /* A player has just moved, so stop the previously running\r
13034    clock and (if in clock mode) start the other one.\r
13035    We redisplay both clocks in case we're in ICS mode, because\r
13036    ICS gives us an update to both clocks after every move.\r
13037    Note that this routine is called *after* forwardMostMove\r
13038    is updated, so the last fractional tick must be subtracted\r
13039    from the color that is *not* on move now.\r
13040 */\r
13041 void\r
13042 SwitchClocks()\r
13043 {\r
13044     long lastTickLength;\r
13045     TimeMark now;\r
13046     int flagged = FALSE;\r
13047 \r
13048     GetTimeMark(&now);\r
13049 \r
13050     if (StopClockTimer() && appData.clockMode) {\r
13051         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13052         if (WhiteOnMove(forwardMostMove)) {\r
13053             if(blackNPS >= 0) lastTickLength = 0;\r
13054             blackTimeRemaining -= lastTickLength;\r
13055            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13056 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13057                  pvInfoList[forwardMostMove-1].time =               // use GUI time\r
13058                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
13059         } else {\r
13060            if(whiteNPS >= 0) lastTickLength = 0;\r
13061            whiteTimeRemaining -= lastTickLength;\r
13062            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13063 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13064                  pvInfoList[forwardMostMove-1].time = \r
13065                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
13066         }\r
13067         flagged = CheckFlags();\r
13068     }\r
13069     CheckTimeControl();\r
13070 \r
13071     if (flagged || !appData.clockMode) return;\r
13072 \r
13073     switch (gameMode) {\r
13074       case MachinePlaysBlack:\r
13075       case MachinePlaysWhite:\r
13076       case BeginningOfGame:\r
13077         if (pausing) return;\r
13078         break;\r
13079 \r
13080       case EditGame:\r
13081       case PlayFromGameFile:\r
13082       case IcsExamining:\r
13083         return;\r
13084 \r
13085       default:\r
13086         break;\r
13087     }\r
13088 \r
13089     tickStartTM = now;\r
13090     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13091       whiteTimeRemaining : blackTimeRemaining);\r
13092     StartClockTimer(intendedTickLength);\r
13093 }\r
13094         \r
13095 \r
13096 /* Stop both clocks */\r
13097 void\r
13098 StopClocks()\r
13099 {       \r
13100     long lastTickLength;\r
13101     TimeMark now;\r
13102 \r
13103     if (!StopClockTimer()) return;\r
13104     if (!appData.clockMode) return;\r
13105 \r
13106     GetTimeMark(&now);\r
13107 \r
13108     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13109     if (WhiteOnMove(forwardMostMove)) {\r
13110         if(whiteNPS >= 0) lastTickLength = 0;\r
13111         whiteTimeRemaining -= lastTickLength;\r
13112         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
13113     } else {\r
13114         if(blackNPS >= 0) lastTickLength = 0;\r
13115         blackTimeRemaining -= lastTickLength;\r
13116         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
13117     }\r
13118     CheckFlags();\r
13119 }\r
13120         \r
13121 /* Start clock of player on move.  Time may have been reset, so\r
13122    if clock is already running, stop and restart it. */\r
13123 void\r
13124 StartClocks()\r
13125 {\r
13126     (void) StopClockTimer(); /* in case it was running already */\r
13127     DisplayBothClocks();\r
13128     if (CheckFlags()) return;\r
13129 \r
13130     if (!appData.clockMode) return;\r
13131     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
13132 \r
13133     GetTimeMark(&tickStartTM);\r
13134     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13135       whiteTimeRemaining : blackTimeRemaining);\r
13136 \r
13137    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
13138     whiteNPS = blackNPS = -1; \r
13139     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
13140        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
13141         whiteNPS = first.nps;\r
13142     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
13143        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
13144         blackNPS = first.nps;\r
13145     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
13146         whiteNPS = second.nps;\r
13147     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
13148         blackNPS = second.nps;\r
13149     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
13150 \r
13151     StartClockTimer(intendedTickLength);\r
13152 }\r
13153 \r
13154 char *\r
13155 TimeString(ms)\r
13156      long ms;\r
13157 {\r
13158     long second, minute, hour, day;\r
13159     char *sign = "";\r
13160     static char buf[32];\r
13161     \r
13162     if (ms > 0 && ms <= 9900) {\r
13163       /* convert milliseconds to tenths, rounding up */\r
13164       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
13165 \r
13166       sprintf(buf, " %03.1f ", tenths/10.0);\r
13167       return buf;\r
13168     }\r
13169 \r
13170     /* convert milliseconds to seconds, rounding up */\r
13171     /* use floating point to avoid strangeness of integer division\r
13172        with negative dividends on many machines */\r
13173     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
13174 \r
13175     if (second < 0) {\r
13176         sign = "-";\r
13177         second = -second;\r
13178     }\r
13179     \r
13180     day = second / (60 * 60 * 24);\r
13181     second = second % (60 * 60 * 24);\r
13182     hour = second / (60 * 60);\r
13183     second = second % (60 * 60);\r
13184     minute = second / 60;\r
13185     second = second % 60;\r
13186     \r
13187     if (day > 0)\r
13188       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
13189               sign, day, hour, minute, second);\r
13190     else if (hour > 0)\r
13191       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
13192     else\r
13193       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
13194     \r
13195     return buf;\r
13196 }\r
13197 \r
13198 \r
13199 /*\r
13200  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
13201  */\r
13202 char *\r
13203 StrStr(string, match)\r
13204      char *string, *match;\r
13205 {\r
13206     int i, length;\r
13207     \r
13208     length = strlen(match);\r
13209     \r
13210     for (i = strlen(string) - length; i >= 0; i--, string++)\r
13211       if (!strncmp(match, string, length))\r
13212         return string;\r
13213     \r
13214     return NULL;\r
13215 }\r
13216 \r
13217 char *\r
13218 StrCaseStr(string, match)\r
13219      char *string, *match;\r
13220 {\r
13221     int i, j, length;\r
13222     \r
13223     length = strlen(match);\r
13224     \r
13225     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
13226         for (j = 0; j < length; j++) {\r
13227             if (ToLower(match[j]) != ToLower(string[j]))\r
13228               break;\r
13229         }\r
13230         if (j == length) return string;\r
13231     }\r
13232 \r
13233     return NULL;\r
13234 }\r
13235 \r
13236 #ifndef _amigados\r
13237 int\r
13238 StrCaseCmp(s1, s2)\r
13239      char *s1, *s2;\r
13240 {\r
13241     char c1, c2;\r
13242     \r
13243     for (;;) {\r
13244         c1 = ToLower(*s1++);\r
13245         c2 = ToLower(*s2++);\r
13246         if (c1 > c2) return 1;\r
13247         if (c1 < c2) return -1;\r
13248         if (c1 == NULLCHAR) return 0;\r
13249     }\r
13250 }\r
13251 \r
13252 \r
13253 int\r
13254 ToLower(c)\r
13255      int c;\r
13256 {\r
13257     return isupper(c) ? tolower(c) : c;\r
13258 }\r
13259 \r
13260 \r
13261 int\r
13262 ToUpper(c)\r
13263      int c;\r
13264 {\r
13265     return islower(c) ? toupper(c) : c;\r
13266 }\r
13267 #endif /* !_amigados    */\r
13268 \r
13269 char *\r
13270 StrSave(s)\r
13271      char *s;\r
13272 {\r
13273     char *ret;\r
13274 \r
13275     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
13276         strcpy(ret, s);\r
13277     }\r
13278     return ret;\r
13279 }\r
13280 \r
13281 char *\r
13282 StrSavePtr(s, savePtr)\r
13283      char *s, **savePtr;\r
13284 {\r
13285     if (*savePtr) {\r
13286         free(*savePtr);\r
13287     }\r
13288     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
13289         strcpy(*savePtr, s);\r
13290     }\r
13291     return(*savePtr);\r
13292 }\r
13293 \r
13294 char *\r
13295 PGNDate()\r
13296 {\r
13297     time_t clock;\r
13298     struct tm *tm;\r
13299     char buf[MSG_SIZ];\r
13300 \r
13301     clock = time((time_t *)NULL);\r
13302     tm = localtime(&clock);\r
13303     sprintf(buf, "%04d.%02d.%02d",\r
13304             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
13305     return StrSave(buf);\r
13306 }\r
13307 \r
13308 \r
13309 char *\r
13310 PositionToFEN(move, useFEN960)\r
13311      int move;\r
13312      int useFEN960;\r
13313 {\r
13314     int i, j, fromX, fromY, toX, toY;\r
13315     int whiteToPlay;\r
13316     char buf[128];\r
13317     char *p, *q;\r
13318     int emptycount;\r
13319     ChessSquare piece;\r
13320 \r
13321     whiteToPlay = (gameMode == EditPosition) ?\r
13322       !blackPlaysFirst : (move % 2 == 0);\r
13323     p = buf;\r
13324 \r
13325     /* Piece placement data */\r
13326     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13327         emptycount = 0;\r
13328         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
13329             if (boards[move][i][j] == EmptySquare) {\r
13330                 emptycount++;\r
13331             } else { ChessSquare piece = boards[move][i][j];\r
13332                 if (emptycount > 0) {\r
13333                     if(emptycount<10) /* [HGM] can be >= 10 */\r
13334                         *p++ = '0' + emptycount;\r
13335                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13336                     emptycount = 0;\r
13337                 }\r
13338                 if(PieceToChar(piece) == '+') {\r
13339                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
13340                     *p++ = '+';\r
13341                     piece = (ChessSquare)(DEMOTED piece);\r
13342                 } \r
13343                 *p++ = PieceToChar(piece);\r
13344                 if(p[-1] == '~') {\r
13345                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
13346                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
13347                     *p++ = '~';\r
13348                 }\r
13349             }\r
13350         }\r
13351         if (emptycount > 0) {\r
13352             if(emptycount<10) /* [HGM] can be >= 10 */\r
13353                 *p++ = '0' + emptycount;\r
13354             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13355             emptycount = 0;\r
13356         }\r
13357         *p++ = '/';\r
13358     }\r
13359     *(p - 1) = ' ';\r
13360 \r
13361     /* [HGM] print Crazyhouse or Shogi holdings */\r
13362     if( gameInfo.holdingsWidth ) {\r
13363         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
13364         q = p;\r
13365         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
13366             piece = boards[move][i][BOARD_WIDTH-1];\r
13367             if( piece != EmptySquare )\r
13368               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
13369                   *p++ = PieceToChar(piece);\r
13370         }\r
13371         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
13372             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
13373             if( piece != EmptySquare )\r
13374               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
13375                   *p++ = PieceToChar(piece);\r
13376         }\r
13377 \r
13378         if( q == p ) *p++ = '-';\r
13379         *p++ = ']';\r
13380         *p++ = ' ';\r
13381     }\r
13382 \r
13383     /* Active color */\r
13384     *p++ = whiteToPlay ? 'w' : 'b';\r
13385     *p++ = ' ';\r
13386 \r
13387   if(nrCastlingRights) {\r
13388      q = p;\r
13389      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
13390        /* [HGM] write directly from rights */\r
13391            if(castlingRights[move][2] >= 0 &&\r
13392               castlingRights[move][0] >= 0   )\r
13393                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
13394            if(castlingRights[move][2] >= 0 &&\r
13395               castlingRights[move][1] >= 0   )\r
13396                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
13397            if(castlingRights[move][5] >= 0 &&\r
13398               castlingRights[move][3] >= 0   )\r
13399                 *p++ = castlingRights[move][3] + AAA;\r
13400            if(castlingRights[move][5] >= 0 &&\r
13401               castlingRights[move][4] >= 0   )\r
13402                 *p++ = castlingRights[move][4] + AAA;\r
13403      } else {\r
13404 \r
13405         /* [HGM] write true castling rights */\r
13406         if( nrCastlingRights == 6 ) {\r
13407             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
13408                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
13409             if(castlingRights[move][1] == BOARD_LEFT &&\r
13410                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
13411             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
13412                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
13413             if(castlingRights[move][4] == BOARD_LEFT &&\r
13414                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
13415         }\r
13416      }\r
13417      if (q == p) *p++ = '-'; /* No castling rights */\r
13418      *p++ = ' ';\r
13419   }\r
13420 \r
13421   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13422      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13423     /* En passant target square */\r
13424     if (move > backwardMostMove) {\r
13425         fromX = moveList[move - 1][0] - AAA;\r
13426         fromY = moveList[move - 1][1] - ONE;\r
13427         toX = moveList[move - 1][2] - AAA;\r
13428         toY = moveList[move - 1][3] - ONE;\r
13429         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
13430             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
13431             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
13432             fromX == toX) {\r
13433             /* 2-square pawn move just happened */\r
13434             *p++ = toX + AAA;\r
13435             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
13436         } else {\r
13437             *p++ = '-';\r
13438         }\r
13439     } else {\r
13440         *p++ = '-';\r
13441     }\r
13442     *p++ = ' ';\r
13443   }\r
13444 \r
13445     /* [HGM] find reversible plies */\r
13446     {   int i = 0, j=move;\r
13447 \r
13448         if (appData.debugMode) { int k;\r
13449             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
13450             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
13451                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
13452 \r
13453         }\r
13454 \r
13455         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
13456         if( j == backwardMostMove ) i += initialRulePlies;\r
13457         sprintf(p, "%d ", i);\r
13458         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
13459     }\r
13460     /* Fullmove number */\r
13461     sprintf(p, "%d", (move / 2) + 1);\r
13462     \r
13463     return StrSave(buf);\r
13464 }\r
13465 \r
13466 Boolean\r
13467 ParseFEN(board, blackPlaysFirst, fen)\r
13468     Board board;\r
13469      int *blackPlaysFirst;\r
13470      char *fen;\r
13471 {\r
13472     int i, j;\r
13473     char *p;\r
13474     int emptycount;\r
13475     ChessSquare piece;\r
13476 \r
13477     p = fen;\r
13478 \r
13479     /* [HGM] by default clear Crazyhouse holdings, if present */\r
13480     if(gameInfo.holdingsWidth) {\r
13481        for(i=0; i<BOARD_HEIGHT; i++) {\r
13482            board[i][0]             = EmptySquare; /* black holdings */\r
13483            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
13484            board[i][1]             = (ChessSquare) 0; /* black counts */\r
13485            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
13486        }\r
13487     }\r
13488 \r
13489     /* Piece placement data */\r
13490     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13491         j = 0;\r
13492         for (;;) {\r
13493             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
13494                 if (*p == '/') p++;\r
13495                 emptycount = gameInfo.boardWidth - j;\r
13496                 while (emptycount--)\r
13497                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13498                 break;\r
13499 #if(BOARD_SIZE >= 10)\r
13500             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
13501                 p++; emptycount=10;\r
13502                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13503                 while (emptycount--)\r
13504                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13505 #endif\r
13506             } else if (isdigit(*p)) {\r
13507                 emptycount = *p++ - '0';\r
13508                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
13509                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13510                 while (emptycount--)\r
13511                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13512             } else if (*p == '+' || isalpha(*p)) {\r
13513                 if (j >= gameInfo.boardWidth) return FALSE;\r
13514                 if(*p=='+') {\r
13515                     piece = CharToPiece(*++p);\r
13516                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
13517                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
13518                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
13519                 } else piece = CharToPiece(*p++);\r
13520 \r
13521                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
13522                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
13523                     piece = (ChessSquare) (PROMOTED piece);\r
13524                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
13525                     p++;\r
13526                 }\r
13527                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
13528             } else {\r
13529                 return FALSE;\r
13530             }\r
13531         }\r
13532     }\r
13533     while (*p == '/' || *p == ' ') p++;\r
13534 \r
13535     /* [HGM] look for Crazyhouse holdings here */\r
13536     while(*p==' ') p++;\r
13537     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
13538         if(*p == '[') p++;\r
13539         if(*p == '-' ) *p++; /* empty holdings */ else {\r
13540             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
13541             /* if we would allow FEN reading to set board size, we would   */\r
13542             /* have to add holdings and shift the board read so far here   */\r
13543             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
13544                 *p++;\r
13545                 if((int) piece >= (int) BlackPawn ) {\r
13546                     i = (int)piece - (int)BlackPawn;\r
13547                     i = PieceToNumber((ChessSquare)i);\r
13548                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13549                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
13550                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
13551                 } else {\r
13552                     i = (int)piece - (int)WhitePawn;\r
13553                     i = PieceToNumber((ChessSquare)i);\r
13554                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13555                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
13556                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
13557                 }\r
13558             }\r
13559         }\r
13560         if(*p == ']') *p++;\r
13561     }\r
13562 \r
13563     while(*p == ' ') p++;\r
13564 \r
13565     /* Active color */\r
13566     switch (*p++) {\r
13567       case 'w':\r
13568         *blackPlaysFirst = FALSE;\r
13569         break;\r
13570       case 'b': \r
13571         *blackPlaysFirst = TRUE;\r
13572         break;\r
13573       default:\r
13574         return FALSE;\r
13575     }\r
13576 \r
13577     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
13578     /* return the extra info in global variiables             */\r
13579 \r
13580     /* set defaults in case FEN is incomplete */\r
13581     FENepStatus = EP_UNKNOWN;\r
13582     for(i=0; i<nrCastlingRights; i++ ) {\r
13583         FENcastlingRights[i] =\r
13584             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
13585     }   /* assume possible unless obviously impossible */\r
13586     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
13587     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
13588     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
13589     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
13590     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
13591     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
13592     FENrulePlies = 0;\r
13593 \r
13594     while(*p==' ') p++;\r
13595     if(nrCastlingRights) {\r
13596       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
13597           /* castling indicator present, so default becomes no castlings */\r
13598           for(i=0; i<nrCastlingRights; i++ ) {\r
13599                  FENcastlingRights[i] = -1;\r
13600           }\r
13601       }\r
13602       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
13603              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
13604              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
13605              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
13606         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
13607 \r
13608         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
13609             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
13610             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
13611         }\r
13612         switch(c) {\r
13613           case'K':\r
13614               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
13615               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
13616               FENcastlingRights[2] = whiteKingFile;\r
13617               break;\r
13618           case'Q':\r
13619               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
13620               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
13621               FENcastlingRights[2] = whiteKingFile;\r
13622               break;\r
13623           case'k':\r
13624               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
13625               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
13626               FENcastlingRights[5] = blackKingFile;\r
13627               break;\r
13628           case'q':\r
13629               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
13630               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
13631               FENcastlingRights[5] = blackKingFile;\r
13632           case '-':\r
13633               break;\r
13634           default: /* FRC castlings */\r
13635               if(c >= 'a') { /* black rights */\r
13636                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13637                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
13638                   if(i == BOARD_RGHT) break;\r
13639                   FENcastlingRights[5] = i;\r
13640                   c -= AAA;\r
13641                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
13642                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
13643                   if(c > i)\r
13644                       FENcastlingRights[3] = c;\r
13645                   else\r
13646                       FENcastlingRights[4] = c;\r
13647               } else { /* white rights */\r
13648                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13649                     if(board[0][i] == WhiteKing) break;\r
13650                   if(i == BOARD_RGHT) break;\r
13651                   FENcastlingRights[2] = i;\r
13652                   c -= AAA - 'a' + 'A';\r
13653                   if(board[0][c] >= WhiteKing) break;\r
13654                   if(c > i)\r
13655                       FENcastlingRights[0] = c;\r
13656                   else\r
13657                       FENcastlingRights[1] = c;\r
13658               }\r
13659         }\r
13660       }\r
13661     if (appData.debugMode) {\r
13662         fprintf(debugFP, "FEN castling rights:");\r
13663         for(i=0; i<nrCastlingRights; i++)\r
13664         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
13665         fprintf(debugFP, "\n");\r
13666     }\r
13667 \r
13668       while(*p==' ') p++;\r
13669     }\r
13670 \r
13671     /* read e.p. field in games that know e.p. capture */\r
13672     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13673        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13674       if(*p=='-') {\r
13675         p++; FENepStatus = EP_NONE;\r
13676       } else {\r
13677          char c = *p++ - AAA;\r
13678 \r
13679          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
13680          if(*p >= '0' && *p <='9') *p++;\r
13681          FENepStatus = c;\r
13682       }\r
13683     }\r
13684 \r
13685 \r
13686     if(sscanf(p, "%d", &i) == 1) {\r
13687         FENrulePlies = i; /* 50-move ply counter */\r
13688         /* (The move number is still ignored)    */\r
13689     }\r
13690 \r
13691     return TRUE;\r
13692 }\r
13693       \r
13694 void\r
13695 EditPositionPasteFEN(char *fen)\r
13696 {\r
13697   if (fen != NULL) {\r
13698     Board initial_position;\r
13699 \r
13700     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
13701       DisplayError(_("Bad FEN position in clipboard"), 0);\r
13702       return ;\r
13703     } else {\r
13704       int savedBlackPlaysFirst = blackPlaysFirst;\r
13705       EditPositionEvent();\r
13706       blackPlaysFirst = savedBlackPlaysFirst;\r
13707       CopyBoard(boards[0], initial_position);\r
13708           /* [HGM] copy FEN attributes as well */\r
13709           {   int i;\r
13710               initialRulePlies = FENrulePlies;\r
13711               epStatus[0] = FENepStatus;\r
13712               for( i=0; i<nrCastlingRights; i++ )\r
13713                   castlingRights[0][i] = FENcastlingRights[i];\r
13714           }\r
13715       EditPositionDone();\r
13716       DisplayBothClocks();\r
13717       DrawPosition(FALSE, boards[currentMove]);\r
13718     }\r
13719   }\r
13720 }\r