changes from H.G. Muller; version 4.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 \r
124 /* A point in time */\r
125 typedef struct {\r
126     long sec;  /* Assuming this is >= 32 bits */\r
127     int ms;    /* Assuming this is >= 16 bits */\r
128 } TimeMark;\r
129 \r
130 int establish P((void));\r
131 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,\r
132                          char *buf, int count, int error));\r
133 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,\r
134                       char *buf, int count, int error));\r
135 void SendToICS P((char *s));\r
136 void SendToICSDelayed P((char *s, long msdelay));\r
137 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,\r
138                       int toX, int toY));\r
139 void InitPosition P((int redraw));\r
140 void HandleMachineMove P((char *message, ChessProgramState *cps));\r
141 int AutoPlayOneMove P((void));\r
142 int LoadGameOneMove P((ChessMove readAhead));\r
143 int LoadGameFromFile P((char *filename, int n, char *title, int useList));\r
144 int LoadPositionFromFile P((char *filename, int n, char *title));\r
145 int SavePositionToFile P((char *filename));\r
146 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,\r
147                   Board board));\r
148 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));\r
149 void ShowMove P((int fromX, int fromY, int toX, int toY));\r
150 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,\r
151                    /*char*/int promoChar));\r
152 void BackwardInner P((int target));\r
153 void ForwardInner P((int target));\r
154 void GameEnds P((ChessMove result, char *resultDetails, int whosays));\r
155 void EditPositionDone P((void));\r
156 void PrintOpponents P((FILE *fp));\r
157 void PrintPosition P((FILE *fp, int move));\r
158 void StartChessProgram P((ChessProgramState *cps));\r
159 void SendToProgram P((char *message, ChessProgramState *cps));\r
160 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));\r
161 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,\r
162                            char *buf, int count, int error));\r
163 void SendTimeControl P((ChessProgramState *cps,\r
164                         int mps, long tc, int inc, int sd, int st));\r
165 char *TimeControlTagValue P((void));\r
166 void Attention P((ChessProgramState *cps));\r
167 void FeedMovesToProgram P((ChessProgramState *cps, int upto));\r
168 void ResurrectChessProgram P((void));\r
169 void DisplayComment P((int moveNumber, char *text));\r
170 void DisplayMove P((int moveNumber));\r
171 void DisplayAnalysis P((void));\r
172 \r
173 void ParseGameHistory P((char *game));\r
174 void ParseBoard12 P((char *string));\r
175 void StartClocks P((void));\r
176 void SwitchClocks P((void));\r
177 void StopClocks P((void));\r
178 void ResetClocks P((void));\r
179 char *PGNDate P((void));\r
180 void SetGameInfo P((void));\r
181 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));\r
182 int RegisterMove P((void));\r
183 void MakeRegisteredMove P((void));\r
184 void TruncateGame P((void));\r
185 int looking_at P((char *, int *, char *));\r
186 void CopyPlayerNameIntoFileName P((char **, char *));\r
187 char *SavePart P((char *));\r
188 int SaveGameOldStyle P((FILE *));\r
189 int SaveGamePGN P((FILE *));\r
190 void GetTimeMark P((TimeMark *));\r
191 long SubtractTimeMarks P((TimeMark *, TimeMark *));\r
192 int CheckFlags P((void));\r
193 long NextTickLength P((long));\r
194 void CheckTimeControl P((void));\r
195 void show_bytes P((FILE *, char *, int));\r
196 int string_to_rating P((char *str));\r
197 void ParseFeatures P((char* args, ChessProgramState *cps));\r
198 void InitBackEnd3 P((void));\r
199 void FeatureDone P((ChessProgramState* cps, int val));\r
200 void InitChessProgram P((ChessProgramState *cps, int setup));\r
201 ChessProgramState *WhitePlayer();\r
202 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c\r
203 int VerifyDisplayMode P(());\r
204 \r
205 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment\r
206 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c\r
207 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move\r
208 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book\r
209 extern char installDir[MSG_SIZ];\r
210 \r
211 extern int tinyLayout, smallLayout;\r
212 ChessProgramStats programStats;\r
213 static int exiting = 0; /* [HGM] moved to top */\r
214 static int setboardSpoiledMachineBlack = 0, errorExitFlag = 0;\r
215 extern int startedFromPositionFile;\r
216 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */\r
217 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */\r
218 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */\r
219 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */\r
220 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */\r
221 int opponentKibitzes;\r
222 \r
223 /* States for ics_getting_history */\r
224 #define H_FALSE 0\r
225 #define H_REQUESTED 1\r
226 #define H_GOT_REQ_HEADER 2\r
227 #define H_GOT_UNREQ_HEADER 3\r
228 #define H_GETTING_MOVES 4\r
229 #define H_GOT_UNWANTED_HEADER 5\r
230 \r
231 /* whosays values for GameEnds */\r
232 #define GE_ICS 0\r
233 #define GE_ENGINE 1\r
234 #define GE_PLAYER 2\r
235 #define GE_FILE 3\r
236 #define GE_XBOARD 4\r
237 #define GE_ENGINE1 5\r
238 #define GE_ENGINE2 6\r
239 \r
240 /* Maximum number of games in a cmail message */\r
241 #define CMAIL_MAX_GAMES 20\r
242 \r
243 /* Different types of move when calling RegisterMove */\r
244 #define CMAIL_MOVE   0\r
245 #define CMAIL_RESIGN 1\r
246 #define CMAIL_DRAW   2\r
247 #define CMAIL_ACCEPT 3\r
248 \r
249 /* Different types of result to remember for each game */\r
250 #define CMAIL_NOT_RESULT 0\r
251 #define CMAIL_OLD_RESULT 1\r
252 #define CMAIL_NEW_RESULT 2\r
253 \r
254 /* Telnet protocol constants */\r
255 #define TN_WILL 0373\r
256 #define TN_WONT 0374\r
257 #define TN_DO   0375\r
258 #define TN_DONT 0376\r
259 #define TN_IAC  0377\r
260 #define TN_ECHO 0001\r
261 #define TN_SGA  0003\r
262 #define TN_PORT 23\r
263 \r
264 /* [AS] */\r
265 static char * safeStrCpy( char * dst, const char * src, size_t count )\r
266 {\r
267     assert( dst != NULL );\r
268     assert( src != NULL );\r
269     assert( count > 0 );\r
270 \r
271     strncpy( dst, src, count );\r
272     dst[ count-1 ] = '\0';\r
273     return dst;\r
274 }\r
275 \r
276 static char * safeStrCat( char * dst, const char * src, size_t count )\r
277 {\r
278     size_t  dst_len;\r
279 \r
280     assert( dst != NULL );\r
281     assert( src != NULL );\r
282     assert( count > 0 );\r
283 \r
284     dst_len = strlen(dst);\r
285 \r
286     assert( count > dst_len ); /* Buffer size must be greater than current length */\r
287 \r
288     safeStrCpy( dst + dst_len, src, count - dst_len );\r
289 \r
290     return dst;\r
291 }\r
292 \r
293 /* Fake up flags for now, as we aren't keeping track of castling\r
294    availability yet. [HGM] Change of logic: the flag now only\r
295    indicates the type of castlings allowed by the rule of the game.\r
296    The actual rights themselves are maintained in the array\r
297    castlingRights, as part of the game history, and are not probed\r
298    by this function.\r
299  */\r
300 int\r
301 PosFlags(index)\r
302 {\r
303   int flags = F_ALL_CASTLE_OK;\r
304   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;\r
305   switch (gameInfo.variant) {\r
306   case VariantSuicide:\r
307     flags &= ~F_ALL_CASTLE_OK;\r
308   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!\r
309     flags |= F_IGNORE_CHECK;\r
310     break;\r
311   case VariantAtomic:\r
312     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;\r
313     break;\r
314   case VariantKriegspiel:\r
315     flags |= F_KRIEGSPIEL_CAPTURE;\r
316     break;\r
317   case VariantCapaRandom: \r
318   case VariantFischeRandom:\r
319     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */\r
320   case VariantNoCastle:\r
321   case VariantShatranj:\r
322   case VariantCourier:\r
323     flags &= ~F_ALL_CASTLE_OK;\r
324     break;\r
325   default:\r
326     break;\r
327   }\r
328   return flags;\r
329 }\r
330 \r
331 FILE *gameFileFP, *debugFP;\r
332 \r
333 /* \r
334     [AS] Note: sometimes, the sscanf() function is used to parse the input\r
335     into a fixed-size buffer. Because of this, we must be prepared to\r
336     receive strings as long as the size of the input buffer, which is currently\r
337     set to 4K for Windows and 8K for the rest.\r
338     So, we must either allocate sufficiently large buffers here, or\r
339     reduce the size of the input buffer in the input reading part.\r
340 */\r
341 \r
342 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];\r
343 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];\r
344 char thinkOutput1[MSG_SIZ*10];\r
345 \r
346 ChessProgramState first, second;\r
347 \r
348 /* premove variables */\r
349 int premoveToX = 0;\r
350 int premoveToY = 0;\r
351 int premoveFromX = 0;\r
352 int premoveFromY = 0;\r
353 int premovePromoChar = 0;\r
354 int gotPremove = 0;\r
355 Boolean alarmSounded;\r
356 /* end premove variables */\r
357 \r
358 #define ICS_GENERIC 0\r
359 #define ICS_ICC 1\r
360 #define ICS_FICS 2\r
361 #define ICS_CHESSNET 3 /* not really supported */\r
362 int ics_type = ICS_GENERIC;\r
363 char *ics_prefix = "$";\r
364 \r
365 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;\r
366 int pauseExamForwardMostMove = 0;\r
367 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;\r
368 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];\r
369 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;\r
370 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;\r
371 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;\r
372 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;\r
373 int whiteFlag = FALSE, blackFlag = FALSE;\r
374 int userOfferedDraw = FALSE;\r
375 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;\r
376 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;\r
377 int cmailMoveType[CMAIL_MAX_GAMES];\r
378 long ics_clock_paused = 0;\r
379 ProcRef icsPR = NoProc, cmailPR = NoProc;\r
380 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;\r
381 GameMode gameMode = BeginningOfGame;\r
382 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];\r
383 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];\r
384 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */\r
385 int hiddenThinkOutputState = 0; /* [AS] */\r
386 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */\r
387 int adjudicateLossPlies = 6;\r
388 char white_holding[64], black_holding[64];\r
389 TimeMark lastNodeCountTime;\r
390 long lastNodeCount=0;\r
391 int have_sent_ICS_logon = 0;\r
392 int movesPerSession;\r
393 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;\r
394 long timeControl_2; /* [AS] Allow separate time controls */\r
395 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */\r
396 long timeRemaining[2][MAX_MOVES];\r
397 int matchGame = 0;\r
398 TimeMark programStartTime;\r
399 char ics_handle[MSG_SIZ];\r
400 int have_set_title = 0;\r
401 \r
402 /* animateTraining preserves the state of appData.animate\r
403  * when Training mode is activated. This allows the\r
404  * response to be animated when appData.animate == TRUE and\r
405  * appData.animateDragging == TRUE.\r
406  */\r
407 Boolean animateTraining;\r
408 \r
409 GameInfo gameInfo;\r
410 \r
411 AppData appData;\r
412 \r
413 Board boards[MAX_MOVES];\r
414 /* [HGM] Following 7 needed for accurate legality tests: */\r
415 char  epStatus[MAX_MOVES];\r
416 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1\r
417 char  castlingRank[BOARD_SIZE]; // and corresponding ranks\r
418 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];\r
419 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status\r
420 int   initialRulePlies, FENrulePlies;\r
421 char  FENepStatus;\r
422 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)\r
423 int loadFlag = 0; \r
424 int shuffleOpenings;\r
425 \r
426 ChessSquare  FIDEArray[2][BOARD_SIZE] = {\r
427     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
428         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
429     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
430         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
431 };\r
432 \r
433 ChessSquare twoKingsArray[2][BOARD_SIZE] = {\r
434     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
435         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },\r
436     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
437         BlackKing, BlackKing, BlackKnight, BlackRook }\r
438 };\r
439 \r
440 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {\r
441     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,\r
442         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },\r
443     { BlackRook, BlackMan, BlackBishop, BlackQueen,\r
444         BlackUnicorn, BlackBishop, BlackMan, BlackRook }\r
445 };\r
446 \r
447 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */\r
448     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,\r
449         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
450     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,\r
451         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
452 };\r
453 \r
454 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */\r
455     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,\r
456         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
457     { BlackRook, BlackKnight, BlackAlfil, BlackKing,\r
458         BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
459 };\r
460 \r
461 \r
462 #if (BOARD_SIZE>=10)\r
463 ChessSquare ShogiArray[2][BOARD_SIZE] = {\r
464     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,\r
465         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },\r
466     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,\r
467         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }\r
468 };\r
469 \r
470 ChessSquare XiangqiArray[2][BOARD_SIZE] = {\r
471     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,\r
472         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
473     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,\r
474         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
475 };\r
476 \r
477 ChessSquare CapablancaArray[2][BOARD_SIZE] = {\r
478     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, \r
479         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },\r
480     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, \r
481         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
482 };\r
483 \r
484 ChessSquare GreatArray[2][BOARD_SIZE] = {\r
485     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, \r
486         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },\r
487     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, \r
488         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },\r
489 };\r
490 \r
491 ChessSquare JanusArray[2][BOARD_SIZE] = {\r
492     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, \r
493         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },\r
494     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, \r
495         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }\r
496 };\r
497 \r
498 #ifdef GOTHIC\r
499 ChessSquare GothicArray[2][BOARD_SIZE] = {\r
500     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, \r
501         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },\r
502     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, \r
503         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }\r
504 };\r
505 #else // !GOTHIC\r
506 #define GothicArray CapablancaArray\r
507 #endif // !GOTHIC\r
508 \r
509 #ifdef FALCON\r
510 ChessSquare FalconArray[2][BOARD_SIZE] = {\r
511     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, \r
512         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },\r
513     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, \r
514         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }\r
515 };\r
516 #else // !FALCON\r
517 #define FalconArray CapablancaArray\r
518 #endif // !FALCON\r
519 \r
520 #else // !(BOARD_SIZE>=10)\r
521 #define XiangqiPosition FIDEArray\r
522 #define CapablancaArray FIDEArray\r
523 #define GothicArray FIDEArray\r
524 #define GreatArray FIDEArray\r
525 #endif // !(BOARD_SIZE>=10)\r
526 \r
527 #if (BOARD_SIZE>=12)\r
528 ChessSquare CourierArray[2][BOARD_SIZE] = {\r
529     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,\r
530         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },\r
531     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,\r
532         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }\r
533 };\r
534 #else // !(BOARD_SIZE>=12)\r
535 #define CourierArray CapablancaArray\r
536 #endif // !(BOARD_SIZE>=12)\r
537 \r
538 \r
539 Board initialPosition;\r
540 \r
541 \r
542 /* Convert str to a rating. Checks for special cases of "----",\r
543 \r
544    "++++", etc. Also strips ()'s */\r
545 int\r
546 string_to_rating(str)\r
547   char *str;\r
548 {\r
549   while(*str && !isdigit(*str)) ++str;\r
550   if (!*str)\r
551     return 0;   /* One of the special "no rating" cases */\r
552   else\r
553     return atoi(str);\r
554 }\r
555 \r
556 void\r
557 ClearProgramStats()\r
558 {\r
559     /* Init programStats */\r
560     programStats.movelist[0] = 0;\r
561     programStats.depth = 0;\r
562     programStats.nr_moves = 0;\r
563     programStats.moves_left = 0;\r
564     programStats.nodes = 0;\r
565     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output\r
566     programStats.score = 0;\r
567     programStats.got_only_move = 0;\r
568     programStats.got_fail = 0;\r
569     programStats.line_is_book = 0;\r
570 }\r
571 \r
572 void\r
573 InitBackEnd1()\r
574 {\r
575     int matched, min, sec;\r
576 \r
577     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options\r
578 \r
579     GetTimeMark(&programStartTime);\r
580 \r
581     ClearProgramStats();\r
582     programStats.ok_to_send = 1;\r
583     programStats.seen_stat = 0;\r
584 \r
585     /*\r
586      * Initialize game list\r
587      */\r
588     ListNew(&gameList);\r
589 \r
590 \r
591     /*\r
592      * Internet chess server status\r
593      */\r
594     if (appData.icsActive) {\r
595         appData.matchMode = FALSE;\r
596         appData.matchGames = 0;\r
597 #if ZIPPY       \r
598         appData.noChessProgram = !appData.zippyPlay;\r
599 #else\r
600         appData.zippyPlay = FALSE;\r
601         appData.zippyTalk = FALSE;\r
602         appData.noChessProgram = TRUE;\r
603 #endif\r
604         if (*appData.icsHelper != NULLCHAR) {\r
605             appData.useTelnet = TRUE;\r
606             appData.telnetProgram = appData.icsHelper;\r
607         }\r
608     } else {\r
609         appData.zippyTalk = appData.zippyPlay = FALSE;\r
610     }\r
611 \r
612     /* [AS] Initialize pv info list [HGM] and game state */\r
613     {\r
614         int i, j;\r
615 \r
616         for( i=0; i<MAX_MOVES; i++ ) {\r
617             pvInfoList[i].depth = -1;\r
618             epStatus[i]=EP_NONE;\r
619             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
620         }\r
621     }\r
622 \r
623     /*\r
624      * Parse timeControl resource\r
625      */\r
626     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,\r
627                           appData.movesPerSession)) {\r
628         char buf[MSG_SIZ];\r
629         sprintf(buf, "bad timeControl option %s", appData.timeControl);\r
630         DisplayFatalError(buf, 0, 2);\r
631     }\r
632 \r
633     /*\r
634      * Parse searchTime resource\r
635      */\r
636     if (*appData.searchTime != NULLCHAR) {\r
637         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);\r
638         if (matched == 1) {\r
639             searchTime = min * 60;\r
640         } else if (matched == 2) {\r
641             searchTime = min * 60 + sec;\r
642         } else {\r
643             char buf[MSG_SIZ];\r
644             sprintf(buf, "bad searchTime option %s", appData.searchTime);\r
645             DisplayFatalError(buf, 0, 2);\r
646         }\r
647     }\r
648 \r
649     /* [AS] Adjudication threshold */\r
650     adjudicateLossThreshold = appData.adjudicateLossThreshold;\r
651     \r
652     first.which = "first";\r
653     second.which = "second";\r
654     first.maybeThinking = second.maybeThinking = FALSE;\r
655     first.pr = second.pr = NoProc;\r
656     first.isr = second.isr = NULL;\r
657     first.sendTime = second.sendTime = 2;\r
658     first.sendDrawOffers = 1;\r
659     if (appData.firstPlaysBlack) {\r
660         first.twoMachinesColor = "black\n";\r
661         second.twoMachinesColor = "white\n";\r
662     } else {\r
663         first.twoMachinesColor = "white\n";\r
664         second.twoMachinesColor = "black\n";\r
665     }\r
666     first.program = appData.firstChessProgram;\r
667     second.program = appData.secondChessProgram;\r
668     first.host = appData.firstHost;\r
669     second.host = appData.secondHost;\r
670     first.dir = appData.firstDirectory;\r
671     second.dir = appData.secondDirectory;\r
672     first.other = &second;\r
673     second.other = &first;\r
674     first.initString = appData.initString;\r
675     second.initString = appData.secondInitString;\r
676     first.computerString = appData.firstComputerString;\r
677     second.computerString = appData.secondComputerString;\r
678     first.useSigint = second.useSigint = TRUE;\r
679     first.useSigterm = second.useSigterm = TRUE;\r
680     first.reuse = appData.reuseFirst;\r
681     second.reuse = appData.reuseSecond;\r
682     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second\r
683     second.nps = appData.secondNPS;\r
684     first.useSetboard = second.useSetboard = FALSE;\r
685     first.useSAN = second.useSAN = FALSE;\r
686     first.usePing = second.usePing = FALSE;\r
687     first.lastPing = second.lastPing = 0;\r
688     first.lastPong = second.lastPong = 0;\r
689     first.usePlayother = second.usePlayother = FALSE;\r
690     first.useColors = second.useColors = TRUE;\r
691     first.useUsermove = second.useUsermove = FALSE;\r
692     first.sendICS = second.sendICS = FALSE;\r
693     first.sendName = second.sendName = appData.icsActive;\r
694     first.sdKludge = second.sdKludge = FALSE;\r
695     first.stKludge = second.stKludge = FALSE;\r
696     TidyProgramName(first.program, first.host, first.tidy);\r
697     TidyProgramName(second.program, second.host, second.tidy);\r
698     first.matchWins = second.matchWins = 0;\r
699     strcpy(first.variants, appData.variant);\r
700     strcpy(second.variants, appData.variant);\r
701     first.analysisSupport = second.analysisSupport = 2; /* detect */\r
702     first.analyzing = second.analyzing = FALSE;\r
703     first.initDone = second.initDone = FALSE;\r
704 \r
705     /* New features added by Tord: */\r
706     first.useFEN960 = FALSE; second.useFEN960 = FALSE;\r
707     first.useOOCastle = TRUE; second.useOOCastle = TRUE;\r
708     /* End of new features added by Tord. */\r
709 \r
710     /* [HGM] time odds: set factor for each machine */\r
711     first.timeOdds  = appData.firstTimeOdds;\r
712     second.timeOdds = appData.secondTimeOdds;\r
713     { int norm = 1;\r
714         if(appData.timeOddsMode) {\r
715             norm = first.timeOdds;\r
716             if(norm > second.timeOdds) norm = second.timeOdds;\r
717         }\r
718         first.timeOdds /= norm;\r
719         second.timeOdds /= norm;\r
720     }\r
721 \r
722     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/\r
723     first.accumulateTC = appData.firstAccumulateTC;\r
724     second.accumulateTC = appData.secondAccumulateTC;\r
725     first.maxNrOfSessions = second.maxNrOfSessions = 1;\r
726 \r
727     /* [HGM] debug */\r
728     first.debug = second.debug = FALSE;\r
729     first.supportsNPS = second.supportsNPS = UNKNOWN;\r
730 \r
731     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
732     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
733     first.isUCI = appData.firstIsUCI; /* [AS] */\r
734     second.isUCI = appData.secondIsUCI; /* [AS] */\r
735     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
736     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
737 \r
738     if (appData.firstProtocolVersion > PROTOVER ||\r
739         appData.firstProtocolVersion < 1) {\r
740       char buf[MSG_SIZ];\r
741       sprintf(buf, "protocol version %d not supported",\r
742               appData.firstProtocolVersion);\r
743       DisplayFatalError(buf, 0, 2);\r
744     } else {\r
745       first.protocolVersion = appData.firstProtocolVersion;\r
746     }\r
747 \r
748     if (appData.secondProtocolVersion > PROTOVER ||\r
749         appData.secondProtocolVersion < 1) {\r
750       char buf[MSG_SIZ];\r
751       sprintf(buf, "protocol version %d not supported",\r
752               appData.secondProtocolVersion);\r
753       DisplayFatalError(buf, 0, 2);\r
754     } else {\r
755       second.protocolVersion = appData.secondProtocolVersion;\r
756     }\r
757 \r
758     if (appData.icsActive) {\r
759         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */\r
760     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
761         appData.clockMode = FALSE;\r
762         first.sendTime = second.sendTime = 0;\r
763     }\r
764     \r
765 #if ZIPPY\r
766     /* Override some settings from environment variables, for backward\r
767        compatibility.  Unfortunately it's not feasible to have the env\r
768        vars just set defaults, at least in xboard.  Ugh.\r
769     */\r
770     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
771       ZippyInit();\r
772     }\r
773 #endif\r
774     \r
775     if (appData.noChessProgram) {\r
776         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
777                                         + strlen(PATCHLEVEL));\r
778         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
779     } else {\r
780 #if 0\r
781         char *p, *q;\r
782         q = first.program;\r
783         while (*q != ' ' && *q != NULLCHAR) q++;\r
784         p = q;\r
785         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */\r
786         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
787                                         + strlen(PATCHLEVEL) + (q - p));\r
788         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
789         strncat(programVersion, p, q - p);\r
790 #else\r
791         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */\r
792         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
793                                         + strlen(PATCHLEVEL) + strlen(first.tidy));\r
794         sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);\r
795 #endif\r
796     }\r
797 \r
798     if (!appData.icsActive) {\r
799       char buf[MSG_SIZ];\r
800       /* Check for variants that are supported only in ICS mode,\r
801          or not at all.  Some that are accepted here nevertheless\r
802          have bugs; see comments below.\r
803       */\r
804       VariantClass variant = StringToVariant(appData.variant);\r
805       switch (variant) {\r
806       case VariantBughouse:     /* need four players and two boards */\r
807       case VariantKriegspiel:   /* need to hide pieces and move details */\r
808       /* case VariantFischeRandom: (Fabien: moved below) */\r
809         sprintf(buf, "Variant %s supported only in ICS mode", appData.variant);\r
810         DisplayFatalError(buf, 0, 2);\r
811         return;\r
812 \r
813       case VariantUnknown:\r
814       case VariantLoadable:\r
815       case Variant29:\r
816       case Variant30:\r
817       case Variant31:\r
818       case Variant32:\r
819       case Variant33:\r
820       case Variant34:\r
821       case Variant35:\r
822       case Variant36:\r
823       default:\r
824         sprintf(buf, "Unknown variant name %s", appData.variant);\r
825         DisplayFatalError(buf, 0, 2);\r
826         return;\r
827 \r
828       case VariantXiangqi:    /* [HGM] repetition rules not implemented */\r
829       case VariantFairy:      /* [HGM] TestLegality definitely off! */\r
830       case VariantGothic:     /* [HGM] should work */\r
831       case VariantCapablanca: /* [HGM] should work */\r
832       case VariantCourier:    /* [HGM] initial forced moves not implemented */\r
833       case VariantShogi:      /* [HGM] drops not tested for legality */\r
834       case VariantKnightmate: /* [HGM] should work */\r
835       case VariantCylinder:   /* [HGM] untested */\r
836       case VariantFalcon:     /* [HGM] untested */\r
837       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)\r
838                                  offboard interposition not understood */\r
839       case VariantNormal:     /* definitely works! */\r
840       case VariantWildCastle: /* pieces not automatically shuffled */\r
841       case VariantNoCastle:   /* pieces not automatically shuffled */\r
842       case VariantFischeRandom: /* [HGM] works and shuffles pieces */\r
843       case VariantLosers:     /* should work except for win condition,\r
844                                  and doesn't know captures are mandatory */\r
845       case VariantSuicide:    /* should work except for win condition,\r
846                                  and doesn't know captures are mandatory */\r
847       case VariantGiveaway:   /* should work except for win condition,\r
848                                  and doesn't know captures are mandatory */\r
849       case VariantTwoKings:   /* should work */\r
850       case VariantAtomic:     /* should work except for win condition */\r
851       case Variant3Check:     /* should work except for win condition */\r
852       case VariantShatranj:   /* should work except for all win conditions */\r
853       case VariantBerolina:   /* might work if TestLegality is off */\r
854       case VariantCapaRandom: /* should work */\r
855       case VariantJanus:      /* should work */\r
856       case VariantSuper:      /* experimental */\r
857       case VariantGreat:      /* experimental, requires legality testing to be off */\r
858         break;\r
859       }\r
860     }\r
861 \r
862     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard\r
863     InitEngineUCI( installDir, &second );\r
864 }\r
865 \r
866 int NextIntegerFromString( char ** str, long * value )\r
867 {\r
868     int result = -1;\r
869     char * s = *str;\r
870 \r
871     while( *s == ' ' || *s == '\t' ) {\r
872         s++;\r
873     }\r
874 \r
875     *value = 0;\r
876 \r
877     if( *s >= '0' && *s <= '9' ) {\r
878         while( *s >= '0' && *s <= '9' ) {\r
879             *value = *value * 10 + (*s - '0');\r
880             s++;\r
881         }\r
882 \r
883         result = 0;\r
884     }\r
885 \r
886     *str = s;\r
887 \r
888     return result;\r
889 }\r
890 \r
891 int NextTimeControlFromString( char ** str, long * value )\r
892 {\r
893     long temp;\r
894     int result = NextIntegerFromString( str, &temp );\r
895 \r
896     if( result == 0 ) {\r
897         *value = temp * 60; /* Minutes */\r
898         if( **str == ':' ) {\r
899             (*str)++;\r
900             result = NextIntegerFromString( str, &temp );\r
901             *value += temp; /* Seconds */\r
902         }\r
903     }\r
904 \r
905     return result;\r
906 }\r
907 \r
908 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)\r
909 {   /* [HGM] routine added to read '+moves/time' for secondary time control */\r
910     int result = -1; long temp, temp2;\r
911 \r
912     if(**str != '+') return -1; // old params remain in force!\r
913     (*str)++;\r
914     if( NextTimeControlFromString( str, &temp ) ) return -1;\r
915 \r
916     if(**str != '/') {\r
917         /* time only: incremental or sudden-death time control */\r
918         if(**str == '+') { /* increment follows; read it */\r
919             (*str)++;\r
920             if(result = NextIntegerFromString( str, &temp2)) return -1;\r
921             *inc = temp2 * 1000;\r
922         } else *inc = 0;\r
923         *moves = 0; *tc = temp * 1000; \r
924         return 0;\r
925     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */\r
926 \r
927     (*str)++; /* classical time control */\r
928     result = NextTimeControlFromString( str, &temp2);\r
929     if(result == 0) {\r
930         *moves = temp/60;\r
931         *tc    = temp2 * 1000;\r
932         *inc   = 0;\r
933     }\r
934     return result;\r
935 }\r
936 \r
937 int GetTimeQuota(int movenr)\r
938 {   /* [HGM] get time to add from the multi-session time-control string */\r
939     int moves=1; /* kludge to force reading of first session */\r
940     long time, increment;\r
941     char *s = fullTimeControlString;\r
942 \r
943     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);\r
944     do {\r
945         if(moves) NextSessionFromString(&s, &moves, &time, &increment);\r
946         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);\r
947         if(movenr == -1) return time;    /* last move before new session     */\r
948         if(!moves) return increment;     /* current session is incremental   */\r
949         if(movenr >= 0) movenr -= moves; /* we already finished this session */\r
950     } while(movenr >= -1);               /* try again for next session       */\r
951 \r
952     return 0; // no new time quota on this move\r
953 }\r
954 \r
955 int\r
956 ParseTimeControl(tc, ti, mps)\r
957      char *tc;\r
958      int ti;\r
959      int mps;\r
960 {\r
961 #if 0\r
962     int matched, min, sec;\r
963 \r
964     matched = sscanf(tc, "%d:%d", &min, &sec);\r
965     if (matched == 1) {\r
966         timeControl = min * 60 * 1000;\r
967     } else if (matched == 2) {\r
968         timeControl = (min * 60 + sec) * 1000;\r
969     } else {\r
970         return FALSE;\r
971     }\r
972 #else\r
973     long tc1;\r
974     long tc2;\r
975     char buf[MSG_SIZ];\r
976 \r
977     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;\r
978     if(ti > 0) {\r
979         if(mps)\r
980              sprintf(buf, "+%d/%s+%d", mps, tc, ti);\r
981         else sprintf(buf, "+%s+%d", tc, ti);\r
982     } else {\r
983         if(mps)\r
984              sprintf(buf, "+%d/%s", mps, tc);\r
985         else sprintf(buf, "+%s", tc);\r
986     }\r
987     fullTimeControlString = StrSave(buf);\r
988 \r
989     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
990         return FALSE;\r
991     }\r
992 \r
993     if( *tc == '/' ) {\r
994         /* Parse second time control */\r
995         tc++;\r
996 \r
997         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
998             return FALSE;\r
999         }\r
1000 \r
1001         if( tc2 == 0 ) {\r
1002             return FALSE;\r
1003         }\r
1004 \r
1005         timeControl_2 = tc2 * 1000;\r
1006     }\r
1007     else {\r
1008         timeControl_2 = 0;\r
1009     }\r
1010 \r
1011     if( tc1 == 0 ) {\r
1012         return FALSE;\r
1013     }\r
1014 \r
1015     timeControl = tc1 * 1000;\r
1016 #endif\r
1017 \r
1018     if (ti >= 0) {\r
1019         timeIncrement = ti * 1000;  /* convert to ms */\r
1020         movesPerSession = 0;\r
1021     } else {\r
1022         timeIncrement = 0;\r
1023         movesPerSession = mps;\r
1024     }\r
1025     return TRUE;\r
1026 }\r
1027 \r
1028 void\r
1029 InitBackEnd2()\r
1030 {\r
1031     if (appData.debugMode) {\r
1032         fprintf(debugFP, "%s\n", programVersion);\r
1033     }\r
1034 \r
1035     if (appData.matchGames > 0) {\r
1036         appData.matchMode = TRUE;\r
1037     } else if (appData.matchMode) {\r
1038         appData.matchGames = 1;\r
1039     }\r
1040     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */\r
1041         appData.matchGames = appData.sameColorGames;\r
1042     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */\r
1043         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;\r
1044         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;\r
1045     }\r
1046     Reset(TRUE, FALSE);\r
1047     if (appData.noChessProgram || first.protocolVersion == 1) {\r
1048       InitBackEnd3();\r
1049     } else {\r
1050       /* kludge: allow timeout for initial "feature" commands */\r
1051       FreezeUI();\r
1052       DisplayMessage("", "Starting chess program");\r
1053       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
1054     }\r
1055 }\r
1056 \r
1057 void\r
1058 InitBackEnd3 P((void))\r
1059 {\r
1060     GameMode initialMode;\r
1061     char buf[MSG_SIZ];\r
1062     int err;\r
1063 \r
1064     InitChessProgram(&first, startedFromSetupPosition);\r
1065 \r
1066     if (appData.icsActive) {\r
1067         err = establish();\r
1068         if (err != 0) {\r
1069             if (*appData.icsCommPort != NULLCHAR) {\r
1070                 sprintf(buf, "Could not open comm port %s",  \r
1071                         appData.icsCommPort);\r
1072             } else {\r
1073                 sprintf(buf, "Could not connect to host %s, port %s",  \r
1074                         appData.icsHost, appData.icsPort);\r
1075             }\r
1076             DisplayFatalError(buf, err, 1);\r
1077             return;\r
1078         }\r
1079         SetICSMode();\r
1080         telnetISR =\r
1081           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
1082         fromUserISR =\r
1083           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
1084     } else if (appData.noChessProgram) {\r
1085         SetNCPMode();\r
1086     } else {\r
1087         SetGNUMode();\r
1088     }\r
1089 \r
1090     if (*appData.cmailGameName != NULLCHAR) {\r
1091         SetCmailMode();\r
1092         OpenLoopback(&cmailPR);\r
1093         cmailISR =\r
1094           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
1095     }\r
1096     \r
1097     ThawUI();\r
1098     DisplayMessage("", "");\r
1099     if (StrCaseCmp(appData.initialMode, "") == 0) {\r
1100       initialMode = BeginningOfGame;\r
1101     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
1102       initialMode = TwoMachinesPlay;\r
1103     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
1104       initialMode = AnalyzeFile; \r
1105     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
1106       initialMode = AnalyzeMode;\r
1107     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
1108       initialMode = MachinePlaysWhite;\r
1109     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
1110       initialMode = MachinePlaysBlack;\r
1111     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
1112       initialMode = EditGame;\r
1113     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
1114       initialMode = EditPosition;\r
1115     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
1116       initialMode = Training;\r
1117     } else {\r
1118       sprintf(buf, "Unknown initialMode %s", appData.initialMode);\r
1119       DisplayFatalError(buf, 0, 2);\r
1120       return;\r
1121     }\r
1122 \r
1123     if (appData.matchMode) {\r
1124         /* Set up machine vs. machine match */\r
1125         if (appData.noChessProgram) {\r
1126             DisplayFatalError("Can't have a match with no chess programs",\r
1127                               0, 2);\r
1128             return;\r
1129         }\r
1130         matchMode = TRUE;\r
1131         matchGame = 1;\r
1132         if (*appData.loadGameFile != NULLCHAR) {\r
1133             int index = appData.loadGameIndex; // [HGM] autoinc\r
1134             if(index<0) lastIndex = index = 1;\r
1135             if (!LoadGameFromFile(appData.loadGameFile,\r
1136                                   index,\r
1137                                   appData.loadGameFile, FALSE)) {\r
1138                 DisplayFatalError("Bad game file", 0, 1);\r
1139                 return;\r
1140             }\r
1141         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1142             int index = appData.loadPositionIndex; // [HGM] autoinc\r
1143             if(index<0) lastIndex = index = 1;\r
1144             if (!LoadPositionFromFile(appData.loadPositionFile,\r
1145                                       index,\r
1146                                       appData.loadPositionFile)) {\r
1147                 DisplayFatalError("Bad position file", 0, 1);\r
1148                 return;\r
1149             }\r
1150         }\r
1151         TwoMachinesEvent();\r
1152     } else if (*appData.cmailGameName != NULLCHAR) {\r
1153         /* Set up cmail mode */\r
1154         ReloadCmailMsgEvent(TRUE);\r
1155     } else {\r
1156         /* Set up other modes */\r
1157         if (initialMode == AnalyzeFile) {\r
1158           if (*appData.loadGameFile == NULLCHAR) {\r
1159             DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1);\r
1160             return;\r
1161           }\r
1162         }\r
1163         if (*appData.loadGameFile != NULLCHAR) {\r
1164             (void) LoadGameFromFile(appData.loadGameFile,\r
1165                                     appData.loadGameIndex,\r
1166                                     appData.loadGameFile, TRUE);\r
1167         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1168             (void) LoadPositionFromFile(appData.loadPositionFile,\r
1169                                         appData.loadPositionIndex,\r
1170                                         appData.loadPositionFile);\r
1171             /* [HGM] try to make self-starting even after FEN load */\r
1172             /* to allow automatic setup of fairy variants with wtm */\r
1173             if(initialMode == BeginningOfGame && !blackPlaysFirst) {\r
1174                 gameMode = BeginningOfGame;\r
1175                 setboardSpoiledMachineBlack = 1;\r
1176             }\r
1177             /* [HGM] loadPos: make that every new game uses the setup */\r
1178             /* from file as long as we do not switch variant          */\r
1179             if(!blackPlaysFirst) { int i;\r
1180                 startedFromPositionFile = TRUE;\r
1181                 CopyBoard(filePosition, boards[0]);\r
1182                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];\r
1183             }\r
1184         }\r
1185         if (initialMode == AnalyzeMode) {\r
1186           if (appData.noChessProgram) {\r
1187             DisplayFatalError("Analysis mode requires a chess engine", 0, 2);\r
1188             return;\r
1189           }\r
1190           if (appData.icsActive) {\r
1191             DisplayFatalError("Analysis mode does not work with ICS mode",0,2);\r
1192             return;\r
1193           }\r
1194           AnalyzeModeEvent();\r
1195         } else if (initialMode == AnalyzeFile) {\r
1196           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent\r
1197           ShowThinkingEvent();\r
1198           AnalyzeFileEvent();\r
1199           AnalysisPeriodicEvent(1);\r
1200         } else if (initialMode == MachinePlaysWhite) {\r
1201           if (appData.noChessProgram) {\r
1202             DisplayFatalError("MachineWhite mode requires a chess engine",\r
1203                               0, 2);\r
1204             return;\r
1205           }\r
1206           if (appData.icsActive) {\r
1207             DisplayFatalError("MachineWhite mode does not work with ICS mode",\r
1208                               0, 2);\r
1209             return;\r
1210           }\r
1211           MachineWhiteEvent();\r
1212         } else if (initialMode == MachinePlaysBlack) {\r
1213           if (appData.noChessProgram) {\r
1214             DisplayFatalError("MachineBlack mode requires a chess engine",\r
1215                               0, 2);\r
1216             return;\r
1217           }\r
1218           if (appData.icsActive) {\r
1219             DisplayFatalError("MachineBlack mode does not work with ICS mode",\r
1220                               0, 2);\r
1221             return;\r
1222           }\r
1223           MachineBlackEvent();\r
1224         } else if (initialMode == TwoMachinesPlay) {\r
1225           if (appData.noChessProgram) {\r
1226             DisplayFatalError("TwoMachines mode requires a chess engine",\r
1227                               0, 2);\r
1228             return;\r
1229           }\r
1230           if (appData.icsActive) {\r
1231             DisplayFatalError("TwoMachines mode does not work with ICS mode",\r
1232                               0, 2);\r
1233             return;\r
1234           }\r
1235           TwoMachinesEvent();\r
1236         } else if (initialMode == EditGame) {\r
1237           EditGameEvent();\r
1238         } else if (initialMode == EditPosition) {\r
1239           EditPositionEvent();\r
1240         } else if (initialMode == Training) {\r
1241           if (*appData.loadGameFile == NULLCHAR) {\r
1242             DisplayFatalError("Training mode requires a game file", 0, 2);\r
1243             return;\r
1244           }\r
1245           TrainingEvent();\r
1246         }\r
1247     }\r
1248 }\r
1249 \r
1250 /*\r
1251  * Establish will establish a contact to a remote host.port.\r
1252  * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
1253  *  used to talk to the host.\r
1254  * Returns 0 if okay, error code if not.\r
1255  */\r
1256 int\r
1257 establish()\r
1258 {\r
1259     char buf[MSG_SIZ];\r
1260 \r
1261     if (*appData.icsCommPort != NULLCHAR) {\r
1262         /* Talk to the host through a serial comm port */\r
1263         return OpenCommPort(appData.icsCommPort, &icsPR);\r
1264 \r
1265     } else if (*appData.gateway != NULLCHAR) {\r
1266         if (*appData.remoteShell == NULLCHAR) {\r
1267             /* Use the rcmd protocol to run telnet program on a gateway host */\r
1268             sprintf(buf, "%s %s %s",\r
1269                     appData.telnetProgram, appData.icsHost, appData.icsPort);\r
1270             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
1271 \r
1272         } else {\r
1273             /* Use the rsh program to run telnet program on a gateway host */\r
1274             if (*appData.remoteUser == NULLCHAR) {\r
1275                 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,\r
1276                         appData.gateway, appData.telnetProgram,\r
1277                         appData.icsHost, appData.icsPort);\r
1278             } else {\r
1279                 sprintf(buf, "%s %s -l %s %s %s %s",\r
1280                         appData.remoteShell, appData.gateway, \r
1281                         appData.remoteUser, appData.telnetProgram,\r
1282                         appData.icsHost, appData.icsPort);\r
1283             }\r
1284             return StartChildProcess(buf, "", &icsPR);\r
1285 \r
1286         }\r
1287     } else if (appData.useTelnet) {\r
1288         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
1289 \r
1290     } else {\r
1291         /* TCP socket interface differs somewhat between\r
1292            Unix and NT; handle details in the front end.\r
1293            */\r
1294         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
1295     }\r
1296 }\r
1297 \r
1298 void\r
1299 show_bytes(fp, buf, count)\r
1300      FILE *fp;\r
1301      char *buf;\r
1302      int count;\r
1303 {\r
1304     while (count--) {\r
1305         if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
1306             fprintf(fp, "\\%03o", *buf & 0xff);\r
1307         } else {\r
1308             putc(*buf, fp);\r
1309         }\r
1310         buf++;\r
1311     }\r
1312     fflush(fp);\r
1313 }\r
1314 \r
1315 /* Returns an errno value */\r
1316 int\r
1317 OutputMaybeTelnet(pr, message, count, outError)\r
1318      ProcRef pr;\r
1319      char *message;\r
1320      int count;\r
1321      int *outError;\r
1322 {\r
1323     char buf[8192], *p, *q, *buflim;\r
1324     int left, newcount, outcount;\r
1325 \r
1326     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
1327         *appData.gateway != NULLCHAR) {\r
1328         if (appData.debugMode) {\r
1329             fprintf(debugFP, ">ICS: ");\r
1330             show_bytes(debugFP, message, count);\r
1331             fprintf(debugFP, "\n");\r
1332         }\r
1333         return OutputToProcess(pr, message, count, outError);\r
1334     }\r
1335 \r
1336     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
1337     p = message;\r
1338     q = buf;\r
1339     left = count;\r
1340     newcount = 0;\r
1341     while (left) {\r
1342         if (q >= buflim) {\r
1343             if (appData.debugMode) {\r
1344                 fprintf(debugFP, ">ICS: ");\r
1345                 show_bytes(debugFP, buf, newcount);\r
1346                 fprintf(debugFP, "\n");\r
1347             }\r
1348             outcount = OutputToProcess(pr, buf, newcount, outError);\r
1349             if (outcount < newcount) return -1; /* to be sure */\r
1350             q = buf;\r
1351             newcount = 0;\r
1352         }\r
1353         if (*p == '\n') {\r
1354             *q++ = '\r';\r
1355             newcount++;\r
1356         } else if (((unsigned char) *p) == TN_IAC) {\r
1357             *q++ = (char) TN_IAC;\r
1358             newcount ++;\r
1359         }\r
1360         *q++ = *p++;\r
1361         newcount++;\r
1362         left--;\r
1363     }\r
1364     if (appData.debugMode) {\r
1365         fprintf(debugFP, ">ICS: ");\r
1366         show_bytes(debugFP, buf, newcount);\r
1367         fprintf(debugFP, "\n");\r
1368     }\r
1369     outcount = OutputToProcess(pr, buf, newcount, outError);\r
1370     if (outcount < newcount) return -1; /* to be sure */\r
1371     return count;\r
1372 }\r
1373 \r
1374 void\r
1375 read_from_player(isr, closure, message, count, error)\r
1376      InputSourceRef isr;\r
1377      VOIDSTAR closure;\r
1378      char *message;\r
1379      int count;\r
1380      int error;\r
1381 {\r
1382     int outError, outCount;\r
1383     static int gotEof = 0;\r
1384 \r
1385     /* Pass data read from player on to ICS */\r
1386     if (count > 0) {\r
1387         gotEof = 0;\r
1388         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
1389         if (outCount < count) {\r
1390             DisplayFatalError("Error writing to ICS", outError, 1);\r
1391         }\r
1392     } else if (count < 0) {\r
1393         RemoveInputSource(isr);\r
1394         DisplayFatalError("Error reading from keyboard", error, 1);\r
1395     } else if (gotEof++ > 0) {\r
1396         RemoveInputSource(isr);\r
1397         DisplayFatalError("Got end of file from keyboard", 0, 0);\r
1398     }\r
1399 }\r
1400 \r
1401 void\r
1402 SendToICS(s)\r
1403      char *s;\r
1404 {\r
1405     int count, outCount, outError;\r
1406 \r
1407     if (icsPR == NULL) return;\r
1408 \r
1409     count = strlen(s);\r
1410     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
1411     if (outCount < count) {\r
1412         DisplayFatalError("Error writing to ICS", outError, 1);\r
1413     }\r
1414 }\r
1415 \r
1416 /* This is used for sending logon scripts to the ICS. Sending\r
1417    without a delay causes problems when using timestamp on ICC\r
1418    (at least on my machine). */\r
1419 void\r
1420 SendToICSDelayed(s,msdelay)\r
1421      char *s;\r
1422      long msdelay;\r
1423 {\r
1424     int count, outCount, outError;\r
1425 \r
1426     if (icsPR == NULL) return;\r
1427 \r
1428     count = strlen(s);\r
1429     if (appData.debugMode) {\r
1430         fprintf(debugFP, ">ICS: ");\r
1431         show_bytes(debugFP, s, count);\r
1432         fprintf(debugFP, "\n");\r
1433     }\r
1434     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
1435                                       msdelay);\r
1436     if (outCount < count) {\r
1437         DisplayFatalError("Error writing to ICS", outError, 1);\r
1438     }\r
1439 }\r
1440 \r
1441 \r
1442 /* Remove all highlighting escape sequences in s\r
1443    Also deletes any suffix starting with '(' \r
1444    */\r
1445 char *\r
1446 StripHighlightAndTitle(s)\r
1447      char *s;\r
1448 {\r
1449     static char retbuf[MSG_SIZ];\r
1450     char *p = retbuf;\r
1451 \r
1452     while (*s != NULLCHAR) {\r
1453         while (*s == '\033') {\r
1454             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1455             if (*s != NULLCHAR) s++;\r
1456         }\r
1457         while (*s != NULLCHAR && *s != '\033') {\r
1458             if (*s == '(' || *s == '[') {\r
1459                 *p = NULLCHAR;\r
1460                 return retbuf;\r
1461             }\r
1462             *p++ = *s++;\r
1463         }\r
1464     }\r
1465     *p = NULLCHAR;\r
1466     return retbuf;\r
1467 }\r
1468 \r
1469 /* Remove all highlighting escape sequences in s */\r
1470 char *\r
1471 StripHighlight(s)\r
1472      char *s;\r
1473 {\r
1474     static char retbuf[MSG_SIZ];\r
1475     char *p = retbuf;\r
1476 \r
1477     while (*s != NULLCHAR) {\r
1478         while (*s == '\033') {\r
1479             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1480             if (*s != NULLCHAR) s++;\r
1481         }\r
1482         while (*s != NULLCHAR && *s != '\033') {\r
1483             *p++ = *s++;\r
1484         }\r
1485     }\r
1486     *p = NULLCHAR;\r
1487     return retbuf;\r
1488 }\r
1489 \r
1490 char *variantNames[] = VARIANT_NAMES;\r
1491 char *\r
1492 VariantName(v)\r
1493      VariantClass v;\r
1494 {\r
1495     return variantNames[v];\r
1496 }\r
1497 \r
1498 \r
1499 /* Identify a variant from the strings the chess servers use or the\r
1500    PGN Variant tag names we use. */\r
1501 VariantClass\r
1502 StringToVariant(e)\r
1503      char *e;\r
1504 {\r
1505     char *p;\r
1506     int wnum = -1;\r
1507     VariantClass v = VariantNormal;\r
1508     int i, found = FALSE;\r
1509     char buf[MSG_SIZ];\r
1510 \r
1511     if (!e) return v;\r
1512 \r
1513     /* [HGM] skip over optional board-size prefixes */\r
1514     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||\r
1515         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
1516         while( *e++ != '_');\r
1517     }\r
1518 \r
1519     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1520       if (StrCaseStr(e, variantNames[i])) {\r
1521         v = (VariantClass) i;\r
1522         found = TRUE;\r
1523         break;\r
1524       }\r
1525     }\r
1526 \r
1527     if (!found) {\r
1528       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1529           || StrCaseStr(e, "wild/fr") \r
1530           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {\r
1531         v = VariantFischeRandom;\r
1532       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1533                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1534         p += i;\r
1535         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1536         if (isdigit(*p)) {\r
1537           wnum = atoi(p);\r
1538         } else {\r
1539           wnum = -1;\r
1540         }\r
1541         switch (wnum) {\r
1542         case 0: /* FICS only, actually */\r
1543         case 1:\r
1544           /* Castling legal even if K starts on d-file */\r
1545           v = VariantWildCastle;\r
1546           break;\r
1547         case 2:\r
1548         case 3:\r
1549         case 4:\r
1550           /* Castling illegal even if K & R happen to start in\r
1551              normal positions. */\r
1552           v = VariantNoCastle;\r
1553           break;\r
1554         case 5:\r
1555         case 7:\r
1556         case 8:\r
1557         case 10:\r
1558         case 11:\r
1559         case 12:\r
1560         case 13:\r
1561         case 14:\r
1562         case 15:\r
1563         case 18:\r
1564         case 19:\r
1565           /* Castling legal iff K & R start in normal positions */\r
1566           v = VariantNormal;\r
1567           break;\r
1568         case 6:\r
1569         case 20:\r
1570         case 21:\r
1571           /* Special wilds for position setup; unclear what to do here */\r
1572           v = VariantLoadable;\r
1573           break;\r
1574         case 9:\r
1575           /* Bizarre ICC game */\r
1576           v = VariantTwoKings;\r
1577           break;\r
1578         case 16:\r
1579           v = VariantKriegspiel;\r
1580           break;\r
1581         case 17:\r
1582           v = VariantLosers;\r
1583           break;\r
1584         case 22:\r
1585           v = VariantFischeRandom;\r
1586           break;\r
1587         case 23:\r
1588           v = VariantCrazyhouse;\r
1589           break;\r
1590         case 24:\r
1591           v = VariantBughouse;\r
1592           break;\r
1593         case 25:\r
1594           v = Variant3Check;\r
1595           break;\r
1596         case 26:\r
1597           /* Not quite the same as FICS suicide! */\r
1598           v = VariantGiveaway;\r
1599           break;\r
1600         case 27:\r
1601           v = VariantAtomic;\r
1602           break;\r
1603         case 28:\r
1604           v = VariantShatranj;\r
1605           break;\r
1606 \r
1607         /* Temporary names for future ICC types.  The name *will* change in \r
1608            the next xboard/WinBoard release after ICC defines it. */\r
1609         case 29:\r
1610           v = Variant29;\r
1611           break;\r
1612         case 30:\r
1613           v = Variant30;\r
1614           break;\r
1615         case 31:\r
1616           v = Variant31;\r
1617           break;\r
1618         case 32:\r
1619           v = Variant32;\r
1620           break;\r
1621         case 33:\r
1622           v = Variant33;\r
1623           break;\r
1624         case 34:\r
1625           v = Variant34;\r
1626           break;\r
1627         case 35:\r
1628           v = Variant35;\r
1629           break;\r
1630         case 36:\r
1631           v = Variant36;\r
1632           break;\r
1633         case 37:\r
1634           v = VariantShogi;\r
1635           break;\r
1636         case 38:\r
1637           v = VariantXiangqi;\r
1638           break;\r
1639         case 39:\r
1640           v = VariantCourier;\r
1641           break;\r
1642         case 40:\r
1643           v = VariantGothic;\r
1644           break;\r
1645         case 41:\r
1646           v = VariantCapablanca;\r
1647           break;\r
1648         case 42:\r
1649           v = VariantKnightmate;\r
1650           break;\r
1651         case 43:\r
1652           v = VariantFairy;\r
1653           break;\r
1654         case 44:\r
1655           v = VariantCylinder;\r
1656           break;\r
1657         case 45:\r
1658           v = VariantFalcon;\r
1659           break;\r
1660         case 46:\r
1661           v = VariantCapaRandom;\r
1662           break;\r
1663         case 47:\r
1664           v = VariantBerolina;\r
1665           break;\r
1666         case 48:\r
1667           v = VariantJanus;\r
1668           break;\r
1669         case 49:\r
1670           v = VariantSuper;\r
1671           break;\r
1672         case 50:\r
1673           v = VariantGreat;\r
1674           break;\r
1675         case -1:\r
1676           /* Found "wild" or "w" in the string but no number;\r
1677              must assume it's normal chess. */\r
1678           v = VariantNormal;\r
1679           break;\r
1680         default:\r
1681           sprintf(buf, "Unknown wild type %d", wnum);\r
1682           DisplayError(buf, 0);\r
1683           v = VariantUnknown;\r
1684           break;\r
1685         }\r
1686       }\r
1687     }\r
1688     if (appData.debugMode) {\r
1689       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",\r
1690               e, wnum, VariantName(v));\r
1691     }\r
1692     return v;\r
1693 }\r
1694 \r
1695 static int leftover_start = 0, leftover_len = 0;\r
1696 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1697 \r
1698 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1699    advance *index beyond it, and set leftover_start to the new value of\r
1700    *index; else return FALSE.  If pattern contains the character '*', it\r
1701    matches any sequence of characters not containing '\r', '\n', or the\r
1702    character following the '*' (if any), and the matched sequence(s) are\r
1703    copied into star_match.\r
1704    */\r
1705 int\r
1706 looking_at(buf, index, pattern)\r
1707      char *buf;\r
1708      int *index;\r
1709      char *pattern;\r
1710 {\r
1711     char *bufp = &buf[*index], *patternp = pattern;\r
1712     int star_count = 0;\r
1713     char *matchp = star_match[0];\r
1714     \r
1715     for (;;) {\r
1716         if (*patternp == NULLCHAR) {\r
1717             *index = leftover_start = bufp - buf;\r
1718             *matchp = NULLCHAR;\r
1719             return TRUE;\r
1720         }\r
1721         if (*bufp == NULLCHAR) return FALSE;\r
1722         if (*patternp == '*') {\r
1723             if (*bufp == *(patternp + 1)) {\r
1724                 *matchp = NULLCHAR;\r
1725                 matchp = star_match[++star_count];\r
1726                 patternp += 2;\r
1727                 bufp++;\r
1728                 continue;\r
1729             } else if (*bufp == '\n' || *bufp == '\r') {\r
1730                 patternp++;\r
1731                 if (*patternp == NULLCHAR)\r
1732                   continue;\r
1733                 else\r
1734                   return FALSE;\r
1735             } else {\r
1736                 *matchp++ = *bufp++;\r
1737                 continue;\r
1738             }\r
1739         }\r
1740         if (*patternp != *bufp) return FALSE;\r
1741         patternp++;\r
1742         bufp++;\r
1743     }\r
1744 }\r
1745 \r
1746 void\r
1747 SendToPlayer(data, length)\r
1748      char *data;\r
1749      int length;\r
1750 {\r
1751     int error, outCount;\r
1752     outCount = OutputToProcess(NoProc, data, length, &error);\r
1753     if (outCount < length) {\r
1754         DisplayFatalError("Error writing to display", error, 1);\r
1755     }\r
1756 }\r
1757 \r
1758 void\r
1759 PackHolding(packed, holding)\r
1760      char packed[];\r
1761      char *holding;\r
1762 {\r
1763     char *p = holding;\r
1764     char *q = packed;\r
1765     int runlength = 0;\r
1766     int curr = 9999;\r
1767     do {\r
1768         if (*p == curr) {\r
1769             runlength++;\r
1770         } else {\r
1771             switch (runlength) {\r
1772               case 0:\r
1773                 break;\r
1774               case 1:\r
1775                 *q++ = curr;\r
1776                 break;\r
1777               case 2:\r
1778                 *q++ = curr;\r
1779                 *q++ = curr;\r
1780                 break;\r
1781               default:\r
1782                 sprintf(q, "%d", runlength);\r
1783                 while (*q) q++;\r
1784                 *q++ = curr;\r
1785                 break;\r
1786             }\r
1787             runlength = 1;\r
1788             curr = *p;\r
1789         }\r
1790     } while (*p++);\r
1791     *q = NULLCHAR;\r
1792 }\r
1793 \r
1794 /* Telnet protocol requests from the front end */\r
1795 void\r
1796 TelnetRequest(ddww, option)\r
1797      unsigned char ddww, option;\r
1798 {\r
1799     unsigned char msg[3];\r
1800     int outCount, outError;\r
1801 \r
1802     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1803 \r
1804     if (appData.debugMode) {\r
1805         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1806         switch (ddww) {\r
1807           case TN_DO:\r
1808             ddwwStr = "DO";\r
1809             break;\r
1810           case TN_DONT:\r
1811             ddwwStr = "DONT";\r
1812             break;\r
1813           case TN_WILL:\r
1814             ddwwStr = "WILL";\r
1815             break;\r
1816           case TN_WONT:\r
1817             ddwwStr = "WONT";\r
1818             break;\r
1819           default:\r
1820             ddwwStr = buf1;\r
1821             sprintf(buf1, "%d", ddww);\r
1822             break;\r
1823         }\r
1824         switch (option) {\r
1825           case TN_ECHO:\r
1826             optionStr = "ECHO";\r
1827             break;\r
1828           default:\r
1829             optionStr = buf2;\r
1830             sprintf(buf2, "%d", option);\r
1831             break;\r
1832         }\r
1833         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1834     }\r
1835     msg[0] = TN_IAC;\r
1836     msg[1] = ddww;\r
1837     msg[2] = option;\r
1838     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1839     if (outCount < 3) {\r
1840         DisplayFatalError("Error writing to ICS", outError, 1);\r
1841     }\r
1842 }\r
1843 \r
1844 void\r
1845 DoEcho()\r
1846 {\r
1847     if (!appData.icsActive) return;\r
1848     TelnetRequest(TN_DO, TN_ECHO);\r
1849 }\r
1850 \r
1851 void\r
1852 DontEcho()\r
1853 {\r
1854     if (!appData.icsActive) return;\r
1855     TelnetRequest(TN_DONT, TN_ECHO);\r
1856 }\r
1857 \r
1858 void\r
1859 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
1860 {\r
1861     /* put the holdings sent to us by the server on the board holdings area */\r
1862     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
1863     char p;\r
1864     ChessSquare piece;\r
1865 \r
1866     if(gameInfo.holdingsWidth < 2)  return;\r
1867 \r
1868     if( (int)lowestPiece >= BlackPawn ) {\r
1869         holdingsColumn = 0;\r
1870         countsColumn = 1;\r
1871         holdingsStartRow = BOARD_HEIGHT-1;\r
1872         direction = -1;\r
1873     } else {\r
1874         holdingsColumn = BOARD_WIDTH-1;\r
1875         countsColumn = BOARD_WIDTH-2;\r
1876         holdingsStartRow = 0;\r
1877         direction = 1;\r
1878     }\r
1879 \r
1880     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
1881         board[i][holdingsColumn] = EmptySquare;\r
1882         board[i][countsColumn]   = (ChessSquare) 0;\r
1883     }\r
1884     while( (p=*holdings++) != NULLCHAR ) {\r
1885         piece = CharToPiece( ToUpper(p) );\r
1886         if(piece == EmptySquare) continue;\r
1887         /*j = (int) piece - (int) WhitePawn;*/\r
1888         j = PieceToNumber(piece);\r
1889         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1890         if(j < 0) continue;               /* should not happen */\r
1891         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );\r
1892         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
1893         board[holdingsStartRow+j*direction][countsColumn]++;\r
1894     }\r
1895 \r
1896 }\r
1897 \r
1898 \r
1899 void\r
1900 VariantSwitch(Board board, VariantClass newVariant)\r
1901 {\r
1902    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;\r
1903    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;\r
1904    Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;\r
1905 \r
1906    startedFromPositionFile = FALSE;\r
1907    if(gameInfo.variant == newVariant) return;\r
1908 \r
1909    /* [HGM] This routine is called each time an assignment is made to\r
1910     * gameInfo.variant during a game, to make sure the board sizes\r
1911     * are set to match the new variant. If that means adding or deleting\r
1912     * holdings, we shift the playing board accordingly\r
1913     * This kludge is needed because in ICS observe mode, we get boards\r
1914     * of an ongoing game without knowing the variant, and learn about the\r
1915     * latter only later. This can be because of the move list we requested,\r
1916     * in which case the game history is refilled from the beginning anyway,\r
1917     * but also when receiving holdings of a crazyhouse game. In the latter\r
1918     * case we want to add those holdings to the already received position.\r
1919     */\r
1920 \r
1921 \r
1922   if (appData.debugMode) {\r
1923     fprintf(debugFP, "Switch board from %s to %s\n",\r
1924                VariantName(gameInfo.variant), VariantName(newVariant));\r
1925     setbuf(debugFP, NULL);\r
1926   }\r
1927     shuffleOpenings = 0;       /* [HGM] shuffle */\r
1928     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
1929     switch(newVariant) {\r
1930             case VariantShogi:\r
1931               newWidth = 9;  newHeight = 9;\r
1932               gameInfo.holdingsSize = 7;\r
1933             case VariantBughouse:\r
1934             case VariantCrazyhouse:\r
1935               newHoldingsWidth = 2; break;\r
1936             default:\r
1937               newHoldingsWidth = gameInfo.holdingsSize = 0;\r
1938     }\r
1939 \r
1940     if(newWidth  != gameInfo.boardWidth  ||\r
1941        newHeight != gameInfo.boardHeight ||\r
1942        newHoldingsWidth != gameInfo.holdingsWidth ) {\r
1943 \r
1944         /* shift position to new playing area, if needed */\r
1945         if(newHoldingsWidth > gameInfo.holdingsWidth) {\r
1946            for(i=0; i<BOARD_HEIGHT; i++) \r
1947                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)\r
1948                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
1949                                                      board[i][j];\r
1950            for(i=0; i<newHeight; i++) {\r
1951                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;\r
1952                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;\r
1953            }\r
1954         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {\r
1955            for(i=0; i<BOARD_HEIGHT; i++)\r
1956                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
1957                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
1958                                                  board[i][j];\r
1959         }\r
1960 \r
1961         gameInfo.boardWidth  = newWidth;\r
1962         gameInfo.boardHeight = newHeight;\r
1963         gameInfo.holdingsWidth = newHoldingsWidth;\r
1964         gameInfo.variant = newVariant;\r
1965         InitDrawingSizes(-2, 0);\r
1966 \r
1967         /* [HGM] The following should definitely be solved in a better way */\r
1968 #if 0\r
1969         CopyBoard(board, tempBoard); /* save position in case it is board[0] */\r
1970         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];\r
1971         saveEP = epStatus[0];\r
1972 #endif\r
1973         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */\r
1974 #if 0\r
1975         epStatus[0] = saveEP;\r
1976         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];\r
1977         CopyBoard(tempBoard, board); /* restore position received from ICS   */\r
1978 #endif\r
1979     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }\r
1980 \r
1981     forwardMostMove = oldForwardMostMove;\r
1982     backwardMostMove = oldBackwardMostMove;\r
1983     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */\r
1984 }\r
1985 \r
1986 static int loggedOn = FALSE;\r
1987 \r
1988 /*-- Game start info cache: --*/\r
1989 int gs_gamenum;\r
1990 char gs_kind[MSG_SIZ];\r
1991 static char player1Name[128] = "";\r
1992 static char player2Name[128] = "";\r
1993 static int player1Rating = -1;\r
1994 static int player2Rating = -1;\r
1995 /*----------------------------*/\r
1996 \r
1997 ColorClass curColor = ColorNormal;\r
1998 int suppressKibitz = 0;\r
1999 \r
2000 void\r
2001 read_from_ics(isr, closure, data, count, error)\r
2002      InputSourceRef isr;\r
2003      VOIDSTAR closure;\r
2004      char *data;\r
2005      int count;\r
2006      int error;\r
2007 {\r
2008 #define BUF_SIZE 8192\r
2009 #define STARTED_NONE 0\r
2010 #define STARTED_MOVES 1\r
2011 #define STARTED_BOARD 2\r
2012 #define STARTED_OBSERVE 3\r
2013 #define STARTED_HOLDINGS 4\r
2014 #define STARTED_CHATTER 5\r
2015 #define STARTED_COMMENT 6\r
2016 #define STARTED_MOVES_NOHIDE 7\r
2017     \r
2018     static int started = STARTED_NONE;\r
2019     static char parse[20000];\r
2020     static int parse_pos = 0;\r
2021     static char buf[BUF_SIZE + 1];\r
2022     static int firstTime = TRUE, intfSet = FALSE;\r
2023     static ColorClass prevColor = ColorNormal;\r
2024     static int savingComment = FALSE;\r
2025     char str[500];\r
2026     int i, oldi;\r
2027     int buf_len;\r
2028     int next_out;\r
2029     int tkind;\r
2030     char *p;\r
2031 \r
2032 #ifdef WIN32\r
2033     if (appData.debugMode) {\r
2034       if (!error) {\r
2035         fprintf(debugFP, "<ICS: ");\r
2036         show_bytes(debugFP, data, count);\r
2037         fprintf(debugFP, "\n");\r
2038       }\r
2039     }\r
2040 #endif\r
2041 \r
2042     if (appData.debugMode) { int f = forwardMostMove;\r
2043         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,\r
2044                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
2045     }\r
2046     if (count > 0) {\r
2047         /* If last read ended with a partial line that we couldn't parse,\r
2048            prepend it to the new read and try again. */\r
2049         if (leftover_len > 0) {\r
2050             for (i=0; i<leftover_len; i++)\r
2051               buf[i] = buf[leftover_start + i];\r
2052         }\r
2053 \r
2054         /* Copy in new characters, removing nulls and \r's */\r
2055         buf_len = leftover_len;\r
2056         for (i = 0; i < count; i++) {\r
2057             if (data[i] != NULLCHAR && data[i] != '\r')\r
2058               buf[buf_len++] = data[i];\r
2059             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && \r
2060                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') \r
2061                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous\r
2062         }\r
2063 \r
2064         buf[buf_len] = NULLCHAR;\r
2065         next_out = leftover_len;\r
2066         leftover_start = 0;\r
2067         \r
2068         i = 0;\r
2069         while (i < buf_len) {\r
2070             /* Deal with part of the TELNET option negotiation\r
2071                protocol.  We refuse to do anything beyond the\r
2072                defaults, except that we allow the WILL ECHO option,\r
2073                which ICS uses to turn off password echoing when we are\r
2074                directly connected to it.  We reject this option\r
2075                if localLineEditing mode is on (always on in xboard)\r
2076                and we are talking to port 23, which might be a real\r
2077                telnet server that will try to keep WILL ECHO on permanently.\r
2078              */\r
2079             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
2080                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
2081                 unsigned char option;\r
2082                 oldi = i;\r
2083                 switch ((unsigned char) buf[++i]) {\r
2084                   case TN_WILL:\r
2085                     if (appData.debugMode)\r
2086                       fprintf(debugFP, "\n<WILL ");\r
2087                     switch (option = (unsigned char) buf[++i]) {\r
2088                       case TN_ECHO:\r
2089                         if (appData.debugMode)\r
2090                           fprintf(debugFP, "ECHO ");\r
2091                         /* Reply only if this is a change, according\r
2092                            to the protocol rules. */\r
2093                         if (remoteEchoOption) break;\r
2094                         if (appData.localLineEditing &&\r
2095                             atoi(appData.icsPort) == TN_PORT) {\r
2096                             TelnetRequest(TN_DONT, TN_ECHO);\r
2097                         } else {\r
2098                             EchoOff();\r
2099                             TelnetRequest(TN_DO, TN_ECHO);\r
2100                             remoteEchoOption = TRUE;\r
2101                         }\r
2102                         break;\r
2103                       default:\r
2104                         if (appData.debugMode)\r
2105                           fprintf(debugFP, "%d ", option);\r
2106                         /* Whatever this is, we don't want it. */\r
2107                         TelnetRequest(TN_DONT, option);\r
2108                         break;\r
2109                     }\r
2110                     break;\r
2111                   case TN_WONT:\r
2112                     if (appData.debugMode)\r
2113                       fprintf(debugFP, "\n<WONT ");\r
2114                     switch (option = (unsigned char) buf[++i]) {\r
2115                       case TN_ECHO:\r
2116                         if (appData.debugMode)\r
2117                           fprintf(debugFP, "ECHO ");\r
2118                         /* Reply only if this is a change, according\r
2119                            to the protocol rules. */\r
2120                         if (!remoteEchoOption) break;\r
2121                         EchoOn();\r
2122                         TelnetRequest(TN_DONT, TN_ECHO);\r
2123                         remoteEchoOption = FALSE;\r
2124                         break;\r
2125                       default:\r
2126                         if (appData.debugMode)\r
2127                           fprintf(debugFP, "%d ", (unsigned char) option);\r
2128                         /* Whatever this is, it must already be turned\r
2129                            off, because we never agree to turn on\r
2130                            anything non-default, so according to the\r
2131                            protocol rules, we don't reply. */\r
2132                         break;\r
2133                     }\r
2134                     break;\r
2135                   case TN_DO:\r
2136                     if (appData.debugMode)\r
2137                       fprintf(debugFP, "\n<DO ");\r
2138                     switch (option = (unsigned char) buf[++i]) {\r
2139                       default:\r
2140                         /* Whatever this is, we refuse to do it. */\r
2141                         if (appData.debugMode)\r
2142                           fprintf(debugFP, "%d ", option);\r
2143                         TelnetRequest(TN_WONT, option);\r
2144                         break;\r
2145                     }\r
2146                     break;\r
2147                   case TN_DONT:\r
2148                     if (appData.debugMode)\r
2149                       fprintf(debugFP, "\n<DONT ");\r
2150                     switch (option = (unsigned char) buf[++i]) {\r
2151                       default:\r
2152                         if (appData.debugMode)\r
2153                           fprintf(debugFP, "%d ", option);\r
2154                         /* Whatever this is, we are already not doing\r
2155                            it, because we never agree to do anything\r
2156                            non-default, so according to the protocol\r
2157                            rules, we don't reply. */\r
2158                         break;\r
2159                     }\r
2160                     break;\r
2161                   case TN_IAC:\r
2162                     if (appData.debugMode)\r
2163                       fprintf(debugFP, "\n<IAC ");\r
2164                     /* Doubled IAC; pass it through */\r
2165                     i--;\r
2166                     break;\r
2167                   default:\r
2168                     if (appData.debugMode)\r
2169                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
2170                     /* Drop all other telnet commands on the floor */\r
2171                     break;\r
2172                 }\r
2173                 if (oldi > next_out)\r
2174                   SendToPlayer(&buf[next_out], oldi - next_out);\r
2175                 if (++i > next_out)\r
2176                   next_out = i;\r
2177                 continue;\r
2178             }\r
2179                 \r
2180             /* OK, this at least will *usually* work */\r
2181             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
2182                 loggedOn = TRUE;\r
2183             }\r
2184             \r
2185             if (loggedOn && !intfSet) {\r
2186                 if (ics_type == ICS_ICC) {\r
2187                   sprintf(str,\r
2188                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
2189                           programVersion);\r
2190 \r
2191                 } else if (ics_type == ICS_CHESSNET) {\r
2192                   sprintf(str, "/style 12\n");\r
2193                 } else {\r
2194                   strcpy(str, "alias $ @\n$set interface ");\r
2195                   strcat(str, programVersion);\r
2196                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
2197 #ifdef WIN32\r
2198                   strcat(str, "$iset nohighlight 1\n");\r
2199 #endif\r
2200                   strcat(str, "$iset lock 1\n$style 12\n");\r
2201                 }\r
2202                 SendToICS(str);\r
2203                 intfSet = TRUE;\r
2204             }\r
2205 \r
2206             if (started == STARTED_COMMENT) {\r
2207                 /* Accumulate characters in comment */\r
2208                 parse[parse_pos++] = buf[i];\r
2209                 if (buf[i] == '\n') {\r
2210                     parse[parse_pos] = NULLCHAR;\r
2211                     if(!suppressKibitz) // [HGM] kibitz\r
2212                         AppendComment(forwardMostMove, StripHighlight(parse));\r
2213                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window\r
2214                         int nrDigit = 0, nrAlph = 0, i;\r
2215                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input\r
2216                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }\r
2217                         parse[parse_pos] = NULLCHAR;\r
2218                         // try to be smart: if it does not look like search info, it should go to\r
2219                         // ICS interaction window after all, not to engine-output window.\r
2220                         for(i=0; i<parse_pos; i++) { // count letters and digits\r
2221                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');\r
2222                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');\r
2223                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');\r
2224                         }\r
2225                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info\r
2226                             OutputKibitz(suppressKibitz, parse);\r
2227                         } else {\r
2228                             char tmp[MSG_SIZ];\r
2229                             sprintf(tmp, "your opponent kibitzes: %s", parse);\r
2230                             SendToPlayer(tmp, strlen(tmp));\r
2231                         }\r
2232                     }\r
2233                     started = STARTED_NONE;\r
2234                 } else {\r
2235                     /* Don't match patterns against characters in chatter */\r
2236                     i++;\r
2237                     continue;\r
2238                 }\r
2239             }\r
2240             if (started == STARTED_CHATTER) {\r
2241                 if (buf[i] != '\n') {\r
2242                     /* Don't match patterns against characters in chatter */\r
2243                     i++;\r
2244                     continue;\r
2245                 }\r
2246                 started = STARTED_NONE;\r
2247             }\r
2248 \r
2249             /* Kludge to deal with rcmd protocol */\r
2250             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2251                 DisplayFatalError(&buf[1], 0, 1);\r
2252                 continue;\r
2253             } else {\r
2254                 firstTime = FALSE;\r
2255             }\r
2256 \r
2257             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2258                 ics_type = ICS_ICC;\r
2259                 ics_prefix = "/";\r
2260                 if (appData.debugMode)\r
2261                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2262                 continue;\r
2263             }\r
2264             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2265                 ics_type = ICS_FICS;\r
2266                 ics_prefix = "$";\r
2267                 if (appData.debugMode)\r
2268                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2269                 continue;\r
2270             }\r
2271             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2272                 ics_type = ICS_CHESSNET;\r
2273                 ics_prefix = "/";\r
2274                 if (appData.debugMode)\r
2275                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2276                 continue;\r
2277             }\r
2278 \r
2279             if (!loggedOn &&\r
2280                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2281                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2282                  looking_at(buf, &i, "will be \"*\""))) {\r
2283               strcpy(ics_handle, star_match[0]);\r
2284               continue;\r
2285             }\r
2286 \r
2287             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2288               char buf[MSG_SIZ];\r
2289               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2290               DisplayIcsInteractionTitle(buf);\r
2291               have_set_title = TRUE;\r
2292             }\r
2293 \r
2294             /* skip finger notes */\r
2295             if (started == STARTED_NONE &&\r
2296                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2297                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2298                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2299               started = STARTED_CHATTER;\r
2300               i += 3;\r
2301               continue;\r
2302             }\r
2303 \r
2304             /* skip formula vars */\r
2305             if (started == STARTED_NONE &&\r
2306                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2307               started = STARTED_CHATTER;\r
2308               i += 3;\r
2309               continue;\r
2310             }\r
2311 \r
2312             oldi = i;\r
2313             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window\r
2314             if (appData.autoKibitz && started == STARTED_NONE && \r
2315                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {\r
2316                 if(looking_at(buf, &i, "* kibitzes: ") &&\r
2317                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || \r
2318                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent\r
2319                         suppressKibitz = TRUE;\r
2320                         if((StrStr(star_match[0], gameInfo.white) == star_match[0])\r
2321                                 && (gameMode == IcsPlayingWhite) ||\r
2322                            (StrStr(star_match[0], gameInfo.black) == star_match[0])\r
2323                                 && (gameMode == IcsPlayingBlack)   ) // opponent kibitz\r
2324                             started = STARTED_CHATTER; // own kibitz we simply discard\r
2325                         else {\r
2326                             started = STARTED_COMMENT; // make sure it will be collected in parse[]\r
2327                             parse_pos = 0; parse[0] = NULLCHAR;\r
2328                             savingComment = TRUE;\r
2329                             suppressKibitz = gameMode != IcsObserving ? 2 :\r
2330                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;\r
2331                         } \r
2332                         continue;\r
2333                 } else\r
2334                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz\r
2335                     started = STARTED_CHATTER;\r
2336                     suppressKibitz = TRUE;\r
2337                 }\r
2338             } // [HGM] kibitz: end of patch\r
2339 \r
2340             if (appData.zippyTalk || appData.zippyPlay) {\r
2341 #if ZIPPY\r
2342                 if (ZippyControl(buf, &i) ||\r
2343                     ZippyConverse(buf, &i) ||\r
2344                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2345                     loggedOn = TRUE;\r
2346                     continue;\r
2347                 }\r
2348 #endif\r
2349             } else {\r
2350                 if (/* Don't color "message" or "messages" output */\r
2351                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2352                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2353                     looking_at(buf, &i, "--* (*:*): ") ||\r
2354                     /* Regular tells and says */\r
2355                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2356                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2357                     looking_at(buf, &i, "* says: ") ||\r
2358                     /* Message notifications (same color as tells) */\r
2359                     looking_at(buf, &i, "* has left a message ") ||\r
2360                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2361                     /* Whispers and kibitzes */\r
2362                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2363                     looking_at(buf, &i, "* kibitzes: ") ||\r
2364                     /* Channel tells */\r
2365                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2366 \r
2367                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2368                       /* Avoid "tells you:" spoofs in channels */\r
2369                      tkind = 3;\r
2370                   }\r
2371                   if (star_match[0][0] == NULLCHAR ||\r
2372                       strchr(star_match[0], ' ') ||\r
2373                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2374                     /* Reject bogus matches */\r
2375                     i = oldi;\r
2376                   } else {\r
2377                     if (appData.colorize) {\r
2378                       if (oldi > next_out) {\r
2379                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2380                         next_out = oldi;\r
2381                       }\r
2382                       switch (tkind) {\r
2383                       case 1:\r
2384                         Colorize(ColorTell, FALSE);\r
2385                         curColor = ColorTell;\r
2386                         break;\r
2387                       case 2:\r
2388                         Colorize(ColorKibitz, FALSE);\r
2389                         curColor = ColorKibitz;\r
2390                         break;\r
2391                       case 3:\r
2392                         p = strrchr(star_match[1], '(');\r
2393                         if (p == NULL) {\r
2394                           p = star_match[1];\r
2395                         } else {\r
2396                           p++;\r
2397                         }\r
2398                         if (atoi(p) == 1) {\r
2399                           Colorize(ColorChannel1, FALSE);\r
2400                           curColor = ColorChannel1;\r
2401                         } else {\r
2402                           Colorize(ColorChannel, FALSE);\r
2403                           curColor = ColorChannel;\r
2404                         }\r
2405                         break;\r
2406                       case 5:\r
2407                         curColor = ColorNormal;\r
2408                         break;\r
2409                       }\r
2410                     }\r
2411                     if (started == STARTED_NONE && appData.autoComment &&\r
2412                         (gameMode == IcsObserving ||\r
2413                          gameMode == IcsPlayingWhite ||\r
2414                          gameMode == IcsPlayingBlack)) {\r
2415                       parse_pos = i - oldi;\r
2416                       memcpy(parse, &buf[oldi], parse_pos);\r
2417                       parse[parse_pos] = NULLCHAR;\r
2418                       started = STARTED_COMMENT;\r
2419                       savingComment = TRUE;\r
2420                     } else {\r
2421                       started = STARTED_CHATTER;\r
2422                       savingComment = FALSE;\r
2423                     }\r
2424                     loggedOn = TRUE;\r
2425                     continue;\r
2426                   }\r
2427                 }\r
2428 \r
2429                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2430                     looking_at(buf, &i, "* c-shouts: ")) {\r
2431                     if (appData.colorize) {\r
2432                         if (oldi > next_out) {\r
2433                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2434                             next_out = oldi;\r
2435                         }\r
2436                         Colorize(ColorSShout, FALSE);\r
2437                         curColor = ColorSShout;\r
2438                     }\r
2439                     loggedOn = TRUE;\r
2440                     started = STARTED_CHATTER;\r
2441                     continue;\r
2442                 }\r
2443 \r
2444                 if (looking_at(buf, &i, "--->")) {\r
2445                     loggedOn = TRUE;\r
2446                     continue;\r
2447                 }\r
2448 \r
2449                 if (looking_at(buf, &i, "* shouts: ") ||\r
2450                     looking_at(buf, &i, "--> ")) {\r
2451                     if (appData.colorize) {\r
2452                         if (oldi > next_out) {\r
2453                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2454                             next_out = oldi;\r
2455                         }\r
2456                         Colorize(ColorShout, FALSE);\r
2457                         curColor = ColorShout;\r
2458                     }\r
2459                     loggedOn = TRUE;\r
2460                     started = STARTED_CHATTER;\r
2461                     continue;\r
2462                 }\r
2463 \r
2464                 if (looking_at( buf, &i, "Challenge:")) {\r
2465                     if (appData.colorize) {\r
2466                         if (oldi > next_out) {\r
2467                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2468                             next_out = oldi;\r
2469                         }\r
2470                         Colorize(ColorChallenge, FALSE);\r
2471                         curColor = ColorChallenge;\r
2472                     }\r
2473                     loggedOn = TRUE;\r
2474                     continue;\r
2475                 }\r
2476 \r
2477                 if (looking_at(buf, &i, "* offers you") ||\r
2478                     looking_at(buf, &i, "* offers to be") ||\r
2479                     looking_at(buf, &i, "* would like to") ||\r
2480                     looking_at(buf, &i, "* requests to") ||\r
2481                     looking_at(buf, &i, "Your opponent offers") ||\r
2482                     looking_at(buf, &i, "Your opponent requests")) {\r
2483 \r
2484                     if (appData.colorize) {\r
2485                         if (oldi > next_out) {\r
2486                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2487                             next_out = oldi;\r
2488                         }\r
2489                         Colorize(ColorRequest, FALSE);\r
2490                         curColor = ColorRequest;\r
2491                     }\r
2492                     continue;\r
2493                 }\r
2494 \r
2495                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2496                     if (appData.colorize) {\r
2497                         if (oldi > next_out) {\r
2498                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2499                             next_out = oldi;\r
2500                         }\r
2501                         Colorize(ColorSeek, FALSE);\r
2502                         curColor = ColorSeek;\r
2503                     }\r
2504                     continue;\r
2505                 }\r
2506             }\r
2507 \r
2508             if (looking_at(buf, &i, "\\   ")) {\r
2509                 if (prevColor != ColorNormal) {\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(prevColor, TRUE);\r
2515                     curColor = prevColor;\r
2516                 }\r
2517                 if (savingComment) {\r
2518                     parse_pos = i - oldi;\r
2519                     memcpy(parse, &buf[oldi], parse_pos);\r
2520                     parse[parse_pos] = NULLCHAR;\r
2521                     started = STARTED_COMMENT;\r
2522                 } else {\r
2523                     started = STARTED_CHATTER;\r
2524                 }\r
2525                 continue;\r
2526             }\r
2527 \r
2528             if (looking_at(buf, &i, "Black Strength :") ||\r
2529                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2530                 looking_at(buf, &i, "<10>") ||\r
2531                 looking_at(buf, &i, "#@#")) {\r
2532                 /* Wrong board style */\r
2533                 loggedOn = TRUE;\r
2534                 SendToICS(ics_prefix);\r
2535                 SendToICS("set style 12\n");\r
2536                 SendToICS(ics_prefix);\r
2537                 SendToICS("refresh\n");\r
2538                 continue;\r
2539             }\r
2540             \r
2541             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2542                 ICSInitScript();\r
2543                 have_sent_ICS_logon = 1;\r
2544                 continue;\r
2545             }\r
2546               \r
2547             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2548                 (looking_at(buf, &i, "\n<12> ") ||\r
2549                  looking_at(buf, &i, "<12> "))) {\r
2550                 loggedOn = TRUE;\r
2551                 if (oldi > next_out) {\r
2552                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2553                 }\r
2554                 next_out = i;\r
2555                 started = STARTED_BOARD;\r
2556                 parse_pos = 0;\r
2557                 continue;\r
2558             }\r
2559 \r
2560             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2561                 looking_at(buf, &i, "<b1> ")) {\r
2562                 if (oldi > next_out) {\r
2563                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2564                 }\r
2565                 next_out = i;\r
2566                 started = STARTED_HOLDINGS;\r
2567                 parse_pos = 0;\r
2568                 continue;\r
2569             }\r
2570 \r
2571             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2572                 loggedOn = TRUE;\r
2573                 /* Header for a move list -- first line */\r
2574 \r
2575                 switch (ics_getting_history) {\r
2576                   case H_FALSE:\r
2577                     switch (gameMode) {\r
2578                       case IcsIdle:\r
2579                       case BeginningOfGame:\r
2580                         /* User typed "moves" or "oldmoves" while we\r
2581                            were idle.  Pretend we asked for these\r
2582                            moves and soak them up so user can step\r
2583                            through them and/or save them.\r
2584                            */\r
2585                         Reset(FALSE, TRUE);\r
2586                         gameMode = IcsObserving;\r
2587                         ModeHighlight();\r
2588                         ics_gamenum = -1;\r
2589                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2590                         break;\r
2591                       case EditGame: /*?*/\r
2592                       case EditPosition: /*?*/\r
2593                         /* Should above feature work in these modes too? */\r
2594                         /* For now it doesn't */\r
2595                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2596                         break;\r
2597                       default:\r
2598                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2599                         break;\r
2600                     }\r
2601                     break;\r
2602                   case H_REQUESTED:\r
2603                     /* Is this the right one? */\r
2604                     if (gameInfo.white && gameInfo.black &&\r
2605                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2606                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2607                         /* All is well */\r
2608                         ics_getting_history = H_GOT_REQ_HEADER;\r
2609                     }\r
2610                     break;\r
2611                   case H_GOT_REQ_HEADER:\r
2612                   case H_GOT_UNREQ_HEADER:\r
2613                   case H_GOT_UNWANTED_HEADER:\r
2614                   case H_GETTING_MOVES:\r
2615                     /* Should not happen */\r
2616                     DisplayError("Error gathering move list: two headers", 0);\r
2617                     ics_getting_history = H_FALSE;\r
2618                     break;\r
2619                 }\r
2620 \r
2621                 /* Save player ratings into gameInfo if needed */\r
2622                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2623                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2624                     (gameInfo.whiteRating == -1 ||\r
2625                      gameInfo.blackRating == -1)) {\r
2626 \r
2627                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2628                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2629                     if (appData.debugMode)\r
2630                       fprintf(debugFP, "Ratings from header: W %d, B %d\n", \r
2631                               gameInfo.whiteRating, gameInfo.blackRating);\r
2632                 }\r
2633                 continue;\r
2634             }\r
2635 \r
2636             if (looking_at(buf, &i,\r
2637               "* * match, initial time: * minute*, increment: * second")) {\r
2638                 /* Header for a move list -- second line */\r
2639                 /* Initial board will follow if this is a wild game */\r
2640                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2641                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2642                 gameInfo.event = StrSave(str);\r
2643                 /* [HGM] we switched variant. Translate boards if needed. */\r
2644                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2645                 continue;\r
2646             }\r
2647 \r
2648             if (looking_at(buf, &i, "Move  ")) {\r
2649                 /* Beginning of a move list */\r
2650                 switch (ics_getting_history) {\r
2651                   case H_FALSE:\r
2652                     /* Normally should not happen */\r
2653                     /* Maybe user hit reset while we were parsing */\r
2654                     break;\r
2655                   case H_REQUESTED:\r
2656                     /* Happens if we are ignoring a move list that is not\r
2657                      * the one we just requested.  Common if the user\r
2658                      * tries to observe two games without turning off\r
2659                      * getMoveList */\r
2660                     break;\r
2661                   case H_GETTING_MOVES:\r
2662                     /* Should not happen */\r
2663                     DisplayError("Error gathering move list: nested", 0);\r
2664                     ics_getting_history = H_FALSE;\r
2665                     break;\r
2666                   case H_GOT_REQ_HEADER:\r
2667                     ics_getting_history = H_GETTING_MOVES;\r
2668                     started = STARTED_MOVES;\r
2669                     parse_pos = 0;\r
2670                     if (oldi > next_out) {\r
2671                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2672                     }\r
2673                     break;\r
2674                   case H_GOT_UNREQ_HEADER:\r
2675                     ics_getting_history = H_GETTING_MOVES;\r
2676                     started = STARTED_MOVES_NOHIDE;\r
2677                     parse_pos = 0;\r
2678                     break;\r
2679                   case H_GOT_UNWANTED_HEADER:\r
2680                     ics_getting_history = H_FALSE;\r
2681                     break;\r
2682                 }\r
2683                 continue;\r
2684             }                           \r
2685             \r
2686             if (looking_at(buf, &i, "% ") ||\r
2687                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2688                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book\r
2689                 savingComment = FALSE;\r
2690                 switch (started) {\r
2691                   case STARTED_MOVES:\r
2692                   case STARTED_MOVES_NOHIDE:\r
2693                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2694                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2695                     ParseGameHistory(parse);\r
2696 #if ZIPPY\r
2697                     if (appData.zippyPlay && first.initDone) {\r
2698                         FeedMovesToProgram(&first, forwardMostMove);\r
2699                         if (gameMode == IcsPlayingWhite) {\r
2700                             if (WhiteOnMove(forwardMostMove)) {\r
2701                                 if (first.sendTime) {\r
2702                                   if (first.useColors) {\r
2703                                     SendToProgram("black\n", &first); \r
2704                                   }\r
2705                                   SendTimeRemaining(&first, TRUE);\r
2706                                 }\r
2707 #if 0\r
2708                                 if (first.useColors) {\r
2709                                   SendToProgram("white\ngo\n", &first);\r
2710                                 } else {\r
2711                                   SendToProgram("go\n", &first);\r
2712                                 }\r
2713 #else\r
2714                                 if (first.useColors) {\r
2715                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent\r
2716                                 }\r
2717                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos\r
2718 #endif\r
2719                                 first.maybeThinking = TRUE;\r
2720                             } else {\r
2721                                 if (first.usePlayother) {\r
2722                                   if (first.sendTime) {\r
2723                                     SendTimeRemaining(&first, TRUE);\r
2724                                   }\r
2725                                   SendToProgram("playother\n", &first);\r
2726                                   firstMove = FALSE;\r
2727                                 } else {\r
2728                                   firstMove = TRUE;\r
2729                                 }\r
2730                             }\r
2731                         } else if (gameMode == IcsPlayingBlack) {\r
2732                             if (!WhiteOnMove(forwardMostMove)) {\r
2733                                 if (first.sendTime) {\r
2734                                   if (first.useColors) {\r
2735                                     SendToProgram("white\n", &first);\r
2736                                   }\r
2737                                   SendTimeRemaining(&first, FALSE);\r
2738                                 }\r
2739 #if 0\r
2740                                 if (first.useColors) {\r
2741                                   SendToProgram("black\ngo\n", &first);\r
2742                                 } else {\r
2743                                   SendToProgram("go\n", &first);\r
2744                                 }\r
2745 #else\r
2746                                 if (first.useColors) {\r
2747                                   SendToProgram("black\n", &first);\r
2748                                 }\r
2749                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);\r
2750 #endif\r
2751                                 first.maybeThinking = TRUE;\r
2752                             } else {\r
2753                                 if (first.usePlayother) {\r
2754                                   if (first.sendTime) {\r
2755                                     SendTimeRemaining(&first, FALSE);\r
2756                                   }\r
2757                                   SendToProgram("playother\n", &first);\r
2758                                   firstMove = FALSE;\r
2759                                 } else {\r
2760                                   firstMove = TRUE;\r
2761                                 }\r
2762                             }\r
2763                         }                       \r
2764                     }\r
2765 #endif\r
2766                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2767                         /* Moves came from oldmoves or moves command\r
2768                            while we weren't doing anything else.\r
2769                            */\r
2770                         currentMove = forwardMostMove;\r
2771                         ClearHighlights();/*!!could figure this out*/\r
2772                         flipView = appData.flipView;\r
2773                         DrawPosition(FALSE, boards[currentMove]);\r
2774                         DisplayBothClocks();\r
2775                         sprintf(str, "%s vs. %s",\r
2776                                 gameInfo.white, gameInfo.black);\r
2777                         DisplayTitle(str);\r
2778                         gameMode = IcsIdle;\r
2779                     } else {\r
2780                         /* Moves were history of an active game */\r
2781                         if (gameInfo.resultDetails != NULL) {\r
2782                             free(gameInfo.resultDetails);\r
2783                             gameInfo.resultDetails = NULL;\r
2784                         }\r
2785                     }\r
2786                     HistorySet(parseList, backwardMostMove,\r
2787                                forwardMostMove, currentMove-1);\r
2788                     DisplayMove(currentMove - 1);\r
2789                     if (started == STARTED_MOVES) next_out = i;\r
2790                     started = STARTED_NONE;\r
2791                     ics_getting_history = H_FALSE;\r
2792                     break;\r
2793 \r
2794                   case STARTED_OBSERVE:\r
2795                     started = STARTED_NONE;\r
2796                     SendToICS(ics_prefix);\r
2797                     SendToICS("refresh\n");\r
2798                     break;\r
2799 \r
2800                   default:\r
2801                     break;\r
2802                 }\r
2803                 if(bookHit) { // [HGM] book: simulate book reply\r
2804                     static char bookMove[MSG_SIZ]; // a bit generous?\r
2805 \r
2806                     programStats.depth = programStats.nodes = programStats.time = \r
2807                     programStats.score = programStats.got_only_move = 0;\r
2808                     sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
2809 \r
2810                     strcpy(bookMove, "move ");\r
2811                     strcat(bookMove, bookHit);\r
2812                     HandleMachineMove(bookMove, &first);\r
2813                 }\r
2814                 continue;\r
2815             }\r
2816             \r
2817             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2818                  started == STARTED_HOLDINGS ||\r
2819                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2820                 /* Accumulate characters in move list or board */\r
2821                 parse[parse_pos++] = buf[i];\r
2822             }\r
2823             \r
2824             /* Start of game messages.  Mostly we detect start of game\r
2825                when the first board image arrives.  On some versions\r
2826                of the ICS, though, we need to do a "refresh" after starting\r
2827                to observe in order to get the current board right away. */\r
2828             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2829                 started = STARTED_OBSERVE;\r
2830                 continue;\r
2831             }\r
2832 \r
2833             /* Handle auto-observe */\r
2834             if (appData.autoObserve &&\r
2835                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2836                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2837                 char *player;\r
2838                 /* Choose the player that was highlighted, if any. */\r
2839                 if (star_match[0][0] == '\033' ||\r
2840                     star_match[1][0] != '\033') {\r
2841                     player = star_match[0];\r
2842                 } else {\r
2843                     player = star_match[2];\r
2844                 }\r
2845                 sprintf(str, "%sobserve %s\n",\r
2846                         ics_prefix, StripHighlightAndTitle(player));\r
2847                 SendToICS(str);\r
2848 \r
2849                 /* Save ratings from notify string */\r
2850                 strcpy(player1Name, star_match[0]);\r
2851                 player1Rating = string_to_rating(star_match[1]);\r
2852                 strcpy(player2Name, star_match[2]);\r
2853                 player2Rating = string_to_rating(star_match[3]);\r
2854 \r
2855                 if (appData.debugMode)\r
2856                   fprintf(debugFP, \r
2857                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2858                           player1Name, player1Rating,\r
2859                           player2Name, player2Rating);\r
2860 \r
2861                 continue;\r
2862             }\r
2863 \r
2864             /* Deal with automatic examine mode after a game,\r
2865                and with IcsObserving -> IcsExamining transition */\r
2866             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2867                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2868 \r
2869                 int gamenum = atoi(star_match[0]);\r
2870                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2871                     gamenum == ics_gamenum) {\r
2872                     /* We were already playing or observing this game;\r
2873                        no need to refetch history */\r
2874                     gameMode = IcsExamining;\r
2875                     if (pausing) {\r
2876                         pauseExamForwardMostMove = forwardMostMove;\r
2877                     } else if (currentMove < forwardMostMove) {\r
2878                         ForwardInner(forwardMostMove);\r
2879                     }\r
2880                 } else {\r
2881                     /* I don't think this case really can happen */\r
2882                     SendToICS(ics_prefix);\r
2883                     SendToICS("refresh\n");\r
2884                 }\r
2885                 continue;\r
2886             }    \r
2887             \r
2888             /* Error messages */\r
2889             if (ics_user_moved) {\r
2890                 if (looking_at(buf, &i, "Illegal move") ||\r
2891                     looking_at(buf, &i, "Not a legal move") ||\r
2892                     looking_at(buf, &i, "Your king is in check") ||\r
2893                     looking_at(buf, &i, "It isn't your turn") ||\r
2894                     looking_at(buf, &i, "It is not your move")) {\r
2895                     /* Illegal move */\r
2896                     ics_user_moved = 0;\r
2897                     if (forwardMostMove > backwardMostMove) {\r
2898                         currentMove = --forwardMostMove;\r
2899                         DisplayMove(currentMove - 1); /* before DMError */\r
2900                         DisplayMoveError("Illegal move (rejected by ICS)");\r
2901                         DrawPosition(FALSE, boards[currentMove]);\r
2902                         SwitchClocks();\r
2903                         DisplayBothClocks();\r
2904                     }\r
2905                     continue;\r
2906                 }\r
2907             }\r
2908 \r
2909             if (looking_at(buf, &i, "still have time") ||\r
2910                 looking_at(buf, &i, "not out of time") ||\r
2911                 looking_at(buf, &i, "either player is out of time") ||\r
2912                 looking_at(buf, &i, "has timeseal; checking")) {\r
2913                 /* We must have called his flag a little too soon */\r
2914                 whiteFlag = blackFlag = FALSE;\r
2915                 continue;\r
2916             }\r
2917 \r
2918             if (looking_at(buf, &i, "added * seconds to") ||\r
2919                 looking_at(buf, &i, "seconds were added to")) {\r
2920                 /* Update the clocks */\r
2921                 SendToICS(ics_prefix);\r
2922                 SendToICS("refresh\n");\r
2923                 continue;\r
2924             }\r
2925 \r
2926             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2927                 ics_clock_paused = TRUE;\r
2928                 StopClocks();\r
2929                 continue;\r
2930             }\r
2931 \r
2932             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2933                 ics_clock_paused = FALSE;\r
2934                 StartClocks();\r
2935                 continue;\r
2936             }\r
2937 \r
2938             /* Grab player ratings from the Creating: message.\r
2939                Note we have to check for the special case when\r
2940                the ICS inserts things like [white] or [black]. */\r
2941             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
2942                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
2943                 /* star_matches:\r
2944                    0    player 1 name (not necessarily white)\r
2945                    1    player 1 rating\r
2946                    2    empty, white, or black (IGNORED)\r
2947                    3    player 2 name (not necessarily black)\r
2948                    4    player 2 rating\r
2949                    \r
2950                    The names/ratings are sorted out when the game\r
2951                    actually starts (below).\r
2952                 */\r
2953                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
2954                 player1Rating = string_to_rating(star_match[1]);\r
2955                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
2956                 player2Rating = string_to_rating(star_match[4]);\r
2957 \r
2958                 if (appData.debugMode)\r
2959                   fprintf(debugFP, \r
2960                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
2961                           player1Name, player1Rating,\r
2962                           player2Name, player2Rating);\r
2963 \r
2964                 continue;\r
2965             }\r
2966             \r
2967             /* Improved generic start/end-of-game messages */\r
2968             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
2969                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
2970                 /* If tkind == 0: */\r
2971                 /* star_match[0] is the game number */\r
2972                 /*           [1] is the white player's name */\r
2973                 /*           [2] is the black player's name */\r
2974                 /* For end-of-game: */\r
2975                 /*           [3] is the reason for the game end */\r
2976                 /*           [4] is a PGN end game-token, preceded by " " */\r
2977                 /* For start-of-game: */\r
2978                 /*           [3] begins with "Creating" or "Continuing" */\r
2979                 /*           [4] is " *" or empty (don't care). */\r
2980                 int gamenum = atoi(star_match[0]);\r
2981                 char *whitename, *blackname, *why, *endtoken;\r
2982                 ChessMove endtype = (ChessMove) 0;\r
2983 \r
2984                 if (tkind == 0) {\r
2985                   whitename = star_match[1];\r
2986                   blackname = star_match[2];\r
2987                   why = star_match[3];\r
2988                   endtoken = star_match[4];\r
2989                 } else {\r
2990                   whitename = star_match[1];\r
2991                   blackname = star_match[3];\r
2992                   why = star_match[5];\r
2993                   endtoken = star_match[6];\r
2994                 }\r
2995 \r
2996                 /* Game start messages */\r
2997                 if (strncmp(why, "Creating ", 9) == 0 ||\r
2998                     strncmp(why, "Continuing ", 11) == 0) {\r
2999                     gs_gamenum = gamenum;\r
3000                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
3001 #if ZIPPY\r
3002                     if (appData.zippyPlay) {\r
3003                         ZippyGameStart(whitename, blackname);\r
3004                     }\r
3005 #endif /*ZIPPY*/\r
3006                     continue;\r
3007                 }\r
3008 \r
3009                 /* Game end messages */\r
3010                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
3011                     ics_gamenum != gamenum) {\r
3012                     continue;\r
3013                 }\r
3014                 while (endtoken[0] == ' ') endtoken++;\r
3015                 switch (endtoken[0]) {\r
3016                   case '*':\r
3017                   default:\r
3018                     endtype = GameUnfinished;\r
3019                     break;\r
3020                   case '0':\r
3021                     endtype = BlackWins;\r
3022                     break;\r
3023                   case '1':\r
3024                     if (endtoken[1] == '/')\r
3025                       endtype = GameIsDrawn;\r
3026                     else\r
3027                       endtype = WhiteWins;\r
3028                     break;\r
3029                 }\r
3030                 GameEnds(endtype, why, GE_ICS);\r
3031 #if ZIPPY\r
3032                 if (appData.zippyPlay && first.initDone) {\r
3033                     ZippyGameEnd(endtype, why);\r
3034                     if (first.pr == NULL) {\r
3035                       /* Start the next process early so that we'll\r
3036                          be ready for the next challenge */\r
3037                       StartChessProgram(&first);\r
3038                     }\r
3039                     /* Send "new" early, in case this command takes\r
3040                        a long time to finish, so that we'll be ready\r
3041                        for the next challenge. */\r
3042                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'\r
3043                     Reset(TRUE, TRUE);\r
3044                 }\r
3045 #endif /*ZIPPY*/\r
3046                 continue;\r
3047             }\r
3048 \r
3049             if (looking_at(buf, &i, "Removing game * from observation") ||\r
3050                 looking_at(buf, &i, "no longer observing game *") ||\r
3051                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
3052                 if (gameMode == IcsObserving &&\r
3053                     atoi(star_match[0]) == ics_gamenum)\r
3054                   {\r
3055                       StopClocks();\r
3056                       gameMode = IcsIdle;\r
3057                       ics_gamenum = -1;\r
3058                       ics_user_moved = FALSE;\r
3059                   }\r
3060                 continue;\r
3061             }\r
3062 \r
3063             if (looking_at(buf, &i, "no longer examining game *")) {\r
3064                 if (gameMode == IcsExamining &&\r
3065                     atoi(star_match[0]) == ics_gamenum)\r
3066                   {\r
3067                       gameMode = IcsIdle;\r
3068                       ics_gamenum = -1;\r
3069                       ics_user_moved = FALSE;\r
3070                   }\r
3071                 continue;\r
3072             }\r
3073 \r
3074             /* Advance leftover_start past any newlines we find,\r
3075                so only partial lines can get reparsed */\r
3076             if (looking_at(buf, &i, "\n")) {\r
3077                 prevColor = curColor;\r
3078                 if (curColor != ColorNormal) {\r
3079                     if (oldi > next_out) {\r
3080                         SendToPlayer(&buf[next_out], oldi - next_out);\r
3081                         next_out = oldi;\r
3082                     }\r
3083                     Colorize(ColorNormal, FALSE);\r
3084                     curColor = ColorNormal;\r
3085                 }\r
3086                 if (started == STARTED_BOARD) {\r
3087                     started = STARTED_NONE;\r
3088                     parse[parse_pos] = NULLCHAR;\r
3089                     ParseBoard12(parse);\r
3090                     ics_user_moved = 0;\r
3091 \r
3092                     /* Send premove here */\r
3093                     if (appData.premove) {\r
3094                       char str[MSG_SIZ];\r
3095                       if (currentMove == 0 &&\r
3096                           gameMode == IcsPlayingWhite &&\r
3097                           appData.premoveWhite) {\r
3098                         sprintf(str, "%s%s\n", ics_prefix,\r
3099                                 appData.premoveWhiteText);\r
3100                         if (appData.debugMode)\r
3101                           fprintf(debugFP, "Sending premove:\n");\r
3102                         SendToICS(str);\r
3103                       } else if (currentMove == 1 &&\r
3104                                  gameMode == IcsPlayingBlack &&\r
3105                                  appData.premoveBlack) {\r
3106                         sprintf(str, "%s%s\n", ics_prefix,\r
3107                                 appData.premoveBlackText);\r
3108                         if (appData.debugMode)\r
3109                           fprintf(debugFP, "Sending premove:\n");\r
3110                         SendToICS(str);\r
3111                       } else if (gotPremove) {\r
3112                         gotPremove = 0;\r
3113                         ClearPremoveHighlights();\r
3114                         if (appData.debugMode)\r
3115                           fprintf(debugFP, "Sending premove:\n");\r
3116                           UserMoveEvent(premoveFromX, premoveFromY, \r
3117                                         premoveToX, premoveToY, \r
3118                                         premovePromoChar);\r
3119                       }\r
3120                     }\r
3121 \r
3122                     /* Usually suppress following prompt */\r
3123                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
3124                         if (looking_at(buf, &i, "*% ")) {\r
3125                             savingComment = FALSE;\r
3126                         }\r
3127                     }\r
3128                     next_out = i;\r
3129                 } else if (started == STARTED_HOLDINGS) {\r
3130                     int gamenum;\r
3131                     char new_piece[MSG_SIZ];\r
3132                     started = STARTED_NONE;\r
3133                     parse[parse_pos] = NULLCHAR;\r
3134                     if (appData.debugMode)\r
3135                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
3136                                                         parse, currentMove);\r
3137                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
3138                         gamenum == ics_gamenum) {\r
3139                         if (gameInfo.variant == VariantNormal) {\r
3140                           /* [HGM] We seem to switch variant during a game!\r
3141                            * Presumably no holdings were displayed, so we have\r
3142                            * to move the position two files to the right to\r
3143                            * create room for them!\r
3144                            */\r
3145                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
3146                           /* Get a move list just to see the header, which\r
3147                              will tell us whether this is really bug or zh */\r
3148                           if (ics_getting_history == H_FALSE) {\r
3149                             ics_getting_history = H_REQUESTED;\r
3150                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3151                             SendToICS(str);\r
3152                           }\r
3153                         }\r
3154                         new_piece[0] = NULLCHAR;\r
3155                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
3156                                &gamenum, white_holding, black_holding,\r
3157                                new_piece);\r
3158                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
3159                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
3160                         /* [HGM] copy holdings to board holdings area */\r
3161                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
3162                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
3163 #if ZIPPY\r
3164                         if (appData.zippyPlay && first.initDone) {\r
3165                             ZippyHoldings(white_holding, black_holding,\r
3166                                           new_piece);\r
3167                         }\r
3168 #endif /*ZIPPY*/\r
3169                         if (tinyLayout || smallLayout) {\r
3170                             char wh[16], bh[16];\r
3171                             PackHolding(wh, white_holding);\r
3172                             PackHolding(bh, black_holding);\r
3173                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
3174                                     gameInfo.white, gameInfo.black);\r
3175                         } else {\r
3176                             sprintf(str, "%s [%s] vs. %s [%s]",\r
3177                                     gameInfo.white, white_holding,\r
3178                                     gameInfo.black, black_holding);\r
3179                         }\r
3180 \r
3181                         DrawPosition(FALSE, boards[currentMove]);\r
3182                         DisplayTitle(str);\r
3183                     }\r
3184                     /* Suppress following prompt */\r
3185                     if (looking_at(buf, &i, "*% ")) {\r
3186                         savingComment = FALSE;\r
3187                     }\r
3188                     next_out = i;\r
3189                 }\r
3190                 continue;\r
3191             }\r
3192 \r
3193             i++;                /* skip unparsed character and loop back */\r
3194         }\r
3195         \r
3196         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window\r
3197             started != STARTED_HOLDINGS && i > next_out) {\r
3198             SendToPlayer(&buf[next_out], i - next_out);\r
3199             next_out = i;\r
3200         }\r
3201         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above\r
3202         \r
3203         leftover_len = buf_len - leftover_start;\r
3204         /* if buffer ends with something we couldn't parse,\r
3205            reparse it after appending the next read */\r
3206         \r
3207     } else if (count == 0) {\r
3208         RemoveInputSource(isr);\r
3209         DisplayFatalError("Connection closed by ICS", 0, 0);\r
3210     } else {\r
3211         DisplayFatalError("Error reading from ICS", error, 1);\r
3212     }\r
3213 }\r
3214 \r
3215 \r
3216 /* Board style 12 looks like this:\r
3217    \r
3218    <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
3219    \r
3220  * The "<12> " is stripped before it gets to this routine.  The two\r
3221  * trailing 0's (flip state and clock ticking) are later addition, and\r
3222  * some chess servers may not have them, or may have only the first.\r
3223  * Additional trailing fields may be added in the future.  \r
3224  */\r
3225 \r
3226 #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
3227 \r
3228 #define RELATION_OBSERVING_PLAYED    0\r
3229 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
3230 #define RELATION_PLAYING_MYMOVE      1\r
3231 #define RELATION_PLAYING_NOTMYMOVE  -1\r
3232 #define RELATION_EXAMINING           2\r
3233 #define RELATION_ISOLATED_BOARD     -3\r
3234 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
3235 \r
3236 void\r
3237 ParseBoard12(string)\r
3238      char *string;\r
3239\r
3240     GameMode newGameMode;\r
3241     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;\r
3242     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;\r
3243     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
3244     char to_play, board_chars[200];\r
3245     char move_str[500], str[500], elapsed_time[500];\r
3246     char black[32], white[32];\r
3247     Board board;\r
3248     int prevMove = currentMove;\r
3249     int ticking = 2;\r
3250     ChessMove moveType;\r
3251     int fromX, fromY, toX, toY;\r
3252     char promoChar;\r
3253     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */\r
3254     char *bookHit = NULL; // [HGM] book\r
3255 \r
3256     fromX = fromY = toX = toY = -1;\r
3257     \r
3258     newGame = FALSE;\r
3259 \r
3260     if (appData.debugMode)\r
3261       fprintf(debugFP, "Parsing board: %s\n", string);\r
3262 \r
3263     move_str[0] = NULLCHAR;\r
3264     elapsed_time[0] = NULLCHAR;\r
3265     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */\r
3266         int  i = 0, j;\r
3267         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {\r
3268             if(string[i] == ' ') { ranks++; files = 0; }\r
3269             else files++;\r
3270             i++;\r
3271         }\r
3272         for(j = 0; j <i; j++) board_chars[j] = string[j];\r
3273         board_chars[i] = '\0';\r
3274         string += i + 1;\r
3275     }\r
3276     n = sscanf(string, PATTERN, &to_play, &double_push,\r
3277                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
3278                &gamenum, white, black, &relation, &basetime, &increment,\r
3279                &white_stren, &black_stren, &white_time, &black_time,\r
3280                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
3281                &ticking);\r
3282 \r
3283     if (n < 21) {\r
3284         sprintf(str, "Failed to parse board string:\n\"%s\"", string);\r
3285         DisplayError(str, 0);\r
3286         return;\r
3287     }\r
3288 \r
3289     /* Convert the move number to internal form */\r
3290     moveNum = (moveNum - 1) * 2;\r
3291     if (to_play == 'B') moveNum++;\r
3292     if (moveNum >= MAX_MOVES) {\r
3293       DisplayFatalError("Game too long; increase MAX_MOVES and recompile",\r
3294                         0, 1);\r
3295       return;\r
3296     }\r
3297     \r
3298     switch (relation) {\r
3299       case RELATION_OBSERVING_PLAYED:\r
3300       case RELATION_OBSERVING_STATIC:\r
3301         if (gamenum == -1) {\r
3302             /* Old ICC buglet */\r
3303             relation = RELATION_OBSERVING_STATIC;\r
3304         }\r
3305         newGameMode = IcsObserving;\r
3306         break;\r
3307       case RELATION_PLAYING_MYMOVE:\r
3308       case RELATION_PLAYING_NOTMYMOVE:\r
3309         newGameMode =\r
3310           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3311             IcsPlayingWhite : IcsPlayingBlack;\r
3312         break;\r
3313       case RELATION_EXAMINING:\r
3314         newGameMode = IcsExamining;\r
3315         break;\r
3316       case RELATION_ISOLATED_BOARD:\r
3317       default:\r
3318         /* Just display this board.  If user was doing something else,\r
3319            we will forget about it until the next board comes. */ \r
3320         newGameMode = IcsIdle;\r
3321         break;\r
3322       case RELATION_STARTING_POSITION:\r
3323         newGameMode = gameMode;\r
3324         break;\r
3325     }\r
3326     \r
3327     /* Modify behavior for initial board display on move listing\r
3328        of wild games.\r
3329        */\r
3330     switch (ics_getting_history) {\r
3331       case H_FALSE:\r
3332       case H_REQUESTED:\r
3333         break;\r
3334       case H_GOT_REQ_HEADER:\r
3335       case H_GOT_UNREQ_HEADER:\r
3336         /* This is the initial position of the current game */\r
3337         gamenum = ics_gamenum;\r
3338         moveNum = 0;            /* old ICS bug workaround */\r
3339         if (to_play == 'B') {\r
3340           startedFromSetupPosition = TRUE;\r
3341           blackPlaysFirst = TRUE;\r
3342           moveNum = 1;\r
3343           if (forwardMostMove == 0) forwardMostMove = 1;\r
3344           if (backwardMostMove == 0) backwardMostMove = 1;\r
3345           if (currentMove == 0) currentMove = 1;\r
3346         }\r
3347         newGameMode = gameMode;\r
3348         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3349         break;\r
3350       case H_GOT_UNWANTED_HEADER:\r
3351         /* This is an initial board that we don't want */\r
3352         return;\r
3353       case H_GETTING_MOVES:\r
3354         /* Should not happen */\r
3355         DisplayError("Error gathering move list: extra board", 0);\r
3356         ics_getting_history = H_FALSE;\r
3357         return;\r
3358     }\r
3359     \r
3360     /* Take action if this is the first board of a new game, or of a\r
3361        different game than is currently being displayed.  */\r
3362     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3363         relation == RELATION_ISOLATED_BOARD) {\r
3364         \r
3365         /* Forget the old game and get the history (if any) of the new one */\r
3366         if (gameMode != BeginningOfGame) {\r
3367           Reset(FALSE, TRUE);\r
3368         }\r
3369         newGame = TRUE;\r
3370         if (appData.autoRaiseBoard) BoardToTop();\r
3371         prevMove = -3;\r
3372         if (gamenum == -1) {\r
3373             newGameMode = IcsIdle;\r
3374         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3375                    appData.getMoveList) {\r
3376             /* Need to get game history */\r
3377             ics_getting_history = H_REQUESTED;\r
3378             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3379             SendToICS(str);\r
3380         }\r
3381         \r
3382         /* Initially flip the board to have black on the bottom if playing\r
3383            black or if the ICS flip flag is set, but let the user change\r
3384            it with the Flip View button. */\r
3385         flipView = appData.autoFlipView ? \r
3386           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3387           appData.flipView;\r
3388         \r
3389         /* Done with values from previous mode; copy in new ones */\r
3390         gameMode = newGameMode;\r
3391         ModeHighlight();\r
3392         ics_gamenum = gamenum;\r
3393         if (gamenum == gs_gamenum) {\r
3394             int klen = strlen(gs_kind);\r
3395             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3396             sprintf(str, "ICS %s", gs_kind);\r
3397             gameInfo.event = StrSave(str);\r
3398         } else {\r
3399             gameInfo.event = StrSave("ICS game");\r
3400         }\r
3401         gameInfo.site = StrSave(appData.icsHost);\r
3402         gameInfo.date = PGNDate();\r
3403         gameInfo.round = StrSave("-");\r
3404         gameInfo.white = StrSave(white);\r
3405         gameInfo.black = StrSave(black);\r
3406         timeControl = basetime * 60 * 1000;\r
3407         timeControl_2 = 0;\r
3408         timeIncrement = increment * 1000;\r
3409         movesPerSession = 0;\r
3410         gameInfo.timeControl = TimeControlTagValue();\r
3411         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3412   if (appData.debugMode) {\r
3413     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3414     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3415     setbuf(debugFP, NULL);\r
3416   }\r
3417 \r
3418         gameInfo.outOfBook = NULL;\r
3419         \r
3420         /* Do we have the ratings? */\r
3421         if (strcmp(player1Name, white) == 0 &&\r
3422             strcmp(player2Name, black) == 0) {\r
3423             if (appData.debugMode)\r
3424               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3425                       player1Rating, player2Rating);\r
3426             gameInfo.whiteRating = player1Rating;\r
3427             gameInfo.blackRating = player2Rating;\r
3428         } else if (strcmp(player2Name, white) == 0 &&\r
3429                    strcmp(player1Name, black) == 0) {\r
3430             if (appData.debugMode)\r
3431               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3432                       player2Rating, player1Rating);\r
3433             gameInfo.whiteRating = player2Rating;\r
3434             gameInfo.blackRating = player1Rating;\r
3435         }\r
3436         player1Name[0] = player2Name[0] = NULLCHAR;\r
3437 \r
3438         /* Silence shouts if requested */\r
3439         if (appData.quietPlay &&\r
3440             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3441             SendToICS(ics_prefix);\r
3442             SendToICS("set shout 0\n");\r
3443         }\r
3444     }\r
3445     \r
3446     /* Deal with midgame name changes */\r
3447     if (!newGame) {\r
3448         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3449             if (gameInfo.white) free(gameInfo.white);\r
3450             gameInfo.white = StrSave(white);\r
3451         }\r
3452         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3453             if (gameInfo.black) free(gameInfo.black);\r
3454             gameInfo.black = StrSave(black);\r
3455         }\r
3456     }\r
3457     \r
3458     /* Throw away game result if anything actually changes in examine mode */\r
3459     if (gameMode == IcsExamining && !newGame) {\r
3460         gameInfo.result = GameUnfinished;\r
3461         if (gameInfo.resultDetails != NULL) {\r
3462             free(gameInfo.resultDetails);\r
3463             gameInfo.resultDetails = NULL;\r
3464         }\r
3465     }\r
3466     \r
3467     /* In pausing && IcsExamining mode, we ignore boards coming\r
3468        in if they are in a different variation than we are. */\r
3469     if (pauseExamInvalid) return;\r
3470     if (pausing && gameMode == IcsExamining) {\r
3471         if (moveNum <= pauseExamForwardMostMove) {\r
3472             pauseExamInvalid = TRUE;\r
3473             forwardMostMove = pauseExamForwardMostMove;\r
3474             return;\r
3475         }\r
3476     }\r
3477     \r
3478   if (appData.debugMode) {\r
3479     fprintf(debugFP, "load %dx%d board\n", files, ranks);\r
3480   }\r
3481     /* Parse the board */\r
3482     for (k = 0; k < ranks; k++) {\r
3483       for (j = 0; j < files; j++)\r
3484         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);\r
3485       if(gameInfo.holdingsWidth > 1) {\r
3486            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3487            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3488       }\r
3489     }\r
3490     CopyBoard(boards[moveNum], board);\r
3491     if (moveNum == 0) {\r
3492         startedFromSetupPosition =\r
3493           !CompareBoards(board, initialPosition);\r
3494         if(startedFromSetupPosition)\r
3495             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */\r
3496     }\r
3497 \r
3498     /* [HGM] Set castling rights. Take the outermost Rooks,\r
3499        to make it also work for FRC opening positions. Note that board12\r
3500        is really defective for later FRC positions, as it has no way to\r
3501        indicate which Rook can castle if they are on the same side of King.\r
3502        For the initial position we grant rights to the outermost Rooks,\r
3503        and remember thos rights, and we then copy them on positions\r
3504        later in an FRC game. This means WB might not recognize castlings with\r
3505        Rooks that have moved back to their original position as illegal,\r
3506        but in ICS mode that is not its job anyway.\r
3507     */\r
3508     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)\r
3509     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;\r
3510 \r
3511         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3512             if(board[0][i] == WhiteRook) j = i;\r
3513         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3514         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3515             if(board[0][i] == WhiteRook) j = i;\r
3516         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3517         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3518             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3519         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3520         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3521             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3522         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3523 \r
3524         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }\r
3525         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3526             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;\r
3527         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3528             if(board[BOARD_HEIGHT-1][k] == bKing)\r
3529                 initialRights[5] = castlingRights[moveNum][5] = k;\r
3530     } else { int r;\r
3531         r = castlingRights[moveNum][0] = initialRights[0];\r
3532         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;\r
3533         r = castlingRights[moveNum][1] = initialRights[1];\r
3534         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;\r
3535         r = castlingRights[moveNum][3] = initialRights[3];\r
3536         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;\r
3537         r = castlingRights[moveNum][4] = initialRights[4];\r
3538         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;\r
3539         /* wildcastle kludge: always assume King has rights */\r
3540         r = castlingRights[moveNum][2] = initialRights[2];\r
3541         r = castlingRights[moveNum][5] = initialRights[5];\r
3542     }\r
3543     /* [HGM] e.p. rights. Assume that ICS sends file number here? */\r
3544     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;\r
3545 \r
3546     \r
3547     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3548         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3549         /* This was an initial position from a move list, not\r
3550            the current position */\r
3551         return;\r
3552     }\r
3553     \r
3554     /* Update currentMove and known move number limits */\r
3555     newMove = newGame || moveNum > forwardMostMove;\r
3556     if (newGame) {\r
3557         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3558         if (gameMode == IcsExamining && moveNum == 0) {\r
3559           /* Workaround for ICS limitation: we are not told the wild\r
3560              type when starting to examine a game.  But if we ask for\r
3561              the move list, the move list header will tell us */\r
3562             ics_getting_history = H_REQUESTED;\r
3563             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3564             SendToICS(str);\r
3565         }\r
3566     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3567                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3568         forwardMostMove = moveNum;\r
3569         if (!pausing || currentMove > forwardMostMove)\r
3570           currentMove = forwardMostMove;\r
3571     } else {\r
3572         /* New part of history that is not contiguous with old part */ \r
3573         if (pausing && gameMode == IcsExamining) {\r
3574             pauseExamInvalid = TRUE;\r
3575             forwardMostMove = pauseExamForwardMostMove;\r
3576             return;\r
3577         }\r
3578         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3579         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3580             ics_getting_history = H_REQUESTED;\r
3581             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3582             SendToICS(str);\r
3583         }\r
3584     }\r
3585     \r
3586     /* Update the clocks */\r
3587     if (strchr(elapsed_time, '.')) {\r
3588       /* Time is in ms */\r
3589       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3590       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3591     } else {\r
3592       /* Time is in seconds */\r
3593       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3594       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3595     }\r
3596       \r
3597 \r
3598 #if ZIPPY\r
3599     if (appData.zippyPlay && newGame &&\r
3600         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3601         gameMode != IcsExamining)\r
3602       ZippyFirstBoard(moveNum, basetime, increment);\r
3603 #endif\r
3604     \r
3605     /* Put the move on the move list, first converting\r
3606        to canonical algebraic form. */\r
3607     if (moveNum > 0) {\r
3608   if (appData.debugMode) {\r
3609     if (appData.debugMode) { int f = forwardMostMove;\r
3610         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,\r
3611                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
3612     }\r
3613     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3614     fprintf(debugFP, "moveNum = %d\n", moveNum);\r
3615     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3616     setbuf(debugFP, NULL);\r
3617   }\r
3618         if (moveNum <= backwardMostMove) {\r
3619             /* We don't know what the board looked like before\r
3620                this move.  Punt. */\r
3621             strcpy(parseList[moveNum - 1], move_str);\r
3622             strcat(parseList[moveNum - 1], " ");\r
3623             strcat(parseList[moveNum - 1], elapsed_time);\r
3624             moveList[moveNum - 1][0] = NULLCHAR;\r
3625         } else if (strcmp(move_str, "none") == 0) {\r
3626             // [HGM] long SAN: swapped order; test for 'none' before parsing move\r
3627             /* Again, we don't know what the board looked like;\r
3628                this is really the start of the game. */\r
3629             parseList[moveNum - 1][0] = NULLCHAR;\r
3630             moveList[moveNum - 1][0] = NULLCHAR;\r
3631             backwardMostMove = moveNum;\r
3632             startedFromSetupPosition = TRUE;\r
3633             fromX = fromY = toX = toY = -1;\r
3634         } else {\r
3635           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. \r
3636           //                 So we parse the long-algebraic move string in stead of the SAN move\r
3637           int valid; char buf[MSG_SIZ], *prom;\r
3638 \r
3639           // str looks something like "Q/a1-a2"; kill the slash\r
3640           if(str[1] == '/') \r
3641                 sprintf(buf, "%c%s", str[0], str+2);\r
3642           else  strcpy(buf, str); // might be castling\r
3643           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) \r
3644                 strcat(buf, prom); // long move lacks promo specification!\r
3645           if(!appData.testLegality) {\r
3646                 if(appData.debugMode) \r
3647                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);\r
3648                 strcpy(move_str, buf);\r
3649           }\r
3650           valid = ParseOneMove(move_str, moveNum - 1, &moveType,\r
3651                                 &fromX, &fromY, &toX, &toY, &promoChar)\r
3652                || ParseOneMove(buf, moveNum - 1, &moveType,\r
3653                                 &fromX, &fromY, &toX, &toY, &promoChar);\r
3654           // end of long SAN patch\r
3655           if (valid) {\r
3656             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3657                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3658                                      fromY, fromX, toY, toX, promoChar,\r
3659                                      parseList[moveNum-1]);\r
3660             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3661                              castlingRights[moveNum]) ) {\r
3662               case MT_NONE:\r
3663               case MT_STALEMATE:\r
3664               default:\r
3665                 break;\r
3666               case MT_CHECK:\r
3667                 if(gameInfo.variant != VariantShogi)\r
3668                     strcat(parseList[moveNum - 1], "+");\r
3669                 break;\r
3670               case MT_CHECKMATE:\r
3671                 strcat(parseList[moveNum - 1], "#");\r
3672                 break;\r
3673             }\r
3674             strcat(parseList[moveNum - 1], " ");\r
3675             strcat(parseList[moveNum - 1], elapsed_time);\r
3676             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3677             strcpy(moveList[moveNum - 1], currentMoveString);\r
3678             strcat(moveList[moveNum - 1], "\n");\r
3679           } else {\r
3680             /* Move from ICS was illegal!?  Punt. */\r
3681   if (appData.debugMode) {\r
3682     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3683     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3684   }\r
3685 #if 0\r
3686             if (appData.testLegality && appData.debugMode) {\r
3687                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3688                 DisplayError(str, 0);\r
3689             }\r
3690 #endif\r
3691             strcpy(parseList[moveNum - 1], move_str);\r
3692             strcat(parseList[moveNum - 1], " ");\r
3693             strcat(parseList[moveNum - 1], elapsed_time);\r
3694             moveList[moveNum - 1][0] = NULLCHAR;\r
3695             fromX = fromY = toX = toY = -1;\r
3696           }\r
3697         }\r
3698   if (appData.debugMode) {\r
3699     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3700     setbuf(debugFP, NULL);\r
3701   }\r
3702 \r
3703 #if ZIPPY\r
3704         /* Send move to chess program (BEFORE animating it). */\r
3705         if (appData.zippyPlay && !newGame && newMove && \r
3706            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3707 \r
3708             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3709                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3710                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3711                     sprintf(str, "Couldn't parse move \"%s\" from ICS",\r
3712                             move_str);\r
3713                     DisplayError(str, 0);\r
3714                 } else {\r
3715                     if (first.sendTime) {\r
3716                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3717                     }\r
3718                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book\r
3719                     if (firstMove && !bookHit) {\r
3720                         firstMove = FALSE;\r
3721                         if (first.useColors) {\r
3722                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3723                                         "white\ngo\n" :\r
3724                                         "black\ngo\n", &first);\r
3725                         } else {\r
3726                           SendToProgram("go\n", &first);\r
3727                         }\r
3728                         first.maybeThinking = TRUE;\r
3729                     }\r
3730                 }\r
3731             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3732               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3733                 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);\r
3734                 DisplayError(str, 0);\r
3735               } else {\r
3736                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!\r
3737                 SendMoveToProgram(moveNum - 1, &first);\r
3738               }\r
3739             }\r
3740         }\r
3741 #endif\r
3742     }\r
3743 \r
3744     if (moveNum > 0 && !gotPremove) {\r
3745         /* If move comes from a remote source, animate it.  If it\r
3746            isn't remote, it will have already been animated. */\r
3747         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3748             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3749         }\r
3750         if (!pausing && appData.highlightLastMove) {\r
3751             SetHighlights(fromX, fromY, toX, toY);\r
3752         }\r
3753     }\r
3754     \r
3755     /* Start the clocks */\r
3756     whiteFlag = blackFlag = FALSE;\r
3757     appData.clockMode = !(basetime == 0 && increment == 0);\r
3758     if (ticking == 0) {\r
3759       ics_clock_paused = TRUE;\r
3760       StopClocks();\r
3761     } else if (ticking == 1) {\r
3762       ics_clock_paused = FALSE;\r
3763     }\r
3764     if (gameMode == IcsIdle ||\r
3765         relation == RELATION_OBSERVING_STATIC ||\r
3766         relation == RELATION_EXAMINING ||\r
3767         ics_clock_paused)\r
3768       DisplayBothClocks();\r
3769     else\r
3770       StartClocks();\r
3771     \r
3772     /* Display opponents and material strengths */\r
3773     if (gameInfo.variant != VariantBughouse &&\r
3774         gameInfo.variant != VariantCrazyhouse) {\r
3775         if (tinyLayout || smallLayout) {\r
3776             if(gameInfo.variant == VariantNormal)\r
3777                 sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3778                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3779                     basetime, increment);\r
3780             else\r
3781                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", \r
3782                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3783                     basetime, increment, (int) gameInfo.variant);\r
3784         } else {\r
3785             if(gameInfo.variant == VariantNormal)\r
3786                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3787                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3788                     basetime, increment);\r
3789             else\r
3790                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", \r
3791                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3792                     basetime, increment, VariantName(gameInfo.variant));\r
3793         }\r
3794         DisplayTitle(str);\r
3795   if (appData.debugMode) {\r
3796     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);\r
3797   }\r
3798     }\r
3799 \r
3800    \r
3801     /* Display the board */\r
3802     if (!pausing) {\r
3803       \r
3804       if (appData.premove)\r
3805           if (!gotPremove || \r
3806              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3807              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3808               ClearPremoveHighlights();\r
3809 \r
3810       DrawPosition(FALSE, boards[currentMove]);\r
3811       DisplayMove(moveNum - 1);\r
3812       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3813         RingBell();\r
3814     }\r
3815 \r
3816     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3817 #if ZIPPY\r
3818     if(bookHit) { // [HGM] book: simulate book reply\r
3819         static char bookMove[MSG_SIZ]; // a bit generous?\r
3820 \r
3821         programStats.depth = programStats.nodes = programStats.time = \r
3822         programStats.score = programStats.got_only_move = 0;\r
3823         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
3824 \r
3825         strcpy(bookMove, "move ");\r
3826         strcat(bookMove, bookHit);\r
3827         HandleMachineMove(bookMove, &first);\r
3828     }\r
3829 #endif\r
3830 }\r
3831 \r
3832 void\r
3833 GetMoveListEvent()\r
3834 {\r
3835     char buf[MSG_SIZ];\r
3836     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3837         ics_getting_history = H_REQUESTED;\r
3838         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3839         SendToICS(buf);\r
3840     }\r
3841 }\r
3842 \r
3843 void\r
3844 AnalysisPeriodicEvent(force)\r
3845      int force;\r
3846 {\r
3847     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3848          && !force) || !appData.periodicUpdates)\r
3849       return;\r
3850 \r
3851     /* Send . command to Crafty to collect stats */\r
3852     SendToProgram(".\n", &first);\r
3853 \r
3854     /* Don't send another until we get a response (this makes\r
3855        us stop sending to old Crafty's which don't understand\r
3856        the "." command (sending illegal cmds resets node count & time,\r
3857        which looks bad)) */\r
3858     programStats.ok_to_send = 0;\r
3859 }\r
3860 \r
3861 void\r
3862 SendMoveToProgram(moveNum, cps)\r
3863      int moveNum;\r
3864      ChessProgramState *cps;\r
3865 {\r
3866     char buf[MSG_SIZ];\r
3867 \r
3868     if (cps->useUsermove) {\r
3869       SendToProgram("usermove ", cps);\r
3870     }\r
3871     if (cps->useSAN) {\r
3872       char *space;\r
3873       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3874         int len = space - parseList[moveNum];\r
3875         memcpy(buf, parseList[moveNum], len);\r
3876         buf[len++] = '\n';\r
3877         buf[len] = NULLCHAR;\r
3878       } else {\r
3879         sprintf(buf, "%s\n", parseList[moveNum]);\r
3880       }\r
3881       SendToProgram(buf, cps);\r
3882     } else {\r
3883       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */\r
3884         AlphaRank(moveList[moveNum], 4);\r
3885         SendToProgram(moveList[moveNum], cps);\r
3886         AlphaRank(moveList[moveNum], 4); // and back\r
3887       } else\r
3888       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3889        * the engine. It would be nice to have a better way to identify castle \r
3890        * moves here. */\r
3891       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)\r
3892                                                                          && cps->useOOCastle) {\r
3893         int fromX = moveList[moveNum][0] - AAA; \r
3894         int fromY = moveList[moveNum][1] - ONE;\r
3895         int toX = moveList[moveNum][2] - AAA; \r
3896         int toY = moveList[moveNum][3] - ONE;\r
3897         if((boards[moveNum][fromY][fromX] == WhiteKing \r
3898             && boards[moveNum][toY][toX] == WhiteRook)\r
3899            || (boards[moveNum][fromY][fromX] == BlackKing \r
3900                && boards[moveNum][toY][toX] == BlackRook)) {\r
3901           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3902           else SendToProgram("O-O-O\n", cps);\r
3903         }\r
3904         else SendToProgram(moveList[moveNum], cps);\r
3905       }\r
3906       else SendToProgram(moveList[moveNum], cps);\r
3907       /* End of additions by Tord */\r
3908     }\r
3909 \r
3910     /* [HGM] setting up the opening has brought engine in force mode! */\r
3911     /*       Send 'go' if we are in a mode where machine should play. */\r
3912     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
3913         (gameMode == TwoMachinesPlay   ||\r
3914 #ifdef ZIPPY\r
3915          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||\r
3916 #endif\r
3917          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
3918         SendToProgram("go\n", cps);\r
3919   if (appData.debugMode) {\r
3920     fprintf(debugFP, "(extra)\n");\r
3921   }\r
3922     }\r
3923     setboardSpoiledMachineBlack = 0;\r
3924 }\r
3925 \r
3926 void\r
3927 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
3928      ChessMove moveType;\r
3929      int fromX, fromY, toX, toY;\r
3930 {\r
3931     char user_move[MSG_SIZ];\r
3932 \r
3933     switch (moveType) {\r
3934       default:\r
3935         sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",\r
3936                 (int)moveType, fromX, fromY, toX, toY);\r
3937         DisplayError(user_move + strlen("say "), 0);\r
3938         break;\r
3939       case WhiteKingSideCastle:\r
3940       case BlackKingSideCastle:\r
3941       case WhiteQueenSideCastleWild:\r
3942       case BlackQueenSideCastleWild:\r
3943       /* PUSH Fabien */\r
3944       case WhiteHSideCastleFR:\r
3945       case BlackHSideCastleFR:\r
3946       /* POP Fabien */\r
3947         sprintf(user_move, "o-o\n");\r
3948         break;\r
3949       case WhiteQueenSideCastle:\r
3950       case BlackQueenSideCastle:\r
3951       case WhiteKingSideCastleWild:\r
3952       case BlackKingSideCastleWild:\r
3953       /* PUSH Fabien */\r
3954       case WhiteASideCastleFR:\r
3955       case BlackASideCastleFR:\r
3956       /* POP Fabien */\r
3957         sprintf(user_move, "o-o-o\n");\r
3958         break;\r
3959       case WhitePromotionQueen:\r
3960       case BlackPromotionQueen:\r
3961       case WhitePromotionRook:\r
3962       case BlackPromotionRook:\r
3963       case WhitePromotionBishop:\r
3964       case BlackPromotionBishop:\r
3965       case WhitePromotionKnight:\r
3966       case BlackPromotionKnight:\r
3967       case WhitePromotionKing:\r
3968       case BlackPromotionKing:\r
3969       case WhitePromotionChancellor:\r
3970       case BlackPromotionChancellor:\r
3971       case WhitePromotionArchbishop:\r
3972       case BlackPromotionArchbishop:\r
3973         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)\r
3974             sprintf(user_move, "%c%c%c%c=%c\n",\r
3975                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
3976                 PieceToChar(WhiteFerz));\r
3977         else if(gameInfo.variant == VariantGreat)\r
3978             sprintf(user_move, "%c%c%c%c=%c\n",\r
3979                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
3980                 PieceToChar(WhiteMan));\r
3981         else\r
3982             sprintf(user_move, "%c%c%c%c=%c\n",\r
3983                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
3984                 PieceToChar(PromoPiece(moveType)));\r
3985         break;\r
3986       case WhiteDrop:\r
3987       case BlackDrop:\r
3988         sprintf(user_move, "%c@%c%c\n",\r
3989                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
3990                 AAA + toX, ONE + toY);\r
3991         break;\r
3992       case NormalMove:\r
3993       case WhiteCapturesEnPassant:\r
3994       case BlackCapturesEnPassant:\r
3995       case IllegalMove:  /* could be a variant we don't quite understand */\r
3996         sprintf(user_move, "%c%c%c%c\n",\r
3997                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
3998         break;\r
3999     }\r
4000     SendToICS(user_move);\r
4001 }\r
4002 \r
4003 void\r
4004 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
4005      int rf, ff, rt, ft;\r
4006      char promoChar;\r
4007      char move[7];\r
4008 {\r
4009     if (rf == DROP_RANK) {\r
4010         sprintf(move, "%c@%c%c\n",\r
4011                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
4012     } else {\r
4013         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
4014             sprintf(move, "%c%c%c%c\n",\r
4015                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
4016         } else {\r
4017             sprintf(move, "%c%c%c%c%c\n",\r
4018                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
4019         }\r
4020     }\r
4021 }\r
4022 \r
4023 void\r
4024 ProcessICSInitScript(f)\r
4025      FILE *f;\r
4026 {\r
4027     char buf[MSG_SIZ];\r
4028 \r
4029     while (fgets(buf, MSG_SIZ, f)) {\r
4030         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
4031     }\r
4032 \r
4033     fclose(f);\r
4034 }\r
4035 \r
4036 \r
4037 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
4038 void\r
4039 AlphaRank(char *move, int n)\r
4040 {\r
4041     char *p = move, c; int x, y;\r
4042 \r
4043     if (appData.debugMode) {\r
4044         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
4045     }\r
4046 \r
4047     if(move[1]=='*' && \r
4048        move[2]>='0' && move[2]<='9' &&\r
4049        move[3]>='a' && move[3]<='x'    ) {\r
4050         move[1] = '@';\r
4051         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4052         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4053     } else\r
4054     if(move[0]>='0' && move[0]<='9' &&\r
4055        move[1]>='a' && move[1]<='x' &&\r
4056        move[2]>='0' && move[2]<='9' &&\r
4057        move[3]>='a' && move[3]<='x'    ) {\r
4058         /* input move, Shogi -> normal */\r
4059         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;\r
4060         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\r
4061         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4062         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4063     } else\r
4064     if(move[1]=='@' &&\r
4065        move[3]>='0' && move[3]<='9' &&\r
4066        move[2]>='a' && move[2]<='x'    ) {\r
4067         move[1] = '*';\r
4068         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4069         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4070     } else\r
4071     if(\r
4072        move[0]>='a' && move[0]<='x' &&\r
4073        move[3]>='0' && move[3]<='9' &&\r
4074        move[2]>='a' && move[2]<='x'    ) {\r
4075          /* output move, normal -> Shogi */\r
4076         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
4077         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
4078         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4079         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4080         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
4081     }\r
4082     if (appData.debugMode) {\r
4083         fprintf(debugFP, "   out = '%s'\n", move);\r
4084     }\r
4085 }\r
4086 \r
4087 /* Parser for moves from gnuchess, ICS, or user typein box */\r
4088 Boolean\r
4089 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
4090      char *move;\r
4091      int moveNum;\r
4092      ChessMove *moveType;\r
4093      int *fromX, *fromY, *toX, *toY;\r
4094      char *promoChar;\r
4095 {       \r
4096     if (appData.debugMode) {\r
4097         fprintf(debugFP, "move to parse: %s\n", move);\r
4098     }\r
4099     *moveType = yylexstr(moveNum, move);\r
4100 \r
4101     switch (*moveType) {\r
4102       case WhitePromotionChancellor:\r
4103       case BlackPromotionChancellor:\r
4104       case WhitePromotionArchbishop:\r
4105       case BlackPromotionArchbishop:\r
4106       case WhitePromotionQueen:\r
4107       case BlackPromotionQueen:\r
4108       case WhitePromotionRook:\r
4109       case BlackPromotionRook:\r
4110       case WhitePromotionBishop:\r
4111       case BlackPromotionBishop:\r
4112       case WhitePromotionKnight:\r
4113       case BlackPromotionKnight:\r
4114       case WhitePromotionKing:\r
4115       case BlackPromotionKing:\r
4116       case NormalMove:\r
4117       case WhiteCapturesEnPassant:\r
4118       case BlackCapturesEnPassant:\r
4119       case WhiteKingSideCastle:\r
4120       case WhiteQueenSideCastle:\r
4121       case BlackKingSideCastle:\r
4122       case BlackQueenSideCastle:\r
4123       case WhiteKingSideCastleWild:\r
4124       case WhiteQueenSideCastleWild:\r
4125       case BlackKingSideCastleWild:\r
4126       case BlackQueenSideCastleWild:\r
4127       /* Code added by Tord: */\r
4128       case WhiteHSideCastleFR:\r
4129       case WhiteASideCastleFR:\r
4130       case BlackHSideCastleFR:\r
4131       case BlackASideCastleFR:\r
4132       /* End of code added by Tord */\r
4133       case IllegalMove:         /* bug or odd chess variant */\r
4134         *fromX = currentMoveString[0] - AAA;\r
4135         *fromY = currentMoveString[1] - ONE;\r
4136         *toX = currentMoveString[2] - AAA;\r
4137         *toY = currentMoveString[3] - ONE;\r
4138         *promoChar = currentMoveString[4];\r
4139         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
4140             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
4141     if (appData.debugMode) {\r
4142         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
4143     }\r
4144             *fromX = *fromY = *toX = *toY = 0;\r
4145             return FALSE;\r
4146         }\r
4147         if (appData.testLegality) {\r
4148           return (*moveType != IllegalMove);\r
4149         } else {\r
4150           return !(fromX == fromY && toX == toY);\r
4151         }\r
4152 \r
4153       case WhiteDrop:\r
4154       case BlackDrop:\r
4155         *fromX = *moveType == WhiteDrop ?\r
4156           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
4157           (int) CharToPiece(ToLower(currentMoveString[0]));\r
4158         *fromY = DROP_RANK;\r
4159         *toX = currentMoveString[2] - AAA;\r
4160         *toY = currentMoveString[3] - ONE;\r
4161         *promoChar = NULLCHAR;\r
4162         return TRUE;\r
4163 \r
4164       case AmbiguousMove:\r
4165       case ImpossibleMove:\r
4166       case (ChessMove) 0:       /* end of file */\r
4167       case ElapsedTime:\r
4168       case Comment:\r
4169       case PGNTag:\r
4170       case NAG:\r
4171       case WhiteWins:\r
4172       case BlackWins:\r
4173       case GameIsDrawn:\r
4174       default:\r
4175     if (appData.debugMode) {\r
4176         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
4177     }\r
4178         /* bug? */\r
4179         *fromX = *fromY = *toX = *toY = 0;\r
4180         *promoChar = NULLCHAR;\r
4181         return FALSE;\r
4182     }\r
4183 }\r
4184 \r
4185 /* [AS] FRC game initialization */\r
4186 static int FindEmptySquare( Board board, int n )\r
4187 {\r
4188     int i = 0;\r
4189 \r
4190     while( 1 ) {\r
4191         while( board[0][i] != EmptySquare ) i++;\r
4192         if( n == 0 )\r
4193             break;\r
4194         n--;\r
4195         i++;\r
4196     }\r
4197 \r
4198     return i;\r
4199 }\r
4200 \r
4201 #if 0\r
4202 static void ShuffleFRC( Board board )\r
4203 {\r
4204     int i;\r
4205 \r
4206     srand( time(0) );\r
4207     \r
4208     for( i=0; i<8; i++ ) {\r
4209         board[0][i] = EmptySquare;\r
4210     }\r
4211 \r
4212     board[0][(rand() % 4)*2  ] = WhiteBishop; /* On dark square */\r
4213     board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */\r
4214     board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;\r
4215     board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;\r
4216     board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;\r
4217     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4218     initialRights[1]  = initialRights[4]  =\r
4219     castlingRights[0][1] = castlingRights[0][4] = i;\r
4220     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4221     initialRights[2]  = initialRights[5]  =\r
4222     castlingRights[0][2] = castlingRights[0][5] = i;\r
4223     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4224     initialRights[0]  = initialRights[3]  =\r
4225     castlingRights[0][0] = castlingRights[0][3] = i;\r
4226 \r
4227     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4228         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4229     }\r
4230 }\r
4231 \r
4232 static unsigned char FRC_KnightTable[10] = {\r
4233     0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33\r
4234 };\r
4235 \r
4236 static void SetupFRC( Board board, int pos_index )\r
4237 {\r
4238     int i;\r
4239     unsigned char knights;\r
4240 \r
4241     /* Bring the position index into a safe range (just in case...) */\r
4242     if( pos_index < 0 ) pos_index = 0;\r
4243 \r
4244     pos_index %= 960;\r
4245 \r
4246     /* Clear the board */\r
4247     for( i=0; i<8; i++ ) {\r
4248         board[0][i] = EmptySquare;\r
4249     }\r
4250 \r
4251     /* Place bishops and queen */\r
4252     board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */\r
4253     pos_index /= 4;\r
4254     \r
4255     board[0][ (pos_index % 4)*2     ] = WhiteBishop; /* On dark square */\r
4256     pos_index /= 4;\r
4257 \r
4258     board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;\r
4259     pos_index /= 6;\r
4260 \r
4261     /* Place knigths */\r
4262     knights = FRC_KnightTable[ pos_index ];\r
4263 \r
4264     board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;\r
4265     board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;\r
4266 \r
4267     /* Place rooks and king */\r
4268     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4269     initialRights[1]  = initialRights[4]  =\r
4270     castlingRights[0][1] = castlingRights[0][4] = i;\r
4271     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4272     initialRights[2]  = initialRights[5]  =\r
4273     castlingRights[0][2] = castlingRights[0][5] = i;\r
4274     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4275     initialRights[0]  = initialRights[3]  =\r
4276     castlingRights[0][0] = castlingRights[0][3] = i;\r
4277 \r
4278     /* Mirror piece placement for black */\r
4279     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4280         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4281     }\r
4282 }\r
4283 #else\r
4284 // [HGM] shuffle: a more general way to suffle opening setups, applicable to arbitrry variants.\r
4285 // All positions will have equal probability, but the current method will not provide a unique\r
4286 // numbering scheme for arrays that contain 3 or more pieces of the same kind.\r
4287 #define DARK 1\r
4288 #define LITE 2\r
4289 #define ANY 3\r
4290 \r
4291 int squaresLeft[4];\r
4292 int piecesLeft[(int)BlackPawn];\r
4293 long long int seed, nrOfShuffles;\r
4294 \r
4295 void GetPositionNumber()\r
4296 {       // sets global variable seed\r
4297         int i;\r
4298 \r
4299         seed = appData.defaultFrcPosition;\r
4300         if(seed < 0) { // randomize based on time for negative FRC position numbers\r
4301                 srandom(time(0)); \r
4302                 for(i=0; i<50; i++) seed += random();\r
4303                 seed = random() ^ random() >> 8 ^ random() << 8;\r
4304                 if(seed<0) seed = -seed;\r
4305         }\r
4306 }\r
4307 \r
4308 int put(Board board, int pieceType, int rank, int n, int shade)\r
4309 // put the piece on the (n-1)-th empty squares of the given shade\r
4310 {\r
4311         int i;\r
4312 \r
4313         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
4314                 if( ((i-BOARD_LEFT)&1)+1 & shade && board[rank][i] == EmptySquare && n-- == 0) {\r
4315                         board[rank][i] = (ChessSquare) pieceType;\r
4316                         squaresLeft[(i-BOARD_LEFT&1) + 1]--;\r
4317                         squaresLeft[ANY]--;\r
4318                         piecesLeft[pieceType]--; \r
4319                         return i;\r
4320                 }\r
4321         }\r
4322         return -1;\r
4323 }\r
4324 \r
4325 \r
4326 void AddOnePiece(Board board, int pieceType, int rank, int shade)\r
4327 // calculate where the next piece goes, (any empty square), and put it there\r
4328 {\r
4329         int i;\r
4330 \r
4331         i = seed % squaresLeft[shade];\r
4332         nrOfShuffles *= squaresLeft[shade];\r
4333         seed /= squaresLeft[shade];\r
4334         put(board, pieceType, rank, i, shade);\r
4335 }\r
4336 \r
4337 void AddTwoPieces(Board board, int pieceType, int rank)\r
4338 // calculate where the next 2 identical pieces go, (any empty square), and put it there\r
4339 {\r
4340         int i, n=squaresLeft[ANY], j=n-1, k;\r
4341 \r
4342         k = n*(n-1)/2; // nr of possibilities, not counting permutations\r
4343         i = seed % k;  // pick one\r
4344         nrOfShuffles *= k;\r
4345         seed /= k;\r
4346         while(i >= j) i -= j--;\r
4347         j = n - 1 - j; i += j;\r
4348         put(board, pieceType, rank, j, ANY);\r
4349         put(board, pieceType, rank, i, ANY);\r
4350 }\r
4351 \r
4352 void SetUpShuffle(Board board, int number)\r
4353 {\r
4354         int i, p, first=1;\r
4355 \r
4356         GetPositionNumber(); nrOfShuffles = 1;\r
4357 \r
4358         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;\r
4359         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;\r
4360         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];\r
4361 \r
4362         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;\r
4363 \r
4364         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board\r
4365             p = (int) board[0][i];\r
4366             if(p < (int) BlackPawn) piecesLeft[p] ++;\r
4367             board[0][i] = EmptySquare;\r
4368         }\r
4369 \r
4370         if(PosFlags(0) & F_ALL_CASTLE_OK) {\r
4371             // shuffles restricted to allow normal castling put KRR first\r
4372             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle\r
4373                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4374             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles\r
4375                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4376             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling\r
4377                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);\r
4378             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling\r
4379                 put(board, WhiteRook, 0, 0, ANY);\r
4380             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle\r
4381         }\r
4382 \r
4383         if((BOARD_RGHT-BOARD_LEFT & 1) == 0)\r
4384             // only for even boards make effort to put pairs of colorbound pieces on opposite colors\r
4385             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {\r
4386                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;\r
4387                 while(piecesLeft[p] >= 2) {\r
4388                     AddOnePiece(board, p, 0, LITE);\r
4389                     AddOnePiece(board, p, 0, DARK);\r
4390                 }\r
4391                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)\r
4392             }\r
4393 \r
4394         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {\r
4395             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere\r
4396             // but we leave King and Rooks for last, to possibly obey FRC restriction\r
4397             if(p == (int)WhiteRook) continue;\r
4398             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations\r
4399             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece\r
4400         }\r
4401 \r
4402         // now everything is placed, except perhaps King (Unicorn) and Rooks\r
4403 \r
4404         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {\r
4405             // Last King gets castling rights\r
4406             while(piecesLeft[(int)WhiteUnicorn]) {\r
4407                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4408                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4409             }\r
4410 \r
4411             while(piecesLeft[(int)WhiteKing]) {\r
4412                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4413                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4414             }\r
4415 \r
4416 \r
4417         } else {\r
4418             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);\r
4419             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);\r
4420         }\r
4421 \r
4422         // Only Rooks can be left; simply place them all\r
4423         while(piecesLeft[(int)WhiteRook]) {\r
4424                 i = put(board, WhiteRook, 0, 0, ANY);\r
4425                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights\r
4426                         if(first) {\r
4427                                 first=0;\r
4428                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;\r
4429                         }\r
4430                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;\r
4431                 }\r
4432         }\r
4433         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white\r
4434             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;\r
4435         }\r
4436 \r
4437         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize\r
4438 }\r
4439 \r
4440 #endif\r
4441 \r
4442 int SetCharTable( char *table, const char * map )\r
4443 /* [HGM] moved here from winboard.c because of its general usefulness */\r
4444 /*       Basically a safe strcpy that uses the last character as King */\r
4445 {\r
4446     int result = FALSE; int NrPieces;\r
4447 \r
4448     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
4449                     && NrPieces >= 12 && !(NrPieces&1)) {\r
4450         int i; /* [HGM] Accept even length from 12 to 34 */\r
4451 \r
4452         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
4453         for( i=0; i<NrPieces/2-1; i++ ) {\r
4454             table[i] = map[i];\r
4455             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
4456         }\r
4457         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
4458         table[(int) BlackKing]  = map[NrPieces-1];\r
4459 \r
4460         result = TRUE;\r
4461     }\r
4462 \r
4463     return result;\r
4464 }\r
4465 \r
4466 void Prelude(Board board)\r
4467 {       // [HGM] superchess: random selection of exo-pieces\r
4468         int i, j, k; ChessSquare p; \r
4469         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };\r
4470 \r
4471         GetPositionNumber(); // use FRC position number\r
4472 \r
4473         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table\r
4474             SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4475             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) \r
4476                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;\r
4477         }\r
4478 \r
4479         j = seed%4;                 seed /= 4; \r
4480         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4481         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4482         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4483         j = seed%3 + (seed%3 >= j); seed /= 3; \r
4484         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4485         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4486         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4487         j = seed%3;                 seed /= 3; \r
4488         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4489         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4490         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4491         j = seed%2 + (seed%2 >= j); seed /= 2; \r
4492         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4493         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4494         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4495         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);\r
4496         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);\r
4497         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);\r
4498         put(board, exoPieces[0],    0, 0, ANY);\r
4499         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];\r
4500 }\r
4501 \r
4502 void\r
4503 InitPosition(redraw)\r
4504      int redraw;\r
4505 {\r
4506     ChessSquare (* pieces)[BOARD_SIZE];\r
4507     int i, j, pawnRow, overrule,\r
4508     oldx = gameInfo.boardWidth,\r
4509     oldy = gameInfo.boardHeight,\r
4510     oldh = gameInfo.holdingsWidth,\r
4511     oldv = gameInfo.variant;\r
4512 \r
4513     currentMove = forwardMostMove = backwardMostMove = 0;\r
4514     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request\r
4515 \r
4516     /* [AS] Initialize pv info list [HGM] and game status */\r
4517     {\r
4518         for( i=0; i<MAX_MOVES; i++ ) {\r
4519             pvInfoList[i].depth = 0;\r
4520             epStatus[i]=EP_NONE;\r
4521             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
4522         }\r
4523 \r
4524         initialRulePlies = 0; /* 50-move counter start */\r
4525 \r
4526         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4527         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4528     }\r
4529 \r
4530     \r
4531     /* [HGM] logic here is completely changed. In stead of full positions */\r
4532     /* the initialized data only consist of the two backranks. The switch */\r
4533     /* selects which one we will use, which is than copied to the Board   */\r
4534     /* initialPosition, which for the rest is initialized by Pawns and    */\r
4535     /* empty squares. This initial position is then copied to boards[0],  */\r
4536     /* possibly after shuffling, so that it remains available.            */\r
4537 \r
4538     gameInfo.holdingsWidth = 0; /* default board sizes */\r
4539     gameInfo.boardWidth    = 8;\r
4540     gameInfo.boardHeight   = 8;\r
4541     gameInfo.holdingsSize  = 0;\r
4542     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
4543     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
4544     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
4545 \r
4546     switch (gameInfo.variant) {\r
4547     case VariantFischeRandom:\r
4548       shuffleOpenings = TRUE;\r
4549     default:\r
4550       pieces = FIDEArray;\r
4551       break;\r
4552     case VariantShatranj:\r
4553       pieces = ShatranjArray;\r
4554       nrCastlingRights = 0;\r
4555       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
4556       break;\r
4557     case VariantTwoKings:\r
4558       pieces = twoKingsArray;\r
4559       nrCastlingRights = 8;                 /* add rights for second King */\r
4560       castlingRights[0][6] = initialRights[2] = 5;\r
4561       castlingRights[0][7] = initialRights[5] = 5;\r
4562       castlingRank[6] = 0;\r
4563       castlingRank[7] = BOARD_HEIGHT-1;\r
4564       break;\r
4565     case VariantCapaRandom:\r
4566       shuffleOpenings = TRUE;\r
4567     case VariantCapablanca:\r
4568       pieces = CapablancaArray;\r
4569       gameInfo.boardWidth = 10;\r
4570       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4571       break;\r
4572     case VariantGothic:\r
4573       pieces = GothicArray;\r
4574       gameInfo.boardWidth = 10;\r
4575       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4576       break;\r
4577     case VariantJanus:\r
4578       pieces = JanusArray;\r
4579       gameInfo.boardWidth = 10;\r
4580       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
4581       nrCastlingRights = 6;\r
4582         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4583         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4584         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH-1>>1;\r
4585         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4586         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4587         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH-1>>1;\r
4588       break;\r
4589     case VariantFalcon:\r
4590       pieces = FalconArray;\r
4591       gameInfo.boardWidth = 10;\r
4592       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4593       break;\r
4594     case VariantXiangqi:\r
4595       pieces = XiangqiArray;\r
4596       gameInfo.boardWidth  = 9;\r
4597       gameInfo.boardHeight = 10;\r
4598       nrCastlingRights = 0;\r
4599       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4600       break;\r
4601     case VariantShogi:\r
4602       pieces = ShogiArray;\r
4603       gameInfo.boardWidth  = 9;\r
4604       gameInfo.boardHeight = 9;\r
4605       gameInfo.holdingsSize = 7;\r
4606       nrCastlingRights = 0;\r
4607       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4608       break;\r
4609     case VariantCourier:\r
4610       pieces = CourierArray;\r
4611       gameInfo.boardWidth  = 12;\r
4612       nrCastlingRights = 0;\r
4613       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4614       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4615       break;\r
4616     case VariantKnightmate:\r
4617       pieces = KnightmateArray;\r
4618       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4619       break;\r
4620     case VariantFairy:\r
4621       pieces = fairyArray;\r
4622       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4623       break;\r
4624     case VariantGreat:\r
4625       pieces = GreatArray;\r
4626       gameInfo.boardWidth = 10;\r
4627       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");\r
4628       gameInfo.holdingsSize = 8;\r
4629       break;\r
4630     case VariantSuper:\r
4631       pieces = FIDEArray;\r
4632       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
4633       gameInfo.holdingsSize = 8;\r
4634       startedFromSetupPosition = TRUE;\r
4635       break;\r
4636     case VariantCrazyhouse:\r
4637     case VariantBughouse:\r
4638       pieces = FIDEArray;\r
4639       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4640       gameInfo.holdingsSize = 5;\r
4641       break;\r
4642     case VariantWildCastle:\r
4643       pieces = FIDEArray;\r
4644       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4645       shuffleOpenings = 1;\r
4646       break;\r
4647     case VariantNoCastle:\r
4648       pieces = FIDEArray;\r
4649       nrCastlingRights = 0;\r
4650       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4651       /* !!?unconstrained back-rank shuffle */\r
4652       shuffleOpenings = 1;\r
4653       break;\r
4654     }\r
4655 \r
4656     overrule = 0;\r
4657     if(appData.NrFiles >= 0) {\r
4658         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4659         gameInfo.boardWidth = appData.NrFiles;\r
4660     }\r
4661     if(appData.NrRanks >= 0) {\r
4662         gameInfo.boardHeight = appData.NrRanks;\r
4663     }\r
4664     if(appData.holdingsSize >= 0) {\r
4665         i = appData.holdingsSize;\r
4666         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4667         gameInfo.holdingsSize = i;\r
4668     }\r
4669     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4670     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4671         DisplayFatalError("Recompile to support this BOARD_SIZE!", 0, 2);\r
4672 \r
4673     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4674     if(pawnRow < 1) pawnRow = 1;\r
4675 \r
4676     /* User pieceToChar list overrules defaults */\r
4677     if(appData.pieceToCharTable != NULL)\r
4678         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4679 \r
4680     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4681 \r
4682         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4683             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4684         for( i=0; i<BOARD_HEIGHT; i++ )\r
4685             initialPosition[i][j] = s;\r
4686 \r
4687         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4688         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4689         initialPosition[pawnRow][j] = WhitePawn;\r
4690         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4691         if(gameInfo.variant == VariantXiangqi) {\r
4692             if(j&1) {\r
4693                 initialPosition[pawnRow][j] = \r
4694                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4695                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4696                    initialPosition[2][j] = WhiteCannon;\r
4697                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4698                 }\r
4699             }\r
4700         }\r
4701         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4702     }\r
4703     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4704 \r
4705             j=BOARD_LEFT+1;\r
4706             initialPosition[1][j] = WhiteBishop;\r
4707             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4708             j=BOARD_RGHT-2;\r
4709             initialPosition[1][j] = WhiteRook;\r
4710             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4711     }\r
4712 \r
4713     if( nrCastlingRights == -1) {\r
4714         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4715         /*       This sets default castling rights from none to normal corners   */\r
4716         /* Variants with other castling rights must set them themselves above    */\r
4717         nrCastlingRights = 6;\r
4718        \r
4719         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4720         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4721         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4722         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4723         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4724         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4725      }\r
4726 \r
4727      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
4728      if(gameInfo.variant == VariantGreat) { // promotion commoners\r
4729         initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-1] = WhiteMan;\r
4730         initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-2] = 9;\r
4731         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;\r
4732         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;\r
4733      }\r
4734 #if 0\r
4735     if(gameInfo.variant == VariantFischeRandom) {\r
4736       if( appData.defaultFrcPosition < 0 ) {\r
4737         ShuffleFRC( initialPosition );\r
4738       }\r
4739       else {\r
4740         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4741       }\r
4742       startedFromSetupPosition = TRUE;\r
4743     } else \r
4744 #else\r
4745   if (appData.debugMode) {\r
4746     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
4747   }\r
4748     if(shuffleOpenings) {\r
4749         SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
4750         startedFromSetupPosition = TRUE;\r
4751     }\r
4752 #endif\r
4753     if(startedFromPositionFile) {\r
4754       /* [HGM] loadPos: use PositionFile for every new game */\r
4755       CopyBoard(initialPosition, filePosition);\r
4756       for(i=0; i<nrCastlingRights; i++)\r
4757           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4758       startedFromSetupPosition = TRUE;\r
4759     }\r
4760 \r
4761     CopyBoard(boards[0], initialPosition);\r
4762 \r
4763     if(oldx != gameInfo.boardWidth ||\r
4764        oldy != gameInfo.boardHeight ||\r
4765        oldh != gameInfo.holdingsWidth\r
4766 #ifdef GOTHIC\r
4767        || oldv == VariantGothic ||        // For licensing popups\r
4768        gameInfo.variant == VariantGothic\r
4769 #endif\r
4770 #ifdef FALCON\r
4771        || oldv == VariantFalcon ||\r
4772        gameInfo.variant == VariantFalcon\r
4773 #endif\r
4774                                          )\r
4775             InitDrawingSizes(-2 ,0);\r
4776 \r
4777     if (redraw)\r
4778       DrawPosition(TRUE, boards[currentMove]);\r
4779 }\r
4780 \r
4781 void\r
4782 SendBoard(cps, moveNum)\r
4783      ChessProgramState *cps;\r
4784      int moveNum;\r
4785 {\r
4786     char message[MSG_SIZ];\r
4787     \r
4788     if (cps->useSetboard) {\r
4789       char* fen = PositionToFEN(moveNum, cps->useFEN960);\r
4790       sprintf(message, "setboard %s\n", fen);\r
4791       SendToProgram(message, cps);\r
4792       free(fen);\r
4793 \r
4794     } else {\r
4795       ChessSquare *bp;\r
4796       int i, j;\r
4797       /* Kludge to set black to move, avoiding the troublesome and now\r
4798        * deprecated "black" command.\r
4799        */\r
4800       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4801 \r
4802       SendToProgram("edit\n", cps);\r
4803       SendToProgram("#\n", cps);\r
4804       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4805         bp = &boards[moveNum][i][BOARD_LEFT];\r
4806         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4807           if ((int) *bp < (int) BlackPawn) {\r
4808             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4809                     AAA + j, ONE + i);\r
4810             if(message[0] == '+' || message[0] == '~') {\r
4811                 sprintf(message, "%c%c%c+\n",\r
4812                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4813                         AAA + j, ONE + i);\r
4814             }\r
4815             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4816                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4817                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4818             }\r
4819             SendToProgram(message, cps);\r
4820           }\r
4821         }\r
4822       }\r
4823     \r
4824       SendToProgram("c\n", cps);\r
4825       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4826         bp = &boards[moveNum][i][BOARD_LEFT];\r
4827         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4828           if (((int) *bp != (int) EmptySquare)\r
4829               && ((int) *bp >= (int) BlackPawn)) {\r
4830             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4831                     AAA + j, ONE + i);\r
4832             if(message[0] == '+' || message[0] == '~') {\r
4833                 sprintf(message, "%c%c%c+\n",\r
4834                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4835                         AAA + j, ONE + i);\r
4836             }\r
4837             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4838                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4839                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4840             }\r
4841             SendToProgram(message, cps);\r
4842           }\r
4843         }\r
4844       }\r
4845     \r
4846       SendToProgram(".\n", cps);\r
4847     }\r
4848     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4849 }\r
4850 \r
4851 int\r
4852 IsPromotion(fromX, fromY, toX, toY)\r
4853      int fromX, fromY, toX, toY;\r
4854 {\r
4855     /* [HGM] add Shogi promotions */\r
4856     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4857     ChessSquare piece;\r
4858 \r
4859     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4860       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4861    /* [HGM] Note to self: line above also weeds out drops */\r
4862     piece = boards[currentMove][fromY][fromX];\r
4863     if(gameInfo.variant == VariantShogi) {\r
4864         promotionZoneSize = 3;\r
4865         highestPromotingPiece = (int)WhiteKing;\r
4866         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4867            and if in normal chess we then allow promotion to King, why not\r
4868            allow promotion of other piece in Shogi?                         */\r
4869     }\r
4870     if((int)piece >= BlackPawn) {\r
4871         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4872              return FALSE;\r
4873         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4874     } else {\r
4875         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4876            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4877     }\r
4878     return ( (int)piece <= highestPromotingPiece );\r
4879 }\r
4880 \r
4881 int\r
4882 InPalace(row, column)\r
4883      int row, column;\r
4884 {   /* [HGM] for Xiangqi */\r
4885     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4886          column < (BOARD_WIDTH + 4)/2 &&\r
4887          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4888     return FALSE;\r
4889 }\r
4890 \r
4891 int\r
4892 PieceForSquare (x, y)\r
4893      int x;\r
4894      int y;\r
4895 {\r
4896   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
4897      return -1;\r
4898   else\r
4899      return boards[currentMove][y][x];\r
4900 }\r
4901 \r
4902 int\r
4903 OKToStartUserMove(x, y)\r
4904      int x, y;\r
4905 {\r
4906     ChessSquare from_piece;\r
4907     int white_piece;\r
4908 \r
4909     if (matchMode) return FALSE;\r
4910     if (gameMode == EditPosition) return TRUE;\r
4911 \r
4912     if (x >= 0 && y >= 0)\r
4913       from_piece = boards[currentMove][y][x];\r
4914     else\r
4915       from_piece = EmptySquare;\r
4916 \r
4917     if (from_piece == EmptySquare) return FALSE;\r
4918 \r
4919     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4920       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4921 \r
4922     switch (gameMode) {\r
4923       case PlayFromGameFile:\r
4924       case AnalyzeFile:\r
4925       case TwoMachinesPlay:\r
4926       case EndOfGame:\r
4927         return FALSE;\r
4928 \r
4929       case IcsObserving:\r
4930       case IcsIdle:\r
4931         return FALSE;\r
4932 \r
4933       case MachinePlaysWhite:\r
4934       case IcsPlayingBlack:\r
4935         if (appData.zippyPlay) return FALSE;\r
4936         if (white_piece) {\r
4937             DisplayMoveError("You are playing Black");\r
4938             return FALSE;\r
4939         }\r
4940         break;\r
4941 \r
4942       case MachinePlaysBlack:\r
4943       case IcsPlayingWhite:\r
4944         if (appData.zippyPlay) return FALSE;\r
4945         if (!white_piece) {\r
4946             DisplayMoveError("You are playing White");\r
4947             return FALSE;\r
4948         }\r
4949         break;\r
4950 \r
4951       case EditGame:\r
4952         if (!white_piece && WhiteOnMove(currentMove)) {\r
4953             DisplayMoveError("It is White's turn");\r
4954             return FALSE;\r
4955         }           \r
4956         if (white_piece && !WhiteOnMove(currentMove)) {\r
4957             DisplayMoveError("It is Black's turn");\r
4958             return FALSE;\r
4959         }           \r
4960         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
4961             /* Editing correspondence game history */\r
4962             /* Could disallow this or prompt for confirmation */\r
4963             cmailOldMove = -1;\r
4964         }\r
4965         if (currentMove < forwardMostMove) {\r
4966             /* Discarding moves */\r
4967             /* Could prompt for confirmation here,\r
4968                but I don't think that's such a good idea */\r
4969             forwardMostMove = currentMove;\r
4970         }\r
4971         break;\r
4972 \r
4973       case BeginningOfGame:\r
4974         if (appData.icsActive) return FALSE;\r
4975         if (!appData.noChessProgram) {\r
4976             if (!white_piece) {\r
4977                 DisplayMoveError("You are playing White");\r
4978                 return FALSE;\r
4979             }\r
4980         }\r
4981         break;\r
4982         \r
4983       case Training:\r
4984         if (!white_piece && WhiteOnMove(currentMove)) {\r
4985             DisplayMoveError("It is White's turn");\r
4986             return FALSE;\r
4987         }           \r
4988         if (white_piece && !WhiteOnMove(currentMove)) {\r
4989             DisplayMoveError("It is Black's turn");\r
4990             return FALSE;\r
4991         }           \r
4992         break;\r
4993 \r
4994       default:\r
4995       case IcsExamining:\r
4996         break;\r
4997     }\r
4998     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
4999         && gameMode != AnalyzeFile && gameMode != Training) {\r
5000         DisplayMoveError("Displayed position is not current");\r
5001         return FALSE;\r
5002     }\r
5003     return TRUE;\r
5004 }\r
5005 \r
5006 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
5007 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
5008 int lastLoadGameUseList = FALSE;\r
5009 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
5010 ChessMove lastLoadGameStart = (ChessMove) 0;\r
5011 \r
5012 \r
5013 ChessMove\r
5014 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
5015      int fromX, fromY, toX, toY;\r
5016      int promoChar;\r
5017 {\r
5018     ChessMove moveType;\r
5019     ChessSquare pdown, pup;\r
5020 \r
5021     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
5022     if ((fromX == toX) && (fromY == toY)) {\r
5023         return ImpossibleMove;\r
5024     }\r
5025 \r
5026     /* [HGM] suppress all moves into holdings area and guard band */\r
5027     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
5028             return ImpossibleMove;\r
5029 \r
5030     /* [HGM] <sameColor> moved to here from winboard.c */\r
5031     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
5032     pdown = boards[currentMove][fromY][fromX];\r
5033     pup = boards[currentMove][toY][toX];\r
5034     if (    gameMode != EditPosition &&\r
5035             (WhitePawn <= pdown && pdown < BlackPawn &&\r
5036              WhitePawn <= pup && pup < BlackPawn  ||\r
5037              BlackPawn <= pdown && pdown < EmptySquare &&\r
5038              BlackPawn <= pup && pup < EmptySquare \r
5039             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
5040                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
5041                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
5042         )           )\r
5043          return ImpossibleMove;\r
5044 \r
5045     /* Check if the user is playing in turn.  This is complicated because we\r
5046        let the user "pick up" a piece before it is his turn.  So the piece he\r
5047        tried to pick up may have been captured by the time he puts it down!\r
5048        Therefore we use the color the user is supposed to be playing in this\r
5049        test, not the color of the piece that is currently on the starting\r
5050        square---except in EditGame mode, where the user is playing both\r
5051        sides; fortunately there the capture race can't happen.  (It can\r
5052        now happen in IcsExamining mode, but that's just too bad.  The user\r
5053        will get a somewhat confusing message in that case.)\r
5054        */\r
5055 \r
5056     switch (gameMode) {\r
5057       case PlayFromGameFile:\r
5058       case AnalyzeFile:\r
5059       case TwoMachinesPlay:\r
5060       case EndOfGame:\r
5061       case IcsObserving:\r
5062       case IcsIdle:\r
5063         /* We switched into a game mode where moves are not accepted,\r
5064            perhaps while the mouse button was down. */\r
5065         return ImpossibleMove;\r
5066 \r
5067       case MachinePlaysWhite:\r
5068         /* User is moving for Black */\r
5069         if (WhiteOnMove(currentMove)) {\r
5070             DisplayMoveError("It is White's turn");\r
5071             return ImpossibleMove;\r
5072         }\r
5073         break;\r
5074 \r
5075       case MachinePlaysBlack:\r
5076         /* User is moving for White */\r
5077         if (!WhiteOnMove(currentMove)) {\r
5078             DisplayMoveError("It is Black's turn");\r
5079             return ImpossibleMove;\r
5080         }\r
5081         break;\r
5082 \r
5083       case EditGame:\r
5084       case IcsExamining:\r
5085       case BeginningOfGame:\r
5086       case AnalyzeMode:\r
5087       case Training:\r
5088         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
5089             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
5090             /* User is moving for Black */\r
5091             if (WhiteOnMove(currentMove)) {\r
5092                 DisplayMoveError("It is White's turn");\r
5093                 return ImpossibleMove;\r
5094             }\r
5095         } else {\r
5096             /* User is moving for White */\r
5097             if (!WhiteOnMove(currentMove)) {\r
5098                 DisplayMoveError("It is Black's turn");\r
5099                 return ImpossibleMove;\r
5100             }\r
5101         }\r
5102         break;\r
5103 \r
5104       case IcsPlayingBlack:\r
5105         /* User is moving for Black */\r
5106         if (WhiteOnMove(currentMove)) {\r
5107             if (!appData.premove) {\r
5108                 DisplayMoveError("It is White's turn");\r
5109             } else if (toX >= 0 && toY >= 0) {\r
5110                 premoveToX = toX;\r
5111                 premoveToY = toY;\r
5112                 premoveFromX = fromX;\r
5113                 premoveFromY = fromY;\r
5114                 premovePromoChar = promoChar;\r
5115                 gotPremove = 1;\r
5116                 if (appData.debugMode) \r
5117                     fprintf(debugFP, "Got premove: fromX %d,"\r
5118                             "fromY %d, toX %d, toY %d\n",\r
5119                             fromX, fromY, toX, toY);\r
5120             }\r
5121             return ImpossibleMove;\r
5122         }\r
5123         break;\r
5124 \r
5125       case IcsPlayingWhite:\r
5126         /* User is moving for White */\r
5127         if (!WhiteOnMove(currentMove)) {\r
5128             if (!appData.premove) {\r
5129                 DisplayMoveError("It is Black's turn");\r
5130             } else if (toX >= 0 && toY >= 0) {\r
5131                 premoveToX = toX;\r
5132                 premoveToY = toY;\r
5133                 premoveFromX = fromX;\r
5134                 premoveFromY = fromY;\r
5135                 premovePromoChar = promoChar;\r
5136                 gotPremove = 1;\r
5137                 if (appData.debugMode) \r
5138                     fprintf(debugFP, "Got premove: fromX %d,"\r
5139                             "fromY %d, toX %d, toY %d\n",\r
5140                             fromX, fromY, toX, toY);\r
5141             }\r
5142             return ImpossibleMove;\r
5143         }\r
5144         break;\r
5145 \r
5146       default:\r
5147         break;\r
5148 \r
5149       case EditPosition:\r
5150         /* EditPosition, empty square, or different color piece;\r
5151            click-click move is possible */\r
5152         if (toX == -2 || toY == -2) {\r
5153             boards[0][fromY][fromX] = EmptySquare;\r
5154             return AmbiguousMove;\r
5155         } else if (toX >= 0 && toY >= 0) {\r
5156             boards[0][toY][toX] = boards[0][fromY][fromX];\r
5157             boards[0][fromY][fromX] = EmptySquare;\r
5158             return AmbiguousMove;\r
5159         }\r
5160         return ImpossibleMove;\r
5161     }\r
5162 \r
5163     /* [HGM] If move started in holdings, it means a drop */\r
5164     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
5165          if( pup != EmptySquare ) return ImpossibleMove;\r
5166          if(appData.testLegality) {\r
5167              /* it would be more logical if LegalityTest() also figured out\r
5168               * which drops are legal. For now we forbid pawns on back rank.\r
5169               * Shogi is on its own here...\r
5170               */\r
5171              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
5172                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
5173                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
5174          }\r
5175          return WhiteDrop; /* Not needed to specify white or black yet */\r
5176     }\r
5177 \r
5178     userOfferedDraw = FALSE;\r
5179         \r
5180     /* [HGM] always test for legality, to get promotion info */\r
5181     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
5182                           epStatus[currentMove], castlingRights[currentMove],\r
5183                                          fromY, fromX, toY, toX, promoChar);\r
5184 \r
5185     /* [HGM] but possibly ignore an IllegalMove result */\r
5186     if (appData.testLegality) {\r
5187         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
5188             DisplayMoveError("Illegal move");\r
5189             return ImpossibleMove;\r
5190         }\r
5191     }\r
5192 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);\r
5193     return moveType;\r
5194     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
5195        function is made into one that returns an OK move type if FinishMove\r
5196        should be called. This to give the calling driver routine the\r
5197        opportunity to finish the userMove input with a promotion popup,\r
5198        without bothering the user with this for invalid or illegal moves */\r
5199 \r
5200 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
5201 }\r
5202 \r
5203 /* Common tail of UserMoveEvent and DropMenuEvent */\r
5204 int\r
5205 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
5206      ChessMove moveType;\r
5207      int fromX, fromY, toX, toY;\r
5208      /*char*/int promoChar;\r
5209 {\r
5210     char *bookHit = 0;\r
5211 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);\r
5212     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { \r
5213         // [HGM] superchess: suppress promotions to non-available piece\r
5214         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5215         if(WhiteOnMove(currentMove)) {\r
5216             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;\r
5217         } else {\r
5218             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;\r
5219         }\r
5220     }\r
5221 \r
5222     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion\r
5223        move type in caller when we know the move is a legal promotion */\r
5224     if(moveType == NormalMove && promoChar)\r
5225         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
5226 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);\r
5227     /* [HGM] convert drag-and-drop piece drops to standard form */\r
5228     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
5229          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
5230          fromX = boards[currentMove][fromY][fromX];\r
5231          fromY = DROP_RANK;\r
5232     }\r
5233 \r
5234     /* [HGM] <popupFix> The following if has been moved here from\r
5235        UserMoveEvent(). Because it seemed to belon here (why not allow\r
5236        piece drops in training games?), and because it can only be\r
5237        performed after it is known to what we promote. */\r
5238     if (gameMode == Training) {\r
5239       /* compare the move played on the board to the next move in the\r
5240        * game. If they match, display the move and the opponent's response. \r
5241        * If they don't match, display an error message.\r
5242        */\r
5243       int saveAnimate;\r
5244       Board testBoard;\r
5245       CopyBoard(testBoard, boards[currentMove]);\r
5246       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
5247 \r
5248       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
5249         ForwardInner(currentMove+1);\r
5250 \r
5251         /* Autoplay the opponent's response.\r
5252          * if appData.animate was TRUE when Training mode was entered,\r
5253          * the response will be animated.\r
5254          */\r
5255         saveAnimate = appData.animate;\r
5256         appData.animate = animateTraining;\r
5257         ForwardInner(currentMove+1);\r
5258         appData.animate = saveAnimate;\r
5259 \r
5260         /* check for the end of the game */\r
5261         if (currentMove >= forwardMostMove) {\r
5262           gameMode = PlayFromGameFile;\r
5263           ModeHighlight();\r
5264           SetTrainingModeOff();\r
5265           DisplayInformation("End of game");\r
5266         }\r
5267       } else {\r
5268         DisplayError("Incorrect move", 0);\r
5269       }\r
5270       return 1;\r
5271     }\r
5272 \r
5273   /* Ok, now we know that the move is good, so we can kill\r
5274      the previous line in Analysis Mode */\r
5275   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
5276     forwardMostMove = currentMove;\r
5277   }\r
5278 \r
5279   /* If we need the chess program but it's dead, restart it */\r
5280   ResurrectChessProgram();\r
5281 \r
5282   /* A user move restarts a paused game*/\r
5283   if (pausing)\r
5284     PauseEvent();\r
5285 \r
5286   thinkOutput[0] = NULLCHAR;\r
5287 \r
5288   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
5289 \r
5290     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) \r
5291                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
5292         // [HGM] superchess: take promotion piece out of holdings\r
5293         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5294         if(WhiteOnMove(forwardMostMove-1)) {\r
5295             if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])\r
5296                 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;\r
5297         } else {\r
5298             if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])\r
5299                 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;\r
5300         }\r
5301     }\r
5302 \r
5303   if (gameMode == BeginningOfGame) {\r
5304     if (appData.noChessProgram) {\r
5305       gameMode = EditGame;\r
5306       SetGameInfo();\r
5307     } else {\r
5308       char buf[MSG_SIZ];\r
5309       gameMode = MachinePlaysBlack;\r
5310       StartClocks();\r
5311       SetGameInfo();\r
5312       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
5313       DisplayTitle(buf);\r
5314       if (first.sendName) {\r
5315         sprintf(buf, "name %s\n", gameInfo.white);\r
5316         SendToProgram(buf, &first);\r
5317       }\r
5318       StartClocks();\r
5319     }\r
5320     ModeHighlight();\r
5321   }\r
5322 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);\r
5323   /* Relay move to ICS or chess engine */\r
5324   if (appData.icsActive) {\r
5325     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
5326         gameMode == IcsExamining) {\r
5327       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5328       ics_user_moved = 1;\r
5329     }\r
5330   } else {\r
5331     if (first.sendTime && (gameMode == BeginningOfGame ||\r
5332                            gameMode == MachinePlaysWhite ||\r
5333                            gameMode == MachinePlaysBlack)) {\r
5334       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
5335     }\r
5336     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
5337          // [HGM] book: if program might be playing, let it use book\r
5338         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);\r
5339         first.maybeThinking = TRUE;\r
5340     } else SendMoveToProgram(forwardMostMove-1, &first);\r
5341     if (currentMove == cmailOldMove + 1) {\r
5342       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
5343     }\r
5344   }\r
5345 \r
5346   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5347 \r
5348   switch (gameMode) {\r
5349   case EditGame:\r
5350     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
5351                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
5352     case MT_NONE:\r
5353     case MT_CHECK:\r
5354       break;\r
5355     case MT_CHECKMATE:\r
5356       if (WhiteOnMove(currentMove)) {\r
5357         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
5358       } else {\r
5359         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
5360       }\r
5361       break;\r
5362     case MT_STALEMATE:\r
5363       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
5364       break;\r
5365     }\r
5366     break;\r
5367     \r
5368   case MachinePlaysBlack:\r
5369   case MachinePlaysWhite:\r
5370     /* disable certain menu options while machine is thinking */\r
5371     SetMachineThinkingEnables();\r
5372     break;\r
5373 \r
5374   default:\r
5375     break;\r
5376   }\r
5377 \r
5378   if(bookHit) { // [HGM] book: simulate book reply\r
5379         static char bookMove[MSG_SIZ]; // a bit generous?\r
5380 \r
5381         programStats.depth = programStats.nodes = programStats.time = \r
5382         programStats.score = programStats.got_only_move = 0;\r
5383         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
5384 \r
5385         strcpy(bookMove, "move ");\r
5386         strcat(bookMove, bookHit);\r
5387         HandleMachineMove(bookMove, &first);\r
5388   }\r
5389   return 1;\r
5390 }\r
5391 \r
5392 void\r
5393 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
5394      int fromX, fromY, toX, toY;\r
5395      int promoChar;\r
5396 {\r
5397     /* [HGM] This routine was added to allow calling of its two logical\r
5398        parts from other modules in the old way. Before, UserMoveEvent()\r
5399        automatically called FinishMove() if the move was OK, and returned\r
5400        otherwise. I separated the two, in order to make it possible to\r
5401        slip a promotion popup in between. But that it always needs two\r
5402        calls, to the first part, (now called UserMoveTest() ), and to\r
5403        FinishMove if the first part succeeded. Calls that do not need\r
5404        to do anything in between, can call this routine the old way. \r
5405     */\r
5406     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
5407 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);\r
5408     if(moveType != ImpossibleMove)\r
5409         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
5410 }\r
5411 \r
5412 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
5413 {\r
5414     char * hint = lastHint;\r
5415     FrontEndProgramStats stats;\r
5416 \r
5417     stats.which = cps == &first ? 0 : 1;\r
5418     stats.depth = cpstats->depth;\r
5419     stats.nodes = cpstats->nodes;\r
5420     stats.score = cpstats->score;\r
5421     stats.time = cpstats->time;\r
5422     stats.pv = cpstats->movelist;\r
5423     stats.hint = lastHint;\r
5424     stats.an_move_index = 0;\r
5425     stats.an_move_count = 0;\r
5426 \r
5427     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
5428         stats.hint = cpstats->move_name;\r
5429         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
5430         stats.an_move_count = cpstats->nr_moves;\r
5431     }\r
5432 \r
5433     SetProgramStats( &stats );\r
5434 }\r
5435 \r
5436 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)\r
5437 {   // [HGM] book: this routine intercepts moves to simulate book replies\r
5438     char *bookHit = NULL;\r
5439 \r
5440     //first determine if the incoming move brings opponent into his book\r
5441     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
5442         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
5443     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");\r
5444     if(bookHit != NULL && !cps->bookSuspend) {\r
5445         // make sure opponent is not going to reply after receiving move to book position\r
5446         SendToProgram("force\n", cps);\r
5447         cps->bookSuspend = TRUE; // flag indicating it has to be restarted\r
5448     }\r
5449     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move\r
5450     // now arrange restart after book miss\r
5451     if(bookHit) {\r
5452         // after a book hit we never send 'go', and the code after the call to this routine\r
5453         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').\r
5454         char buf[MSG_SIZ];\r
5455         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(\r
5456         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it\r
5457         SendToProgram(buf, cps);\r
5458         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'\r
5459     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine\r
5460         SendToProgram("go\n", cps);\r
5461         cps->bookSuspend = FALSE; // after a 'go' we are never suspended\r
5462     } else { // 'go' might be sent based on 'firstMove' after this routine returns\r
5463         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return\r
5464             SendToProgram("go\n", cps); \r
5465         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss\r
5466     }\r
5467     return bookHit; // notify caller of hit, so it can take action to send move to opponent\r
5468 }\r
5469 \r
5470 char *savedMessage;\r
5471 ChessProgramState *savedState;\r
5472 void DeferredBookMove(void)\r
5473 {\r
5474         if(savedState->lastPing != savedState->lastPong)\r
5475                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
5476         else\r
5477         HandleMachineMove(savedMessage, savedState);\r
5478 }\r
5479 \r
5480 void\r
5481 HandleMachineMove(message, cps)\r
5482      char *message;\r
5483      ChessProgramState *cps;\r
5484 {\r
5485     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
5486     char realname[MSG_SIZ];\r
5487     int fromX, fromY, toX, toY;\r
5488     ChessMove moveType;\r
5489     char promoChar;\r
5490     char *p;\r
5491     int machineWhite;\r
5492     char *bookHit;\r
5493 \r
5494 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit\r
5495     /*\r
5496      * Kludge to ignore BEL characters\r
5497      */\r
5498     while (*message == '\007') message++;\r
5499 \r
5500     /*\r
5501      * [HGM] engine debug message: ignore lines starting with '#' character\r
5502      */\r
5503     if(cps->debug && *message == '#') return;\r
5504 \r
5505     /*\r
5506      * Look for book output\r
5507      */\r
5508     if (cps == &first && bookRequested) {\r
5509         if (message[0] == '\t' || message[0] == ' ') {\r
5510             /* Part of the book output is here; append it */\r
5511             strcat(bookOutput, message);\r
5512             strcat(bookOutput, "  \n");\r
5513             return;\r
5514         } else if (bookOutput[0] != NULLCHAR) {\r
5515             /* All of book output has arrived; display it */\r
5516             char *p = bookOutput;\r
5517             while (*p != NULLCHAR) {\r
5518                 if (*p == '\t') *p = ' ';\r
5519                 p++;\r
5520             }\r
5521             DisplayInformation(bookOutput);\r
5522             bookRequested = FALSE;\r
5523             /* Fall through to parse the current output */\r
5524         }\r
5525     }\r
5526 \r
5527     /*\r
5528      * Look for machine move.\r
5529      */\r
5530     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
5531         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
5532     {\r
5533         /* This method is only useful on engines that support ping */\r
5534         if (cps->lastPing != cps->lastPong) {\r
5535           if (gameMode == BeginningOfGame) {\r
5536             /* Extra move from before last new; ignore */\r
5537             if (appData.debugMode) {\r
5538                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5539             }\r
5540           } else {\r
5541             if (appData.debugMode) {\r
5542                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5543                         cps->which, gameMode);\r
5544             }\r
5545 \r
5546             SendToProgram("undo\n", cps);\r
5547           }\r
5548           return;\r
5549         }\r
5550 \r
5551         switch (gameMode) {\r
5552           case BeginningOfGame:\r
5553             /* Extra move from before last reset; ignore */\r
5554             if (appData.debugMode) {\r
5555                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5556             }\r
5557             return;\r
5558 \r
5559           case EndOfGame:\r
5560           case IcsIdle:\r
5561           default:\r
5562             /* Extra move after we tried to stop.  The mode test is\r
5563                not a reliable way of detecting this problem, but it's\r
5564                the best we can do on engines that don't support ping.\r
5565             */\r
5566             if (appData.debugMode) {\r
5567                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5568                         cps->which, gameMode);\r
5569             }\r
5570             SendToProgram("undo\n", cps);\r
5571             return;\r
5572 \r
5573           case MachinePlaysWhite:\r
5574           case IcsPlayingWhite:\r
5575             machineWhite = TRUE;\r
5576             break;\r
5577 \r
5578           case MachinePlaysBlack:\r
5579           case IcsPlayingBlack:\r
5580             machineWhite = FALSE;\r
5581             break;\r
5582 \r
5583           case TwoMachinesPlay:\r
5584             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5585             break;\r
5586         }\r
5587         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5588             if (appData.debugMode) {\r
5589                 fprintf(debugFP,\r
5590                         "Ignoring move out of turn by %s, gameMode %d"\r
5591                         ", forwardMost %d\n",\r
5592                         cps->which, gameMode, forwardMostMove);\r
5593             }\r
5594             return;\r
5595         }\r
5596 \r
5597     if (appData.debugMode) { int f = forwardMostMove;\r
5598         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5599                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5600     }\r
5601         if(cps->alphaRank) AlphaRank(machineMove, 4);\r
5602         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5603                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5604             /* Machine move could not be parsed; ignore it. */\r
5605             sprintf(buf1, "Illegal move \"%s\" from %s machine",\r
5606                     machineMove, cps->which);\r
5607             DisplayError(buf1, 0);\r
5608             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",\r
5609                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5610             if (gameMode == TwoMachinesPlay) {\r
5611               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5612                        buf1, GE_XBOARD);\r
5613             }\r
5614             return;\r
5615         }\r
5616 \r
5617         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5618         /* So we have to redo legality test with true e.p. status here,  */\r
5619         /* to make sure an illegal e.p. capture does not slip through,   */\r
5620         /* to cause a forfeit on a justified illegal-move complaint      */\r
5621         /* of the opponent.                                              */\r
5622         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5623             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5624                                                               ) {\r
5625            ChessMove moveType;\r
5626            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5627                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5628                              fromY, fromX, toY, toX, promoChar);\r
5629             if (appData.debugMode) {\r
5630                 int i;\r
5631                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5632                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5633                 fprintf(debugFP, "castling rights\n");\r
5634             }\r
5635             if(moveType == IllegalMove) {\r
5636                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5637                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5638                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5639                            buf1, GE_XBOARD);\r
5640            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
5641            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5642               when they shouldn't (like TSCP-Gothic) */\r
5643            switch(moveType) {\r
5644              case WhiteASideCastleFR:\r
5645              case BlackASideCastleFR:\r
5646                toX+=2;\r
5647                currentMoveString[2]++;\r
5648                break;\r
5649              case WhiteHSideCastleFR:\r
5650              case BlackHSideCastleFR:\r
5651                toX--;\r
5652                currentMoveString[2]--;\r
5653                break;\r
5654            }\r
5655         }\r
5656         hintRequested = FALSE;\r
5657         lastHint[0] = NULLCHAR;\r
5658         bookRequested = FALSE;\r
5659         /* Program may be pondering now */\r
5660         cps->maybeThinking = TRUE;\r
5661         if (cps->sendTime == 2) cps->sendTime = 1;\r
5662         if (cps->offeredDraw) cps->offeredDraw--;\r
5663 \r
5664 #if ZIPPY\r
5665         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5666             first.initDone) {\r
5667           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5668           ics_user_moved = 1;\r
5669           if(appData.autoKibitz) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
5670                 char buf[3*MSG_SIZ];\r
5671 \r
5672                 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",\r
5673                         programStats.depth,\r
5674                         programStats.score / 100.,\r
5675                         programStats.time / 100.,\r
5676                         (double) programStats.nodes,\r
5677                         programStats.nodes / (10*abs(programStats.time) + 1.),\r
5678                         programStats.movelist);\r
5679                 SendToICS(buf);\r
5680           }\r
5681         }\r
5682 #endif\r
5683         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5684         strcpy(machineMove, currentMoveString);\r
5685         strcat(machineMove, "\n");\r
5686         strcpy(moveList[forwardMostMove], machineMove);\r
5687 \r
5688         /* [AS] Save move info and clear stats for next move */\r
5689         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5690         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5691         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5692         ClearProgramStats();\r
5693         thinkOutput[0] = NULLCHAR;\r
5694         hiddenThinkOutputState = 0;\r
5695 \r
5696         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5697 \r
5698         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5699         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5700             int count = 0;\r
5701 \r
5702             while( count < adjudicateLossPlies ) {\r
5703                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5704 \r
5705                 if( count & 1 ) {\r
5706                     score = -score; /* Flip score for winning side */\r
5707                 }\r
5708 \r
5709                 if( score > adjudicateLossThreshold ) {\r
5710                     break;\r
5711                 }\r
5712 \r
5713                 count++;\r
5714             }\r
5715 \r
5716             if( count >= adjudicateLossPlies ) {\r
5717                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5718 \r
5719                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5720                     "Xboard adjudication", \r
5721                     GE_XBOARD );\r
5722 \r
5723                 return;\r
5724             }\r
5725         }\r
5726 \r
5727         if( gameMode == TwoMachinesPlay ) {\r
5728           // [HGM] some adjudications useful with buggy engines\r
5729             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
5730           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {\r
5731 \r
5732             if(appData.testLegality)\r
5733             // don't wait for engine to announce game end if we can judge ourselves\r
5734             switch (MateTest(boards[forwardMostMove],\r
5735                                  PosFlags(forwardMostMove), epFile,\r
5736                                        castlingRights[forwardMostMove]) ) {\r
5737               case MT_NONE:\r
5738               case MT_CHECK:\r
5739               default:\r
5740                 break;\r
5741               case MT_STALEMATE:\r
5742                 epStatus[forwardMostMove] = EP_STALEMATE;\r
5743                 if(appData.checkMates) {\r
5744                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5745                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5746                     GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",\r
5747                         GE_XBOARD );\r
5748                 }\r
5749                 break;\r
5750               case MT_CHECKMATE:\r
5751                 epStatus[forwardMostMove] = EP_CHECKMATE;\r
5752                 if(appData.checkMates) {\r
5753                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5754                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5755                     GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5756                     "Xboard adjudication: Checkmate", \r
5757                     GE_XBOARD );\r
5758                 }\r
5759                 break;\r
5760             }\r
5761 \r
5762             if( appData.testLegality )\r
5763             {   /* [HGM] Some more adjudications for obstinate engines */\r
5764                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5765                     NrWQ=0, NrBQ=0, NrW=0, bishopsColor = 0,\r
5766                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;\r
5767                 static int moveCount = 6;\r
5768 \r
5769                 /* First absolutely insufficient mating material. Count what is on board. */\r
5770                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5771                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5772                     int m=i;\r
5773 \r
5774                     switch((int) p)\r
5775                     {   /* count B,N,R and other of each side */\r
5776                         case WhiteKnight:\r
5777                              NrWN++; break;\r
5778                         case WhiteBishop:\r
5779                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5780                              bishopsColor |= 1 << ((i^j)&1);\r
5781                              NrWB++; break;\r
5782                         case BlackKnight:\r
5783                              NrBN++; break;\r
5784                         case BlackBishop:\r
5785                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5786                              bishopsColor |= 1 << ((i^j)&1);\r
5787                              NrBB++; break;\r
5788                         case WhiteRook:\r
5789                              NrWR++; break;\r
5790                         case BlackRook:\r
5791                              NrBR++; break;\r
5792                         case WhiteQueen:\r
5793                              NrWQ++; break;\r
5794                         case BlackQueen:\r
5795                              NrBQ++; break;\r
5796                         case EmptySquare: \r
5797                              break;\r
5798                         case BlackPawn:\r
5799                              m = 7-i;\r
5800                         case WhitePawn:\r
5801                              PawnAdvance += m; NrPawns++;\r
5802                     }\r
5803                     NrPieces += (p != EmptySquare);\r
5804                     NrW += ((int)p < (int)BlackPawn);\r
5805                     if(gameInfo.variant == VariantXiangqi && \r
5806                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {\r
5807                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces\r
5808                         NrW -= ((int)p < (int)BlackPawn);\r
5809                     }\r
5810                 }\r
5811 \r
5812                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&\r
5813                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||\r
5814                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color\r
5815                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5816 \r
5817                      /* always flag draws, for judging claims */\r
5818                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5819 \r
5820                      if(appData.materialDraws) {\r
5821                          /* but only adjudicate them if adjudication enabled */\r
5822                          SendToProgram("force\n", cps->other); // suppress reply\r
5823                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */\r
5824                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5825                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5826                          return;\r
5827                      }\r
5828                 }\r
5829 \r
5830                 /* Shatranj baring rule */\r
5831                 if( gameInfo.variant == VariantShatranj && (NrW == 1 || NrPieces - NrW == 1) )\r
5832                 {    /* bare King */\r
5833 \r
5834                      if(--bare < 0 && appData.checkMates) {\r
5835                          /* but only adjudicate them if adjudication enabled */\r
5836                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5837                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5838                          GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
5839                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5840                          return;\r
5841                      }\r
5842                 } else bare = 1;\r
5843 \r
5844                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5845                 if(NrPieces == 4 && \r
5846                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5847                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5848                    || NrWN==2 || NrBN==2     /* KNNK */\r
5849                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5850                   ) ) {\r
5851                      if(--moveCount < 0 && appData.trivialDraws)\r
5852                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5853                           SendToProgram("force\n", cps->other); // suppress reply\r
5854                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5855                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5856                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5857                           return;\r
5858                      }\r
5859                 } else moveCount = 6;\r
5860             }\r
5861           }\r
5862 #if 1\r
5863     if (appData.debugMode) { int i;\r
5864       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5865               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5866               appData.drawRepeats);\r
5867       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5868            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5869 \r
5870     }\r
5871 #endif\r
5872                 /* Check for rep-draws */\r
5873                 count = 0;\r
5874                 for(k = forwardMostMove-2;\r
5875                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5876                         epStatus[k] < EP_UNKNOWN &&\r
5877                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5878                     k-=2)\r
5879                 {   int rights=0;\r
5880 #if 0\r
5881     if (appData.debugMode) {\r
5882       fprintf(debugFP, " loop\n");\r
5883     }\r
5884 #endif\r
5885                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5886 #if 0\r
5887     if (appData.debugMode) {\r
5888       fprintf(debugFP, "match\n");\r
5889     }\r
5890 #endif\r
5891                         /* compare castling rights */\r
5892                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5893                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5894                                 rights++; /* King lost rights, while rook still had them */\r
5895                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5896                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5897                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5898                                    rights++; /* but at least one rook lost them */\r
5899                         }\r
5900                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5901                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5902                                 rights++; \r
5903                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5904                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5905                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5906                                    rights++;\r
5907                         }\r
5908 #if 0\r
5909     if (appData.debugMode) {\r
5910       for(i=0; i<nrCastlingRights; i++)\r
5911       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5912     }\r
5913 \r
5914     if (appData.debugMode) {\r
5915       fprintf(debugFP, " %d %d\n", rights, k);\r
5916     }\r
5917 #endif\r
5918                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5919                             && appData.drawRepeats > 1) {\r
5920                              /* adjudicate after user-specified nr of repeats */\r
5921                              SendToProgram("force\n", cps->other); // suppress reply\r
5922                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5923                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5924                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { \r
5925                                 // [HGM] xiangqi: check for forbidden perpetuals\r
5926                                 int m, ourPerpetual = 1, hisPerpetual = 1;\r
5927                                 for(m=forwardMostMove; m>k; m-=2) {\r
5928                                     if(MateTest(boards[m], PosFlags(m), \r
5929                                                         EP_NONE, castlingRights[m]) != MT_CHECK)\r
5930                                         ourPerpetual = 0; // the current mover did not always check\r
5931                                     if(MateTest(boards[m-1], PosFlags(m-1), \r
5932                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)\r
5933                                         hisPerpetual = 0; // the opponent did not always check\r
5934                                 }\r
5935                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
5936                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5937                                            "Xboard adjudication: perpetual checking", GE_XBOARD );\r
5938                                     return;\r
5939                                 }\r
5940                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet\r
5941                                     break; // (or we would have caught him before). Abort repetition-checking loop.\r
5942                                 // if neither of us is checking all the time, or both are, it is draw\r
5943                                 // (illegal-chase forfeits not implemented yet!)\r
5944                              }\r
5945                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
5946                              return;\r
5947                         }\r
5948                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
5949                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
5950                     }\r
5951                 }\r
5952 \r
5953                 /* Now we test for 50-move draws. Determine ply count */\r
5954                 count = forwardMostMove;\r
5955                 /* look for last irreversble move */\r
5956                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
5957                     count--;\r
5958                 /* if we hit starting position, add initial plies */\r
5959                 if( count == backwardMostMove )\r
5960                     count -= initialRulePlies;\r
5961                 count = forwardMostMove - count; \r
5962                 if( count >= 100)\r
5963                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
5964                          /* this is used to judge if draw claims are legal */\r
5965                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
5966                          SendToProgram("force\n", cps->other); // suppress reply\r
5967                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5968                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5969                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
5970                          return;\r
5971                 }\r
5972 \r
5973                 /* if draw offer is pending, treat it as a draw claim\r
5974                  * when draw condition present, to allow engines a way to\r
5975                  * claim draws before making their move to avoid a race\r
5976                  * condition occurring after their move\r
5977                  */\r
5978                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
5979                          char *p = NULL;\r
5980                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
5981                              p = "Draw claim: 50-move rule";\r
5982                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
5983                              p = "Draw claim: 3-fold repetition";\r
5984                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
5985                              p = "Draw claim: insufficient mating material";\r
5986                          if( p != NULL ) {\r
5987                              SendToProgram("force\n", cps->other); // suppress reply\r
5988                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5989                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
5990                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5991                              return;\r
5992                          }\r
5993                 }\r
5994 \r
5995 \r
5996                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
5997                     SendToProgram("force\n", cps->other); // suppress reply\r
5998                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5999                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6000 \r
6001                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
6002 \r
6003                     return;\r
6004                 }\r
6005         }\r
6006 \r
6007         bookHit = NULL;\r
6008         if (gameMode == TwoMachinesPlay) {\r
6009             /* [HGM] relaying draw offers moved to after reception of move */\r
6010             /* and interpreting offer as claim if it brings draw condition */\r
6011             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
6012                 SendToProgram("draw\n", cps->other);\r
6013             }\r
6014             if (cps->other->sendTime) {\r
6015                 SendTimeRemaining(cps->other,\r
6016                                   cps->other->twoMachinesColor[0] == 'w');\r
6017             }\r
6018             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);\r
6019             if (firstMove && !bookHit) {\r
6020                 firstMove = FALSE;\r
6021                 if (cps->other->useColors) {\r
6022                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
6023                 }\r
6024                 SendToProgram("go\n", cps->other);\r
6025             }\r
6026             cps->other->maybeThinking = TRUE;\r
6027         }\r
6028 \r
6029         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6030         \r
6031         if (!pausing && appData.ringBellAfterMoves) {\r
6032             RingBell();\r
6033         }\r
6034 \r
6035         /* \r
6036          * Reenable menu items that were disabled while\r
6037          * machine was thinking\r
6038          */\r
6039         if (gameMode != TwoMachinesPlay)\r
6040             SetUserThinkingEnables();\r
6041 \r
6042         // [HGM] book: after book hit opponent has received move and is now in force mode\r
6043         // force the book reply into it, and then fake that it outputted this move by jumping\r
6044         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move\r
6045         if(bookHit) {\r
6046                 static char bookMove[MSG_SIZ]; // a bit generous?\r
6047 \r
6048                 strcpy(bookMove, "move ");\r
6049                 strcat(bookMove, bookHit);\r
6050                 message = bookMove;\r
6051                 cps = cps->other;\r
6052                 programStats.depth = programStats.nodes = programStats.time = \r
6053                 programStats.score = programStats.got_only_move = 0;\r
6054                 sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
6055 \r
6056                 if(cps->lastPing != cps->lastPong) {\r
6057                     savedMessage = message; // args for deferred call\r
6058                     savedState = cps;\r
6059                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
6060                     return;\r
6061                 }\r
6062                 goto FakeBookMove;\r
6063         }\r
6064 \r
6065         return;\r
6066     }\r
6067 \r
6068     /* Set special modes for chess engines.  Later something general\r
6069      *  could be added here; for now there is just one kludge feature,\r
6070      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
6071      *  when "xboard" is given as an interactive command.\r
6072      */\r
6073     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
6074         cps->useSigint = FALSE;\r
6075         cps->useSigterm = FALSE;\r
6076     }\r
6077 \r
6078     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
6079      * want this, I was asked to put it in, and obliged.\r
6080      */\r
6081     if (!strncmp(message, "setboard ", 9)) {\r
6082         Board initial_position; int i;\r
6083 \r
6084         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
6085 \r
6086         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
6087             DisplayError("Bad FEN received from engine", 0);\r
6088             return ;\r
6089         } else {\r
6090            Reset(FALSE, FALSE);\r
6091            CopyBoard(boards[0], initial_position);\r
6092            initialRulePlies = FENrulePlies;\r
6093            epStatus[0] = FENepStatus;\r
6094            for( i=0; i<nrCastlingRights; i++ )\r
6095                 castlingRights[0][i] = FENcastlingRights[i];\r
6096            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
6097            else gameMode = MachinePlaysBlack;                 \r
6098            DrawPosition(FALSE, boards[currentMove]);\r
6099         }\r
6100         return;\r
6101     }\r
6102 \r
6103     /*\r
6104      * Look for communication commands\r
6105      */\r
6106     if (!strncmp(message, "telluser ", 9)) {\r
6107         DisplayNote(message + 9);\r
6108         return;\r
6109     }\r
6110     if (!strncmp(message, "tellusererror ", 14)) {\r
6111         DisplayError(message + 14, 0);\r
6112         return;\r
6113     }\r
6114     if (!strncmp(message, "tellopponent ", 13)) {\r
6115       if (appData.icsActive) {\r
6116         if (loggedOn) {\r
6117           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
6118           SendToICS(buf1);\r
6119         }\r
6120       } else {\r
6121         DisplayNote(message + 13);\r
6122       }\r
6123       return;\r
6124     }\r
6125     if (!strncmp(message, "tellothers ", 11)) {\r
6126       if (appData.icsActive) {\r
6127         if (loggedOn) {\r
6128           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
6129           SendToICS(buf1);\r
6130         }\r
6131       }\r
6132       return;\r
6133     }\r
6134     if (!strncmp(message, "tellall ", 8)) {\r
6135       if (appData.icsActive) {\r
6136         if (loggedOn) {\r
6137           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
6138           SendToICS(buf1);\r
6139         }\r
6140       } else {\r
6141         DisplayNote(message + 8);\r
6142       }\r
6143       return;\r
6144     }\r
6145     if (strncmp(message, "warning", 7) == 0) {\r
6146         /* Undocumented feature, use tellusererror in new code */\r
6147         DisplayError(message, 0);\r
6148         return;\r
6149     }\r
6150     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
6151         strcpy(realname, cps->tidy);\r
6152         strcat(realname, " query");\r
6153         AskQuestion(realname, buf2, buf1, cps->pr);\r
6154         return;\r
6155     }\r
6156     /* Commands from the engine directly to ICS.  We don't allow these to be \r
6157      *  sent until we are logged on. Crafty kibitzes have been known to \r
6158      *  interfere with the login process.\r
6159      */\r
6160     if (loggedOn) {\r
6161         if (!strncmp(message, "tellics ", 8)) {\r
6162             SendToICS(message + 8);\r
6163             SendToICS("\n");\r
6164             return;\r
6165         }\r
6166         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
6167             SendToICS(ics_prefix);\r
6168             SendToICS(message + 15);\r
6169             SendToICS("\n");\r
6170             return;\r
6171         }\r
6172         /* The following are for backward compatibility only */\r
6173         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
6174             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
6175             SendToICS(ics_prefix);\r
6176             SendToICS(message);\r
6177             SendToICS("\n");\r
6178             return;\r
6179         }\r
6180     }\r
6181     if (strncmp(message, "feature ", 8) == 0) {\r
6182       ParseFeatures(message+8, cps);\r
6183     }\r
6184     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
6185         return;\r
6186     }\r
6187     /*\r
6188      * If the move is illegal, cancel it and redraw the board.\r
6189      * Also deal with other error cases.  Matching is rather loose\r
6190      * here to accommodate engines written before the spec.\r
6191      */\r
6192     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
6193         strncmp(message, "Error", 5) == 0) {\r
6194         if (StrStr(message, "name") || \r
6195             StrStr(message, "rating") || StrStr(message, "?") ||\r
6196             StrStr(message, "result") || StrStr(message, "board") ||\r
6197             StrStr(message, "bk") || StrStr(message, "computer") ||\r
6198             StrStr(message, "variant") || StrStr(message, "hint") ||\r
6199             StrStr(message, "random") || StrStr(message, "depth") ||\r
6200             StrStr(message, "accepted")) {\r
6201             return;\r
6202         }\r
6203         if (StrStr(message, "protover")) {\r
6204           /* Program is responding to input, so it's apparently done\r
6205              initializing, and this error message indicates it is\r
6206              protocol version 1.  So we don't need to wait any longer\r
6207              for it to initialize and send feature commands. */\r
6208           FeatureDone(cps, 1);\r
6209           cps->protocolVersion = 1;\r
6210           return;\r
6211         }\r
6212         cps->maybeThinking = FALSE;\r
6213 \r
6214         if (StrStr(message, "draw")) {\r
6215             /* Program doesn't have "draw" command */\r
6216             cps->sendDrawOffers = 0;\r
6217             return;\r
6218         }\r
6219         if (cps->sendTime != 1 &&\r
6220             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
6221           /* Program apparently doesn't have "time" or "otim" command */\r
6222           cps->sendTime = 0;\r
6223           return;\r
6224         }\r
6225         if (StrStr(message, "analyze")) {\r
6226             cps->analysisSupport = FALSE;\r
6227             cps->analyzing = FALSE;\r
6228             Reset(FALSE, TRUE);\r
6229             sprintf(buf2, "%s does not support analysis", cps->tidy);\r
6230             DisplayError(buf2, 0);\r
6231             return;\r
6232         }\r
6233         if (StrStr(message, "(no matching move)st")) {\r
6234           /* Special kludge for GNU Chess 4 only */\r
6235           cps->stKludge = TRUE;\r
6236           SendTimeControl(cps, movesPerSession, timeControl,\r
6237                           timeIncrement, appData.searchDepth,\r
6238                           searchTime);\r
6239           return;\r
6240         }\r
6241         if (StrStr(message, "(no matching move)sd")) {\r
6242           /* Special kludge for GNU Chess 4 only */\r
6243           cps->sdKludge = TRUE;\r
6244           SendTimeControl(cps, movesPerSession, timeControl,\r
6245                           timeIncrement, appData.searchDepth,\r
6246                           searchTime);\r
6247           return;\r
6248         }\r
6249         if (!StrStr(message, "llegal")) {\r
6250             return;\r
6251         }\r
6252         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6253             gameMode == IcsIdle) return;\r
6254         if (forwardMostMove <= backwardMostMove) return;\r
6255 #if 0\r
6256         /* Following removed: it caused a bug where a real illegal move\r
6257            message in analyze mored would be ignored. */\r
6258         if (cps == &first && programStats.ok_to_send == 0) {\r
6259             /* Bogus message from Crafty responding to "."  This filtering\r
6260                can miss some of the bad messages, but fortunately the bug \r
6261                is fixed in current Crafty versions, so it doesn't matter. */\r
6262             return;\r
6263         }\r
6264 #endif\r
6265         if (pausing) PauseEvent();\r
6266         if (gameMode == PlayFromGameFile) {\r
6267             /* Stop reading this game file */\r
6268             gameMode = EditGame;\r
6269             ModeHighlight();\r
6270         }\r
6271         currentMove = --forwardMostMove;\r
6272         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
6273         SwitchClocks();\r
6274         DisplayBothClocks();\r
6275         sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",\r
6276                 parseList[currentMove], cps->which);\r
6277         DisplayMoveError(buf1);\r
6278         DrawPosition(FALSE, boards[currentMove]);\r
6279 \r
6280         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
6281         /* only passes fully legal moves                            */\r
6282         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
6283             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
6284                                 "False illegal-move claim", GE_XBOARD );\r
6285         }\r
6286         return;\r
6287     }\r
6288     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
6289         /* Program has a broken "time" command that\r
6290            outputs a string not ending in newline.\r
6291            Don't use it. */\r
6292         cps->sendTime = 0;\r
6293     }\r
6294     \r
6295     /*\r
6296      * If chess program startup fails, exit with an error message.\r
6297      * Attempts to recover here are futile.\r
6298      */\r
6299     if ((StrStr(message, "unknown host") != NULL)\r
6300         || (StrStr(message, "No remote directory") != NULL)\r
6301         || (StrStr(message, "not found") != NULL)\r
6302         || (StrStr(message, "No such file") != NULL)\r
6303         || (StrStr(message, "can't alloc") != NULL)\r
6304         || (StrStr(message, "Permission denied") != NULL)) {\r
6305 \r
6306         cps->maybeThinking = FALSE;\r
6307         sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",\r
6308                 cps->which, cps->program, cps->host, message);\r
6309         RemoveInputSource(cps->isr);\r
6310         DisplayFatalError(buf1, 0, 1);\r
6311         return;\r
6312     }\r
6313     \r
6314     /* \r
6315      * Look for hint output\r
6316      */\r
6317     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
6318         if (cps == &first && hintRequested) {\r
6319             hintRequested = FALSE;\r
6320             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
6321                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
6322                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
6323                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
6324                                     fromY, fromX, toY, toX, promoChar, buf1);\r
6325                 sprintf(buf2, "Hint: %s", buf1);\r
6326                 DisplayInformation(buf2);\r
6327             } else {\r
6328                 /* Hint move could not be parsed!? */\r
6329                 sprintf(buf2,\r
6330                         "Illegal hint move \"%s\"\nfrom %s chess program",\r
6331                         buf1, cps->which);\r
6332                 DisplayError(buf2, 0);\r
6333             }\r
6334         } else {\r
6335             strcpy(lastHint, buf1);\r
6336         }\r
6337         return;\r
6338     }\r
6339 \r
6340     /*\r
6341      * Ignore other messages if game is not in progress\r
6342      */\r
6343     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6344         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
6345 \r
6346     /*\r
6347      * look for win, lose, draw, or draw offer\r
6348      */\r
6349     if (strncmp(message, "1-0", 3) == 0) {\r
6350         char *p, *q, *r = "";\r
6351         p = strchr(message, '{');\r
6352         if (p) {\r
6353             q = strchr(p, '}');\r
6354             if (q) {\r
6355                 *q = NULLCHAR;\r
6356                 r = p + 1;\r
6357             }\r
6358         }\r
6359         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
6360         return;\r
6361     } else if (strncmp(message, "0-1", 3) == 0) {\r
6362         char *p, *q, *r = "";\r
6363         p = strchr(message, '{');\r
6364         if (p) {\r
6365             q = strchr(p, '}');\r
6366             if (q) {\r
6367                 *q = NULLCHAR;\r
6368                 r = p + 1;\r
6369             }\r
6370         }\r
6371         /* Kludge for Arasan 4.1 bug */\r
6372         if (strcmp(r, "Black resigns") == 0) {\r
6373             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
6374             return;\r
6375         }\r
6376         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
6377         return;\r
6378     } else if (strncmp(message, "1/2", 3) == 0) {\r
6379         char *p, *q, *r = "";\r
6380         p = strchr(message, '{');\r
6381         if (p) {\r
6382             q = strchr(p, '}');\r
6383             if (q) {\r
6384                 *q = NULLCHAR;\r
6385                 r = p + 1;\r
6386             }\r
6387         }\r
6388             \r
6389         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
6390         return;\r
6391 \r
6392     } else if (strncmp(message, "White resign", 12) == 0) {\r
6393         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6394         return;\r
6395     } else if (strncmp(message, "Black resign", 12) == 0) {\r
6396         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6397         return;\r
6398     } else if (strncmp(message, "White matches", 13) == 0 ||\r
6399                strncmp(message, "Black matches", 13) == 0   ) {\r
6400         /* [HGM] ignore GNUShogi noises */\r
6401         return;\r
6402     } else if (strncmp(message, "White", 5) == 0 &&\r
6403                message[5] != '(' &&\r
6404                StrStr(message, "Black") == NULL) {\r
6405         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6406         return;\r
6407     } else if (strncmp(message, "Black", 5) == 0 &&\r
6408                message[5] != '(') {\r
6409         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6410         return;\r
6411     } else if (strcmp(message, "resign") == 0 ||\r
6412                strcmp(message, "computer resigns") == 0) {\r
6413         switch (gameMode) {\r
6414           case MachinePlaysBlack:\r
6415           case IcsPlayingBlack:\r
6416             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
6417             break;\r
6418           case MachinePlaysWhite:\r
6419           case IcsPlayingWhite:\r
6420             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
6421             break;\r
6422           case TwoMachinesPlay:\r
6423             if (cps->twoMachinesColor[0] == 'w')\r
6424               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6425             else\r
6426               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6427             break;\r
6428           default:\r
6429             /* can't happen */\r
6430             break;\r
6431         }\r
6432         return;\r
6433     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
6434         switch (gameMode) {\r
6435           case MachinePlaysBlack:\r
6436           case IcsPlayingBlack:\r
6437             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6438             break;\r
6439           case MachinePlaysWhite:\r
6440           case IcsPlayingWhite:\r
6441             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
6442             break;\r
6443           case TwoMachinesPlay:\r
6444             if (cps->twoMachinesColor[0] == 'w')\r
6445               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6446             else\r
6447               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6448             break;\r
6449           default:\r
6450             /* can't happen */\r
6451             break;\r
6452         }\r
6453         return;\r
6454     } else if (strncmp(message, "computer mates", 14) == 0) {\r
6455         switch (gameMode) {\r
6456           case MachinePlaysBlack:\r
6457           case IcsPlayingBlack:\r
6458             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
6459             break;\r
6460           case MachinePlaysWhite:\r
6461           case IcsPlayingWhite:\r
6462             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6463             break;\r
6464           case TwoMachinesPlay:\r
6465             if (cps->twoMachinesColor[0] == 'w')\r
6466               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6467             else\r
6468               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6469             break;\r
6470           default:\r
6471             /* can't happen */\r
6472             break;\r
6473         }\r
6474         return;\r
6475     } else if (strncmp(message, "checkmate", 9) == 0) {\r
6476         if (WhiteOnMove(forwardMostMove)) {\r
6477             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6478         } else {\r
6479             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6480         }\r
6481         return;\r
6482     } else if (strstr(message, "Draw") != NULL ||\r
6483                strstr(message, "game is a draw") != NULL) {\r
6484         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
6485         return;\r
6486     } else if (strstr(message, "offer") != NULL &&\r
6487                strstr(message, "draw") != NULL) {\r
6488 #if ZIPPY\r
6489         if (appData.zippyPlay && first.initDone) {\r
6490             /* Relay offer to ICS */\r
6491             SendToICS(ics_prefix);\r
6492             SendToICS("draw\n");\r
6493         }\r
6494 #endif\r
6495         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
6496         if (gameMode == TwoMachinesPlay) {\r
6497             if (cps->other->offeredDraw) {\r
6498                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6499             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
6500             /* until after we also have move, to see if it is really claim */\r
6501             }\r
6502 #if 0\r
6503               else {\r
6504                 if (cps->other->sendDrawOffers) {\r
6505                     SendToProgram("draw\n", cps->other);\r
6506                 }\r
6507             }\r
6508 #endif\r
6509         } else if (gameMode == MachinePlaysWhite ||\r
6510                    gameMode == MachinePlaysBlack) {\r
6511           if (userOfferedDraw) {\r
6512             DisplayInformation("Machine accepts your draw offer");\r
6513             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6514           } else {\r
6515             DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");\r
6516           }\r
6517         }\r
6518     }\r
6519 \r
6520     \r
6521     /*\r
6522      * Look for thinking output\r
6523      */\r
6524     if ( appData.showThinking // [HGM] thinking: test all options that cause this output\r
6525           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
6526                                 ) {\r
6527         int plylev, mvleft, mvtot, curscore, time;\r
6528         char mvname[MOVE_LEN];\r
6529         unsigned long nodes;\r
6530         char plyext;\r
6531         int ignore = FALSE;\r
6532         int prefixHint = FALSE;\r
6533         mvname[0] = NULLCHAR;\r
6534 \r
6535         switch (gameMode) {\r
6536           case MachinePlaysBlack:\r
6537           case IcsPlayingBlack:\r
6538             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6539             break;\r
6540           case MachinePlaysWhite:\r
6541           case IcsPlayingWhite:\r
6542             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6543             break;\r
6544           case AnalyzeMode:\r
6545           case AnalyzeFile:\r
6546             break;\r
6547           case TwoMachinesPlay:\r
6548             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
6549                 ignore = TRUE;\r
6550             }\r
6551             break;\r
6552           default:\r
6553             ignore = TRUE;\r
6554             break;\r
6555         }\r
6556 \r
6557         if (!ignore) {\r
6558             buf1[0] = NULLCHAR;\r
6559             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
6560                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
6561 \r
6562                 if (plyext != ' ' && plyext != '\t') {\r
6563                     time *= 100;\r
6564                 }\r
6565 \r
6566                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6567                 if( cps->scoreIsAbsolute && \r
6568                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
6569                 {\r
6570                     curscore = -curscore;\r
6571                 }\r
6572 \r
6573 \r
6574                 programStats.depth = plylev;\r
6575                 programStats.nodes = nodes;\r
6576                 programStats.time = time;\r
6577                 programStats.score = curscore;\r
6578                 programStats.got_only_move = 0;\r
6579 \r
6580                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
6581                         int ticklen;\r
6582 \r
6583                         if(cps->nps == 0) ticklen = 10*time;       // use engine reported time\r
6584                         else ticklen = (1000. * nodes) / cps->nps; // convert node count to time\r
6585                         if(WhiteOnMove(forwardMostMove)) \r
6586                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
6587                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
6588                 }\r
6589 \r
6590                 /* Buffer overflow protection */\r
6591                 if (buf1[0] != NULLCHAR) {\r
6592                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
6593                         && appData.debugMode) {\r
6594                         fprintf(debugFP,\r
6595                                 "PV is too long; using the first %d bytes.\n",\r
6596                                 sizeof(programStats.movelist) - 1);\r
6597                     }\r
6598 \r
6599                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
6600                 } else {\r
6601                     sprintf(programStats.movelist, " no PV\n");\r
6602                 }\r
6603 \r
6604                 if (programStats.seen_stat) {\r
6605                     programStats.ok_to_send = 1;\r
6606                 }\r
6607 \r
6608                 if (strchr(programStats.movelist, '(') != NULL) {\r
6609                     programStats.line_is_book = 1;\r
6610                     programStats.nr_moves = 0;\r
6611                     programStats.moves_left = 0;\r
6612                 } else {\r
6613                     programStats.line_is_book = 0;\r
6614                 }\r
6615 \r
6616                 SendProgramStatsToFrontend( cps, &programStats );\r
6617 \r
6618                 /* \r
6619                     [AS] Protect the thinkOutput buffer from overflow... this\r
6620                     is only useful if buf1 hasn't overflowed first!\r
6621                 */\r
6622                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
6623                         plylev, \r
6624                         (gameMode == TwoMachinesPlay ?\r
6625                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
6626                         ((double) curscore) / 100.0,\r
6627                         prefixHint ? lastHint : "",\r
6628                         prefixHint ? " " : "" );\r
6629 \r
6630                 if( buf1[0] != NULLCHAR ) {\r
6631                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
6632 \r
6633                     if( strlen(buf1) > max_len ) {\r
6634                         if( appData.debugMode) {\r
6635                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
6636                         }\r
6637                         buf1[max_len+1] = '\0';\r
6638                     }\r
6639 \r
6640                     strcat( thinkOutput, buf1 );\r
6641                 }\r
6642 \r
6643                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
6644                     DisplayMove(currentMove - 1);\r
6645                     DisplayAnalysis();\r
6646                 }\r
6647                 return;\r
6648 \r
6649             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6650                 /* crafty (9.25+) says "(only move) <move>"\r
6651                  * if there is only 1 legal move\r
6652                  */\r
6653                 sscanf(p, "(only move) %s", buf1);\r
6654                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6655                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6656                 programStats.depth = 1;\r
6657                 programStats.nr_moves = 1;\r
6658                 programStats.moves_left = 1;\r
6659                 programStats.nodes = 1;\r
6660                 programStats.time = 1;\r
6661                 programStats.got_only_move = 1;\r
6662 \r
6663                 /* Not really, but we also use this member to\r
6664                    mean "line isn't going to change" (Crafty\r
6665                    isn't searching, so stats won't change) */\r
6666                 programStats.line_is_book = 1;\r
6667 \r
6668                 SendProgramStatsToFrontend( cps, &programStats );\r
6669                 \r
6670                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {\r
6671                     DisplayMove(currentMove - 1);\r
6672                     DisplayAnalysis();\r
6673                 }\r
6674                 return;\r
6675             } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",\r
6676                               &time, &nodes, &plylev, &mvleft,\r
6677                               &mvtot, mvname) >= 5) {\r
6678                 /* The stat01: line is from Crafty (9.29+) in response\r
6679                    to the "." command */\r
6680                 programStats.seen_stat = 1;\r
6681                 cps->maybeThinking = TRUE;\r
6682 \r
6683                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6684                   return;\r
6685 \r
6686                 programStats.depth = plylev;\r
6687                 programStats.time = time;\r
6688                 programStats.nodes = nodes;\r
6689                 programStats.moves_left = mvleft;\r
6690                 programStats.nr_moves = mvtot;\r
6691                 strcpy(programStats.move_name, mvname);\r
6692                 programStats.ok_to_send = 1;\r
6693                 programStats.movelist[0] = '\0';\r
6694 \r
6695                 SendProgramStatsToFrontend( cps, &programStats );\r
6696 \r
6697                 DisplayAnalysis();\r
6698                 return;\r
6699 \r
6700             } else if (strncmp(message,"++",2) == 0) {\r
6701                 /* Crafty 9.29+ outputs this */\r
6702                 programStats.got_fail = 2;\r
6703                 return;\r
6704 \r
6705             } else if (strncmp(message,"--",2) == 0) {\r
6706                 /* Crafty 9.29+ outputs this */\r
6707                 programStats.got_fail = 1;\r
6708                 return;\r
6709 \r
6710             } else if (thinkOutput[0] != NULLCHAR &&\r
6711                        strncmp(message, "    ", 4) == 0) {\r
6712                 unsigned message_len;\r
6713 \r
6714                 p = message;\r
6715                 while (*p && *p == ' ') p++;\r
6716 \r
6717                 message_len = strlen( p );\r
6718 \r
6719                 /* [AS] Avoid buffer overflow */\r
6720                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6721                     strcat(thinkOutput, " ");\r
6722                     strcat(thinkOutput, p);\r
6723                 }\r
6724 \r
6725                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6726                     strcat(programStats.movelist, " ");\r
6727                     strcat(programStats.movelist, p);\r
6728                 }\r
6729 \r
6730                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {\r
6731                     DisplayMove(currentMove - 1);\r
6732                     DisplayAnalysis();\r
6733                 }\r
6734                 return;\r
6735             }\r
6736         }\r
6737         else {\r
6738             buf1[0] = NULLCHAR;\r
6739 \r
6740             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
6741                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6742             {\r
6743                 ChessProgramStats cpstats;\r
6744 \r
6745                 if (plyext != ' ' && plyext != '\t') {\r
6746                     time *= 100;\r
6747                 }\r
6748 \r
6749                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6750                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6751                     curscore = -curscore;\r
6752                 }\r
6753 \r
6754                 cpstats.depth = plylev;\r
6755                 cpstats.nodes = nodes;\r
6756                 cpstats.time = time;\r
6757                 cpstats.score = curscore;\r
6758                 cpstats.got_only_move = 0;\r
6759                 cpstats.movelist[0] = '\0';\r
6760 \r
6761                 if (buf1[0] != NULLCHAR) {\r
6762                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6763                 }\r
6764 \r
6765                 cpstats.ok_to_send = 0;\r
6766                 cpstats.line_is_book = 0;\r
6767                 cpstats.nr_moves = 0;\r
6768                 cpstats.moves_left = 0;\r
6769 \r
6770                 SendProgramStatsToFrontend( cps, &cpstats );\r
6771             }\r
6772         }\r
6773     }\r
6774 }\r
6775 \r
6776 \r
6777 /* Parse a game score from the character string "game", and\r
6778    record it as the history of the current game.  The game\r
6779    score is NOT assumed to start from the standard position. \r
6780    The display is not updated in any way.\r
6781    */\r
6782 void\r
6783 ParseGameHistory(game)\r
6784      char *game;\r
6785 {\r
6786     ChessMove moveType;\r
6787     int fromX, fromY, toX, toY, boardIndex;\r
6788     char promoChar;\r
6789     char *p, *q;\r
6790     char buf[MSG_SIZ];\r
6791 \r
6792     if (appData.debugMode)\r
6793       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6794 \r
6795     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6796     gameInfo.site = StrSave(appData.icsHost);\r
6797     gameInfo.date = PGNDate();\r
6798     gameInfo.round = StrSave("-");\r
6799 \r
6800     /* Parse out names of players */\r
6801     while (*game == ' ') game++;\r
6802     p = buf;\r
6803     while (*game != ' ') *p++ = *game++;\r
6804     *p = NULLCHAR;\r
6805     gameInfo.white = StrSave(buf);\r
6806     while (*game == ' ') game++;\r
6807     p = buf;\r
6808     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6809     *p = NULLCHAR;\r
6810     gameInfo.black = StrSave(buf);\r
6811 \r
6812     /* Parse moves */\r
6813     boardIndex = blackPlaysFirst ? 1 : 0;\r
6814     yynewstr(game);\r
6815     for (;;) {\r
6816         yyboardindex = boardIndex;\r
6817         moveType = (ChessMove) yylex();\r
6818         switch (moveType) {\r
6819           case IllegalMove:             /* maybe suicide chess, etc. */\r
6820   if (appData.debugMode) {\r
6821     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6822     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6823     setbuf(debugFP, NULL);\r
6824   }\r
6825           case WhitePromotionChancellor:\r
6826           case BlackPromotionChancellor:\r
6827           case WhitePromotionArchbishop:\r
6828           case BlackPromotionArchbishop:\r
6829           case WhitePromotionQueen:\r
6830           case BlackPromotionQueen:\r
6831           case WhitePromotionRook:\r
6832           case BlackPromotionRook:\r
6833           case WhitePromotionBishop:\r
6834           case BlackPromotionBishop:\r
6835           case WhitePromotionKnight:\r
6836           case BlackPromotionKnight:\r
6837           case WhitePromotionKing:\r
6838           case BlackPromotionKing:\r
6839           case NormalMove:\r
6840           case WhiteCapturesEnPassant:\r
6841           case BlackCapturesEnPassant:\r
6842           case WhiteKingSideCastle:\r
6843           case WhiteQueenSideCastle:\r
6844           case BlackKingSideCastle:\r
6845           case BlackQueenSideCastle:\r
6846           case WhiteKingSideCastleWild:\r
6847           case WhiteQueenSideCastleWild:\r
6848           case BlackKingSideCastleWild:\r
6849           case BlackQueenSideCastleWild:\r
6850           /* PUSH Fabien */\r
6851           case WhiteHSideCastleFR:\r
6852           case WhiteASideCastleFR:\r
6853           case BlackHSideCastleFR:\r
6854           case BlackASideCastleFR:\r
6855           /* POP Fabien */\r
6856             fromX = currentMoveString[0] - AAA;\r
6857             fromY = currentMoveString[1] - ONE;\r
6858             toX = currentMoveString[2] - AAA;\r
6859             toY = currentMoveString[3] - ONE;\r
6860             promoChar = currentMoveString[4];\r
6861             break;\r
6862           case WhiteDrop:\r
6863           case BlackDrop:\r
6864             fromX = moveType == WhiteDrop ?\r
6865               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6866             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6867             fromY = DROP_RANK;\r
6868             toX = currentMoveString[2] - AAA;\r
6869             toY = currentMoveString[3] - ONE;\r
6870             promoChar = NULLCHAR;\r
6871             break;\r
6872           case AmbiguousMove:\r
6873             /* bug? */\r
6874             sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);\r
6875   if (appData.debugMode) {\r
6876     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
6877     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6878     setbuf(debugFP, NULL);\r
6879   }\r
6880             DisplayError(buf, 0);\r
6881             return;\r
6882           case ImpossibleMove:\r
6883             /* bug? */\r
6884             sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);\r
6885   if (appData.debugMode) {\r
6886     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
6887     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6888     setbuf(debugFP, NULL);\r
6889   }\r
6890             DisplayError(buf, 0);\r
6891             return;\r
6892           case (ChessMove) 0:   /* end of file */\r
6893             if (boardIndex < backwardMostMove) {\r
6894                 /* Oops, gap.  How did that happen? */\r
6895                 DisplayError("Gap in move list", 0);\r
6896                 return;\r
6897             }\r
6898             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
6899             if (boardIndex > forwardMostMove) {\r
6900                 forwardMostMove = boardIndex;\r
6901             }\r
6902             return;\r
6903           case ElapsedTime:\r
6904             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
6905                 strcat(parseList[boardIndex-1], " ");\r
6906                 strcat(parseList[boardIndex-1], yy_text);\r
6907             }\r
6908             continue;\r
6909           case Comment:\r
6910           case PGNTag:\r
6911           case NAG:\r
6912           default:\r
6913             /* ignore */\r
6914             continue;\r
6915           case WhiteWins:\r
6916           case BlackWins:\r
6917           case GameIsDrawn:\r
6918           case GameUnfinished:\r
6919             if (gameMode == IcsExamining) {\r
6920                 if (boardIndex < backwardMostMove) {\r
6921                     /* Oops, gap.  How did that happen? */\r
6922                     return;\r
6923                 }\r
6924                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6925                 return;\r
6926             }\r
6927             gameInfo.result = moveType;\r
6928             p = strchr(yy_text, '{');\r
6929             if (p == NULL) p = strchr(yy_text, '(');\r
6930             if (p == NULL) {\r
6931                 p = yy_text;\r
6932                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
6933             } else {\r
6934                 q = strchr(p, *p == '{' ? '}' : ')');\r
6935                 if (q != NULL) *q = NULLCHAR;\r
6936                 p++;\r
6937             }\r
6938             gameInfo.resultDetails = StrSave(p);\r
6939             continue;\r
6940         }\r
6941         if (boardIndex >= forwardMostMove &&\r
6942             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
6943             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6944             return;\r
6945         }\r
6946         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
6947                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
6948                                  parseList[boardIndex]);\r
6949         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
6950         /* currentMoveString is set as a side-effect of yylex */\r
6951         strcpy(moveList[boardIndex], currentMoveString);\r
6952         strcat(moveList[boardIndex], "\n");\r
6953         boardIndex++;\r
6954         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
6955         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
6956                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
6957           case MT_NONE:\r
6958           case MT_STALEMATE:\r
6959           default:\r
6960             break;\r
6961           case MT_CHECK:\r
6962             if(gameInfo.variant != VariantShogi)\r
6963                 strcat(parseList[boardIndex - 1], "+");\r
6964             break;\r
6965           case MT_CHECKMATE:\r
6966             strcat(parseList[boardIndex - 1], "#");\r
6967             break;\r
6968         }\r
6969     }\r
6970 }\r
6971 \r
6972 \r
6973 /* Apply a move to the given board  */\r
6974 void\r
6975 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
6976      int fromX, fromY, toX, toY;\r
6977      int promoChar;\r
6978      Board board;\r
6979 {\r
6980   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
6981 \r
6982     /* [HGM] compute & store e.p. status and castling rights for new position */\r
6983     /* if we are updating a board for which those exist (i.e. in boards[])    */\r
6984     if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)\r
6985     { int i, j;\r
6986 \r
6987       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
6988       oldEP = epStatus[p-1];\r
6989       epStatus[p] = EP_NONE;\r
6990 \r
6991       if( board[toY][toX] != EmptySquare ) \r
6992            epStatus[p] = EP_CAPTURE;  \r
6993 \r
6994       if( board[fromY][fromX] == WhitePawn ) {\r
6995            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
6996                epStatus[p] = EP_PAWN_MOVE;\r
6997            if( toY-fromY==2) {\r
6998                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
6999                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7000                       epStatus[p] = toX | berolina;\r
7001                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
7002                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7003                       epStatus[p] = toX;\r
7004            }\r
7005       } else \r
7006       if( board[fromY][fromX] == BlackPawn ) {\r
7007            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7008                epStatus[p] = EP_PAWN_MOVE; \r
7009            if( toY-fromY== -2) {\r
7010                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
7011                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7012                       epStatus[p] = toX | berolina;\r
7013                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
7014                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7015                       epStatus[p] = toX;\r
7016            }\r
7017        }\r
7018 \r
7019        for(i=0; i<nrCastlingRights; i++) {\r
7020            castlingRights[p][i] = castlingRights[p-1][i];\r
7021            if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||\r
7022               castlingRights[p][i] == toX   && castlingRank[i] == toY   \r
7023              ) castlingRights[p][i] = -1; // revoke for moved or captured piece\r
7024        }\r
7025 \r
7026     }\r
7027 \r
7028   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
7029   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
7030        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
7031          \r
7032   if (fromX == toX && fromY == toY) return;\r
7033 \r
7034   if (fromY == DROP_RANK) {\r
7035         /* must be first */\r
7036         piece = board[toY][toX] = (ChessSquare) fromX;\r
7037   } else {\r
7038      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
7039      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
7040      if(gameInfo.variant == VariantKnightmate)\r
7041          king += (int) WhiteUnicorn - (int) WhiteKing;\r
7042 \r
7043     /* Code added by Tord: */\r
7044     /* FRC castling assumed when king captures friendly rook. */\r
7045     if (board[fromY][fromX] == WhiteKing &&\r
7046              board[toY][toX] == WhiteRook) {\r
7047       board[fromY][fromX] = EmptySquare;\r
7048       board[toY][toX] = EmptySquare;\r
7049       if(toX > fromX) {\r
7050         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
7051       } else {\r
7052         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
7053       }\r
7054     } else if (board[fromY][fromX] == BlackKing &&\r
7055                board[toY][toX] == BlackRook) {\r
7056       board[fromY][fromX] = EmptySquare;\r
7057       board[toY][toX] = EmptySquare;\r
7058       if(toX > fromX) {\r
7059         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
7060       } else {\r
7061         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
7062       }\r
7063     /* End of code added by Tord */\r
7064 \r
7065     } else if (board[fromY][fromX] == king\r
7066         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7067         && toY == fromY && toX > fromX+1) {\r
7068         board[fromY][fromX] = EmptySquare;\r
7069         board[toY][toX] = king;\r
7070         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7071         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7072     } else if (board[fromY][fromX] == king\r
7073         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7074                && toY == fromY && toX < fromX-1) {\r
7075         board[fromY][fromX] = EmptySquare;\r
7076         board[toY][toX] = king;\r
7077         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7078         board[fromY][BOARD_LEFT] = EmptySquare;\r
7079     } else if (board[fromY][fromX] == WhitePawn\r
7080                && toY == BOARD_HEIGHT-1\r
7081                && gameInfo.variant != VariantXiangqi\r
7082                ) {\r
7083         /* white pawn promotion */\r
7084         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
7085         if (board[toY][toX] == EmptySquare) {\r
7086             board[toY][toX] = WhiteQueen;\r
7087         }\r
7088         if(gameInfo.variant==VariantBughouse ||\r
7089            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7090             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7091         board[fromY][fromX] = EmptySquare;\r
7092     } else if ((fromY == BOARD_HEIGHT-4)\r
7093                && (toX != fromX)\r
7094                && gameInfo.variant != VariantXiangqi\r
7095                && gameInfo.variant != VariantBerolina\r
7096                && (board[fromY][fromX] == WhitePawn)\r
7097                && (board[toY][toX] == EmptySquare)) {\r
7098         board[fromY][fromX] = EmptySquare;\r
7099         board[toY][toX] = WhitePawn;\r
7100         captured = board[toY - 1][toX];\r
7101         board[toY - 1][toX] = EmptySquare;\r
7102     } else if ((fromY == BOARD_HEIGHT-4)\r
7103                && (toX == fromX)\r
7104                && gameInfo.variant == VariantBerolina\r
7105                && (board[fromY][fromX] == WhitePawn)\r
7106                && (board[toY][toX] == EmptySquare)) {\r
7107         board[fromY][fromX] = EmptySquare;\r
7108         board[toY][toX] = WhitePawn;\r
7109         if(oldEP & EP_BEROLIN_A) {\r
7110                 captured = board[fromY][fromX-1];\r
7111                 board[fromY][fromX-1] = EmptySquare;\r
7112         }else{  captured = board[fromY][fromX+1];\r
7113                 board[fromY][fromX+1] = EmptySquare;\r
7114         }\r
7115     } else if (board[fromY][fromX] == king\r
7116         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7117                && toY == fromY && toX > fromX+1) {\r
7118         board[fromY][fromX] = EmptySquare;\r
7119         board[toY][toX] = king;\r
7120         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7121         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7122     } else if (board[fromY][fromX] == king\r
7123         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7124                && toY == fromY && toX < fromX-1) {\r
7125         board[fromY][fromX] = EmptySquare;\r
7126         board[toY][toX] = king;\r
7127         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7128         board[fromY][BOARD_LEFT] = EmptySquare;\r
7129     } else if (fromY == 7 && fromX == 3\r
7130                && board[fromY][fromX] == BlackKing\r
7131                && toY == 7 && toX == 5) {\r
7132         board[fromY][fromX] = EmptySquare;\r
7133         board[toY][toX] = BlackKing;\r
7134         board[fromY][7] = EmptySquare;\r
7135         board[toY][4] = BlackRook;\r
7136     } else if (fromY == 7 && fromX == 3\r
7137                && board[fromY][fromX] == BlackKing\r
7138                && toY == 7 && toX == 1) {\r
7139         board[fromY][fromX] = EmptySquare;\r
7140         board[toY][toX] = BlackKing;\r
7141         board[fromY][0] = EmptySquare;\r
7142         board[toY][2] = BlackRook;\r
7143     } else if (board[fromY][fromX] == BlackPawn\r
7144                && toY == 0\r
7145                && gameInfo.variant != VariantXiangqi\r
7146                ) {\r
7147         /* black pawn promotion */\r
7148         board[0][toX] = CharToPiece(ToLower(promoChar));\r
7149         if (board[0][toX] == EmptySquare) {\r
7150             board[0][toX] = BlackQueen;\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 == 3)\r
7157                && (toX != fromX)\r
7158                && gameInfo.variant != VariantXiangqi\r
7159                && gameInfo.variant != VariantBerolina\r
7160                && (board[fromY][fromX] == BlackPawn)\r
7161                && (board[toY][toX] == EmptySquare)) {\r
7162         board[fromY][fromX] = EmptySquare;\r
7163         board[toY][toX] = BlackPawn;\r
7164         captured = board[toY + 1][toX];\r
7165         board[toY + 1][toX] = EmptySquare;\r
7166     } else if ((fromY == 3)\r
7167                && (toX == fromX)\r
7168                && gameInfo.variant == VariantBerolina\r
7169                && (board[fromY][fromX] == BlackPawn)\r
7170                && (board[toY][toX] == EmptySquare)) {\r
7171         board[fromY][fromX] = EmptySquare;\r
7172         board[toY][toX] = BlackPawn;\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 {\r
7180         board[toY][toX] = board[fromY][fromX];\r
7181         board[fromY][fromX] = EmptySquare;\r
7182     }\r
7183 \r
7184     /* [HGM] now we promote for Shogi, if needed */\r
7185     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
7186         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7187   }\r
7188 \r
7189     if (gameInfo.holdingsWidth != 0) {\r
7190 \r
7191       /* !!A lot more code needs to be written to support holdings  */\r
7192       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
7193       /* penultimate board files, so they are automaticlly stored   */\r
7194       /* in the game history.                                       */\r
7195       if (fromY == DROP_RANK) {\r
7196         /* Delete from holdings, by decreasing count */\r
7197         /* and erasing image if necessary            */\r
7198         p = (int) fromX;\r
7199         if(p < (int) BlackPawn) { /* white drop */\r
7200              p -= (int)WhitePawn;\r
7201              if(p >= gameInfo.holdingsSize) p = 0;\r
7202              if(--board[p][BOARD_WIDTH-2] == 0)\r
7203                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
7204         } else {                  /* black drop */\r
7205              p -= (int)BlackPawn;\r
7206              if(p >= gameInfo.holdingsSize) p = 0;\r
7207              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
7208                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
7209         }\r
7210       }\r
7211       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
7212           && gameInfo.variant != VariantBughouse        ) {\r
7213         /* [HGM] holdings: Add to holdings, if holdings exist */\r
7214         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { \r
7215                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
7216                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
7217         }\r
7218         p = (int) captured;\r
7219         if (p >= (int) BlackPawn) {\r
7220           p -= (int)BlackPawn;\r
7221           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7222                   /* in Shogi restore piece to its original  first */\r
7223                   captured = (ChessSquare) (DEMOTED captured);\r
7224                   p = DEMOTED p;\r
7225           }\r
7226           p = PieceToNumber((ChessSquare)p);\r
7227           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
7228           board[p][BOARD_WIDTH-2]++;\r
7229           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;\r
7230         } else {\r
7231           p -= (int)WhitePawn;\r
7232           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7233                   captured = (ChessSquare) (DEMOTED captured);\r
7234                   p = DEMOTED p;\r
7235           }\r
7236           p = PieceToNumber((ChessSquare)p);\r
7237           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
7238           board[BOARD_HEIGHT-1-p][1]++;\r
7239           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;\r
7240         }\r
7241       }\r
7242 \r
7243     } else if (gameInfo.variant == VariantAtomic) {\r
7244       if (captured != EmptySquare) {\r
7245         int y, x;\r
7246         for (y = toY-1; y <= toY+1; y++) {\r
7247           for (x = toX-1; x <= toX+1; x++) {\r
7248             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
7249                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
7250               board[y][x] = EmptySquare;\r
7251             }\r
7252           }\r
7253         }\r
7254         board[toY][toX] = EmptySquare;\r
7255       }\r
7256     }\r
7257     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
7258         /* [HGM] Shogi promotions */\r
7259         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7260     }\r
7261 \r
7262 }\r
7263 \r
7264 /* Updates forwardMostMove */\r
7265 void\r
7266 MakeMove(fromX, fromY, toX, toY, promoChar)\r
7267      int fromX, fromY, toX, toY;\r
7268      int promoChar;\r
7269 {\r
7270     forwardMostMove++;\r
7271 \r
7272     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting */\r
7273         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
7274         piece = boards[forwardMostMove-1][fromY][fromX];\r
7275         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
7276         if(gameInfo.variant == VariantKnightmate)\r
7277             king += (int) WhiteUnicorn - (int) WhiteKing;\r
7278         if(forwardMostMove == 1) {\r
7279             if(blackPlaysFirst) \r
7280                 fprintf(serverMoves, "%s;", second.tidy);\r
7281             fprintf(serverMoves, "%s;", first.tidy);\r
7282             if(!blackPlaysFirst) \r
7283                 fprintf(serverMoves, "%s;", second.tidy);\r
7284         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
7285         lastLoadFlag = loadFlag;\r
7286         // print base move\r
7287         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
7288         // print castling suffix\r
7289         if( toY == fromY && piece == king ) {\r
7290             if(toX-fromX > 1)\r
7291                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
7292             if(fromX-toX >1)\r
7293                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
7294         }\r
7295         // e.p. suffix\r
7296         if( (boards[forwardMostMove-1][fromY][fromX] == WhitePawn ||\r
7297              boards[forwardMostMove-1][fromY][fromX] == BlackPawn   ) &&\r
7298              boards[forwardMostMove-1][toY][toX] == EmptySquare\r
7299              && fromX != toX )\r
7300                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
7301         // promotion suffix\r
7302         if(promoChar != NULLCHAR)\r
7303                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
7304         if(!loadFlag) {\r
7305             fprintf(serverMoves, "/%d/%d",\r
7306                pvInfoList[forwardMostMove-1].depth, pvInfoList[forwardMostMove-1].score);\r
7307             if(forwardMostMove & 1) timeLeft = whiteTimeRemaining/1000;\r
7308             else                    timeLeft = blackTimeRemaining/1000;\r
7309             fprintf(serverMoves, "/%d", timeLeft);\r
7310         }\r
7311         fflush(serverMoves);\r
7312     }\r
7313 \r
7314     if (forwardMostMove >= MAX_MOVES) {\r
7315       DisplayFatalError("Game too long; increase MAX_MOVES and recompile",\r
7316                         0, 1);\r
7317       return;\r
7318     }\r
7319     SwitchClocks();\r
7320     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
7321     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
7322     if (commentList[forwardMostMove] != NULL) {\r
7323         free(commentList[forwardMostMove]);\r
7324         commentList[forwardMostMove] = NULL;\r
7325     }\r
7326     CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);\r
7327     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);\r
7328     gameInfo.result = GameUnfinished;\r
7329     if (gameInfo.resultDetails != NULL) {\r
7330         free(gameInfo.resultDetails);\r
7331         gameInfo.resultDetails = NULL;\r
7332     }\r
7333     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
7334                               moveList[forwardMostMove - 1]);\r
7335     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
7336                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
7337                              fromY, fromX, toY, toX, promoChar,\r
7338                              parseList[forwardMostMove - 1]);\r
7339     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
7340                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
7341                             castlingRights[forwardMostMove]) ) {\r
7342       case MT_NONE:\r
7343       case MT_STALEMATE:\r
7344       default:\r
7345         break;\r
7346       case MT_CHECK:\r
7347         if(gameInfo.variant != VariantShogi)\r
7348             strcat(parseList[forwardMostMove - 1], "+");\r
7349         break;\r
7350       case MT_CHECKMATE:\r
7351         strcat(parseList[forwardMostMove - 1], "#");\r
7352         break;\r
7353     }\r
7354     if (appData.debugMode) {\r
7355         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
7356     }\r
7357 \r
7358 }\r
7359 \r
7360 /* Updates currentMove if not pausing */\r
7361 void\r
7362 ShowMove(fromX, fromY, toX, toY)\r
7363 {\r
7364     int instant = (gameMode == PlayFromGameFile) ?\r
7365         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
7366     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
7367         if (!instant) {\r
7368             if (forwardMostMove == currentMove + 1) {\r
7369                 AnimateMove(boards[forwardMostMove - 1],\r
7370                             fromX, fromY, toX, toY);\r
7371             }\r
7372             if (appData.highlightLastMove) {\r
7373                 SetHighlights(fromX, fromY, toX, toY);\r
7374             }\r
7375         }\r
7376         currentMove = forwardMostMove;\r
7377     }\r
7378 \r
7379     if (instant) return;\r
7380 \r
7381     DisplayMove(currentMove - 1);\r
7382     DrawPosition(FALSE, boards[currentMove]);\r
7383     DisplayBothClocks();\r
7384     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
7385 }\r
7386 \r
7387 void SendEgtPath(ChessProgramState *cps)\r
7388 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */\r
7389         char buf[MSG_SIZ], name[MSG_SIZ], *p;\r
7390 \r
7391         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;\r
7392 \r
7393         while(*p) {\r
7394             char c, *q = name+1, *r, *s;\r
7395 \r
7396             name[0] = ','; // extract next format name from feature and copy with prefixed ','\r
7397             while(*p && *p != ',') *q++ = *p++;\r
7398             *q++ = ':'; *q = 0;\r
7399             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && \r
7400                 strcmp(name, ",nalimov:") == 0 ) {\r
7401                 // take nalimov path from the menu-changeable option first, if it is defined\r
7402                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);\r
7403                 SendToProgram(buf,cps);     // send egtbpath command for nalimov\r
7404             } else\r
7405             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||\r
7406                 (s = StrStr(appData.egtFormats, name)) != NULL) {\r
7407                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma\r
7408                 s = r = StrStr(s, ":") + 1; // beginning of path info\r
7409                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string\r
7410                 c = *r; *r = 0;             // temporarily null-terminate path info\r
7411                     *--q = 0;               // strip of trailig ':' from name\r
7412                     sprintf(buf, "egtbpath %s %s\n", name+1, s);\r
7413                 *r = c;\r
7414                 SendToProgram(buf,cps);     // send egtbpath command for this format\r
7415             }\r
7416             if(*p == ',') p++; // read away comma to position for next format name\r
7417         }\r
7418 }\r
7419 \r
7420 void\r
7421 InitChessProgram(cps, setup)\r
7422      ChessProgramState *cps;\r
7423      int setup; /* [HGM] needed to setup FRC opening position */\r
7424 {\r
7425     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
7426     if (appData.noChessProgram) return;\r
7427     hintRequested = FALSE;\r
7428     bookRequested = FALSE;\r
7429 \r
7430     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */\r
7431     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */\r
7432     if(cps->memSize) { /* [HGM] memory */\r
7433         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);\r
7434         SendToProgram(buf, cps);\r
7435     }\r
7436     SendEgtPath(cps); /* [HGM] EGT */\r
7437     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */\r
7438         sprintf(buf, "cores %d\n", appData.smpCores);\r
7439         SendToProgram(buf, cps);\r
7440     }\r
7441 \r
7442     SendToProgram(cps->initString, cps);\r
7443     if (gameInfo.variant != VariantNormal &&\r
7444         gameInfo.variant != VariantLoadable\r
7445         /* [HGM] also send variant if board size non-standard */\r
7446         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
7447                                             ) {\r
7448       char *v = VariantName(gameInfo.variant);\r
7449       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
7450         /* [HGM] in protocol 1 we have to assume all variants valid */\r
7451         sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);\r
7452         DisplayFatalError(buf, 0, 1);\r
7453         return;\r
7454       }\r
7455 \r
7456       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
7457       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7458       if( gameInfo.variant == VariantXiangqi )\r
7459            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
7460       if( gameInfo.variant == VariantShogi )\r
7461            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
7462       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
7463            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
7464       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
7465                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
7466            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7467       if( gameInfo.variant == VariantCourier )\r
7468            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7469       if( gameInfo.variant == VariantSuper )\r
7470            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7471       if( gameInfo.variant == VariantGreat )\r
7472            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7473 \r
7474       if(overruled) {\r
7475            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
7476                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
7477            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
7478            if(StrStr(cps->variants, b) == NULL) { \r
7479                // specific sized variant not known, check if general sizing allowed\r
7480                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
7481                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
7482                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
7483                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
7484                        DisplayFatalError(buf, 0, 1);\r
7485                        return;\r
7486                    }\r
7487                    /* [HGM] here we really should compare with the maximum supported board size */\r
7488                }\r
7489            }\r
7490       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
7491       sprintf(buf, "variant %s\n", b);\r
7492       SendToProgram(buf, cps);\r
7493     }\r
7494     currentlyInitializedVariant = gameInfo.variant;\r
7495 \r
7496     /* [HGM] send opening position in FRC to first engine */\r
7497     if(setup) {\r
7498           SendToProgram("force\n", cps);\r
7499           SendBoard(cps, 0);\r
7500           /* engine is now in force mode! Set flag to wake it up after first move. */\r
7501           setboardSpoiledMachineBlack = 1;\r
7502     }\r
7503 \r
7504     if (cps->sendICS) {\r
7505       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
7506       SendToProgram(buf, cps);\r
7507     }\r
7508     cps->maybeThinking = FALSE;\r
7509     cps->offeredDraw = 0;\r
7510     if (!appData.icsActive) {\r
7511         SendTimeControl(cps, movesPerSession, timeControl,\r
7512                         timeIncrement, appData.searchDepth,\r
7513                         searchTime);\r
7514     }\r
7515     if (appData.showThinking \r
7516         // [HGM] thinking: four options require thinking output to be sent\r
7517         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
7518                                 ) {\r
7519         SendToProgram("post\n", cps);\r
7520     }\r
7521     SendToProgram("hard\n", cps);\r
7522     if (!appData.ponderNextMove) {\r
7523         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
7524            it without being sure what state we are in first.  "hard"\r
7525            is not a toggle, so that one is OK.\r
7526          */\r
7527         SendToProgram("easy\n", cps);\r
7528     }\r
7529     if (cps->usePing) {\r
7530       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
7531       SendToProgram(buf, cps);\r
7532     }\r
7533     cps->initDone = TRUE;\r
7534 }   \r
7535 \r
7536 \r
7537 void\r
7538 StartChessProgram(cps)\r
7539      ChessProgramState *cps;\r
7540 {\r
7541     char buf[MSG_SIZ];\r
7542     int err;\r
7543 \r
7544     if (appData.noChessProgram) return;\r
7545     cps->initDone = FALSE;\r
7546 \r
7547     if (strcmp(cps->host, "localhost") == 0) {\r
7548         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
7549     } else if (*appData.remoteShell == NULLCHAR) {\r
7550         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
7551     } else {\r
7552         if (*appData.remoteUser == NULLCHAR) {\r
7553             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
7554                     cps->program);\r
7555         } else {\r
7556             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
7557                     cps->host, appData.remoteUser, cps->program);\r
7558         }\r
7559         err = StartChildProcess(buf, "", &cps->pr);\r
7560     }\r
7561     \r
7562     if (err != 0) {\r
7563         sprintf(buf, "Startup failure on '%s'", cps->program);\r
7564         DisplayFatalError(buf, err, 1);\r
7565         cps->pr = NoProc;\r
7566         cps->isr = NULL;\r
7567         return;\r
7568     }\r
7569     \r
7570     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
7571     if (cps->protocolVersion > 1) {\r
7572       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
7573       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options\r
7574       cps->comboCnt = 0;  //                and values of combo boxes\r
7575       SendToProgram(buf, cps);\r
7576     } else {\r
7577       SendToProgram("xboard\n", cps);\r
7578     }\r
7579 }\r
7580 \r
7581 \r
7582 void\r
7583 TwoMachinesEventIfReady P((void))\r
7584 {\r
7585   if (first.lastPing != first.lastPong) {\r
7586     DisplayMessage("", "Waiting for first chess program");\r
7587     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);\r
7588     return;\r
7589   }\r
7590   if (second.lastPing != second.lastPong) {\r
7591     DisplayMessage("", "Waiting for second chess program");\r
7592     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);\r
7593     return;\r
7594   }\r
7595   ThawUI();\r
7596   TwoMachinesEvent();\r
7597 }\r
7598 \r
7599 void\r
7600 NextMatchGame P((void))\r
7601 {\r
7602     int index; /* [HGM] autoinc: step lod index during match */\r
7603     Reset(FALSE, TRUE);\r
7604     if (*appData.loadGameFile != NULLCHAR) {\r
7605         index = appData.loadGameIndex;\r
7606         if(index < 0) { // [HGM] autoinc\r
7607             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7608             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7609         } \r
7610         LoadGameFromFile(appData.loadGameFile,\r
7611                          index,\r
7612                          appData.loadGameFile, FALSE);\r
7613     } else if (*appData.loadPositionFile != NULLCHAR) {\r
7614         index = appData.loadPositionIndex;\r
7615         if(index < 0) { // [HGM] autoinc\r
7616             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7617             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7618         } \r
7619         LoadPositionFromFile(appData.loadPositionFile,\r
7620                              index,\r
7621                              appData.loadPositionFile);\r
7622     }\r
7623     TwoMachinesEventIfReady();\r
7624 }\r
7625 \r
7626 void UserAdjudicationEvent( int result )\r
7627 {\r
7628     ChessMove gameResult = GameIsDrawn;\r
7629 \r
7630     if( result > 0 ) {\r
7631         gameResult = WhiteWins;\r
7632     }\r
7633     else if( result < 0 ) {\r
7634         gameResult = BlackWins;\r
7635     }\r
7636 \r
7637     if( gameMode == TwoMachinesPlay ) {\r
7638         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
7639     }\r
7640 }\r
7641 \r
7642 \r
7643 void\r
7644 GameEnds(result, resultDetails, whosays)\r
7645      ChessMove result;\r
7646      char *resultDetails;\r
7647      int whosays;\r
7648 {\r
7649     GameMode nextGameMode;\r
7650     int isIcsGame;\r
7651     char buf[MSG_SIZ];\r
7652 \r
7653     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
7654     endingGame = 1;\r
7655 \r
7656     if (appData.debugMode) {\r
7657       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
7658               result, resultDetails ? resultDetails : "(null)", whosays);\r
7659     }\r
7660 \r
7661     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
7662         /* If we are playing on ICS, the server decides when the\r
7663            game is over, but the engine can offer to draw, claim \r
7664            a draw, or resign. \r
7665          */\r
7666 #if ZIPPY\r
7667         if (appData.zippyPlay && first.initDone) {\r
7668             if (result == GameIsDrawn) {\r
7669                 /* In case draw still needs to be claimed */\r
7670                 SendToICS(ics_prefix);\r
7671                 SendToICS("draw\n");\r
7672             } else if (StrCaseStr(resultDetails, "resign")) {\r
7673                 SendToICS(ics_prefix);\r
7674                 SendToICS("resign\n");\r
7675             }\r
7676         }\r
7677 #endif\r
7678         endingGame = 0; /* [HGM] crash */\r
7679         return;\r
7680     }\r
7681 \r
7682     /* If we're loading the game from a file, stop */\r
7683     if (whosays == GE_FILE) {\r
7684       (void) StopLoadGameTimer();\r
7685       gameFileFP = NULL;\r
7686     }\r
7687 \r
7688     /* Cancel draw offers */\r
7689     first.offeredDraw = second.offeredDraw = 0;\r
7690 \r
7691     /* If this is an ICS game, only ICS can really say it's done;\r
7692        if not, anyone can. */\r
7693     isIcsGame = (gameMode == IcsPlayingWhite || \r
7694                  gameMode == IcsPlayingBlack || \r
7695                  gameMode == IcsObserving    || \r
7696                  gameMode == IcsExamining);\r
7697 \r
7698     if (!isIcsGame || whosays == GE_ICS) {\r
7699         /* OK -- not an ICS game, or ICS said it was done */\r
7700         StopClocks();\r
7701         if (!isIcsGame && !appData.noChessProgram) \r
7702           SetUserThinkingEnables();\r
7703     \r
7704         /* [HGM] if a machine claims the game end we verify this claim */\r
7705         if(gameMode == TwoMachinesPlay && appData.testClaims) {\r
7706             if(appData.testLegality && whosays >= GE_ENGINE1 ) {\r
7707                 char claimer;\r
7708 \r
7709                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
7710                                             first.twoMachinesColor[0] :\r
7711                                             second.twoMachinesColor[0] ;\r
7712                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) &&\r
7713                     (result == WhiteWins && claimer == 'w' ||\r
7714                      result == BlackWins && claimer == 'b'   ) ) {\r
7715                 if (appData.debugMode) {\r
7716                      fprintf(debugFP, "result=%d sp=%d move=%d\n",\r
7717                         result, epStatus[forwardMostMove], forwardMostMove);\r
7718                 }\r
7719                       /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7720                       if(epStatus[forwardMostMove] != EP_CHECKMATE &&\r
7721                          result != (WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins)) {\r
7722                               sprintf(buf, "False win claim: '%s'", resultDetails);\r
7723                               result = claimer == 'w' ? BlackWins : WhiteWins;\r
7724                               resultDetails = buf;\r
7725                       }\r
7726                 } else\r
7727                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
7728                     && (forwardMostMove <= backwardMostMove ||\r
7729                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
7730                         (claimer=='b')==(forwardMostMove&1))\r
7731                                                                                   ) {\r
7732                       /* [HGM] verify: draws that were not flagged are false claims */\r
7733                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
7734                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7735                       resultDetails = buf;\r
7736                 }\r
7737                 /* (Claiming a loss is accepted no questions asked!) */\r
7738             }\r
7739             /* [HGM] bare: don't allow bare King to win */\r
7740             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)\r
7741                          && result != GameIsDrawn)\r
7742             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
7743                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
7744                         int p = (int)boards[forwardMostMove][i][j] - color;\r
7745                         if(p >= 0 && p <= (int)WhiteKing) k++;\r
7746                 }\r
7747                 if (appData.debugMode) {\r
7748                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",\r
7749                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);\r
7750                 }\r
7751                 if(k <= 1) {\r
7752                         result = GameIsDrawn;\r
7753                         sprintf(buf, "%s but bare king", resultDetails);\r
7754                         resultDetails = buf;\r
7755                 }\r
7756             }\r
7757         }\r
7758 \r
7759 \r
7760         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
7761             if(result==WhiteWins) c = '+';\r
7762             if(result==BlackWins) c = '-';\r
7763             if(resultDetails != NULL)\r
7764                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
7765         }\r
7766         if (resultDetails != NULL) {\r
7767             gameInfo.result = result;\r
7768             gameInfo.resultDetails = StrSave(resultDetails);\r
7769 \r
7770             /* display last move only if game was not loaded from file */\r
7771             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7772                 DisplayMove(currentMove - 1);\r
7773     \r
7774             if (forwardMostMove != 0) {\r
7775                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7776                     if (*appData.saveGameFile != NULLCHAR) {\r
7777                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7778                     } else if (appData.autoSaveGames) {\r
7779                         AutoSaveGame();\r
7780                     }\r
7781                     if (*appData.savePositionFile != NULLCHAR) {\r
7782                         SavePositionToFile(appData.savePositionFile);\r
7783                     }\r
7784                 }\r
7785             }\r
7786 \r
7787             /* Tell program how game ended in case it is learning */\r
7788             /* [HGM] Moved this to after saving the PGN, just in case */\r
7789             /* engine died and we got here through time loss. In that */\r
7790             /* case we will get a fatal error writing the pipe, which */\r
7791             /* would otherwise lose us the PGN.                       */\r
7792             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7793             /* output during GameEnds should never be fatal anymore   */\r
7794             if (gameMode == MachinePlaysWhite ||\r
7795                 gameMode == MachinePlaysBlack ||\r
7796                 gameMode == TwoMachinesPlay ||\r
7797                 gameMode == IcsPlayingWhite ||\r
7798                 gameMode == IcsPlayingBlack ||\r
7799                 gameMode == BeginningOfGame) {\r
7800                 char buf[MSG_SIZ];\r
7801                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7802                         resultDetails);\r
7803                 if (first.pr != NoProc) {\r
7804                     SendToProgram(buf, &first);\r
7805                 }\r
7806                 if (second.pr != NoProc &&\r
7807                     gameMode == TwoMachinesPlay) {\r
7808                     SendToProgram(buf, &second);\r
7809                 }\r
7810             }\r
7811         }\r
7812 \r
7813         if (appData.icsActive) {\r
7814             if (appData.quietPlay &&\r
7815                 (gameMode == IcsPlayingWhite ||\r
7816                  gameMode == IcsPlayingBlack)) {\r
7817                 SendToICS(ics_prefix);\r
7818                 SendToICS("set shout 1\n");\r
7819             }\r
7820             nextGameMode = IcsIdle;\r
7821             ics_user_moved = FALSE;\r
7822             /* clean up premove.  It's ugly when the game has ended and the\r
7823              * premove highlights are still on the board.\r
7824              */\r
7825             if (gotPremove) {\r
7826               gotPremove = FALSE;\r
7827               ClearPremoveHighlights();\r
7828               DrawPosition(FALSE, boards[currentMove]);\r
7829             }\r
7830             if (whosays == GE_ICS) {\r
7831                 switch (result) {\r
7832                 case WhiteWins:\r
7833                     if (gameMode == IcsPlayingWhite)\r
7834                         PlayIcsWinSound();\r
7835                     else if(gameMode == IcsPlayingBlack)\r
7836                         PlayIcsLossSound();\r
7837                     break;\r
7838                 case BlackWins:\r
7839                     if (gameMode == IcsPlayingBlack)\r
7840                         PlayIcsWinSound();\r
7841                     else if(gameMode == IcsPlayingWhite)\r
7842                         PlayIcsLossSound();\r
7843                     break;\r
7844                 case GameIsDrawn:\r
7845                     PlayIcsDrawSound();\r
7846                     break;\r
7847                 default:\r
7848                     PlayIcsUnfinishedSound();\r
7849                 }\r
7850             }\r
7851         } else if (gameMode == EditGame ||\r
7852                    gameMode == PlayFromGameFile || \r
7853                    gameMode == AnalyzeMode || \r
7854                    gameMode == AnalyzeFile) {\r
7855             nextGameMode = gameMode;\r
7856         } else {\r
7857             nextGameMode = EndOfGame;\r
7858         }\r
7859         pausing = FALSE;\r
7860         ModeHighlight();\r
7861     } else {\r
7862         nextGameMode = gameMode;\r
7863     }\r
7864 \r
7865     if (appData.noChessProgram) {\r
7866         gameMode = nextGameMode;\r
7867         ModeHighlight();\r
7868         endingGame = 0; /* [HGM] crash */\r
7869         return;\r
7870     }\r
7871 \r
7872     if (first.reuse) {\r
7873         /* Put first chess program into idle state */\r
7874         if (first.pr != NoProc &&\r
7875             (gameMode == MachinePlaysWhite ||\r
7876              gameMode == MachinePlaysBlack ||\r
7877              gameMode == TwoMachinesPlay ||\r
7878              gameMode == IcsPlayingWhite ||\r
7879              gameMode == IcsPlayingBlack ||\r
7880              gameMode == BeginningOfGame)) {\r
7881             SendToProgram("force\n", &first);\r
7882             if (first.usePing) {\r
7883               char buf[MSG_SIZ];\r
7884               sprintf(buf, "ping %d\n", ++first.lastPing);\r
7885               SendToProgram(buf, &first);\r
7886             }\r
7887         }\r
7888     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7889         /* Kill off first chess program */\r
7890         if (first.isr != NULL)\r
7891           RemoveInputSource(first.isr);\r
7892         first.isr = NULL;\r
7893     \r
7894         if (first.pr != NoProc) {\r
7895             ExitAnalyzeMode();\r
7896             DoSleep( appData.delayBeforeQuit );\r
7897             SendToProgram("quit\n", &first);\r
7898             DoSleep( appData.delayAfterQuit );\r
7899             DestroyChildProcess(first.pr, first.useSigterm);\r
7900         }\r
7901         first.pr = NoProc;\r
7902     }\r
7903     if (second.reuse) {\r
7904         /* Put second chess program into idle state */\r
7905         if (second.pr != NoProc &&\r
7906             gameMode == TwoMachinesPlay) {\r
7907             SendToProgram("force\n", &second);\r
7908             if (second.usePing) {\r
7909               char buf[MSG_SIZ];\r
7910               sprintf(buf, "ping %d\n", ++second.lastPing);\r
7911               SendToProgram(buf, &second);\r
7912             }\r
7913         }\r
7914     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7915         /* Kill off second chess program */\r
7916         if (second.isr != NULL)\r
7917           RemoveInputSource(second.isr);\r
7918         second.isr = NULL;\r
7919     \r
7920         if (second.pr != NoProc) {\r
7921             DoSleep( appData.delayBeforeQuit );\r
7922             SendToProgram("quit\n", &second);\r
7923             DoSleep( appData.delayAfterQuit );\r
7924             DestroyChildProcess(second.pr, second.useSigterm);\r
7925         }\r
7926         second.pr = NoProc;\r
7927     }\r
7928 \r
7929     if (matchMode && gameMode == TwoMachinesPlay) {\r
7930         switch (result) {\r
7931         case WhiteWins:\r
7932           if (first.twoMachinesColor[0] == 'w') {\r
7933             first.matchWins++;\r
7934           } else {\r
7935             second.matchWins++;\r
7936           }\r
7937           break;\r
7938         case BlackWins:\r
7939           if (first.twoMachinesColor[0] == 'b') {\r
7940             first.matchWins++;\r
7941           } else {\r
7942             second.matchWins++;\r
7943           }\r
7944           break;\r
7945         default:\r
7946           break;\r
7947         }\r
7948         if (matchGame < appData.matchGames) {\r
7949             char *tmp;\r
7950             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */\r
7951                 tmp = first.twoMachinesColor;\r
7952                 first.twoMachinesColor = second.twoMachinesColor;\r
7953                 second.twoMachinesColor = tmp;\r
7954             }\r
7955             gameMode = nextGameMode;\r
7956             matchGame++;\r
7957             if(appData.matchPause>10000 || appData.matchPause<10)\r
7958                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
7959             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
7960             endingGame = 0; /* [HGM] crash */\r
7961             return;\r
7962         } else {\r
7963             char buf[MSG_SIZ];\r
7964             gameMode = nextGameMode;\r
7965             sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",\r
7966                     first.tidy, second.tidy,\r
7967                     first.matchWins, second.matchWins,\r
7968                     appData.matchGames - (first.matchWins + second.matchWins));\r
7969             DisplayFatalError(buf, 0, 0);\r
7970         }\r
7971     }\r
7972     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
7973         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
7974       ExitAnalyzeMode();\r
7975     gameMode = nextGameMode;\r
7976     ModeHighlight();\r
7977     endingGame = 0;  /* [HGM] crash */\r
7978 }\r
7979 \r
7980 /* Assumes program was just initialized (initString sent).\r
7981    Leaves program in force mode. */\r
7982 void\r
7983 FeedMovesToProgram(cps, upto) \r
7984      ChessProgramState *cps;\r
7985      int upto;\r
7986 {\r
7987     int i;\r
7988     \r
7989     if (appData.debugMode)\r
7990       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
7991               startedFromSetupPosition ? "position and " : "",\r
7992               backwardMostMove, upto, cps->which);\r
7993     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
7994         // [HGM] variantswitch: make engine aware of new variant\r
7995         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
7996                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
7997         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
7998         SendToProgram(buf, cps);\r
7999         currentlyInitializedVariant = gameInfo.variant;\r
8000     }\r
8001     SendToProgram("force\n", cps);\r
8002     if (startedFromSetupPosition) {\r
8003         SendBoard(cps, backwardMostMove);\r
8004     if (appData.debugMode) {\r
8005         fprintf(debugFP, "feedMoves\n");\r
8006     }\r
8007     }\r
8008     for (i = backwardMostMove; i < upto; i++) {\r
8009         SendMoveToProgram(i, cps);\r
8010     }\r
8011 }\r
8012 \r
8013 \r
8014 void\r
8015 ResurrectChessProgram()\r
8016 {\r
8017      /* The chess program may have exited.\r
8018         If so, restart it and feed it all the moves made so far. */\r
8019 \r
8020     if (appData.noChessProgram || first.pr != NoProc) return;\r
8021     \r
8022     StartChessProgram(&first);\r
8023     InitChessProgram(&first, FALSE);\r
8024     FeedMovesToProgram(&first, currentMove);\r
8025 \r
8026     if (!first.sendTime) {\r
8027         /* can't tell gnuchess what its clock should read,\r
8028            so we bow to its notion. */\r
8029         ResetClocks();\r
8030         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
8031         timeRemaining[1][currentMove] = blackTimeRemaining;\r
8032     }\r
8033 \r
8034     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
8035         first.analysisSupport) {\r
8036       SendToProgram("analyze\n", &first);\r
8037       first.analyzing = TRUE;\r
8038     }\r
8039 }\r
8040 \r
8041 /*\r
8042  * Button procedures\r
8043  */\r
8044 void\r
8045 Reset(redraw, init)\r
8046      int redraw, init;\r
8047 {\r
8048     int i;\r
8049 \r
8050     if (appData.debugMode) {\r
8051         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
8052                 redraw, init, gameMode);\r
8053     }\r
8054     pausing = pauseExamInvalid = FALSE;\r
8055     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
8056     firstMove = TRUE;\r
8057     whiteFlag = blackFlag = FALSE;\r
8058     userOfferedDraw = FALSE;\r
8059     hintRequested = bookRequested = FALSE;\r
8060     first.maybeThinking = FALSE;\r
8061     second.maybeThinking = FALSE;\r
8062     first.bookSuspend = FALSE; // [HGM] book\r
8063     second.bookSuspend = FALSE;\r
8064     thinkOutput[0] = NULLCHAR;\r
8065     lastHint[0] = NULLCHAR;\r
8066     ClearGameInfo(&gameInfo);\r
8067     gameInfo.variant = StringToVariant(appData.variant);\r
8068     ics_user_moved = ics_clock_paused = FALSE;\r
8069     ics_getting_history = H_FALSE;\r
8070     ics_gamenum = -1;\r
8071     white_holding[0] = black_holding[0] = NULLCHAR;\r
8072     ClearProgramStats();\r
8073     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode\r
8074     \r
8075     ResetFrontEnd();\r
8076     ClearHighlights();\r
8077     flipView = appData.flipView;\r
8078     ClearPremoveHighlights();\r
8079     gotPremove = FALSE;\r
8080     alarmSounded = FALSE;\r
8081 \r
8082     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8083     if(appData.serverMovesName != NULL) {\r
8084         /* [HGM] prepare to make moves file for broadcasting */\r
8085         clock_t t = clock();\r
8086         if(serverMoves != NULL) fclose(serverMoves);\r
8087         serverMoves = fopen(appData.serverMovesName, "r");\r
8088         if(serverMoves != NULL) {\r
8089             fclose(serverMoves);\r
8090             /* delay 15 sec before overwriting, so all clients can see end */\r
8091             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
8092         }\r
8093         serverMoves = fopen(appData.serverMovesName, "w");\r
8094     }\r
8095 \r
8096     ExitAnalyzeMode();\r
8097     gameMode = BeginningOfGame;\r
8098     ModeHighlight();\r
8099     if(appData.icsActive) gameInfo.variant = VariantNormal;\r
8100     InitPosition(redraw);\r
8101     for (i = 0; i < MAX_MOVES; i++) {\r
8102         if (commentList[i] != NULL) {\r
8103             free(commentList[i]);\r
8104             commentList[i] = NULL;\r
8105         }\r
8106     }\r
8107     ResetClocks();\r
8108     timeRemaining[0][0] = whiteTimeRemaining;\r
8109     timeRemaining[1][0] = blackTimeRemaining;\r
8110     if (first.pr == NULL) {\r
8111         StartChessProgram(&first);\r
8112     }\r
8113     if (init) {\r
8114             InitChessProgram(&first, startedFromSetupPosition);\r
8115     }\r
8116     DisplayTitle("");\r
8117     DisplayMessage("", "");\r
8118     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8119 }\r
8120 \r
8121 void\r
8122 AutoPlayGameLoop()\r
8123 {\r
8124     for (;;) {\r
8125         if (!AutoPlayOneMove())\r
8126           return;\r
8127         if (matchMode || appData.timeDelay == 0)\r
8128           continue;\r
8129         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
8130           return;\r
8131         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
8132         break;\r
8133     }\r
8134 }\r
8135 \r
8136 \r
8137 int\r
8138 AutoPlayOneMove()\r
8139 {\r
8140     int fromX, fromY, toX, toY;\r
8141 \r
8142     if (appData.debugMode) {\r
8143       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
8144     }\r
8145 \r
8146     if (gameMode != PlayFromGameFile)\r
8147       return FALSE;\r
8148 \r
8149     if (currentMove >= forwardMostMove) {\r
8150       gameMode = EditGame;\r
8151       ModeHighlight();\r
8152 \r
8153       /* [AS] Clear current move marker at the end of a game */\r
8154       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
8155 \r
8156       return FALSE;\r
8157     }\r
8158     \r
8159     toX = moveList[currentMove][2] - AAA;\r
8160     toY = moveList[currentMove][3] - ONE;\r
8161 \r
8162     if (moveList[currentMove][1] == '@') {\r
8163         if (appData.highlightLastMove) {\r
8164             SetHighlights(-1, -1, toX, toY);\r
8165         }\r
8166     } else {\r
8167         fromX = moveList[currentMove][0] - AAA;\r
8168         fromY = moveList[currentMove][1] - ONE;\r
8169 \r
8170         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
8171 \r
8172         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
8173 \r
8174         if (appData.highlightLastMove) {\r
8175             SetHighlights(fromX, fromY, toX, toY);\r
8176         }\r
8177     }\r
8178     DisplayMove(currentMove);\r
8179     SendMoveToProgram(currentMove++, &first);\r
8180     DisplayBothClocks();\r
8181     DrawPosition(FALSE, boards[currentMove]);\r
8182     // [HGM] PV info: always display, routine tests if empty\r
8183     DisplayComment(currentMove - 1, commentList[currentMove]);\r
8184     return TRUE;\r
8185 }\r
8186 \r
8187 \r
8188 int\r
8189 LoadGameOneMove(readAhead)\r
8190      ChessMove readAhead;\r
8191 {\r
8192     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
8193     char promoChar = NULLCHAR;\r
8194     ChessMove moveType;\r
8195     char move[MSG_SIZ];\r
8196     char *p, *q;\r
8197     \r
8198     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
8199         gameMode != AnalyzeMode && gameMode != Training) {\r
8200         gameFileFP = NULL;\r
8201         return FALSE;\r
8202     }\r
8203     \r
8204     yyboardindex = forwardMostMove;\r
8205     if (readAhead != (ChessMove)0) {\r
8206       moveType = readAhead;\r
8207     } else {\r
8208       if (gameFileFP == NULL)\r
8209           return FALSE;\r
8210       moveType = (ChessMove) yylex();\r
8211     }\r
8212     \r
8213     done = FALSE;\r
8214     switch (moveType) {\r
8215       case Comment:\r
8216         if (appData.debugMode) \r
8217           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8218         p = yy_text;\r
8219         if (*p == '{' || *p == '[' || *p == '(') {\r
8220             p[strlen(p) - 1] = NULLCHAR;\r
8221             p++;\r
8222         }\r
8223 \r
8224         /* append the comment but don't display it */\r
8225         while (*p == '\n') p++;\r
8226         AppendComment(currentMove, p);\r
8227         return TRUE;\r
8228 \r
8229       case WhiteCapturesEnPassant:\r
8230       case BlackCapturesEnPassant:\r
8231       case WhitePromotionChancellor:\r
8232       case BlackPromotionChancellor:\r
8233       case WhitePromotionArchbishop:\r
8234       case BlackPromotionArchbishop:\r
8235       case WhitePromotionCentaur:\r
8236       case BlackPromotionCentaur:\r
8237       case WhitePromotionQueen:\r
8238       case BlackPromotionQueen:\r
8239       case WhitePromotionRook:\r
8240       case BlackPromotionRook:\r
8241       case WhitePromotionBishop:\r
8242       case BlackPromotionBishop:\r
8243       case WhitePromotionKnight:\r
8244       case BlackPromotionKnight:\r
8245       case WhitePromotionKing:\r
8246       case BlackPromotionKing:\r
8247       case NormalMove:\r
8248       case WhiteKingSideCastle:\r
8249       case WhiteQueenSideCastle:\r
8250       case BlackKingSideCastle:\r
8251       case BlackQueenSideCastle:\r
8252       case WhiteKingSideCastleWild:\r
8253       case WhiteQueenSideCastleWild:\r
8254       case BlackKingSideCastleWild:\r
8255       case BlackQueenSideCastleWild:\r
8256       /* PUSH Fabien */\r
8257       case WhiteHSideCastleFR:\r
8258       case WhiteASideCastleFR:\r
8259       case BlackHSideCastleFR:\r
8260       case BlackASideCastleFR:\r
8261       /* POP Fabien */\r
8262         if (appData.debugMode)\r
8263           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8264         fromX = currentMoveString[0] - AAA;\r
8265         fromY = currentMoveString[1] - ONE;\r
8266         toX = currentMoveString[2] - AAA;\r
8267         toY = currentMoveString[3] - ONE;\r
8268         promoChar = currentMoveString[4];\r
8269         break;\r
8270 \r
8271       case WhiteDrop:\r
8272       case BlackDrop:\r
8273         if (appData.debugMode)\r
8274           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8275         fromX = moveType == WhiteDrop ?\r
8276           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
8277         (int) CharToPiece(ToLower(currentMoveString[0]));\r
8278         fromY = DROP_RANK;\r
8279         toX = currentMoveString[2] - AAA;\r
8280         toY = currentMoveString[3] - ONE;\r
8281         break;\r
8282 \r
8283       case WhiteWins:\r
8284       case BlackWins:\r
8285       case GameIsDrawn:\r
8286       case GameUnfinished:\r
8287         if (appData.debugMode)\r
8288           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
8289         p = strchr(yy_text, '{');\r
8290         if (p == NULL) p = strchr(yy_text, '(');\r
8291         if (p == NULL) {\r
8292             p = yy_text;\r
8293             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
8294         } else {\r
8295             q = strchr(p, *p == '{' ? '}' : ')');\r
8296             if (q != NULL) *q = NULLCHAR;\r
8297             p++;\r
8298         }\r
8299         GameEnds(moveType, p, GE_FILE);\r
8300         done = TRUE;\r
8301         if (cmailMsgLoaded) {\r
8302             ClearHighlights();\r
8303             flipView = WhiteOnMove(currentMove);\r
8304             if (moveType == GameUnfinished) flipView = !flipView;\r
8305             if (appData.debugMode)\r
8306               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
8307         }\r
8308         break;\r
8309 \r
8310       case (ChessMove) 0:       /* end of file */\r
8311         if (appData.debugMode)\r
8312           fprintf(debugFP, "Parser hit end of file\n");\r
8313         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8314                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8315           case MT_NONE:\r
8316           case MT_CHECK:\r
8317             break;\r
8318           case MT_CHECKMATE:\r
8319             if (WhiteOnMove(currentMove)) {\r
8320                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8321             } else {\r
8322                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8323             }\r
8324             break;\r
8325           case MT_STALEMATE:\r
8326             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8327             break;\r
8328         }\r
8329         done = TRUE;\r
8330         break;\r
8331 \r
8332       case MoveNumberOne:\r
8333         if (lastLoadGameStart == GNUChessGame) {\r
8334             /* GNUChessGames have numbers, but they aren't move numbers */\r
8335             if (appData.debugMode)\r
8336               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8337                       yy_text, (int) moveType);\r
8338             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8339         }\r
8340         /* else fall thru */\r
8341 \r
8342       case XBoardGame:\r
8343       case GNUChessGame:\r
8344       case PGNTag:\r
8345         /* Reached start of next game in file */\r
8346         if (appData.debugMode)\r
8347           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
8348         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8349                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8350           case MT_NONE:\r
8351           case MT_CHECK:\r
8352             break;\r
8353           case MT_CHECKMATE:\r
8354             if (WhiteOnMove(currentMove)) {\r
8355                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8356             } else {\r
8357                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8358             }\r
8359             break;\r
8360           case MT_STALEMATE:\r
8361             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8362             break;\r
8363         }\r
8364         done = TRUE;\r
8365         break;\r
8366 \r
8367       case PositionDiagram:     /* should not happen; ignore */\r
8368       case ElapsedTime:         /* ignore */\r
8369       case NAG:                 /* ignore */\r
8370         if (appData.debugMode)\r
8371           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8372                   yy_text, (int) moveType);\r
8373         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8374 \r
8375       case IllegalMove:\r
8376         if (appData.testLegality) {\r
8377             if (appData.debugMode)\r
8378               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
8379             sprintf(move, "Illegal move: %d.%s%s",\r
8380                     (forwardMostMove / 2) + 1,\r
8381                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8382             DisplayError(move, 0);\r
8383             done = TRUE;\r
8384         } else {\r
8385             if (appData.debugMode)\r
8386               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
8387                       yy_text, currentMoveString);\r
8388             fromX = currentMoveString[0] - AAA;\r
8389             fromY = currentMoveString[1] - ONE;\r
8390             toX = currentMoveString[2] - AAA;\r
8391             toY = currentMoveString[3] - ONE;\r
8392             promoChar = currentMoveString[4];\r
8393         }\r
8394         break;\r
8395 \r
8396       case AmbiguousMove:\r
8397         if (appData.debugMode)\r
8398           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
8399         sprintf(move, "Ambiguous move: %d.%s%s",\r
8400                 (forwardMostMove / 2) + 1,\r
8401                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8402         DisplayError(move, 0);\r
8403         done = TRUE;\r
8404         break;\r
8405 \r
8406       default:\r
8407       case ImpossibleMove:\r
8408         if (appData.debugMode)\r
8409           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);\r
8410         sprintf(move, "Illegal move: %d.%s%s",\r
8411                 (forwardMostMove / 2) + 1,\r
8412                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8413         DisplayError(move, 0);\r
8414         done = TRUE;\r
8415         break;\r
8416     }\r
8417 \r
8418     if (done) {\r
8419         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
8420             DrawPosition(FALSE, boards[currentMove]);\r
8421             DisplayBothClocks();\r
8422             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
8423               DisplayComment(currentMove - 1, commentList[currentMove]);\r
8424         }\r
8425         (void) StopLoadGameTimer();\r
8426         gameFileFP = NULL;\r
8427         cmailOldMove = forwardMostMove;\r
8428         return FALSE;\r
8429     } else {\r
8430         /* currentMoveString is set as a side-effect of yylex */\r
8431         strcat(currentMoveString, "\n");\r
8432         strcpy(moveList[forwardMostMove], currentMoveString);\r
8433         \r
8434         thinkOutput[0] = NULLCHAR;\r
8435         MakeMove(fromX, fromY, toX, toY, promoChar);\r
8436         currentMove = forwardMostMove;\r
8437         return TRUE;\r
8438     }\r
8439 }\r
8440 \r
8441 /* Load the nth game from the given file */\r
8442 int\r
8443 LoadGameFromFile(filename, n, title, useList)\r
8444      char *filename;\r
8445      int n;\r
8446      char *title;\r
8447      /*Boolean*/ int useList;\r
8448 {\r
8449     FILE *f;\r
8450     char buf[MSG_SIZ];\r
8451 \r
8452     if (strcmp(filename, "-") == 0) {\r
8453         f = stdin;\r
8454         title = "stdin";\r
8455     } else {\r
8456         f = fopen(filename, "rb");\r
8457         if (f == NULL) {\r
8458             sprintf(buf, "Can't open \"%s\"", filename);\r
8459             DisplayError(buf, errno);\r
8460             return FALSE;\r
8461         }\r
8462     }\r
8463     if (fseek(f, 0, 0) == -1) {\r
8464         /* f is not seekable; probably a pipe */\r
8465         useList = FALSE;\r
8466     }\r
8467     if (useList && n == 0) {\r
8468         int error = GameListBuild(f);\r
8469         if (error) {\r
8470             DisplayError("Cannot build game list", error);\r
8471         } else if (!ListEmpty(&gameList) &&\r
8472                    ((ListGame *) gameList.tailPred)->number > 1) {\r
8473             GameListPopUp(f, title);\r
8474             return TRUE;\r
8475         }\r
8476         GameListDestroy();\r
8477         n = 1;\r
8478     }\r
8479     if (n == 0) n = 1;\r
8480     return LoadGame(f, n, title, FALSE);\r
8481 }\r
8482 \r
8483 \r
8484 void\r
8485 MakeRegisteredMove()\r
8486 {\r
8487     int fromX, fromY, toX, toY;\r
8488     char promoChar;\r
8489     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8490         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
8491           case CMAIL_MOVE:\r
8492           case CMAIL_DRAW:\r
8493             if (appData.debugMode)\r
8494               fprintf(debugFP, "Restoring %s for game %d\n",\r
8495                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8496     \r
8497             thinkOutput[0] = NULLCHAR;\r
8498             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
8499             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
8500             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
8501             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
8502             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
8503             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
8504             MakeMove(fromX, fromY, toX, toY, promoChar);\r
8505             ShowMove(fromX, fromY, toX, toY);\r
8506               \r
8507             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8508                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8509               case MT_NONE:\r
8510               case MT_CHECK:\r
8511                 break;\r
8512                 \r
8513               case MT_CHECKMATE:\r
8514                 if (WhiteOnMove(currentMove)) {\r
8515                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
8516                 } else {\r
8517                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
8518                 }\r
8519                 break;\r
8520                 \r
8521               case MT_STALEMATE:\r
8522                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
8523                 break;\r
8524             }\r
8525 \r
8526             break;\r
8527             \r
8528           case CMAIL_RESIGN:\r
8529             if (WhiteOnMove(currentMove)) {\r
8530                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
8531             } else {\r
8532                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
8533             }\r
8534             break;\r
8535             \r
8536           case CMAIL_ACCEPT:\r
8537             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
8538             break;\r
8539               \r
8540           default:\r
8541             break;\r
8542         }\r
8543     }\r
8544 \r
8545     return;\r
8546 }\r
8547 \r
8548 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
8549 int\r
8550 CmailLoadGame(f, gameNumber, title, useList)\r
8551      FILE *f;\r
8552      int gameNumber;\r
8553      char *title;\r
8554      int useList;\r
8555 {\r
8556     int retVal;\r
8557 \r
8558     if (gameNumber > nCmailGames) {\r
8559         DisplayError("No more games in this message", 0);\r
8560         return FALSE;\r
8561     }\r
8562     if (f == lastLoadGameFP) {\r
8563         int offset = gameNumber - lastLoadGameNumber;\r
8564         if (offset == 0) {\r
8565             cmailMsg[0] = NULLCHAR;\r
8566             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8567                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8568                 nCmailMovesRegistered--;\r
8569             }\r
8570             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
8571             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
8572                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
8573             }\r
8574         } else {\r
8575             if (! RegisterMove()) return FALSE;\r
8576         }\r
8577     }\r
8578 \r
8579     retVal = LoadGame(f, gameNumber, title, useList);\r
8580 \r
8581     /* Make move registered during previous look at this game, if any */\r
8582     MakeRegisteredMove();\r
8583 \r
8584     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
8585         commentList[currentMove]\r
8586           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
8587         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8588     }\r
8589 \r
8590     return retVal;\r
8591 }\r
8592 \r
8593 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
8594 int\r
8595 ReloadGame(offset)\r
8596      int offset;\r
8597 {\r
8598     int gameNumber = lastLoadGameNumber + offset;\r
8599     if (lastLoadGameFP == NULL) {\r
8600         DisplayError("No game has been loaded yet", 0);\r
8601         return FALSE;\r
8602     }\r
8603     if (gameNumber <= 0) {\r
8604         DisplayError("Can't back up any further", 0);\r
8605         return FALSE;\r
8606     }\r
8607     if (cmailMsgLoaded) {\r
8608         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
8609                              lastLoadGameTitle, lastLoadGameUseList);\r
8610     } else {\r
8611         return LoadGame(lastLoadGameFP, gameNumber,\r
8612                         lastLoadGameTitle, lastLoadGameUseList);\r
8613     }\r
8614 }\r
8615 \r
8616 \r
8617 \r
8618 /* Load the nth game from open file f */\r
8619 int\r
8620 LoadGame(f, gameNumber, title, useList)\r
8621      FILE *f;\r
8622      int gameNumber;\r
8623      char *title;\r
8624      int useList;\r
8625 {\r
8626     ChessMove cm;\r
8627     char buf[MSG_SIZ];\r
8628     int gn = gameNumber;\r
8629     ListGame *lg = NULL;\r
8630     int numPGNTags = 0;\r
8631     int err;\r
8632     GameMode oldGameMode;\r
8633     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
8634 \r
8635     if (appData.debugMode) \r
8636         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
8637 \r
8638     if (gameMode == Training )\r
8639         SetTrainingModeOff();\r
8640 \r
8641     oldGameMode = gameMode;\r
8642     if (gameMode != BeginningOfGame) {\r
8643       Reset(FALSE, TRUE);\r
8644     }\r
8645 \r
8646     gameFileFP = f;\r
8647     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
8648         fclose(lastLoadGameFP);\r
8649     }\r
8650 \r
8651     if (useList) {\r
8652         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
8653         \r
8654         if (lg) {\r
8655             fseek(f, lg->offset, 0);\r
8656             GameListHighlight(gameNumber);\r
8657             gn = 1;\r
8658         }\r
8659         else {\r
8660             DisplayError("Game number out of range", 0);\r
8661             return FALSE;\r
8662         }\r
8663     } else {\r
8664         GameListDestroy();\r
8665         if (fseek(f, 0, 0) == -1) {\r
8666             if (f == lastLoadGameFP ?\r
8667                 gameNumber == lastLoadGameNumber + 1 :\r
8668                 gameNumber == 1) {\r
8669                 gn = 1;\r
8670             } else {\r
8671                 DisplayError("Can't seek on game file", 0);\r
8672                 return FALSE;\r
8673             }\r
8674         }\r
8675     }\r
8676     lastLoadGameFP = f;\r
8677     lastLoadGameNumber = gameNumber;\r
8678     strcpy(lastLoadGameTitle, title);\r
8679     lastLoadGameUseList = useList;\r
8680 \r
8681     yynewfile(f);\r
8682 \r
8683     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
8684         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
8685                 lg->gameInfo.black);\r
8686             DisplayTitle(buf);\r
8687     } else if (*title != NULLCHAR) {\r
8688         if (gameNumber > 1) {\r
8689             sprintf(buf, "%s %d", title, gameNumber);\r
8690             DisplayTitle(buf);\r
8691         } else {\r
8692             DisplayTitle(title);\r
8693         }\r
8694     }\r
8695 \r
8696     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
8697         gameMode = PlayFromGameFile;\r
8698         ModeHighlight();\r
8699     }\r
8700 \r
8701     currentMove = forwardMostMove = backwardMostMove = 0;\r
8702     CopyBoard(boards[0], initialPosition);\r
8703     StopClocks();\r
8704 \r
8705     /*\r
8706      * Skip the first gn-1 games in the file.\r
8707      * Also skip over anything that precedes an identifiable \r
8708      * start of game marker, to avoid being confused by \r
8709      * garbage at the start of the file.  Currently \r
8710      * recognized start of game markers are the move number "1",\r
8711      * the pattern "gnuchess .* game", the pattern\r
8712      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
8713      * A game that starts with one of the latter two patterns\r
8714      * will also have a move number 1, possibly\r
8715      * following a position diagram.\r
8716      * 5-4-02: Let's try being more lenient and allowing a game to\r
8717      * start with an unnumbered move.  Does that break anything?\r
8718      */\r
8719     cm = lastLoadGameStart = (ChessMove) 0;\r
8720     while (gn > 0) {\r
8721         yyboardindex = forwardMostMove;\r
8722         cm = (ChessMove) yylex();\r
8723         switch (cm) {\r
8724           case (ChessMove) 0:\r
8725             if (cmailMsgLoaded) {\r
8726                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
8727             } else {\r
8728                 Reset(TRUE, TRUE);\r
8729                 DisplayError("Game not found in file", 0);\r
8730             }\r
8731             return FALSE;\r
8732 \r
8733           case GNUChessGame:\r
8734           case XBoardGame:\r
8735             gn--;\r
8736             lastLoadGameStart = cm;\r
8737             break;\r
8738             \r
8739           case MoveNumberOne:\r
8740             switch (lastLoadGameStart) {\r
8741               case GNUChessGame:\r
8742               case XBoardGame:\r
8743               case PGNTag:\r
8744                 break;\r
8745               case MoveNumberOne:\r
8746               case (ChessMove) 0:\r
8747                 gn--;           /* count this game */\r
8748                 lastLoadGameStart = cm;\r
8749                 break;\r
8750               default:\r
8751                 /* impossible */\r
8752                 break;\r
8753             }\r
8754             break;\r
8755 \r
8756           case PGNTag:\r
8757             switch (lastLoadGameStart) {\r
8758               case GNUChessGame:\r
8759               case PGNTag:\r
8760               case MoveNumberOne:\r
8761               case (ChessMove) 0:\r
8762                 gn--;           /* count this game */\r
8763                 lastLoadGameStart = cm;\r
8764                 break;\r
8765               case XBoardGame:\r
8766                 lastLoadGameStart = cm; /* game counted already */\r
8767                 break;\r
8768               default:\r
8769                 /* impossible */\r
8770                 break;\r
8771             }\r
8772             if (gn > 0) {\r
8773                 do {\r
8774                     yyboardindex = forwardMostMove;\r
8775                     cm = (ChessMove) yylex();\r
8776                 } while (cm == PGNTag || cm == Comment);\r
8777             }\r
8778             break;\r
8779 \r
8780           case WhiteWins:\r
8781           case BlackWins:\r
8782           case GameIsDrawn:\r
8783             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8784                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8785                     != CMAIL_OLD_RESULT) {\r
8786                     nCmailResults ++ ;\r
8787                     cmailResult[  CMAIL_MAX_GAMES\r
8788                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8789                 }\r
8790             }\r
8791             break;\r
8792 \r
8793           case NormalMove:\r
8794             /* Only a NormalMove can be at the start of a game\r
8795              * without a position diagram. */\r
8796             if (lastLoadGameStart == (ChessMove) 0) {\r
8797               gn--;\r
8798               lastLoadGameStart = MoveNumberOne;\r
8799             }\r
8800             break;\r
8801 \r
8802           default:\r
8803             break;\r
8804         }\r
8805     }\r
8806     \r
8807     if (appData.debugMode)\r
8808       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8809 \r
8810     if (cm == XBoardGame) {\r
8811         /* Skip any header junk before position diagram and/or move 1 */\r
8812         for (;;) {\r
8813             yyboardindex = forwardMostMove;\r
8814             cm = (ChessMove) yylex();\r
8815 \r
8816             if (cm == (ChessMove) 0 ||\r
8817                 cm == GNUChessGame || cm == XBoardGame) {\r
8818                 /* Empty game; pretend end-of-file and handle later */\r
8819                 cm = (ChessMove) 0;\r
8820                 break;\r
8821             }\r
8822 \r
8823             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8824                 cm == PGNTag || cm == Comment)\r
8825               break;\r
8826         }\r
8827     } else if (cm == GNUChessGame) {\r
8828         if (gameInfo.event != NULL) {\r
8829             free(gameInfo.event);\r
8830         }\r
8831         gameInfo.event = StrSave(yy_text);\r
8832     }   \r
8833 \r
8834     startedFromSetupPosition = FALSE;\r
8835     while (cm == PGNTag) {\r
8836         if (appData.debugMode) \r
8837           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8838         err = ParsePGNTag(yy_text, &gameInfo);\r
8839         if (!err) numPGNTags++;\r
8840 \r
8841         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8842         if(gameInfo.variant != oldVariant) {\r
8843             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8844             InitPosition(TRUE);\r
8845             oldVariant = gameInfo.variant;\r
8846             if (appData.debugMode) \r
8847               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8848         }\r
8849 \r
8850 \r
8851         if (gameInfo.fen != NULL) {\r
8852           Board initial_position;\r
8853           startedFromSetupPosition = TRUE;\r
8854           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8855             Reset(TRUE, TRUE);\r
8856             DisplayError("Bad FEN position in file", 0);\r
8857             return FALSE;\r
8858           }\r
8859           CopyBoard(boards[0], initial_position);\r
8860           if (blackPlaysFirst) {\r
8861             currentMove = forwardMostMove = backwardMostMove = 1;\r
8862             CopyBoard(boards[1], initial_position);\r
8863             strcpy(moveList[0], "");\r
8864             strcpy(parseList[0], "");\r
8865             timeRemaining[0][1] = whiteTimeRemaining;\r
8866             timeRemaining[1][1] = blackTimeRemaining;\r
8867             if (commentList[0] != NULL) {\r
8868               commentList[1] = commentList[0];\r
8869               commentList[0] = NULL;\r
8870             }\r
8871           } else {\r
8872             currentMove = forwardMostMove = backwardMostMove = 0;\r
8873           }\r
8874           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */\r
8875           {   int i;\r
8876               initialRulePlies = FENrulePlies;\r
8877               epStatus[forwardMostMove] = FENepStatus;\r
8878               for( i=0; i< nrCastlingRights; i++ )\r
8879                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
8880           }\r
8881           yyboardindex = forwardMostMove;\r
8882           free(gameInfo.fen);\r
8883           gameInfo.fen = NULL;\r
8884         }\r
8885 \r
8886         yyboardindex = forwardMostMove;\r
8887         cm = (ChessMove) yylex();\r
8888 \r
8889         /* Handle comments interspersed among the tags */\r
8890         while (cm == Comment) {\r
8891             char *p;\r
8892             if (appData.debugMode) \r
8893               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8894             p = yy_text;\r
8895             if (*p == '{' || *p == '[' || *p == '(') {\r
8896                 p[strlen(p) - 1] = NULLCHAR;\r
8897                 p++;\r
8898             }\r
8899             while (*p == '\n') p++;\r
8900             AppendComment(currentMove, p);\r
8901             yyboardindex = forwardMostMove;\r
8902             cm = (ChessMove) yylex();\r
8903         }\r
8904     }\r
8905 \r
8906     /* don't rely on existence of Event tag since if game was\r
8907      * pasted from clipboard the Event tag may not exist\r
8908      */\r
8909     if (numPGNTags > 0){\r
8910         char *tags;\r
8911         if (gameInfo.variant == VariantNormal) {\r
8912           gameInfo.variant = StringToVariant(gameInfo.event);\r
8913         }\r
8914         if (!matchMode) {\r
8915           if( appData.autoDisplayTags ) {\r
8916             tags = PGNTags(&gameInfo);\r
8917             TagsPopUp(tags, CmailMsg());\r
8918             free(tags);\r
8919           }\r
8920         }\r
8921     } else {\r
8922         /* Make something up, but don't display it now */\r
8923         SetGameInfo();\r
8924         TagsPopDown();\r
8925     }\r
8926 \r
8927     if (cm == PositionDiagram) {\r
8928         int i, j;\r
8929         char *p;\r
8930         Board initial_position;\r
8931 \r
8932         if (appData.debugMode)\r
8933           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
8934 \r
8935         if (!startedFromSetupPosition) {\r
8936             p = yy_text;\r
8937             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
8938               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
8939                 switch (*p) {\r
8940                   case '[':\r
8941                   case '-':\r
8942                   case ' ':\r
8943                   case '\t':\r
8944                   case '\n':\r
8945                   case '\r':\r
8946                     break;\r
8947                   default:\r
8948                     initial_position[i][j++] = CharToPiece(*p);\r
8949                     break;\r
8950                 }\r
8951             while (*p == ' ' || *p == '\t' ||\r
8952                    *p == '\n' || *p == '\r') p++;\r
8953         \r
8954             if (strncmp(p, "black", strlen("black"))==0)\r
8955               blackPlaysFirst = TRUE;\r
8956             else\r
8957               blackPlaysFirst = FALSE;\r
8958             startedFromSetupPosition = TRUE;\r
8959         \r
8960             CopyBoard(boards[0], initial_position);\r
8961             if (blackPlaysFirst) {\r
8962                 currentMove = forwardMostMove = backwardMostMove = 1;\r
8963                 CopyBoard(boards[1], initial_position);\r
8964                 strcpy(moveList[0], "");\r
8965                 strcpy(parseList[0], "");\r
8966                 timeRemaining[0][1] = whiteTimeRemaining;\r
8967                 timeRemaining[1][1] = blackTimeRemaining;\r
8968                 if (commentList[0] != NULL) {\r
8969                     commentList[1] = commentList[0];\r
8970                     commentList[0] = NULL;\r
8971                 }\r
8972             } else {\r
8973                 currentMove = forwardMostMove = backwardMostMove = 0;\r
8974             }\r
8975         }\r
8976         yyboardindex = forwardMostMove;\r
8977         cm = (ChessMove) yylex();\r
8978     }\r
8979 \r
8980     if (first.pr == NoProc) {\r
8981         StartChessProgram(&first);\r
8982     }\r
8983     InitChessProgram(&first, FALSE);\r
8984     SendToProgram("force\n", &first);\r
8985     if (startedFromSetupPosition) {\r
8986         SendBoard(&first, forwardMostMove);\r
8987     if (appData.debugMode) {\r
8988         fprintf(debugFP, "Load Game\n");\r
8989     }\r
8990         DisplayBothClocks();\r
8991     }      \r
8992 \r
8993     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
8994     loadFlag = appData.suppressLoadMoves;\r
8995 \r
8996     while (cm == Comment) {\r
8997         char *p;\r
8998         if (appData.debugMode) \r
8999           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
9000         p = yy_text;\r
9001         if (*p == '{' || *p == '[' || *p == '(') {\r
9002             p[strlen(p) - 1] = NULLCHAR;\r
9003             p++;\r
9004         }\r
9005         while (*p == '\n') p++;\r
9006         AppendComment(currentMove, p);\r
9007         yyboardindex = forwardMostMove;\r
9008         cm = (ChessMove) yylex();\r
9009     }\r
9010 \r
9011     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
9012         cm == WhiteWins || cm == BlackWins ||\r
9013         cm == GameIsDrawn || cm == GameUnfinished) {\r
9014         DisplayMessage("", "No moves in game");\r
9015         if (cmailMsgLoaded) {\r
9016             if (appData.debugMode)\r
9017               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
9018             ClearHighlights();\r
9019             flipView = FALSE;\r
9020         }\r
9021         DrawPosition(FALSE, boards[currentMove]);\r
9022         DisplayBothClocks();\r
9023         gameMode = EditGame;\r
9024         ModeHighlight();\r
9025         gameFileFP = NULL;\r
9026         cmailOldMove = 0;\r
9027         return TRUE;\r
9028     }\r
9029 \r
9030     // [HGM] PV info: routine tests if comment empty\r
9031     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
9032         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9033     }\r
9034     if (!matchMode && appData.timeDelay != 0) \r
9035       DrawPosition(FALSE, boards[currentMove]);\r
9036 \r
9037     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
9038       programStats.ok_to_send = 1;\r
9039     }\r
9040 \r
9041     /* if the first token after the PGN tags is a move\r
9042      * and not move number 1, retrieve it from the parser \r
9043      */\r
9044     if (cm != MoveNumberOne)\r
9045         LoadGameOneMove(cm);\r
9046 \r
9047     /* load the remaining moves from the file */\r
9048     while (LoadGameOneMove((ChessMove)0)) {\r
9049       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9050       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9051     }\r
9052 \r
9053     /* rewind to the start of the game */\r
9054     currentMove = backwardMostMove;\r
9055 \r
9056     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9057 \r
9058     if (oldGameMode == AnalyzeFile ||\r
9059         oldGameMode == AnalyzeMode) {\r
9060       AnalyzeFileEvent();\r
9061     }\r
9062 \r
9063     if (matchMode || appData.timeDelay == 0) {\r
9064       ToEndEvent();\r
9065       gameMode = EditGame;\r
9066       ModeHighlight();\r
9067     } else if (appData.timeDelay > 0) {\r
9068       AutoPlayGameLoop();\r
9069     }\r
9070 \r
9071     if (appData.debugMode) \r
9072         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
9073 \r
9074     loadFlag = 0; /* [HGM] true game starts */\r
9075     return TRUE;\r
9076 }\r
9077 \r
9078 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
9079 int\r
9080 ReloadPosition(offset)\r
9081      int offset;\r
9082 {\r
9083     int positionNumber = lastLoadPositionNumber + offset;\r
9084     if (lastLoadPositionFP == NULL) {\r
9085         DisplayError("No position has been loaded yet", 0);\r
9086         return FALSE;\r
9087     }\r
9088     if (positionNumber <= 0) {\r
9089         DisplayError("Can't back up any further", 0);\r
9090         return FALSE;\r
9091     }\r
9092     return LoadPosition(lastLoadPositionFP, positionNumber,\r
9093                         lastLoadPositionTitle);\r
9094 }\r
9095 \r
9096 /* Load the nth position from the given file */\r
9097 int\r
9098 LoadPositionFromFile(filename, n, title)\r
9099      char *filename;\r
9100      int n;\r
9101      char *title;\r
9102 {\r
9103     FILE *f;\r
9104     char buf[MSG_SIZ];\r
9105 \r
9106     if (strcmp(filename, "-") == 0) {\r
9107         return LoadPosition(stdin, n, "stdin");\r
9108     } else {\r
9109         f = fopen(filename, "rb");\r
9110         if (f == NULL) {\r
9111             sprintf(buf, "Can't open \"%s\"", filename);\r
9112             DisplayError(buf, errno);\r
9113             return FALSE;\r
9114         } else {\r
9115             return LoadPosition(f, n, title);\r
9116         }\r
9117     }\r
9118 }\r
9119 \r
9120 /* Load the nth position from the given open file, and close it */\r
9121 int\r
9122 LoadPosition(f, positionNumber, title)\r
9123      FILE *f;\r
9124      int positionNumber;\r
9125      char *title;\r
9126 {\r
9127     char *p, line[MSG_SIZ];\r
9128     Board initial_position;\r
9129     int i, j, fenMode, pn;\r
9130     \r
9131     if (gameMode == Training )\r
9132         SetTrainingModeOff();\r
9133 \r
9134     if (gameMode != BeginningOfGame) {\r
9135         Reset(FALSE, TRUE);\r
9136     }\r
9137     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
9138         fclose(lastLoadPositionFP);\r
9139     }\r
9140     if (positionNumber == 0) positionNumber = 1;\r
9141     lastLoadPositionFP = f;\r
9142     lastLoadPositionNumber = positionNumber;\r
9143     strcpy(lastLoadPositionTitle, title);\r
9144     if (first.pr == NoProc) {\r
9145       StartChessProgram(&first);\r
9146       InitChessProgram(&first, FALSE);\r
9147     }    \r
9148     pn = positionNumber;\r
9149     if (positionNumber < 0) {\r
9150         /* Negative position number means to seek to that byte offset */\r
9151         if (fseek(f, -positionNumber, 0) == -1) {\r
9152             DisplayError("Can't seek on position file", 0);\r
9153             return FALSE;\r
9154         };\r
9155         pn = 1;\r
9156     } else {\r
9157         if (fseek(f, 0, 0) == -1) {\r
9158             if (f == lastLoadPositionFP ?\r
9159                 positionNumber == lastLoadPositionNumber + 1 :\r
9160                 positionNumber == 1) {\r
9161                 pn = 1;\r
9162             } else {\r
9163                 DisplayError("Can't seek on position file", 0);\r
9164                 return FALSE;\r
9165             }\r
9166         }\r
9167     }\r
9168     /* See if this file is FEN or old-style xboard */\r
9169     if (fgets(line, MSG_SIZ, f) == NULL) {\r
9170         DisplayError("Position not found in file", 0);\r
9171         return FALSE;\r
9172     }\r
9173 #if 0\r
9174     switch (line[0]) {\r
9175       case '#':  case 'x':\r
9176       default:\r
9177         fenMode = FALSE;\r
9178         break;\r
9179       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
9180       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
9181       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
9182       case '7':  case '8':  case '9':\r
9183       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
9184       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
9185       case 'C':  case 'W':             case 'c':  case 'w': \r
9186         fenMode = TRUE;\r
9187         break;\r
9188     }\r
9189 #else\r
9190     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
9191     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
9192 #endif\r
9193 \r
9194     if (pn >= 2) {\r
9195         if (fenMode || line[0] == '#') pn--;\r
9196         while (pn > 0) {\r
9197             /* skip positions before number pn */\r
9198             if (fgets(line, MSG_SIZ, f) == NULL) {\r
9199                 Reset(TRUE, TRUE);\r
9200                 DisplayError("Position not found in file", 0);\r
9201                 return FALSE;\r
9202             }\r
9203             if (fenMode || line[0] == '#') pn--;\r
9204         }\r
9205     }\r
9206 \r
9207     if (fenMode) {\r
9208         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
9209             DisplayError("Bad FEN position in file", 0);\r
9210             return FALSE;\r
9211         }\r
9212     } else {\r
9213         (void) fgets(line, MSG_SIZ, f);\r
9214         (void) fgets(line, MSG_SIZ, f);\r
9215     \r
9216         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
9217             (void) fgets(line, MSG_SIZ, f);\r
9218             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
9219                 if (*p == ' ')\r
9220                   continue;\r
9221                 initial_position[i][j++] = CharToPiece(*p);\r
9222             }\r
9223         }\r
9224     \r
9225         blackPlaysFirst = FALSE;\r
9226         if (!feof(f)) {\r
9227             (void) fgets(line, MSG_SIZ, f);\r
9228             if (strncmp(line, "black", strlen("black"))==0)\r
9229               blackPlaysFirst = TRUE;\r
9230         }\r
9231     }\r
9232     startedFromSetupPosition = TRUE;\r
9233     \r
9234     SendToProgram("force\n", &first);\r
9235     CopyBoard(boards[0], initial_position);\r
9236     if (blackPlaysFirst) {\r
9237         currentMove = forwardMostMove = backwardMostMove = 1;\r
9238         strcpy(moveList[0], "");\r
9239         strcpy(parseList[0], "");\r
9240         CopyBoard(boards[1], initial_position);\r
9241         DisplayMessage("", "Black to play");\r
9242     } else {\r
9243         currentMove = forwardMostMove = backwardMostMove = 0;\r
9244         DisplayMessage("", "White to play");\r
9245     }\r
9246           /* [HGM] copy FEN attributes as well */\r
9247           {   int i;\r
9248               initialRulePlies = FENrulePlies;\r
9249               epStatus[forwardMostMove] = FENepStatus;\r
9250               for( i=0; i< nrCastlingRights; i++ )\r
9251                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
9252           }\r
9253     SendBoard(&first, forwardMostMove);\r
9254     if (appData.debugMode) {\r
9255 int i, j;\r
9256   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}\r
9257   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");\r
9258         fprintf(debugFP, "Load Position\n");\r
9259     }\r
9260 \r
9261     if (positionNumber > 1) {\r
9262         sprintf(line, "%s %d", title, positionNumber);\r
9263         DisplayTitle(line);\r
9264     } else {\r
9265         DisplayTitle(title);\r
9266     }\r
9267     gameMode = EditGame;\r
9268     ModeHighlight();\r
9269     ResetClocks();\r
9270     timeRemaining[0][1] = whiteTimeRemaining;\r
9271     timeRemaining[1][1] = blackTimeRemaining;\r
9272     DrawPosition(FALSE, boards[currentMove]);\r
9273    \r
9274     return TRUE;\r
9275 }\r
9276 \r
9277 \r
9278 void\r
9279 CopyPlayerNameIntoFileName(dest, src)\r
9280      char **dest, *src;\r
9281 {\r
9282     while (*src != NULLCHAR && *src != ',') {\r
9283         if (*src == ' ') {\r
9284             *(*dest)++ = '_';\r
9285             src++;\r
9286         } else {\r
9287             *(*dest)++ = *src++;\r
9288         }\r
9289     }\r
9290 }\r
9291 \r
9292 char *DefaultFileName(ext)\r
9293      char *ext;\r
9294 {\r
9295     static char def[MSG_SIZ];\r
9296     char *p;\r
9297 \r
9298     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
9299         p = def;\r
9300         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
9301         *p++ = '-';\r
9302         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
9303         *p++ = '.';\r
9304         strcpy(p, ext);\r
9305     } else {\r
9306         def[0] = NULLCHAR;\r
9307     }\r
9308     return def;\r
9309 }\r
9310 \r
9311 /* Save the current game to the given file */\r
9312 int\r
9313 SaveGameToFile(filename, append)\r
9314      char *filename;\r
9315      int append;\r
9316 {\r
9317     FILE *f;\r
9318     char buf[MSG_SIZ];\r
9319 \r
9320     if (strcmp(filename, "-") == 0) {\r
9321         return SaveGame(stdout, 0, NULL);\r
9322     } else {\r
9323         f = fopen(filename, append ? "a" : "w");\r
9324         if (f == NULL) {\r
9325             sprintf(buf, "Can't open \"%s\"", filename);\r
9326             DisplayError(buf, errno);\r
9327             return FALSE;\r
9328         } else {\r
9329             return SaveGame(f, 0, NULL);\r
9330         }\r
9331     }\r
9332 }\r
9333 \r
9334 char *\r
9335 SavePart(str)\r
9336      char *str;\r
9337 {\r
9338     static char buf[MSG_SIZ];\r
9339     char *p;\r
9340     \r
9341     p = strchr(str, ' ');\r
9342     if (p == NULL) return str;\r
9343     strncpy(buf, str, p - str);\r
9344     buf[p - str] = NULLCHAR;\r
9345     return buf;\r
9346 }\r
9347 \r
9348 #define PGN_MAX_LINE 75\r
9349 \r
9350 #define PGN_SIDE_WHITE  0\r
9351 #define PGN_SIDE_BLACK  1\r
9352 \r
9353 /* [AS] */\r
9354 static int FindFirstMoveOutOfBook( int side )\r
9355 {\r
9356     int result = -1;\r
9357 \r
9358     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
9359         int index = backwardMostMove;\r
9360         int has_book_hit = 0;\r
9361 \r
9362         if( (index % 2) != side ) {\r
9363             index++;\r
9364         }\r
9365 \r
9366         while( index < forwardMostMove ) {\r
9367             /* Check to see if engine is in book */\r
9368             int depth = pvInfoList[index].depth;\r
9369             int score = pvInfoList[index].score;\r
9370             int in_book = 0;\r
9371 \r
9372             if( depth <= 2 ) {\r
9373                 in_book = 1;\r
9374             }\r
9375             else if( score == 0 && depth == 63 ) {\r
9376                 in_book = 1; /* Zappa */\r
9377             }\r
9378             else if( score == 2 && depth == 99 ) {\r
9379                 in_book = 1; /* Abrok */\r
9380             }\r
9381 \r
9382             has_book_hit += in_book;\r
9383 \r
9384             if( ! in_book ) {\r
9385                 result = index;\r
9386 \r
9387                 break;\r
9388             }\r
9389 \r
9390             index += 2;\r
9391         }\r
9392     }\r
9393 \r
9394     return result;\r
9395 }\r
9396 \r
9397 /* [AS] */\r
9398 void GetOutOfBookInfo( char * buf )\r
9399 {\r
9400     int oob[2];\r
9401     int i;\r
9402     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9403 \r
9404     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
9405     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
9406 \r
9407     *buf = '\0';\r
9408 \r
9409     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
9410         for( i=0; i<2; i++ ) {\r
9411             int idx = oob[i];\r
9412 \r
9413             if( idx >= 0 ) {\r
9414                 if( i > 0 && oob[0] >= 0 ) {\r
9415                     strcat( buf, "   " );\r
9416                 }\r
9417 \r
9418                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
9419                 sprintf( buf+strlen(buf), "%s%.2f", \r
9420                     pvInfoList[idx].score >= 0 ? "+" : "",\r
9421                     pvInfoList[idx].score / 100.0 );\r
9422             }\r
9423         }\r
9424     }\r
9425 }\r
9426 \r
9427 /* Save game in PGN style and close the file */\r
9428 int\r
9429 SaveGamePGN(f)\r
9430      FILE *f;\r
9431 {\r
9432     int i, offset, linelen, newblock;\r
9433     time_t tm;\r
9434     char *movetext;\r
9435     char numtext[32];\r
9436     int movelen, numlen, blank;\r
9437     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
9438 \r
9439     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9440     \r
9441     tm = time((time_t *) NULL);\r
9442     \r
9443     PrintPGNTags(f, &gameInfo);\r
9444     \r
9445     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9446         char *fen = PositionToFEN(backwardMostMove, 1);\r
9447         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
9448         fprintf(f, "\n{--------------\n");\r
9449         PrintPosition(f, backwardMostMove);\r
9450         fprintf(f, "--------------}\n");\r
9451         free(fen);\r
9452     }\r
9453     else {\r
9454         /* [AS] Out of book annotation */\r
9455         if( appData.saveOutOfBookInfo ) {\r
9456             char buf[64];\r
9457 \r
9458             GetOutOfBookInfo( buf );\r
9459 \r
9460             if( buf[0] != '\0' ) {\r
9461                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
9462             }\r
9463         }\r
9464 \r
9465         fprintf(f, "\n");\r
9466     }\r
9467 \r
9468     i = backwardMostMove;\r
9469     linelen = 0;\r
9470     newblock = TRUE;\r
9471 \r
9472     while (i < forwardMostMove) {\r
9473         /* Print comments preceding this move */\r
9474         if (commentList[i] != NULL) {\r
9475             if (linelen > 0) fprintf(f, "\n");\r
9476             fprintf(f, "{\n%s}\n", commentList[i]);\r
9477             linelen = 0;\r
9478             newblock = TRUE;\r
9479         }\r
9480 \r
9481         /* Format move number */\r
9482         if ((i % 2) == 0) {\r
9483             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
9484         } else {\r
9485             if (newblock) {\r
9486                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
9487             } else {\r
9488                 numtext[0] = NULLCHAR;\r
9489             }\r
9490         }\r
9491         numlen = strlen(numtext);\r
9492         newblock = FALSE;\r
9493 \r
9494         /* Print move number */\r
9495         blank = linelen > 0 && numlen > 0;\r
9496         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
9497             fprintf(f, "\n");\r
9498             linelen = 0;\r
9499             blank = 0;\r
9500         }\r
9501         if (blank) {\r
9502             fprintf(f, " ");\r
9503             linelen++;\r
9504         }\r
9505         fprintf(f, numtext);\r
9506         linelen += numlen;\r
9507 \r
9508         /* Get move */\r
9509         movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */\r
9510 \r
9511         /* Print move */\r
9512         blank = linelen > 0 && movelen > 0;\r
9513         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9514             fprintf(f, "\n");\r
9515             linelen = 0;\r
9516             blank = 0;\r
9517         }\r
9518         if (blank) {\r
9519             fprintf(f, " ");\r
9520             linelen++;\r
9521         }\r
9522         fprintf(f, parseList[i]);\r
9523         linelen += movelen;\r
9524 \r
9525         /* [AS] Add PV info if present */\r
9526         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9527             /* [HGM] add time */\r
9528             char buf[MSG_SIZ]; int seconds = 0;\r
9529 \r
9530 #if 0\r
9531             if(i >= backwardMostMove) {\r
9532                 if(WhiteOnMove(i))\r
9533                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]\r
9534                                   + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;\r
9535                 else\r
9536                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]\r
9537                                   + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;\r
9538             }\r
9539             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest\r
9540 #else\r
9541             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time\r
9542 #endif\r
9543     if (appData.debugMode,0) {\r
9544         fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",\r
9545                 timeRemaining[0][i+1], timeRemaining[0][i],\r
9546                      timeRemaining[1][i+1], timeRemaining[1][i], seconds\r
9547         );\r
9548     }\r
9549 \r
9550             if( seconds <= 0) buf[0] = 0; else\r
9551             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {\r
9552                 seconds = (seconds + 4)/10; // round to full seconds\r
9553                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else\r
9554                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
9555             }\r
9556 \r
9557             sprintf( move_buffer, "{%s%.2f/%d%s}", \r
9558                 pvInfoList[i].score >= 0 ? "+" : "",\r
9559                 pvInfoList[i].score / 100.0,\r
9560                 pvInfoList[i].depth,\r
9561                 buf );\r
9562 \r
9563             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */\r
9564 \r
9565             /* Print score/depth */\r
9566             blank = linelen > 0 && movelen > 0;\r
9567             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9568                 fprintf(f, "\n");\r
9569                 linelen = 0;\r
9570                 blank = 0;\r
9571             }\r
9572             if (blank) {\r
9573                 fprintf(f, " ");\r
9574                 linelen++;\r
9575             }\r
9576             fprintf(f, move_buffer);\r
9577             linelen += movelen;\r
9578         }\r
9579 \r
9580         i++;\r
9581     }\r
9582     \r
9583     /* Start a new line */\r
9584     if (linelen > 0) fprintf(f, "\n");\r
9585 \r
9586     /* Print comments after last move */\r
9587     if (commentList[i] != NULL) {\r
9588         fprintf(f, "{\n%s}\n", commentList[i]);\r
9589     }\r
9590 \r
9591     /* Print result */\r
9592     if (gameInfo.resultDetails != NULL &&\r
9593         gameInfo.resultDetails[0] != NULLCHAR) {\r
9594         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
9595                 PGNResult(gameInfo.result));\r
9596     } else {\r
9597         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9598     }\r
9599 \r
9600     fclose(f);\r
9601     return TRUE;\r
9602 }\r
9603 \r
9604 /* Save game in old style and close the file */\r
9605 int\r
9606 SaveGameOldStyle(f)\r
9607      FILE *f;\r
9608 {\r
9609     int i, offset;\r
9610     time_t tm;\r
9611     \r
9612     tm = time((time_t *) NULL);\r
9613     \r
9614     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
9615     PrintOpponents(f);\r
9616     \r
9617     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9618         fprintf(f, "\n[--------------\n");\r
9619         PrintPosition(f, backwardMostMove);\r
9620         fprintf(f, "--------------]\n");\r
9621     } else {\r
9622         fprintf(f, "\n");\r
9623     }\r
9624 \r
9625     i = backwardMostMove;\r
9626     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9627 \r
9628     while (i < forwardMostMove) {\r
9629         if (commentList[i] != NULL) {\r
9630             fprintf(f, "[%s]\n", commentList[i]);\r
9631         }\r
9632 \r
9633         if ((i % 2) == 1) {\r
9634             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
9635             i++;\r
9636         } else {\r
9637             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
9638             i++;\r
9639             if (commentList[i] != NULL) {\r
9640                 fprintf(f, "\n");\r
9641                 continue;\r
9642             }\r
9643             if (i >= forwardMostMove) {\r
9644                 fprintf(f, "\n");\r
9645                 break;\r
9646             }\r
9647             fprintf(f, "%s\n", parseList[i]);\r
9648             i++;\r
9649         }\r
9650     }\r
9651     \r
9652     if (commentList[i] != NULL) {\r
9653         fprintf(f, "[%s]\n", commentList[i]);\r
9654     }\r
9655 \r
9656     /* This isn't really the old style, but it's close enough */\r
9657     if (gameInfo.resultDetails != NULL &&\r
9658         gameInfo.resultDetails[0] != NULLCHAR) {\r
9659         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
9660                 gameInfo.resultDetails);\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 the current game to open file f and close the file */\r
9670 int\r
9671 SaveGame(f, dummy, dummy2)\r
9672      FILE *f;\r
9673      int dummy;\r
9674      char *dummy2;\r
9675 {\r
9676     if (gameMode == EditPosition) EditPositionDone();\r
9677     if (appData.oldSaveStyle)\r
9678       return SaveGameOldStyle(f);\r
9679     else\r
9680       return SaveGamePGN(f);\r
9681 }\r
9682 \r
9683 /* Save the current position to the given file */\r
9684 int\r
9685 SavePositionToFile(filename)\r
9686      char *filename;\r
9687 {\r
9688     FILE *f;\r
9689     char buf[MSG_SIZ];\r
9690 \r
9691     if (strcmp(filename, "-") == 0) {\r
9692         return SavePosition(stdout, 0, NULL);\r
9693     } else {\r
9694         f = fopen(filename, "a");\r
9695         if (f == NULL) {\r
9696             sprintf(buf, "Can't open \"%s\"", filename);\r
9697             DisplayError(buf, errno);\r
9698             return FALSE;\r
9699         } else {\r
9700             SavePosition(f, 0, NULL);\r
9701             return TRUE;\r
9702         }\r
9703     }\r
9704 }\r
9705 \r
9706 /* Save the current position to the given open file and close the file */\r
9707 int\r
9708 SavePosition(f, dummy, dummy2)\r
9709      FILE *f;\r
9710      int dummy;\r
9711      char *dummy2;\r
9712 {\r
9713     time_t tm;\r
9714     char *fen;\r
9715     \r
9716     if (appData.oldSaveStyle) {\r
9717         tm = time((time_t *) NULL);\r
9718     \r
9719         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
9720         PrintOpponents(f);\r
9721         fprintf(f, "[--------------\n");\r
9722         PrintPosition(f, currentMove);\r
9723         fprintf(f, "--------------]\n");\r
9724     } else {\r
9725         fen = PositionToFEN(currentMove, 1);\r
9726         fprintf(f, "%s\n", fen);\r
9727         free(fen);\r
9728     }\r
9729     fclose(f);\r
9730     return TRUE;\r
9731 }\r
9732 \r
9733 void\r
9734 ReloadCmailMsgEvent(unregister)\r
9735      int unregister;\r
9736 {\r
9737 #if !WIN32\r
9738     static char *inFilename = NULL;\r
9739     static char *outFilename;\r
9740     int i;\r
9741     struct stat inbuf, outbuf;\r
9742     int status;\r
9743     \r
9744     /* Any registered moves are unregistered if unregister is set, */\r
9745     /* i.e. invoked by the signal handler */\r
9746     if (unregister) {\r
9747         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9748             cmailMoveRegistered[i] = FALSE;\r
9749             if (cmailCommentList[i] != NULL) {\r
9750                 free(cmailCommentList[i]);\r
9751                 cmailCommentList[i] = NULL;\r
9752             }\r
9753         }\r
9754         nCmailMovesRegistered = 0;\r
9755     }\r
9756 \r
9757     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9758         cmailResult[i] = CMAIL_NOT_RESULT;\r
9759     }\r
9760     nCmailResults = 0;\r
9761 \r
9762     if (inFilename == NULL) {\r
9763         /* Because the filenames are static they only get malloced once  */\r
9764         /* and they never get freed                                      */\r
9765         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
9766         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
9767 \r
9768         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
9769         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
9770     }\r
9771     \r
9772     status = stat(outFilename, &outbuf);\r
9773     if (status < 0) {\r
9774         cmailMailedMove = FALSE;\r
9775     } else {\r
9776         status = stat(inFilename, &inbuf);\r
9777         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
9778     }\r
9779     \r
9780     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
9781        counts the games, notes how each one terminated, etc.\r
9782        \r
9783        It would be nice to remove this kludge and instead gather all\r
9784        the information while building the game list.  (And to keep it\r
9785        in the game list nodes instead of having a bunch of fixed-size\r
9786        parallel arrays.)  Note this will require getting each game's\r
9787        termination from the PGN tags, as the game list builder does\r
9788        not process the game moves.  --mann\r
9789        */\r
9790     cmailMsgLoaded = TRUE;\r
9791     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
9792     \r
9793     /* Load first game in the file or popup game menu */\r
9794     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
9795 \r
9796 #endif /* !WIN32 */\r
9797     return;\r
9798 }\r
9799 \r
9800 int\r
9801 RegisterMove()\r
9802 {\r
9803     FILE *f;\r
9804     char string[MSG_SIZ];\r
9805 \r
9806     if (   cmailMailedMove\r
9807         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9808         return TRUE;            /* Allow free viewing  */\r
9809     }\r
9810 \r
9811     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9812     /* with the move registered when the conditions for registering no   */\r
9813     /* longer hold                                                       */\r
9814     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9815         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9816         nCmailMovesRegistered --;\r
9817 \r
9818         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9819           {\r
9820               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9821               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9822           }\r
9823     }\r
9824 \r
9825     if (cmailOldMove == -1) {\r
9826         DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);\r
9827         return FALSE;\r
9828     }\r
9829 \r
9830     if (currentMove > cmailOldMove + 1) {\r
9831         DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);\r
9832         return FALSE;\r
9833     }\r
9834 \r
9835     if (currentMove < cmailOldMove) {\r
9836         DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);\r
9837         return FALSE;\r
9838     }\r
9839 \r
9840     if (forwardMostMove > currentMove) {\r
9841         /* Silently truncate extra moves */\r
9842         TruncateGame();\r
9843     }\r
9844 \r
9845     if (   (currentMove == cmailOldMove + 1)\r
9846         || (   (currentMove == cmailOldMove)\r
9847             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9848                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9849         if (gameInfo.result != GameUnfinished) {\r
9850             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9851         }\r
9852 \r
9853         if (commentList[currentMove] != NULL) {\r
9854             cmailCommentList[lastLoadGameNumber - 1]\r
9855               = StrSave(commentList[currentMove]);\r
9856         }\r
9857         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9858 \r
9859         if (appData.debugMode)\r
9860           fprintf(debugFP, "Saving %s for game %d\n",\r
9861                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9862 \r
9863         sprintf(string,\r
9864                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
9865         \r
9866         f = fopen(string, "w");\r
9867         if (appData.oldSaveStyle) {\r
9868             SaveGameOldStyle(f); /* also closes the file */\r
9869             \r
9870             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
9871             f = fopen(string, "w");\r
9872             SavePosition(f, 0, NULL); /* also closes the file */\r
9873         } else {\r
9874             fprintf(f, "{--------------\n");\r
9875             PrintPosition(f, currentMove);\r
9876             fprintf(f, "--------------}\n\n");\r
9877             \r
9878             SaveGame(f, 0, NULL); /* also closes the file*/\r
9879         }\r
9880         \r
9881         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
9882         nCmailMovesRegistered ++;\r
9883     } else if (nCmailGames == 1) {\r
9884         DisplayError("You have not made a move yet", 0);\r
9885         return FALSE;\r
9886     }\r
9887 \r
9888     return TRUE;\r
9889 }\r
9890 \r
9891 void\r
9892 MailMoveEvent()\r
9893 {\r
9894 #if !WIN32\r
9895     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
9896     FILE *commandOutput;\r
9897     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
9898     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
9899     int nBuffers;\r
9900     int i;\r
9901     int archived;\r
9902     char *arcDir;\r
9903 \r
9904     if (! cmailMsgLoaded) {\r
9905         DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);\r
9906         return;\r
9907     }\r
9908 \r
9909     if (nCmailGames == nCmailResults) {\r
9910         DisplayError("No unfinished games", 0);\r
9911         return;\r
9912     }\r
9913 \r
9914 #if CMAIL_PROHIBIT_REMAIL\r
9915     if (cmailMailedMove) {\r
9916         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
9917         DisplayError(msg, 0);\r
9918         return;\r
9919     }\r
9920 #endif\r
9921 \r
9922     if (! (cmailMailedMove || RegisterMove())) return;\r
9923     \r
9924     if (   cmailMailedMove\r
9925         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
9926         sprintf(string, partCommandString,\r
9927                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
9928         commandOutput = popen(string, "rb");\r
9929 \r
9930         if (commandOutput == NULL) {\r
9931             DisplayError("Failed to invoke cmail", 0);\r
9932         } else {\r
9933             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
9934                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
9935             }\r
9936             if (nBuffers > 1) {\r
9937                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
9938                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
9939                 nBytes = MSG_SIZ - 1;\r
9940             } else {\r
9941                 (void) memcpy(msg, buffer, nBytes);\r
9942             }\r
9943             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
9944 \r
9945             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
9946                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
9947 \r
9948                 archived = TRUE;\r
9949                 for (i = 0; i < nCmailGames; i ++) {\r
9950                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
9951                         archived = FALSE;\r
9952                     }\r
9953                 }\r
9954                 if (   archived\r
9955                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
9956                         != NULL)) {\r
9957                     sprintf(buffer, "%s/%s.%s.archive",\r
9958                             arcDir,\r
9959                             appData.cmailGameName,\r
9960                             gameInfo.date);\r
9961                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
9962                     cmailMsgLoaded = FALSE;\r
9963                 }\r
9964             }\r
9965 \r
9966             DisplayInformation(msg);\r
9967             pclose(commandOutput);\r
9968         }\r
9969     } else {\r
9970         if ((*cmailMsg) != '\0') {\r
9971             DisplayInformation(cmailMsg);\r
9972         }\r
9973     }\r
9974 \r
9975     return;\r
9976 #endif /* !WIN32 */\r
9977 }\r
9978 \r
9979 char *\r
9980 CmailMsg()\r
9981 {\r
9982 #if WIN32\r
9983     return NULL;\r
9984 #else\r
9985     int  prependComma = 0;\r
9986     char number[5];\r
9987     char string[MSG_SIZ];       /* Space for game-list */\r
9988     int  i;\r
9989     \r
9990     if (!cmailMsgLoaded) return "";\r
9991 \r
9992     if (cmailMailedMove) {\r
9993         sprintf(cmailMsg, "Waiting for reply from opponent\n");\r
9994     } else {\r
9995         /* Create a list of games left */\r
9996         sprintf(string, "[");\r
9997         for (i = 0; i < nCmailGames; i ++) {\r
9998             if (! (   cmailMoveRegistered[i]\r
9999                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
10000                 if (prependComma) {\r
10001                     sprintf(number, ",%d", i + 1);\r
10002                 } else {\r
10003                     sprintf(number, "%d", i + 1);\r
10004                     prependComma = 1;\r
10005                 }\r
10006                 \r
10007                 strcat(string, number);\r
10008             }\r
10009         }\r
10010         strcat(string, "]");\r
10011 \r
10012         if (nCmailMovesRegistered + nCmailResults == 0) {\r
10013             switch (nCmailGames) {\r
10014               case 1:\r
10015                 sprintf(cmailMsg,\r
10016                         "Still need to make move for game\n");\r
10017                 break;\r
10018                 \r
10019               case 2:\r
10020                 sprintf(cmailMsg,\r
10021                         "Still need to make moves for both games\n");\r
10022                 break;\r
10023                 \r
10024               default:\r
10025                 sprintf(cmailMsg,\r
10026                         "Still need to make moves for all %d games\n",\r
10027                         nCmailGames);\r
10028                 break;\r
10029             }\r
10030         } else {\r
10031             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
10032               case 1:\r
10033                 sprintf(cmailMsg,\r
10034                         "Still need to make a move for game %s\n",\r
10035                         string);\r
10036                 break;\r
10037                 \r
10038               case 0:\r
10039                 if (nCmailResults == nCmailGames) {\r
10040                     sprintf(cmailMsg, "No unfinished games\n");\r
10041                 } else {\r
10042                     sprintf(cmailMsg, "Ready to send mail\n");\r
10043                 }\r
10044                 break;\r
10045                 \r
10046               default:\r
10047                 sprintf(cmailMsg,\r
10048                         "Still need to make moves for games %s\n",\r
10049                         string);\r
10050             }\r
10051         }\r
10052     }\r
10053     return cmailMsg;\r
10054 #endif /* WIN32 */\r
10055 }\r
10056 \r
10057 void\r
10058 ResetGameEvent()\r
10059 {\r
10060     if (gameMode == Training)\r
10061       SetTrainingModeOff();\r
10062 \r
10063     Reset(TRUE, TRUE);\r
10064     cmailMsgLoaded = FALSE;\r
10065     if (appData.icsActive) {\r
10066       SendToICS(ics_prefix);\r
10067       SendToICS("refresh\n");\r
10068     }\r
10069 }\r
10070 \r
10071 void\r
10072 ExitEvent(status)\r
10073      int status;\r
10074 {\r
10075     exiting++;\r
10076     if (exiting > 2) {\r
10077       /* Give up on clean exit */\r
10078       exit(status);\r
10079     }\r
10080     if (exiting > 1) {\r
10081       /* Keep trying for clean exit */\r
10082       return;\r
10083     }\r
10084 \r
10085     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
10086 \r
10087     if (telnetISR != NULL) {\r
10088       RemoveInputSource(telnetISR);\r
10089     }\r
10090     if (icsPR != NoProc) {\r
10091       DestroyChildProcess(icsPR, TRUE);\r
10092     }\r
10093 #if 0\r
10094     /* Save game if resource set and not already saved by GameEnds() */\r
10095     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
10096                              && forwardMostMove > 0) {\r
10097       if (*appData.saveGameFile != NULLCHAR) {\r
10098         SaveGameToFile(appData.saveGameFile, TRUE);\r
10099       } else if (appData.autoSaveGames) {\r
10100         AutoSaveGame();\r
10101       }\r
10102       if (*appData.savePositionFile != NULLCHAR) {\r
10103         SavePositionToFile(appData.savePositionFile);\r
10104       }\r
10105     }\r
10106     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10107 #else\r
10108     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
10109     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);\r
10110 #endif\r
10111     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
10112     /* make sure this other one finishes before killing it!                  */\r
10113     if(endingGame) { int count = 0;\r
10114         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
10115         while(endingGame && count++ < 10) DoSleep(1);\r
10116         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
10117     }\r
10118 \r
10119     /* Kill off chess programs */\r
10120     if (first.pr != NoProc) {\r
10121         ExitAnalyzeMode();\r
10122         \r
10123         DoSleep( appData.delayBeforeQuit );\r
10124         SendToProgram("quit\n", &first);\r
10125         DoSleep( appData.delayAfterQuit );\r
10126         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
10127     }\r
10128     if (second.pr != NoProc) {\r
10129         DoSleep( appData.delayBeforeQuit );\r
10130         SendToProgram("quit\n", &second);\r
10131         DoSleep( appData.delayAfterQuit );\r
10132         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
10133     }\r
10134     if (first.isr != NULL) {\r
10135         RemoveInputSource(first.isr);\r
10136     }\r
10137     if (second.isr != NULL) {\r
10138         RemoveInputSource(second.isr);\r
10139     }\r
10140 \r
10141     ShutDownFrontEnd();\r
10142     exit(status);\r
10143 }\r
10144 \r
10145 void\r
10146 PauseEvent()\r
10147 {\r
10148     if (appData.debugMode)\r
10149         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
10150     if (pausing) {\r
10151         pausing = FALSE;\r
10152         ModeHighlight();\r
10153         if (gameMode == MachinePlaysWhite ||\r
10154             gameMode == MachinePlaysBlack) {\r
10155             StartClocks();\r
10156         } else {\r
10157             DisplayBothClocks();\r
10158         }\r
10159         if (gameMode == PlayFromGameFile) {\r
10160             if (appData.timeDelay >= 0) \r
10161                 AutoPlayGameLoop();\r
10162         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
10163             Reset(FALSE, TRUE);\r
10164             SendToICS(ics_prefix);\r
10165             SendToICS("refresh\n");\r
10166         } else if (currentMove < forwardMostMove) {\r
10167             ForwardInner(forwardMostMove);\r
10168         }\r
10169         pauseExamInvalid = FALSE;\r
10170     } else {\r
10171         switch (gameMode) {\r
10172           default:\r
10173             return;\r
10174           case IcsExamining:\r
10175             pauseExamForwardMostMove = forwardMostMove;\r
10176             pauseExamInvalid = FALSE;\r
10177             /* fall through */\r
10178           case IcsObserving:\r
10179           case IcsPlayingWhite:\r
10180           case IcsPlayingBlack:\r
10181             pausing = TRUE;\r
10182             ModeHighlight();\r
10183             return;\r
10184           case PlayFromGameFile:\r
10185             (void) StopLoadGameTimer();\r
10186             pausing = TRUE;\r
10187             ModeHighlight();\r
10188             break;\r
10189           case BeginningOfGame:\r
10190             if (appData.icsActive) return;\r
10191             /* else fall through */\r
10192           case MachinePlaysWhite:\r
10193           case MachinePlaysBlack:\r
10194           case TwoMachinesPlay:\r
10195             if (forwardMostMove == 0)\r
10196               return;           /* don't pause if no one has moved */\r
10197             if ((gameMode == MachinePlaysWhite &&\r
10198                  !WhiteOnMove(forwardMostMove)) ||\r
10199                 (gameMode == MachinePlaysBlack &&\r
10200                  WhiteOnMove(forwardMostMove))) {\r
10201                 StopClocks();\r
10202             }\r
10203             pausing = TRUE;\r
10204             ModeHighlight();\r
10205             break;\r
10206         }\r
10207     }\r
10208 }\r
10209 \r
10210 void\r
10211 EditCommentEvent()\r
10212 {\r
10213     char title[MSG_SIZ];\r
10214 \r
10215     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
10216         strcpy(title, "Edit comment");\r
10217     } else {\r
10218         sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,\r
10219                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
10220                 parseList[currentMove - 1]);\r
10221     }\r
10222 \r
10223     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
10224 }\r
10225 \r
10226 \r
10227 void\r
10228 EditTagsEvent()\r
10229 {\r
10230     char *tags = PGNTags(&gameInfo);\r
10231     EditTagsPopUp(tags);\r
10232     free(tags);\r
10233 }\r
10234 \r
10235 void\r
10236 AnalyzeModeEvent()\r
10237 {\r
10238     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
10239       return;\r
10240 \r
10241     if (gameMode != AnalyzeFile) {\r
10242         EditGameEvent();\r
10243         if (gameMode != EditGame) return;\r
10244         ResurrectChessProgram();\r
10245         SendToProgram("analyze\n", &first);\r
10246         first.analyzing = TRUE;\r
10247         /*first.maybeThinking = TRUE;*/\r
10248         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10249         AnalysisPopUp("Analysis",\r
10250                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");\r
10251     }\r
10252     gameMode = AnalyzeMode;\r
10253     pausing = FALSE;\r
10254     ModeHighlight();\r
10255     SetGameInfo();\r
10256 \r
10257     StartAnalysisClock();\r
10258     GetTimeMark(&lastNodeCountTime);\r
10259     lastNodeCount = 0;\r
10260 }\r
10261 \r
10262 void\r
10263 AnalyzeFileEvent()\r
10264 {\r
10265     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
10266       return;\r
10267 \r
10268     if (gameMode != AnalyzeMode) {\r
10269         EditGameEvent();\r
10270         if (gameMode != EditGame) return;\r
10271         ResurrectChessProgram();\r
10272         SendToProgram("analyze\n", &first);\r
10273         first.analyzing = TRUE;\r
10274         /*first.maybeThinking = TRUE;*/\r
10275         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10276         AnalysisPopUp("Analysis",\r
10277                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");\r
10278     }\r
10279     gameMode = AnalyzeFile;\r
10280     pausing = FALSE;\r
10281     ModeHighlight();\r
10282     SetGameInfo();\r
10283 \r
10284     StartAnalysisClock();\r
10285     GetTimeMark(&lastNodeCountTime);\r
10286     lastNodeCount = 0;\r
10287 }\r
10288 \r
10289 void\r
10290 MachineWhiteEvent()\r
10291 {\r
10292     char buf[MSG_SIZ];\r
10293     char *bookHit = NULL;\r
10294 \r
10295     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
10296       return;\r
10297 \r
10298 \r
10299     if (gameMode == PlayFromGameFile || \r
10300         gameMode == TwoMachinesPlay  || \r
10301         gameMode == Training         || \r
10302         gameMode == AnalyzeMode      || \r
10303         gameMode == EndOfGame)\r
10304         EditGameEvent();\r
10305 \r
10306     if (gameMode == EditPosition) \r
10307         EditPositionDone();\r
10308 \r
10309     if (!WhiteOnMove(currentMove)) {\r
10310         DisplayError("It is not White's turn", 0);\r
10311         return;\r
10312     }\r
10313   \r
10314     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10315       ExitAnalyzeMode();\r
10316 \r
10317     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10318         gameMode == AnalyzeFile)\r
10319         TruncateGame();\r
10320 \r
10321     ResurrectChessProgram();    /* in case it isn't running */\r
10322     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */\r
10323         gameMode = MachinePlaysWhite;\r
10324         ResetClocks();\r
10325     } else\r
10326     gameMode = MachinePlaysWhite;\r
10327     pausing = FALSE;\r
10328     ModeHighlight();\r
10329     SetGameInfo();\r
10330     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10331     DisplayTitle(buf);\r
10332     if (first.sendName) {\r
10333       sprintf(buf, "name %s\n", gameInfo.black);\r
10334       SendToProgram(buf, &first);\r
10335     }\r
10336     if (first.sendTime) {\r
10337       if (first.useColors) {\r
10338         SendToProgram("black\n", &first); /*gnu kludge*/\r
10339       }\r
10340       SendTimeRemaining(&first, TRUE);\r
10341     }\r
10342     if (first.useColors) {\r
10343       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately\r
10344     }\r
10345     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10346     SetMachineThinkingEnables();\r
10347     first.maybeThinking = TRUE;\r
10348     StartClocks();\r
10349 \r
10350     if (appData.autoFlipView && !flipView) {\r
10351       flipView = !flipView;\r
10352       DrawPosition(FALSE, NULL);\r
10353       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10354     }\r
10355 \r
10356     if(bookHit) { // [HGM] book: simulate book reply\r
10357         static char bookMove[MSG_SIZ]; // a bit generous?\r
10358 \r
10359         programStats.depth = programStats.nodes = programStats.time = \r
10360         programStats.score = programStats.got_only_move = 0;\r
10361         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10362 \r
10363         strcpy(bookMove, "move ");\r
10364         strcat(bookMove, bookHit);\r
10365         HandleMachineMove(bookMove, &first);\r
10366     }\r
10367 }\r
10368 \r
10369 void\r
10370 MachineBlackEvent()\r
10371 {\r
10372     char buf[MSG_SIZ];\r
10373    char *bookHit = NULL;\r
10374 \r
10375     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
10376         return;\r
10377 \r
10378 \r
10379     if (gameMode == PlayFromGameFile || \r
10380         gameMode == TwoMachinesPlay  || \r
10381         gameMode == Training         || \r
10382         gameMode == AnalyzeMode      || \r
10383         gameMode == EndOfGame)\r
10384         EditGameEvent();\r
10385 \r
10386     if (gameMode == EditPosition) \r
10387         EditPositionDone();\r
10388 \r
10389     if (WhiteOnMove(currentMove)) {\r
10390         DisplayError("It is not Black's turn", 0);\r
10391         return;\r
10392     }\r
10393     \r
10394     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10395       ExitAnalyzeMode();\r
10396 \r
10397     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10398         gameMode == AnalyzeFile)\r
10399         TruncateGame();\r
10400 \r
10401     ResurrectChessProgram();    /* in case it isn't running */\r
10402     gameMode = MachinePlaysBlack;\r
10403     pausing = FALSE;\r
10404     ModeHighlight();\r
10405     SetGameInfo();\r
10406     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10407     DisplayTitle(buf);\r
10408     if (first.sendName) {\r
10409       sprintf(buf, "name %s\n", gameInfo.white);\r
10410       SendToProgram(buf, &first);\r
10411     }\r
10412     if (first.sendTime) {\r
10413       if (first.useColors) {\r
10414         SendToProgram("white\n", &first); /*gnu kludge*/\r
10415       }\r
10416       SendTimeRemaining(&first, FALSE);\r
10417     }\r
10418     if (first.useColors) {\r
10419       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately\r
10420     }\r
10421     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10422     SetMachineThinkingEnables();\r
10423     first.maybeThinking = TRUE;\r
10424     StartClocks();\r
10425 \r
10426     if (appData.autoFlipView && flipView) {\r
10427       flipView = !flipView;\r
10428       DrawPosition(FALSE, NULL);\r
10429       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10430     }\r
10431     if(bookHit) { // [HGM] book: simulate book reply\r
10432         static char bookMove[MSG_SIZ]; // a bit generous?\r
10433 \r
10434         programStats.depth = programStats.nodes = programStats.time = \r
10435         programStats.score = programStats.got_only_move = 0;\r
10436         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10437 \r
10438         strcpy(bookMove, "move ");\r
10439         strcat(bookMove, bookHit);\r
10440         HandleMachineMove(bookMove, &first);\r
10441     }\r
10442 }\r
10443 \r
10444 \r
10445 void\r
10446 DisplayTwoMachinesTitle()\r
10447 {\r
10448     char buf[MSG_SIZ];\r
10449     if (appData.matchGames > 0) {\r
10450         if (first.twoMachinesColor[0] == 'w') {\r
10451             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10452                     gameInfo.white, gameInfo.black,\r
10453                     first.matchWins, second.matchWins,\r
10454                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10455         } else {\r
10456             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10457                     gameInfo.white, gameInfo.black,\r
10458                     second.matchWins, first.matchWins,\r
10459                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10460         }\r
10461     } else {\r
10462         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10463     }\r
10464     DisplayTitle(buf);\r
10465 }\r
10466 \r
10467 void\r
10468 TwoMachinesEvent P((void))\r
10469 {\r
10470     int i;\r
10471     char buf[MSG_SIZ];\r
10472     ChessProgramState *onmove;\r
10473     char *bookHit = NULL;\r
10474     \r
10475     if (appData.noChessProgram) return;\r
10476 \r
10477     switch (gameMode) {\r
10478       case TwoMachinesPlay:\r
10479         return;\r
10480       case MachinePlaysWhite:\r
10481       case MachinePlaysBlack:\r
10482         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10483             DisplayError("Wait until your turn,\nor select Move Now", 0);\r
10484             return;\r
10485         }\r
10486         /* fall through */\r
10487       case BeginningOfGame:\r
10488       case PlayFromGameFile:\r
10489       case EndOfGame:\r
10490         EditGameEvent();\r
10491         if (gameMode != EditGame) return;\r
10492         break;\r
10493       case EditPosition:\r
10494         EditPositionDone();\r
10495         break;\r
10496       case AnalyzeMode:\r
10497       case AnalyzeFile:\r
10498         ExitAnalyzeMode();\r
10499         break;\r
10500       case EditGame:\r
10501       default:\r
10502         break;\r
10503     }\r
10504 \r
10505     forwardMostMove = currentMove;\r
10506     ResurrectChessProgram();    /* in case first program isn't running */\r
10507 \r
10508     if (second.pr == NULL) {\r
10509         StartChessProgram(&second);\r
10510         if (second.protocolVersion == 1) {\r
10511           TwoMachinesEventIfReady();\r
10512         } else {\r
10513           /* kludge: allow timeout for initial "feature" command */\r
10514           FreezeUI();\r
10515           DisplayMessage("", "Starting second chess program");\r
10516           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
10517         }\r
10518         return;\r
10519     }\r
10520     DisplayMessage("", "");\r
10521     InitChessProgram(&second, FALSE);\r
10522     SendToProgram("force\n", &second);\r
10523     if (startedFromSetupPosition) {\r
10524         SendBoard(&second, backwardMostMove);\r
10525     if (appData.debugMode) {\r
10526         fprintf(debugFP, "Two Machines\n");\r
10527     }\r
10528     }\r
10529     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
10530         SendMoveToProgram(i, &second);\r
10531     }\r
10532 \r
10533     gameMode = TwoMachinesPlay;\r
10534     pausing = FALSE;\r
10535     ModeHighlight();\r
10536     SetGameInfo();\r
10537     DisplayTwoMachinesTitle();\r
10538     firstMove = TRUE;\r
10539     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
10540         onmove = &first;\r
10541     } else {\r
10542         onmove = &second;\r
10543     }\r
10544 \r
10545     SendToProgram(first.computerString, &first);\r
10546     if (first.sendName) {\r
10547       sprintf(buf, "name %s\n", second.tidy);\r
10548       SendToProgram(buf, &first);\r
10549     }\r
10550     SendToProgram(second.computerString, &second);\r
10551     if (second.sendName) {\r
10552       sprintf(buf, "name %s\n", first.tidy);\r
10553       SendToProgram(buf, &second);\r
10554     }\r
10555 \r
10556     ResetClocks();\r
10557     if (!first.sendTime || !second.sendTime) {\r
10558         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10559         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10560     }\r
10561     if (onmove->sendTime) {\r
10562       if (onmove->useColors) {\r
10563         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
10564       }\r
10565       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
10566     }\r
10567     if (onmove->useColors) {\r
10568       SendToProgram(onmove->twoMachinesColor, onmove);\r
10569     }\r
10570     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move\r
10571 //    SendToProgram("go\n", onmove);\r
10572     onmove->maybeThinking = TRUE;\r
10573     SetMachineThinkingEnables();\r
10574 \r
10575     StartClocks();\r
10576 \r
10577     if(bookHit) { // [HGM] book: simulate book reply\r
10578         static char bookMove[MSG_SIZ]; // a bit generous?\r
10579 \r
10580         programStats.depth = programStats.nodes = programStats.time = \r
10581         programStats.score = programStats.got_only_move = 0;\r
10582         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10583 \r
10584         strcpy(bookMove, "move ");\r
10585         strcat(bookMove, bookHit);\r
10586         HandleMachineMove(bookMove, &first);\r
10587     }\r
10588 }\r
10589 \r
10590 void\r
10591 TrainingEvent()\r
10592 {\r
10593     if (gameMode == Training) {\r
10594       SetTrainingModeOff();\r
10595       gameMode = PlayFromGameFile;\r
10596       DisplayMessage("", "Training mode off");\r
10597     } else {\r
10598       gameMode = Training;\r
10599       animateTraining = appData.animate;\r
10600 \r
10601       /* make sure we are not already at the end of the game */\r
10602       if (currentMove < forwardMostMove) {\r
10603         SetTrainingModeOn();\r
10604         DisplayMessage("", "Training mode on");\r
10605       } else {\r
10606         gameMode = PlayFromGameFile;\r
10607         DisplayError("Already at end of game", 0);\r
10608       }\r
10609     }\r
10610     ModeHighlight();\r
10611 }\r
10612 \r
10613 void\r
10614 IcsClientEvent()\r
10615 {\r
10616     if (!appData.icsActive) return;\r
10617     switch (gameMode) {\r
10618       case IcsPlayingWhite:\r
10619       case IcsPlayingBlack:\r
10620       case IcsObserving:\r
10621       case IcsIdle:\r
10622       case BeginningOfGame:\r
10623       case IcsExamining:\r
10624         return;\r
10625 \r
10626       case EditGame:\r
10627         break;\r
10628 \r
10629       case EditPosition:\r
10630         EditPositionDone();\r
10631         break;\r
10632 \r
10633       case AnalyzeMode:\r
10634       case AnalyzeFile:\r
10635         ExitAnalyzeMode();\r
10636         break;\r
10637         \r
10638       default:\r
10639         EditGameEvent();\r
10640         break;\r
10641     }\r
10642 \r
10643     gameMode = IcsIdle;\r
10644     ModeHighlight();\r
10645     return;\r
10646 }\r
10647 \r
10648 \r
10649 void\r
10650 EditGameEvent()\r
10651 {\r
10652     int i;\r
10653 \r
10654     switch (gameMode) {\r
10655       case Training:\r
10656         SetTrainingModeOff();\r
10657         break;\r
10658       case MachinePlaysWhite:\r
10659       case MachinePlaysBlack:\r
10660       case BeginningOfGame:\r
10661         SendToProgram("force\n", &first);\r
10662         SetUserThinkingEnables();\r
10663         break;\r
10664       case PlayFromGameFile:\r
10665         (void) StopLoadGameTimer();\r
10666         if (gameFileFP != NULL) {\r
10667             gameFileFP = NULL;\r
10668         }\r
10669         break;\r
10670       case EditPosition:\r
10671         EditPositionDone();\r
10672         break;\r
10673       case AnalyzeMode:\r
10674       case AnalyzeFile:\r
10675         ExitAnalyzeMode();\r
10676         SendToProgram("force\n", &first);\r
10677         break;\r
10678       case TwoMachinesPlay:\r
10679         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10680         ResurrectChessProgram();\r
10681         SetUserThinkingEnables();\r
10682         break;\r
10683       case EndOfGame:\r
10684         ResurrectChessProgram();\r
10685         break;\r
10686       case IcsPlayingBlack:\r
10687       case IcsPlayingWhite:\r
10688         DisplayError("Warning: You are still playing a game", 0);\r
10689         break;\r
10690       case IcsObserving:\r
10691         DisplayError("Warning: You are still observing a game", 0);\r
10692         break;\r
10693       case IcsExamining:\r
10694         DisplayError("Warning: You are still examining a game", 0);\r
10695         break;\r
10696       case IcsIdle:\r
10697         break;\r
10698       case EditGame:\r
10699       default:\r
10700         return;\r
10701     }\r
10702     \r
10703     pausing = FALSE;\r
10704     StopClocks();\r
10705     first.offeredDraw = second.offeredDraw = 0;\r
10706 \r
10707     if (gameMode == PlayFromGameFile) {\r
10708         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10709         blackTimeRemaining = timeRemaining[1][currentMove];\r
10710         DisplayTitle("");\r
10711     }\r
10712 \r
10713     if (gameMode == MachinePlaysWhite ||\r
10714         gameMode == MachinePlaysBlack ||\r
10715         gameMode == TwoMachinesPlay ||\r
10716         gameMode == EndOfGame) {\r
10717         i = forwardMostMove;\r
10718         while (i > currentMove) {\r
10719             SendToProgram("undo\n", &first);\r
10720             i--;\r
10721         }\r
10722         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10723         blackTimeRemaining = timeRemaining[1][currentMove];\r
10724         DisplayBothClocks();\r
10725         if (whiteFlag || blackFlag) {\r
10726             whiteFlag = blackFlag = 0;\r
10727         }\r
10728         DisplayTitle("");\r
10729     }           \r
10730     \r
10731     gameMode = EditGame;\r
10732     ModeHighlight();\r
10733     SetGameInfo();\r
10734 }\r
10735 \r
10736 \r
10737 void\r
10738 EditPositionEvent()\r
10739 {\r
10740     if (gameMode == EditPosition) {\r
10741         EditGameEvent();\r
10742         return;\r
10743     }\r
10744     \r
10745     EditGameEvent();\r
10746     if (gameMode != EditGame) return;\r
10747     \r
10748     gameMode = EditPosition;\r
10749     ModeHighlight();\r
10750     SetGameInfo();\r
10751     if (currentMove > 0)\r
10752       CopyBoard(boards[0], boards[currentMove]);\r
10753     \r
10754     blackPlaysFirst = !WhiteOnMove(currentMove);\r
10755     ResetClocks();\r
10756     currentMove = forwardMostMove = backwardMostMove = 0;\r
10757     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10758     DisplayMove(-1);\r
10759 }\r
10760 \r
10761 void\r
10762 ExitAnalyzeMode()\r
10763 {\r
10764     if (first.analysisSupport && first.analyzing) {\r
10765       SendToProgram("exit\n", &first);\r
10766       first.analyzing = FALSE;\r
10767     }\r
10768     AnalysisPopDown();\r
10769     thinkOutput[0] = NULLCHAR;\r
10770 }\r
10771 \r
10772 void\r
10773 EditPositionDone()\r
10774 {\r
10775     startedFromSetupPosition = TRUE;\r
10776     InitChessProgram(&first, FALSE);\r
10777     SendToProgram("force\n", &first);\r
10778     if (blackPlaysFirst) {\r
10779         strcpy(moveList[0], "");\r
10780         strcpy(parseList[0], "");\r
10781         currentMove = forwardMostMove = backwardMostMove = 1;\r
10782         CopyBoard(boards[1], boards[0]);\r
10783         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */\r
10784         { int i;\r
10785           epStatus[1] = epStatus[0];\r
10786           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];\r
10787         }\r
10788     } else {\r
10789         currentMove = forwardMostMove = backwardMostMove = 0;\r
10790     }\r
10791     SendBoard(&first, forwardMostMove);\r
10792     if (appData.debugMode) {\r
10793         fprintf(debugFP, "EditPosDone\n");\r
10794     }\r
10795     DisplayTitle("");\r
10796     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10797     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10798     gameMode = EditGame;\r
10799     ModeHighlight();\r
10800     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10801     ClearHighlights(); /* [AS] */\r
10802 }\r
10803 \r
10804 /* Pause for `ms' milliseconds */\r
10805 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10806 void\r
10807 TimeDelay(ms)\r
10808      long ms;\r
10809 {\r
10810     TimeMark m1, m2;\r
10811 \r
10812     GetTimeMark(&m1);\r
10813     do {\r
10814         GetTimeMark(&m2);\r
10815     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
10816 }\r
10817 \r
10818 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10819 void\r
10820 SendMultiLineToICS(buf)\r
10821      char *buf;\r
10822 {\r
10823     char temp[MSG_SIZ+1], *p;\r
10824     int len;\r
10825 \r
10826     len = strlen(buf);\r
10827     if (len > MSG_SIZ)\r
10828       len = MSG_SIZ;\r
10829   \r
10830     strncpy(temp, buf, len);\r
10831     temp[len] = 0;\r
10832 \r
10833     p = temp;\r
10834     while (*p) {\r
10835         if (*p == '\n' || *p == '\r')\r
10836           *p = ' ';\r
10837         ++p;\r
10838     }\r
10839 \r
10840     strcat(temp, "\n");\r
10841     SendToICS(temp);\r
10842     SendToPlayer(temp, strlen(temp));\r
10843 }\r
10844 \r
10845 void\r
10846 SetWhiteToPlayEvent()\r
10847 {\r
10848     if (gameMode == EditPosition) {\r
10849         blackPlaysFirst = FALSE;\r
10850         DisplayBothClocks();    /* works because currentMove is 0 */\r
10851     } else if (gameMode == IcsExamining) {\r
10852         SendToICS(ics_prefix);\r
10853         SendToICS("tomove white\n");\r
10854     }\r
10855 }\r
10856 \r
10857 void\r
10858 SetBlackToPlayEvent()\r
10859 {\r
10860     if (gameMode == EditPosition) {\r
10861         blackPlaysFirst = TRUE;\r
10862         currentMove = 1;        /* kludge */\r
10863         DisplayBothClocks();\r
10864         currentMove = 0;\r
10865     } else if (gameMode == IcsExamining) {\r
10866         SendToICS(ics_prefix);\r
10867         SendToICS("tomove black\n");\r
10868     }\r
10869 }\r
10870 \r
10871 void\r
10872 EditPositionMenuEvent(selection, x, y)\r
10873      ChessSquare selection;\r
10874      int x, y;\r
10875 {\r
10876     char buf[MSG_SIZ];\r
10877     ChessSquare piece = boards[0][y][x];\r
10878 \r
10879     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
10880 \r
10881     switch (selection) {\r
10882       case ClearBoard:\r
10883         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
10884             SendToICS(ics_prefix);\r
10885             SendToICS("bsetup clear\n");\r
10886         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
10887             SendToICS(ics_prefix);\r
10888             SendToICS("clearboard\n");\r
10889         } else {\r
10890             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;\r
10891                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */\r
10892                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
10893                     if (gameMode == IcsExamining) {\r
10894                         if (boards[currentMove][y][x] != EmptySquare) {\r
10895                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
10896                                     AAA + x, ONE + y);\r
10897                             SendToICS(buf);\r
10898                         }\r
10899                     } else {\r
10900                         boards[0][y][x] = p;\r
10901                     }\r
10902                 }\r
10903             }\r
10904         }\r
10905         if (gameMode == EditPosition) {\r
10906             DrawPosition(FALSE, boards[0]);\r
10907         }\r
10908         break;\r
10909 \r
10910       case WhitePlay:\r
10911         SetWhiteToPlayEvent();\r
10912         break;\r
10913 \r
10914       case BlackPlay:\r
10915         SetBlackToPlayEvent();\r
10916         break;\r
10917 \r
10918       case EmptySquare:\r
10919         if (gameMode == IcsExamining) {\r
10920             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
10921             SendToICS(buf);\r
10922         } else {\r
10923             boards[0][y][x] = EmptySquare;\r
10924             DrawPosition(FALSE, boards[0]);\r
10925         }\r
10926         break;\r
10927 \r
10928       case PromotePiece:\r
10929         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
10930            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
10931             selection = (ChessSquare) (PROMOTED piece);\r
10932         } else if(piece == EmptySquare) selection = WhiteSilver;\r
10933         else selection = (ChessSquare)((int)piece - 1);\r
10934         goto defaultlabel;\r
10935 \r
10936       case DemotePiece:\r
10937         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
10938            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
10939             selection = (ChessSquare) (DEMOTED piece);\r
10940         } else if(piece == EmptySquare) selection = BlackSilver;\r
10941         else selection = (ChessSquare)((int)piece + 1);       \r
10942         goto defaultlabel;\r
10943 \r
10944       case WhiteQueen:\r
10945       case BlackQueen:\r
10946         if(gameInfo.variant == VariantShatranj ||\r
10947            gameInfo.variant == VariantXiangqi  ||\r
10948            gameInfo.variant == VariantCourier    )\r
10949             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
10950         goto defaultlabel;\r
10951 \r
10952       case WhiteKing:\r
10953       case BlackKing:\r
10954         if(gameInfo.variant == VariantXiangqi)\r
10955             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
10956         if(gameInfo.variant == VariantKnightmate)\r
10957             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
10958       default:\r
10959         defaultlabel:\r
10960         if (gameMode == IcsExamining) {\r
10961             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
10962                     PieceToChar(selection), AAA + x, ONE + y);\r
10963             SendToICS(buf);\r
10964         } else {\r
10965             boards[0][y][x] = selection;\r
10966             DrawPosition(FALSE, boards[0]);\r
10967         }\r
10968         break;\r
10969     }\r
10970 }\r
10971 \r
10972 \r
10973 void\r
10974 DropMenuEvent(selection, x, y)\r
10975      ChessSquare selection;\r
10976      int x, y;\r
10977 {\r
10978     ChessMove moveType;\r
10979 \r
10980     switch (gameMode) {\r
10981       case IcsPlayingWhite:\r
10982       case MachinePlaysBlack:\r
10983         if (!WhiteOnMove(currentMove)) {\r
10984             DisplayMoveError("It is Black's turn");\r
10985             return;\r
10986         }\r
10987         moveType = WhiteDrop;\r
10988         break;\r
10989       case IcsPlayingBlack:\r
10990       case MachinePlaysWhite:\r
10991         if (WhiteOnMove(currentMove)) {\r
10992             DisplayMoveError("It is White's turn");\r
10993             return;\r
10994         }\r
10995         moveType = BlackDrop;\r
10996         break;\r
10997       case EditGame:\r
10998         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
10999         break;\r
11000       default:\r
11001         return;\r
11002     }\r
11003 \r
11004     if (moveType == BlackDrop && selection < BlackPawn) {\r
11005       selection = (ChessSquare) ((int) selection\r
11006                                  + (int) BlackPawn - (int) WhitePawn);\r
11007     }\r
11008     if (boards[currentMove][y][x] != EmptySquare) {\r
11009         DisplayMoveError("That square is occupied");\r
11010         return;\r
11011     }\r
11012 \r
11013     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
11014 }\r
11015 \r
11016 void\r
11017 AcceptEvent()\r
11018 {\r
11019     /* Accept a pending offer of any kind from opponent */\r
11020     \r
11021     if (appData.icsActive) {\r
11022         SendToICS(ics_prefix);\r
11023         SendToICS("accept\n");\r
11024     } else if (cmailMsgLoaded) {\r
11025         if (currentMove == cmailOldMove &&\r
11026             commentList[cmailOldMove] != NULL &&\r
11027             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11028                    "Black offers a draw" : "White offers a draw")) {\r
11029             TruncateGame();\r
11030             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11031             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11032         } else {\r
11033             DisplayError("There is no pending offer on this move", 0);\r
11034             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11035         }\r
11036     } else {\r
11037         /* Not used for offers from chess program */\r
11038     }\r
11039 }\r
11040 \r
11041 void\r
11042 DeclineEvent()\r
11043 {\r
11044     /* Decline a pending offer of any kind from opponent */\r
11045     \r
11046     if (appData.icsActive) {\r
11047         SendToICS(ics_prefix);\r
11048         SendToICS("decline\n");\r
11049     } else if (cmailMsgLoaded) {\r
11050         if (currentMove == cmailOldMove &&\r
11051             commentList[cmailOldMove] != NULL &&\r
11052             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11053                    "Black offers a draw" : "White offers a draw")) {\r
11054 #ifdef NOTDEF\r
11055             AppendComment(cmailOldMove, "Draw declined");\r
11056             DisplayComment(cmailOldMove - 1, "Draw declined");\r
11057 #endif /*NOTDEF*/\r
11058         } else {\r
11059             DisplayError("There is no pending offer on this move", 0);\r
11060         }\r
11061     } else {\r
11062         /* Not used for offers from chess program */\r
11063     }\r
11064 }\r
11065 \r
11066 void\r
11067 RematchEvent()\r
11068 {\r
11069     /* Issue ICS rematch command */\r
11070     if (appData.icsActive) {\r
11071         SendToICS(ics_prefix);\r
11072         SendToICS("rematch\n");\r
11073     }\r
11074 }\r
11075 \r
11076 void\r
11077 CallFlagEvent()\r
11078 {\r
11079     /* Call your opponent's flag (claim a win on time) */\r
11080     if (appData.icsActive) {\r
11081         SendToICS(ics_prefix);\r
11082         SendToICS("flag\n");\r
11083     } else {\r
11084         switch (gameMode) {\r
11085           default:\r
11086             return;\r
11087           case MachinePlaysWhite:\r
11088             if (whiteFlag) {\r
11089                 if (blackFlag)\r
11090                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11091                            GE_PLAYER);\r
11092                 else\r
11093                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
11094             } else {\r
11095                 DisplayError("Your opponent is not out of time", 0);\r
11096             }\r
11097             break;\r
11098           case MachinePlaysBlack:\r
11099             if (blackFlag) {\r
11100                 if (whiteFlag)\r
11101                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11102                            GE_PLAYER);\r
11103                 else\r
11104                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
11105             } else {\r
11106                 DisplayError("Your opponent is not out of time", 0);\r
11107             }\r
11108             break;\r
11109         }\r
11110     }\r
11111 }\r
11112 \r
11113 void\r
11114 DrawEvent()\r
11115 {\r
11116     /* Offer draw or accept pending draw offer from opponent */\r
11117     \r
11118     if (appData.icsActive) {\r
11119         /* Note: tournament rules require draw offers to be\r
11120            made after you make your move but before you punch\r
11121            your clock.  Currently ICS doesn't let you do that;\r
11122            instead, you immediately punch your clock after making\r
11123            a move, but you can offer a draw at any time. */\r
11124         \r
11125         SendToICS(ics_prefix);\r
11126         SendToICS("draw\n");\r
11127     } else if (cmailMsgLoaded) {\r
11128         if (currentMove == cmailOldMove &&\r
11129             commentList[cmailOldMove] != NULL &&\r
11130             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11131                    "Black offers a draw" : "White offers a draw")) {\r
11132             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11133             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11134         } else if (currentMove == cmailOldMove + 1) {\r
11135             char *offer = WhiteOnMove(cmailOldMove) ?\r
11136               "White offers a draw" : "Black offers a draw";\r
11137             AppendComment(currentMove, offer);\r
11138             DisplayComment(currentMove - 1, offer);\r
11139             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
11140         } else {\r
11141             DisplayError("You must make your move before offering a draw", 0);\r
11142             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11143         }\r
11144     } else if (first.offeredDraw) {\r
11145         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
11146     } else {\r
11147         if (first.sendDrawOffers) {\r
11148             SendToProgram("draw\n", &first);\r
11149             userOfferedDraw = TRUE;\r
11150         }\r
11151     }\r
11152 }\r
11153 \r
11154 void\r
11155 AdjournEvent()\r
11156 {\r
11157     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
11158     \r
11159     if (appData.icsActive) {\r
11160         SendToICS(ics_prefix);\r
11161         SendToICS("adjourn\n");\r
11162     } else {\r
11163         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
11164     }\r
11165 }\r
11166 \r
11167 \r
11168 void\r
11169 AbortEvent()\r
11170 {\r
11171     /* Offer Abort or accept pending Abort offer from opponent */\r
11172     \r
11173     if (appData.icsActive) {\r
11174         SendToICS(ics_prefix);\r
11175         SendToICS("abort\n");\r
11176     } else {\r
11177         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
11178     }\r
11179 }\r
11180 \r
11181 void\r
11182 ResignEvent()\r
11183 {\r
11184     /* Resign.  You can do this even if it's not your turn. */\r
11185     \r
11186     if (appData.icsActive) {\r
11187         SendToICS(ics_prefix);\r
11188         SendToICS("resign\n");\r
11189     } else {\r
11190         switch (gameMode) {\r
11191           case MachinePlaysWhite:\r
11192             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11193             break;\r
11194           case MachinePlaysBlack:\r
11195             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11196             break;\r
11197           case EditGame:\r
11198             if (cmailMsgLoaded) {\r
11199                 TruncateGame();\r
11200                 if (WhiteOnMove(cmailOldMove)) {\r
11201                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11202                 } else {\r
11203                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11204                 }\r
11205                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
11206             }\r
11207             break;\r
11208           default:\r
11209             break;\r
11210         }\r
11211     }\r
11212 }\r
11213 \r
11214 \r
11215 void\r
11216 StopObservingEvent()\r
11217 {\r
11218     /* Stop observing current games */\r
11219     SendToICS(ics_prefix);\r
11220     SendToICS("unobserve\n");\r
11221 }\r
11222 \r
11223 void\r
11224 StopExaminingEvent()\r
11225 {\r
11226     /* Stop observing current game */\r
11227     SendToICS(ics_prefix);\r
11228     SendToICS("unexamine\n");\r
11229 }\r
11230 \r
11231 void\r
11232 ForwardInner(target)\r
11233      int target;\r
11234 {\r
11235     int limit;\r
11236 \r
11237     if (appData.debugMode)\r
11238         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
11239                 target, currentMove, forwardMostMove);\r
11240 \r
11241     if (gameMode == EditPosition)\r
11242       return;\r
11243 \r
11244     if (gameMode == PlayFromGameFile && !pausing)\r
11245       PauseEvent();\r
11246     \r
11247     if (gameMode == IcsExamining && pausing)\r
11248       limit = pauseExamForwardMostMove;\r
11249     else\r
11250       limit = forwardMostMove;\r
11251     \r
11252     if (target > limit) target = limit;\r
11253 \r
11254     if (target > 0 && moveList[target - 1][0]) {\r
11255         int fromX, fromY, toX, toY;\r
11256         toX = moveList[target - 1][2] - AAA;\r
11257         toY = moveList[target - 1][3] - ONE;\r
11258         if (moveList[target - 1][1] == '@') {\r
11259             if (appData.highlightLastMove) {\r
11260                 SetHighlights(-1, -1, toX, toY);\r
11261             }\r
11262         } else {\r
11263             fromX = moveList[target - 1][0] - AAA;\r
11264             fromY = moveList[target - 1][1] - ONE;\r
11265             if (target == currentMove + 1) {\r
11266                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
11267             }\r
11268             if (appData.highlightLastMove) {\r
11269                 SetHighlights(fromX, fromY, toX, toY);\r
11270             }\r
11271         }\r
11272     }\r
11273     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
11274         gameMode == Training || gameMode == PlayFromGameFile || \r
11275         gameMode == AnalyzeFile) {\r
11276         while (currentMove < target) {\r
11277             SendMoveToProgram(currentMove++, &first);\r
11278         }\r
11279     } else {\r
11280         currentMove = target;\r
11281     }\r
11282     \r
11283     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11284         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11285         blackTimeRemaining = timeRemaining[1][currentMove];\r
11286     }\r
11287     DisplayBothClocks();\r
11288     DisplayMove(currentMove - 1);\r
11289     DrawPosition(FALSE, boards[currentMove]);\r
11290     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11291     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
11292         DisplayComment(currentMove - 1, commentList[currentMove]);\r
11293     }\r
11294 }\r
11295 \r
11296 \r
11297 void\r
11298 ForwardEvent()\r
11299 {\r
11300     if (gameMode == IcsExamining && !pausing) {\r
11301         SendToICS(ics_prefix);\r
11302         SendToICS("forward\n");\r
11303     } else {\r
11304         ForwardInner(currentMove + 1);\r
11305     }\r
11306 }\r
11307 \r
11308 void\r
11309 ToEndEvent()\r
11310 {\r
11311     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11312         /* to optimze, we temporarily turn off analysis mode while we feed\r
11313          * the remaining moves to the engine. Otherwise we get analysis output\r
11314          * after each move.\r
11315          */ \r
11316         if (first.analysisSupport) {\r
11317           SendToProgram("exit\nforce\n", &first);\r
11318           first.analyzing = FALSE;\r
11319         }\r
11320     }\r
11321         \r
11322     if (gameMode == IcsExamining && !pausing) {\r
11323         SendToICS(ics_prefix);\r
11324         SendToICS("forward 999999\n");\r
11325     } else {\r
11326         ForwardInner(forwardMostMove);\r
11327     }\r
11328 \r
11329     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11330         /* we have fed all the moves, so reactivate analysis mode */\r
11331         SendToProgram("analyze\n", &first);\r
11332         first.analyzing = TRUE;\r
11333         /*first.maybeThinking = TRUE;*/\r
11334         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11335     }\r
11336 }\r
11337 \r
11338 void\r
11339 BackwardInner(target)\r
11340      int target;\r
11341 {\r
11342     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
11343 \r
11344     if (appData.debugMode)\r
11345         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
11346                 target, currentMove, forwardMostMove);\r
11347 \r
11348     if (gameMode == EditPosition) return;\r
11349     if (currentMove <= backwardMostMove) {\r
11350         ClearHighlights();\r
11351         DrawPosition(full_redraw, boards[currentMove]);\r
11352         return;\r
11353     }\r
11354     if (gameMode == PlayFromGameFile && !pausing)\r
11355       PauseEvent();\r
11356     \r
11357     if (moveList[target][0]) {\r
11358         int fromX, fromY, toX, toY;\r
11359         toX = moveList[target][2] - AAA;\r
11360         toY = moveList[target][3] - ONE;\r
11361         if (moveList[target][1] == '@') {\r
11362             if (appData.highlightLastMove) {\r
11363                 SetHighlights(-1, -1, toX, toY);\r
11364             }\r
11365         } else {\r
11366             fromX = moveList[target][0] - AAA;\r
11367             fromY = moveList[target][1] - ONE;\r
11368             if (target == currentMove - 1) {\r
11369                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
11370             }\r
11371             if (appData.highlightLastMove) {\r
11372                 SetHighlights(fromX, fromY, toX, toY);\r
11373             }\r
11374         }\r
11375     }\r
11376     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
11377         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
11378         while (currentMove > target) {\r
11379             SendToProgram("undo\n", &first);\r
11380             currentMove--;\r
11381         }\r
11382     } else {\r
11383         currentMove = target;\r
11384     }\r
11385     \r
11386     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11387         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11388         blackTimeRemaining = timeRemaining[1][currentMove];\r
11389     }\r
11390     DisplayBothClocks();\r
11391     DisplayMove(currentMove - 1);\r
11392     DrawPosition(full_redraw, boards[currentMove]);\r
11393     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11394     // [HGM] PV info: routine tests if comment empty\r
11395     DisplayComment(currentMove - 1, commentList[currentMove]);\r
11396 }\r
11397 \r
11398 void\r
11399 BackwardEvent()\r
11400 {\r
11401     if (gameMode == IcsExamining && !pausing) {\r
11402         SendToICS(ics_prefix);\r
11403         SendToICS("backward\n");\r
11404     } else {\r
11405         BackwardInner(currentMove - 1);\r
11406     }\r
11407 }\r
11408 \r
11409 void\r
11410 ToStartEvent()\r
11411 {\r
11412     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11413         /* to optimze, we temporarily turn off analysis mode while we undo\r
11414          * all the moves. Otherwise we get analysis output after each undo.\r
11415          */ \r
11416         if (first.analysisSupport) {\r
11417           SendToProgram("exit\nforce\n", &first);\r
11418           first.analyzing = FALSE;\r
11419         }\r
11420     }\r
11421 \r
11422     if (gameMode == IcsExamining && !pausing) {\r
11423         SendToICS(ics_prefix);\r
11424         SendToICS("backward 999999\n");\r
11425     } else {\r
11426         BackwardInner(backwardMostMove);\r
11427     }\r
11428 \r
11429     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11430         /* we have fed all the moves, so reactivate analysis mode */\r
11431         SendToProgram("analyze\n", &first);\r
11432         first.analyzing = TRUE;\r
11433         /*first.maybeThinking = TRUE;*/\r
11434         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11435     }\r
11436 }\r
11437 \r
11438 void\r
11439 ToNrEvent(int to)\r
11440 {\r
11441   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
11442   if (to >= forwardMostMove) to = forwardMostMove;\r
11443   if (to <= backwardMostMove) to = backwardMostMove;\r
11444   if (to < currentMove) {\r
11445     BackwardInner(to);\r
11446   } else {\r
11447     ForwardInner(to);\r
11448   }\r
11449 }\r
11450 \r
11451 void\r
11452 RevertEvent()\r
11453 {\r
11454     if (gameMode != IcsExamining) {\r
11455         DisplayError("You are not examining a game", 0);\r
11456         return;\r
11457     }\r
11458     if (pausing) {\r
11459         DisplayError("You can't revert while pausing", 0);\r
11460         return;\r
11461     }\r
11462     SendToICS(ics_prefix);\r
11463     SendToICS("revert\n");\r
11464 }\r
11465 \r
11466 void\r
11467 RetractMoveEvent()\r
11468 {\r
11469     switch (gameMode) {\r
11470       case MachinePlaysWhite:\r
11471       case MachinePlaysBlack:\r
11472         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
11473             DisplayError("Wait until your turn,\nor select Move Now", 0);\r
11474             return;\r
11475         }\r
11476         if (forwardMostMove < 2) return;\r
11477         currentMove = forwardMostMove = forwardMostMove - 2;\r
11478         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11479         blackTimeRemaining = timeRemaining[1][currentMove];\r
11480         DisplayBothClocks();\r
11481         DisplayMove(currentMove - 1);\r
11482         ClearHighlights();/*!! could figure this out*/\r
11483         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
11484         SendToProgram("remove\n", &first);\r
11485         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
11486         break;\r
11487 \r
11488       case BeginningOfGame:\r
11489       default:\r
11490         break;\r
11491 \r
11492       case IcsPlayingWhite:\r
11493       case IcsPlayingBlack:\r
11494         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
11495             SendToICS(ics_prefix);\r
11496             SendToICS("takeback 2\n");\r
11497         } else {\r
11498             SendToICS(ics_prefix);\r
11499             SendToICS("takeback 1\n");\r
11500         }\r
11501         break;\r
11502     }\r
11503 }\r
11504 \r
11505 void\r
11506 MoveNowEvent()\r
11507 {\r
11508     ChessProgramState *cps;\r
11509 \r
11510     switch (gameMode) {\r
11511       case MachinePlaysWhite:\r
11512         if (!WhiteOnMove(forwardMostMove)) {\r
11513             DisplayError("It is your turn", 0);\r
11514             return;\r
11515         }\r
11516         cps = &first;\r
11517         break;\r
11518       case MachinePlaysBlack:\r
11519         if (WhiteOnMove(forwardMostMove)) {\r
11520             DisplayError("It is your turn", 0);\r
11521             return;\r
11522         }\r
11523         cps = &first;\r
11524         break;\r
11525       case TwoMachinesPlay:\r
11526         if (WhiteOnMove(forwardMostMove) ==\r
11527             (first.twoMachinesColor[0] == 'w')) {\r
11528             cps = &first;\r
11529         } else {\r
11530             cps = &second;\r
11531         }\r
11532         break;\r
11533       case BeginningOfGame:\r
11534       default:\r
11535         return;\r
11536     }\r
11537     SendToProgram("?\n", cps);\r
11538 }\r
11539 \r
11540 void\r
11541 TruncateGameEvent()\r
11542 {\r
11543     EditGameEvent();\r
11544     if (gameMode != EditGame) return;\r
11545     TruncateGame();\r
11546 }\r
11547 \r
11548 void\r
11549 TruncateGame()\r
11550 {\r
11551     if (forwardMostMove > currentMove) {\r
11552         if (gameInfo.resultDetails != NULL) {\r
11553             free(gameInfo.resultDetails);\r
11554             gameInfo.resultDetails = NULL;\r
11555             gameInfo.result = GameUnfinished;\r
11556         }\r
11557         forwardMostMove = currentMove;\r
11558         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
11559                    currentMove-1);\r
11560     }\r
11561 }\r
11562 \r
11563 void\r
11564 HintEvent()\r
11565 {\r
11566     if (appData.noChessProgram) return;\r
11567     switch (gameMode) {\r
11568       case MachinePlaysWhite:\r
11569         if (WhiteOnMove(forwardMostMove)) {\r
11570             DisplayError("Wait until your turn", 0);\r
11571             return;\r
11572         }\r
11573         break;\r
11574       case BeginningOfGame:\r
11575       case MachinePlaysBlack:\r
11576         if (!WhiteOnMove(forwardMostMove)) {\r
11577             DisplayError("Wait until your turn", 0);\r
11578             return;\r
11579         }\r
11580         break;\r
11581       default:\r
11582         DisplayError("No hint available", 0);\r
11583         return;\r
11584     }\r
11585     SendToProgram("hint\n", &first);\r
11586     hintRequested = TRUE;\r
11587 }\r
11588 \r
11589 void\r
11590 BookEvent()\r
11591 {\r
11592     if (appData.noChessProgram) return;\r
11593     switch (gameMode) {\r
11594       case MachinePlaysWhite:\r
11595         if (WhiteOnMove(forwardMostMove)) {\r
11596             DisplayError("Wait until your turn", 0);\r
11597             return;\r
11598         }\r
11599         break;\r
11600       case BeginningOfGame:\r
11601       case MachinePlaysBlack:\r
11602         if (!WhiteOnMove(forwardMostMove)) {\r
11603             DisplayError("Wait until your turn", 0);\r
11604             return;\r
11605         }\r
11606         break;\r
11607       case EditPosition:\r
11608         EditPositionDone();\r
11609         break;\r
11610       case TwoMachinesPlay:\r
11611         return;\r
11612       default:\r
11613         break;\r
11614     }\r
11615     SendToProgram("bk\n", &first);\r
11616     bookOutput[0] = NULLCHAR;\r
11617     bookRequested = TRUE;\r
11618 }\r
11619 \r
11620 void\r
11621 AboutGameEvent()\r
11622 {\r
11623     char *tags = PGNTags(&gameInfo);\r
11624     TagsPopUp(tags, CmailMsg());\r
11625     free(tags);\r
11626 }\r
11627 \r
11628 /* end button procedures */\r
11629 \r
11630 void\r
11631 PrintPosition(fp, move)\r
11632      FILE *fp;\r
11633      int move;\r
11634 {\r
11635     int i, j;\r
11636     \r
11637     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11638         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11639             char c = PieceToChar(boards[move][i][j]);\r
11640             fputc(c == 'x' ? '.' : c, fp);\r
11641             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
11642         }\r
11643     }\r
11644     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
11645       fprintf(fp, "white to play\n");\r
11646     else\r
11647       fprintf(fp, "black to play\n");\r
11648 }\r
11649 \r
11650 void\r
11651 PrintOpponents(fp)\r
11652      FILE *fp;\r
11653 {\r
11654     if (gameInfo.white != NULL) {\r
11655         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
11656     } else {\r
11657         fprintf(fp, "\n");\r
11658     }\r
11659 }\r
11660 \r
11661 /* Find last component of program's own name, using some heuristics */\r
11662 void\r
11663 TidyProgramName(prog, host, buf)\r
11664      char *prog, *host, buf[MSG_SIZ];\r
11665 {\r
11666     char *p, *q;\r
11667     int local = (strcmp(host, "localhost") == 0);\r
11668     while (!local && (p = strchr(prog, ';')) != NULL) {\r
11669         p++;\r
11670         while (*p == ' ') p++;\r
11671         prog = p;\r
11672     }\r
11673     if (*prog == '"' || *prog == '\'') {\r
11674         q = strchr(prog + 1, *prog);\r
11675     } else {\r
11676         q = strchr(prog, ' ');\r
11677     }\r
11678     if (q == NULL) q = prog + strlen(prog);\r
11679     p = q;\r
11680     while (p >= prog && *p != '/' && *p != '\\') p--;\r
11681     p++;\r
11682     if(p == prog && *p == '"') p++;\r
11683     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
11684     memcpy(buf, p, q - p);\r
11685     buf[q - p] = NULLCHAR;\r
11686     if (!local) {\r
11687         strcat(buf, "@");\r
11688         strcat(buf, host);\r
11689     }\r
11690 }\r
11691 \r
11692 char *\r
11693 TimeControlTagValue()\r
11694 {\r
11695     char buf[MSG_SIZ];\r
11696     if (!appData.clockMode) {\r
11697         strcpy(buf, "-");\r
11698     } else if (movesPerSession > 0) {\r
11699         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
11700     } else if (timeIncrement == 0) {\r
11701         sprintf(buf, "%ld", timeControl/1000);\r
11702     } else {\r
11703         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
11704     }\r
11705     return StrSave(buf);\r
11706 }\r
11707 \r
11708 void\r
11709 SetGameInfo()\r
11710 {\r
11711     /* This routine is used only for certain modes */\r
11712     VariantClass v = gameInfo.variant;\r
11713     ClearGameInfo(&gameInfo);\r
11714     gameInfo.variant = v;\r
11715 \r
11716     switch (gameMode) {\r
11717       case MachinePlaysWhite:\r
11718         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11719         gameInfo.site = StrSave(HostName());\r
11720         gameInfo.date = PGNDate();\r
11721         gameInfo.round = StrSave("-");\r
11722         gameInfo.white = StrSave(first.tidy);\r
11723         gameInfo.black = StrSave(UserName());\r
11724         gameInfo.timeControl = TimeControlTagValue();\r
11725         break;\r
11726 \r
11727       case MachinePlaysBlack:\r
11728         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11729         gameInfo.site = StrSave(HostName());\r
11730         gameInfo.date = PGNDate();\r
11731         gameInfo.round = StrSave("-");\r
11732         gameInfo.white = StrSave(UserName());\r
11733         gameInfo.black = StrSave(first.tidy);\r
11734         gameInfo.timeControl = TimeControlTagValue();\r
11735         break;\r
11736 \r
11737       case TwoMachinesPlay:\r
11738         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11739         gameInfo.site = StrSave(HostName());\r
11740         gameInfo.date = PGNDate();\r
11741         if (matchGame > 0) {\r
11742             char buf[MSG_SIZ];\r
11743             sprintf(buf, "%d", matchGame);\r
11744             gameInfo.round = StrSave(buf);\r
11745         } else {\r
11746             gameInfo.round = StrSave("-");\r
11747         }\r
11748         if (first.twoMachinesColor[0] == 'w') {\r
11749             gameInfo.white = StrSave(first.tidy);\r
11750             gameInfo.black = StrSave(second.tidy);\r
11751         } else {\r
11752             gameInfo.white = StrSave(second.tidy);\r
11753             gameInfo.black = StrSave(first.tidy);\r
11754         }\r
11755         gameInfo.timeControl = TimeControlTagValue();\r
11756         break;\r
11757 \r
11758       case EditGame:\r
11759         gameInfo.event = StrSave("Edited game");\r
11760         gameInfo.site = StrSave(HostName());\r
11761         gameInfo.date = PGNDate();\r
11762         gameInfo.round = StrSave("-");\r
11763         gameInfo.white = StrSave("-");\r
11764         gameInfo.black = StrSave("-");\r
11765         break;\r
11766 \r
11767       case EditPosition:\r
11768         gameInfo.event = StrSave("Edited position");\r
11769         gameInfo.site = StrSave(HostName());\r
11770         gameInfo.date = PGNDate();\r
11771         gameInfo.round = StrSave("-");\r
11772         gameInfo.white = StrSave("-");\r
11773         gameInfo.black = StrSave("-");\r
11774         break;\r
11775 \r
11776       case IcsPlayingWhite:\r
11777       case IcsPlayingBlack:\r
11778       case IcsObserving:\r
11779       case IcsExamining:\r
11780         break;\r
11781 \r
11782       case PlayFromGameFile:\r
11783         gameInfo.event = StrSave("Game from non-PGN file");\r
11784         gameInfo.site = StrSave(HostName());\r
11785         gameInfo.date = PGNDate();\r
11786         gameInfo.round = StrSave("-");\r
11787         gameInfo.white = StrSave("?");\r
11788         gameInfo.black = StrSave("?");\r
11789         break;\r
11790 \r
11791       default:\r
11792         break;\r
11793     }\r
11794 }\r
11795 \r
11796 void\r
11797 ReplaceComment(index, text)\r
11798      int index;\r
11799      char *text;\r
11800 {\r
11801     int len;\r
11802 \r
11803     while (*text == '\n') text++;\r
11804     len = strlen(text);\r
11805     while (len > 0 && text[len - 1] == '\n') len--;\r
11806 \r
11807     if (commentList[index] != NULL)\r
11808       free(commentList[index]);\r
11809 \r
11810     if (len == 0) {\r
11811         commentList[index] = NULL;\r
11812         return;\r
11813     }\r
11814     commentList[index] = (char *) malloc(len + 2);\r
11815     strncpy(commentList[index], text, len);\r
11816     commentList[index][len] = '\n';\r
11817     commentList[index][len + 1] = NULLCHAR;\r
11818 }\r
11819 \r
11820 void\r
11821 CrushCRs(text)\r
11822      char *text;\r
11823 {\r
11824   char *p = text;\r
11825   char *q = text;\r
11826   char ch;\r
11827 \r
11828   do {\r
11829     ch = *p++;\r
11830     if (ch == '\r') continue;\r
11831     *q++ = ch;\r
11832   } while (ch != '\0');\r
11833 }\r
11834 \r
11835 void\r
11836 AppendComment(index, text)\r
11837      int index;\r
11838      char *text;\r
11839 {\r
11840     int oldlen, len;\r
11841     char *old;\r
11842 \r
11843     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11844 \r
11845     CrushCRs(text);\r
11846     while (*text == '\n') text++;\r
11847     len = strlen(text);\r
11848     while (len > 0 && text[len - 1] == '\n') len--;\r
11849 \r
11850     if (len == 0) return;\r
11851 \r
11852     if (commentList[index] != NULL) {\r
11853         old = commentList[index];\r
11854         oldlen = strlen(old);\r
11855         commentList[index] = (char *) malloc(oldlen + len + 2);\r
11856         strcpy(commentList[index], old);\r
11857         free(old);\r
11858         strncpy(&commentList[index][oldlen], text, len);\r
11859         commentList[index][oldlen + len] = '\n';\r
11860         commentList[index][oldlen + len + 1] = NULLCHAR;\r
11861     } else {\r
11862         commentList[index] = (char *) malloc(len + 2);\r
11863         strncpy(commentList[index], text, len);\r
11864         commentList[index][len] = '\n';\r
11865         commentList[index][len + 1] = NULLCHAR;\r
11866     }\r
11867 }\r
11868 \r
11869 static char * FindStr( char * text, char * sub_text )\r
11870 {\r
11871     char * result = strstr( text, sub_text );\r
11872 \r
11873     if( result != NULL ) {\r
11874         result += strlen( sub_text );\r
11875     }\r
11876 \r
11877     return result;\r
11878 }\r
11879 \r
11880 /* [AS] Try to extract PV info from PGN comment */\r
11881 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
11882 char *GetInfoFromComment( int index, char * text )\r
11883 {\r
11884     char * sep = text;\r
11885 \r
11886     if( text != NULL && index > 0 ) {\r
11887         int score = 0;\r
11888         int depth = 0;\r
11889         int time = -1, sec = 0, deci;\r
11890         char * s_eval = FindStr( text, "[%eval " );\r
11891         char * s_emt = FindStr( text, "[%emt " );\r
11892 \r
11893         if( s_eval != NULL || s_emt != NULL ) {\r
11894             /* New style */\r
11895             char delim;\r
11896 \r
11897             if( s_eval != NULL ) {\r
11898                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
11899                     return text;\r
11900                 }\r
11901 \r
11902                 if( delim != ']' ) {\r
11903                     return text;\r
11904                 }\r
11905             }\r
11906 \r
11907             if( s_emt != NULL ) {\r
11908             }\r
11909         }\r
11910         else {\r
11911             /* We expect something like: [+|-]nnn.nn/dd */\r
11912             int score_lo = 0;\r
11913 \r
11914             sep = strchr( text, '/' );\r
11915             if( sep == NULL || sep < (text+4) ) {\r
11916                 return text;\r
11917             }\r
11918 \r
11919             time = -1; sec = -1; deci = -1;\r
11920             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
11921                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&\r
11922                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
11923                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
11924                 return text;\r
11925             }\r
11926 \r
11927             if( score_lo < 0 || score_lo >= 100 ) {\r
11928                 return text;\r
11929             }\r
11930 \r
11931             if(sec >= 0) time = 600*time + 10*sec; else\r
11932             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec\r
11933 \r
11934             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
11935 \r
11936             /* [HGM] PV time: now locate end of PV info */\r
11937             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
11938             if(time >= 0)\r
11939             while( *++sep >= '0' && *sep <= '9'); // strip time\r
11940             if(sec >= 0)\r
11941             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
11942             if(deci >= 0)\r
11943             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds\r
11944             while(*sep == ' ') sep++;\r
11945         }\r
11946 \r
11947         if( depth <= 0 ) {\r
11948             return text;\r
11949         }\r
11950 \r
11951         if( time < 0 ) {\r
11952             time = -1;\r
11953         }\r
11954 \r
11955         pvInfoList[index-1].depth = depth;\r
11956         pvInfoList[index-1].score = score;\r
11957         pvInfoList[index-1].time  = 10*time; // centi-sec\r
11958     }\r
11959     return sep;\r
11960 }\r
11961 \r
11962 void\r
11963 SendToProgram(message, cps)\r
11964      char *message;\r
11965      ChessProgramState *cps;\r
11966 {\r
11967     int count, outCount, error;\r
11968     char buf[MSG_SIZ];\r
11969 \r
11970     if (cps->pr == NULL) return;\r
11971     Attention(cps);\r
11972     \r
11973     if (appData.debugMode) {\r
11974         TimeMark now;\r
11975         GetTimeMark(&now);\r
11976         fprintf(debugFP, "%ld >%-6s: %s", \r
11977                 SubtractTimeMarks(&now, &programStartTime),\r
11978                 cps->which, message);\r
11979     }\r
11980     \r
11981     count = strlen(message);\r
11982     outCount = OutputToProcess(cps->pr, message, count, &error);\r
11983     if (outCount < count && !exiting \r
11984                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
11985         sprintf(buf, "Error writing to %s chess program", cps->which);\r
11986         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
11987             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
11988                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
11989                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
11990             } else {\r
11991                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
11992             }\r
11993             gameInfo.resultDetails = buf;\r
11994         }\r
11995         DisplayFatalError(buf, error, 1);\r
11996     }\r
11997 }\r
11998 \r
11999 void\r
12000 ReceiveFromProgram(isr, closure, message, count, error)\r
12001      InputSourceRef isr;\r
12002      VOIDSTAR closure;\r
12003      char *message;\r
12004      int count;\r
12005      int error;\r
12006 {\r
12007     char *end_str;\r
12008     char buf[MSG_SIZ];\r
12009     ChessProgramState *cps = (ChessProgramState *)closure;\r
12010 \r
12011     if (isr != cps->isr) return; /* Killed intentionally */\r
12012     if (count <= 0) {\r
12013         if (count == 0) {\r
12014             sprintf(buf,\r
12015                     "Error: %s chess program (%s) exited unexpectedly",\r
12016                     cps->which, cps->program);\r
12017         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12018                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12019                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12020                     sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
12021                 } else {\r
12022                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12023                 }\r
12024                 gameInfo.resultDetails = buf;\r
12025             }\r
12026             RemoveInputSource(cps->isr);\r
12027             DisplayFatalError(buf, 0, 1);\r
12028         } else {\r
12029             sprintf(buf,\r
12030                     "Error reading from %s chess program (%s)",\r
12031                     cps->which, cps->program);\r
12032             RemoveInputSource(cps->isr);\r
12033 \r
12034             /* [AS] Program is misbehaving badly... kill it */\r
12035             if( count == -2 ) {\r
12036                 DestroyChildProcess( cps->pr, 9 );\r
12037                 cps->pr = NoProc;\r
12038             }\r
12039 \r
12040             DisplayFatalError(buf, error, 1);\r
12041         }\r
12042         return;\r
12043     }\r
12044     \r
12045     if ((end_str = strchr(message, '\r')) != NULL)\r
12046       *end_str = NULLCHAR;\r
12047     if ((end_str = strchr(message, '\n')) != NULL)\r
12048       *end_str = NULLCHAR;\r
12049     \r
12050     if (appData.debugMode) {\r
12051         TimeMark now; int print = 1;\r
12052         char *quote = ""; char c; int i;\r
12053 \r
12054         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
12055                 char start = message[0];\r
12056                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
12057                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
12058                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&\r
12059                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
12060                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
12061                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&\r
12062                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
12063                         { quote = "# "; print = (appData.engineComments == 2); }\r
12064                 message[0] = start; // restore original message\r
12065         }\r
12066         if(print) {\r
12067                 GetTimeMark(&now);\r
12068                 fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
12069                         SubtractTimeMarks(&now, &programStartTime), cps->which, \r
12070                         quote,\r
12071                         message);\r
12072         }\r
12073     }\r
12074     HandleMachineMove(message, cps);\r
12075 }\r
12076 \r
12077 \r
12078 void\r
12079 SendTimeControl(cps, mps, tc, inc, sd, st)\r
12080      ChessProgramState *cps;\r
12081      int mps, inc, sd, st;\r
12082      long tc;\r
12083 {\r
12084     char buf[MSG_SIZ];\r
12085     int seconds;\r
12086 \r
12087     if( timeControl_2 > 0 ) {\r
12088         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
12089             tc = timeControl_2;\r
12090         }\r
12091     }\r
12092     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
12093     inc /= cps->timeOdds;\r
12094     st  /= cps->timeOdds;\r
12095 \r
12096     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
12097 \r
12098     if (st > 0) {\r
12099       /* Set exact time per move, normally using st command */\r
12100       if (cps->stKludge) {\r
12101         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
12102         seconds = st % 60;\r
12103         if (seconds == 0) {\r
12104           sprintf(buf, "level 1 %d\n", st/60);\r
12105         } else {\r
12106           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
12107         }\r
12108       } else {\r
12109         sprintf(buf, "st %d\n", st);\r
12110       }\r
12111     } else {\r
12112       /* Set conventional or incremental time control, using level command */\r
12113       if (seconds == 0) {\r
12114         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
12115            Fixed in later versions, but still avoid :seconds\r
12116            when seconds is 0. */\r
12117         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
12118       } else {\r
12119         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
12120                 seconds, inc/1000);\r
12121       }\r
12122     }\r
12123     SendToProgram(buf, cps);\r
12124 \r
12125     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
12126     /* Orthogonally, limit search to given depth */\r
12127     if (sd > 0) {\r
12128       if (cps->sdKludge) {\r
12129         sprintf(buf, "depth\n%d\n", sd);\r
12130       } else {\r
12131         sprintf(buf, "sd %d\n", sd);\r
12132       }\r
12133       SendToProgram(buf, cps);\r
12134     }\r
12135 \r
12136     if(cps->nps > 0) { /* [HGM] nps */\r
12137         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
12138         else {\r
12139                 sprintf(buf, "nps %d\n", cps->nps);\r
12140               SendToProgram(buf, cps);\r
12141         }\r
12142     }\r
12143 }\r
12144 \r
12145 ChessProgramState *WhitePlayer()\r
12146 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
12147 {\r
12148     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || \r
12149        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)\r
12150         return &second;\r
12151     return &first;\r
12152 }\r
12153 \r
12154 void\r
12155 SendTimeRemaining(cps, machineWhite)\r
12156      ChessProgramState *cps;\r
12157      int /*boolean*/ machineWhite;\r
12158 {\r
12159     char message[MSG_SIZ];\r
12160     long time, otime;\r
12161 \r
12162     /* Note: this routine must be called when the clocks are stopped\r
12163        or when they have *just* been set or switched; otherwise\r
12164        it will be off by the time since the current tick started.\r
12165     */\r
12166     if (machineWhite) {\r
12167         time = whiteTimeRemaining / 10;\r
12168         otime = blackTimeRemaining / 10;\r
12169     } else {\r
12170         time = blackTimeRemaining / 10;\r
12171         otime = whiteTimeRemaining / 10;\r
12172     }\r
12173     /* [HGM] translate opponent's time by time-odds factor */\r
12174     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
12175     if (appData.debugMode) {\r
12176         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
12177     }\r
12178 \r
12179     if (time <= 0) time = 1;\r
12180     if (otime <= 0) otime = 1;\r
12181     \r
12182     sprintf(message, "time %ld\n", time);\r
12183     SendToProgram(message, cps);\r
12184 \r
12185     sprintf(message, "otim %ld\n", otime);\r
12186     SendToProgram(message, cps);\r
12187 }\r
12188 \r
12189 int\r
12190 BoolFeature(p, name, loc, cps)\r
12191      char **p;\r
12192      char *name;\r
12193      int *loc;\r
12194      ChessProgramState *cps;\r
12195 {\r
12196   char buf[MSG_SIZ];\r
12197   int len = strlen(name);\r
12198   int val;\r
12199   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12200     (*p) += len + 1;\r
12201     sscanf(*p, "%d", &val);\r
12202     *loc = (val != 0);\r
12203     while (**p && **p != ' ') (*p)++;\r
12204     sprintf(buf, "accepted %s\n", name);\r
12205     SendToProgram(buf, cps);\r
12206     return TRUE;\r
12207   }\r
12208   return FALSE;\r
12209 }\r
12210 \r
12211 int\r
12212 IntFeature(p, name, loc, cps)\r
12213      char **p;\r
12214      char *name;\r
12215      int *loc;\r
12216      ChessProgramState *cps;\r
12217 {\r
12218   char buf[MSG_SIZ];\r
12219   int len = strlen(name);\r
12220   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12221     (*p) += len + 1;\r
12222     sscanf(*p, "%d", loc);\r
12223     while (**p && **p != ' ') (*p)++;\r
12224     sprintf(buf, "accepted %s\n", name);\r
12225     SendToProgram(buf, cps);\r
12226     return TRUE;\r
12227   }\r
12228   return FALSE;\r
12229 }\r
12230 \r
12231 int\r
12232 StringFeature(p, name, loc, cps)\r
12233      char **p;\r
12234      char *name;\r
12235      char loc[];\r
12236      ChessProgramState *cps;\r
12237 {\r
12238   char buf[MSG_SIZ];\r
12239   int len = strlen(name);\r
12240   if (strncmp((*p), name, len) == 0\r
12241       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
12242     (*p) += len + 2;\r
12243     sscanf(*p, "%[^\"]", loc);\r
12244     while (**p && **p != '\"') (*p)++;\r
12245     if (**p == '\"') (*p)++;\r
12246     sprintf(buf, "accepted %s\n", name);\r
12247     SendToProgram(buf, cps);\r
12248     return TRUE;\r
12249   }\r
12250   return FALSE;\r
12251 }\r
12252 \r
12253 int \r
12254 ParseOption(Option *opt, ChessProgramState *cps)\r
12255 // [HGM] options: process the string that defines an engine option, and determine\r
12256 // name, type, default value, and allowed value range\r
12257 {\r
12258         char *p, *q, buf[MSG_SIZ];\r
12259         int n, min = (-1)<<31, max = 1<<31, def;\r
12260 \r
12261         if(p = strstr(opt->name, " -spin ")) {\r
12262             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;\r
12263             if(max < min) max = min; // enforce consistency\r
12264             if(def < min) def = min;\r
12265             if(def > max) def = max;\r
12266             opt->value = def;\r
12267             opt->min = min;\r
12268             opt->max = max;\r
12269             opt->type = Spin;\r
12270         } else if(p = strstr(opt->name, " -string ")) {\r
12271             opt->textValue = p+9;\r
12272             opt->type = TextBox;\r
12273         } else if(p = strstr(opt->name, " -check ")) {\r
12274             if(sscanf(p, " -check %d", &def) < 1) return FALSE;\r
12275             opt->value = (def != 0);\r
12276             opt->type = CheckBox;\r
12277         } else if(p = strstr(opt->name, " -combo ")) {\r
12278             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type\r
12279             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices\r
12280             opt->value = n = 0;\r
12281             while(q = StrStr(q, " /// ")) {\r
12282                 n++; *q = 0;    // count choices, and null-terminate each of them\r
12283                 q += 5;\r
12284                 if(*q == '*') { // remember default, which is marked with * prefix\r
12285                     q++;\r
12286                     opt->value = n;\r
12287                 }\r
12288                 cps->comboList[cps->comboCnt++] = q;\r
12289             }\r
12290             cps->comboList[cps->comboCnt++] = NULL;\r
12291             opt->max = n + 1;\r
12292             opt->type = ComboBox;\r
12293         } else if(p = strstr(opt->name, " -button")) {\r
12294             opt->type = Button;\r
12295         } else if(p = strstr(opt->name, " -save")) {\r
12296             opt->type = SaveButton;\r
12297         } else return FALSE;\r
12298         *p = 0; // terminate option name\r
12299         return TRUE;\r
12300 }\r
12301 \r
12302 void\r
12303 FeatureDone(cps, val)\r
12304      ChessProgramState* cps;\r
12305      int val;\r
12306 {\r
12307   DelayedEventCallback cb = GetDelayedEvent();\r
12308   if ((cb == InitBackEnd3 && cps == &first) ||\r
12309       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
12310     CancelDelayedEvent();\r
12311     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
12312   }\r
12313   cps->initDone = val;\r
12314 }\r
12315 \r
12316 /* Parse feature command from engine */\r
12317 void\r
12318 ParseFeatures(args, cps)\r
12319      char* args;\r
12320      ChessProgramState *cps;  \r
12321 {\r
12322   char *p = args;\r
12323   char *q;\r
12324   int val;\r
12325   char buf[MSG_SIZ];\r
12326 \r
12327   for (;;) {\r
12328     while (*p == ' ') p++;\r
12329     if (*p == NULLCHAR) return;\r
12330 \r
12331     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
12332     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
12333     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
12334     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
12335     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
12336     if (BoolFeature(&p, "reuse", &val, cps)) {\r
12337       /* Engine can disable reuse, but can't enable it if user said no */\r
12338       if (!val) cps->reuse = FALSE;\r
12339       continue;\r
12340     }\r
12341     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
12342     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
12343       if (gameMode == TwoMachinesPlay) {\r
12344         DisplayTwoMachinesTitle();\r
12345       } else {\r
12346         DisplayTitle("");\r
12347       }\r
12348       continue;\r
12349     }\r
12350     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
12351     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
12352     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
12353     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
12354     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
12355     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
12356     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
12357     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
12358     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
12359     if (IntFeature(&p, "done", &val, cps)) {\r
12360       FeatureDone(cps, val);\r
12361       continue;\r
12362     }\r
12363     /* Added by Tord: */\r
12364     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
12365     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
12366     /* End of additions by Tord */\r
12367 \r
12368     /* [HGM] added features: */\r
12369     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
12370     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
12371     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
12372     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
12373     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12374     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
12375     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {\r
12376         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature\r
12377         if(cps->nrOptions >= MAX_OPTIONS) {\r
12378             cps->nrOptions--;\r
12379             sprintf(buf, "%s engine has too many options\n", cps->which);\r
12380             DisplayError(buf, 0);\r
12381         }\r
12382         continue;\r
12383     }\r
12384     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12385     /* End of additions by HGM */\r
12386 \r
12387     /* unknown feature: complain and skip */\r
12388     q = p;\r
12389     while (*q && *q != '=') q++;\r
12390     sprintf(buf, "rejected %.*s\n", q-p, p);\r
12391     SendToProgram(buf, cps);\r
12392     p = q;\r
12393     if (*p == '=') {\r
12394       p++;\r
12395       if (*p == '\"') {\r
12396         p++;\r
12397         while (*p && *p != '\"') p++;\r
12398         if (*p == '\"') p++;\r
12399       } else {\r
12400         while (*p && *p != ' ') p++;\r
12401       }\r
12402     }\r
12403   }\r
12404 \r
12405 }\r
12406 \r
12407 void\r
12408 PeriodicUpdatesEvent(newState)\r
12409      int newState;\r
12410 {\r
12411     if (newState == appData.periodicUpdates)\r
12412       return;\r
12413 \r
12414     appData.periodicUpdates=newState;\r
12415 \r
12416     /* Display type changes, so update it now */\r
12417     DisplayAnalysis();\r
12418 \r
12419     /* Get the ball rolling again... */\r
12420     if (newState) {\r
12421         AnalysisPeriodicEvent(1);\r
12422         StartAnalysisClock();\r
12423     }\r
12424 }\r
12425 \r
12426 void\r
12427 PonderNextMoveEvent(newState)\r
12428      int newState;\r
12429 {\r
12430     if (newState == appData.ponderNextMove) return;\r
12431     if (gameMode == EditPosition) EditPositionDone();\r
12432     if (newState) {\r
12433         SendToProgram("hard\n", &first);\r
12434         if (gameMode == TwoMachinesPlay) {\r
12435             SendToProgram("hard\n", &second);\r
12436         }\r
12437     } else {\r
12438         SendToProgram("easy\n", &first);\r
12439         thinkOutput[0] = NULLCHAR;\r
12440         if (gameMode == TwoMachinesPlay) {\r
12441             SendToProgram("easy\n", &second);\r
12442         }\r
12443     }\r
12444     appData.ponderNextMove = newState;\r
12445 }\r
12446 \r
12447 void\r
12448 NewSettingEvent(option, command, value)\r
12449      char *command;\r
12450      int option, value;\r
12451 {\r
12452     char buf[MSG_SIZ];\r
12453 \r
12454     if (gameMode == EditPosition) EditPositionDone();\r
12455     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);\r
12456     SendToProgram(buf, &first);\r
12457     if (gameMode == TwoMachinesPlay) {\r
12458         SendToProgram(buf, &second);\r
12459     }\r
12460 }\r
12461 \r
12462 void\r
12463 ShowThinkingEvent()\r
12464 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup\r
12465 {\r
12466     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated\r
12467     int newState = appData.showThinking\r
12468         // [HGM] thinking: other features now need thinking output as well\r
12469         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();\r
12470     \r
12471     if (oldState == newState) return;\r
12472     oldState = newState;\r
12473     if (gameMode == EditPosition) EditPositionDone();\r
12474     if (oldState) {\r
12475         SendToProgram("post\n", &first);\r
12476         if (gameMode == TwoMachinesPlay) {\r
12477             SendToProgram("post\n", &second);\r
12478         }\r
12479     } else {\r
12480         SendToProgram("nopost\n", &first);\r
12481         thinkOutput[0] = NULLCHAR;\r
12482         if (gameMode == TwoMachinesPlay) {\r
12483             SendToProgram("nopost\n", &second);\r
12484         }\r
12485     }\r
12486 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!\r
12487 }\r
12488 \r
12489 void\r
12490 AskQuestionEvent(title, question, replyPrefix, which)\r
12491      char *title; char *question; char *replyPrefix; char *which;\r
12492 {\r
12493   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
12494   if (pr == NoProc) return;\r
12495   AskQuestion(title, question, replyPrefix, pr);\r
12496 }\r
12497 \r
12498 void\r
12499 DisplayMove(moveNumber)\r
12500      int moveNumber;\r
12501 {\r
12502     char message[MSG_SIZ];\r
12503     char res[MSG_SIZ];\r
12504     char cpThinkOutput[MSG_SIZ];\r
12505 \r
12506     if (moveNumber == forwardMostMove - 1 || \r
12507         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12508 \r
12509         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
12510 \r
12511         if (strchr(cpThinkOutput, '\n')) {\r
12512             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
12513         }\r
12514     } else {\r
12515         *cpThinkOutput = NULLCHAR;\r
12516     }\r
12517 \r
12518     /* [AS] Hide thinking from human user */\r
12519     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
12520         *cpThinkOutput = NULLCHAR;\r
12521         if( thinkOutput[0] != NULLCHAR ) {\r
12522             int i;\r
12523 \r
12524             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
12525                 cpThinkOutput[i] = '.';\r
12526             }\r
12527             cpThinkOutput[i] = NULLCHAR;\r
12528             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
12529         }\r
12530     }\r
12531 \r
12532     if (moveNumber == forwardMostMove - 1 &&\r
12533         gameInfo.resultDetails != NULL) {\r
12534         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
12535             sprintf(res, " %s", PGNResult(gameInfo.result));\r
12536         } else {\r
12537             sprintf(res, " {%s} %s",\r
12538                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
12539         }\r
12540     } else {\r
12541         res[0] = NULLCHAR;\r
12542     }\r
12543     \r
12544     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12545         DisplayMessage(res, cpThinkOutput);\r
12546     } else {\r
12547         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
12548                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
12549                 parseList[moveNumber], res);\r
12550         DisplayMessage(message, cpThinkOutput);\r
12551     }\r
12552 }\r
12553 \r
12554 void\r
12555 DisplayAnalysisText(text)\r
12556      char *text;\r
12557 {\r
12558     char buf[MSG_SIZ];\r
12559 \r
12560     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12561         sprintf(buf, "Analysis (%s)", first.tidy);\r
12562         AnalysisPopUp(buf, text);\r
12563     }\r
12564 }\r
12565 \r
12566 static int\r
12567 only_one_move(str)\r
12568      char *str;\r
12569 {\r
12570     while (*str && isspace(*str)) ++str;\r
12571     while (*str && !isspace(*str)) ++str;\r
12572     if (!*str) return 1;\r
12573     while (*str && isspace(*str)) ++str;\r
12574     if (!*str) return 1;\r
12575     return 0;\r
12576 }\r
12577 \r
12578 void\r
12579 DisplayAnalysis()\r
12580 {\r
12581     char buf[MSG_SIZ];\r
12582     char lst[MSG_SIZ / 2];\r
12583     double nps;\r
12584     static char *xtra[] = { "", " (--)", " (++)" };\r
12585     int h, m, s, cs;\r
12586   \r
12587     if (programStats.time == 0) {\r
12588         programStats.time = 1;\r
12589     }\r
12590   \r
12591     if (programStats.got_only_move) {\r
12592         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
12593     } else {\r
12594         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
12595 \r
12596         nps = (((double)programStats.nodes) /\r
12597                (((double)programStats.time)/100.0));\r
12598 \r
12599         cs = programStats.time % 100;\r
12600         s = programStats.time / 100;\r
12601         h = (s / (60*60));\r
12602         s = s - h*60*60;\r
12603         m = (s/60);\r
12604         s = s - m*60;\r
12605 \r
12606         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
12607           if (programStats.move_name[0] != NULLCHAR) {\r
12608             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12609                     programStats.depth,\r
12610                     programStats.nr_moves-programStats.moves_left,\r
12611                     programStats.nr_moves, programStats.move_name,\r
12612                     ((float)programStats.score)/100.0, lst,\r
12613                     only_one_move(lst)?\r
12614                     xtra[programStats.got_fail] : "",\r
12615                     programStats.nodes, (int)nps, h, m, s, cs);\r
12616           } else {\r
12617             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12618                     programStats.depth,\r
12619                     programStats.nr_moves-programStats.moves_left,\r
12620                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
12621                     lst,\r
12622                     only_one_move(lst)?\r
12623                     xtra[programStats.got_fail] : "",\r
12624                     programStats.nodes, (int)nps, h, m, s, cs);\r
12625           }\r
12626         } else {\r
12627             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12628                     programStats.depth,\r
12629                     ((float)programStats.score)/100.0,\r
12630                     lst,\r
12631                     only_one_move(lst)?\r
12632                     xtra[programStats.got_fail] : "",\r
12633                     programStats.nodes, (int)nps, h, m, s, cs);\r
12634         }\r
12635     }\r
12636     DisplayAnalysisText(buf);\r
12637 }\r
12638 \r
12639 void\r
12640 DisplayComment(moveNumber, text)\r
12641      int moveNumber;\r
12642      char *text;\r
12643 {\r
12644     char title[MSG_SIZ];\r
12645     char buf[8000]; // comment can be long!\r
12646     int score, depth;\r
12647 \r
12648     if( appData.autoDisplayComment ) {\r
12649         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12650             strcpy(title, "Comment");\r
12651         } else {\r
12652             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
12653                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
12654                     parseList[moveNumber]);\r
12655         }\r
12656     } else title[0] = 0;\r
12657 \r
12658     // [HGM] PV info: display PV info together with (or as) comment\r
12659     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
12660         if(text == NULL) text = "";                                           \r
12661         score = pvInfoList[moveNumber].score;\r
12662         sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
12663                               depth, (pvInfoList[moveNumber].time+50)/100, text);\r
12664         CommentPopUp(title, buf);\r
12665     } else\r
12666     if (text != NULL)\r
12667         CommentPopUp(title, text);\r
12668 }\r
12669 \r
12670 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
12671  * might be busy thinking or pondering.  It can be omitted if your\r
12672  * gnuchess is configured to stop thinking immediately on any user\r
12673  * input.  However, that gnuchess feature depends on the FIONREAD\r
12674  * ioctl, which does not work properly on some flavors of Unix.\r
12675  */\r
12676 void\r
12677 Attention(cps)\r
12678      ChessProgramState *cps;\r
12679 {\r
12680 #if ATTENTION\r
12681     if (!cps->useSigint) return;\r
12682     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
12683     switch (gameMode) {\r
12684       case MachinePlaysWhite:\r
12685       case MachinePlaysBlack:\r
12686       case TwoMachinesPlay:\r
12687       case IcsPlayingWhite:\r
12688       case IcsPlayingBlack:\r
12689       case AnalyzeMode:\r
12690       case AnalyzeFile:\r
12691         /* Skip if we know it isn't thinking */\r
12692         if (!cps->maybeThinking) return;\r
12693         if (appData.debugMode)\r
12694           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
12695         InterruptChildProcess(cps->pr);\r
12696         cps->maybeThinking = FALSE;\r
12697         break;\r
12698       default:\r
12699         break;\r
12700     }\r
12701 #endif /*ATTENTION*/\r
12702 }\r
12703 \r
12704 int\r
12705 CheckFlags()\r
12706 {\r
12707     if (whiteTimeRemaining <= 0) {\r
12708         if (!whiteFlag) {\r
12709             whiteFlag = TRUE;\r
12710             if (appData.icsActive) {\r
12711                 if (appData.autoCallFlag &&\r
12712                     gameMode == IcsPlayingBlack && !blackFlag) {\r
12713                   SendToICS(ics_prefix);\r
12714                   SendToICS("flag\n");\r
12715                 }\r
12716             } else {\r
12717                 if (blackFlag) {\r
12718                     if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");\r
12719                 } else {\r
12720                     if(gameMode != TwoMachinesPlay) DisplayTitle("White's flag fell");\r
12721                     if (appData.autoCallFlag) {\r
12722                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
12723                         return TRUE;\r
12724                     }\r
12725                 }\r
12726             }\r
12727         }\r
12728     }\r
12729     if (blackTimeRemaining <= 0) {\r
12730         if (!blackFlag) {\r
12731             blackFlag = TRUE;\r
12732             if (appData.icsActive) {\r
12733                 if (appData.autoCallFlag &&\r
12734                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
12735                   SendToICS(ics_prefix);\r
12736                   SendToICS("flag\n");\r
12737                 }\r
12738             } else {\r
12739                 if (whiteFlag) {\r
12740                     if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");\r
12741                 } else {\r
12742                     if(gameMode != TwoMachinesPlay) DisplayTitle("Black's flag fell");\r
12743                     if (appData.autoCallFlag) {\r
12744                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
12745                         return TRUE;\r
12746                     }\r
12747                 }\r
12748             }\r
12749         }\r
12750     }\r
12751     return FALSE;\r
12752 }\r
12753 \r
12754 void\r
12755 CheckTimeControl()\r
12756 {\r
12757     if (!appData.clockMode || appData.icsActive ||\r
12758         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
12759 \r
12760     /*\r
12761      * add time to clocks when time control is achieved ([HGM] now also used for increment)\r
12762      */\r
12763     if ( !WhiteOnMove(forwardMostMove) )\r
12764         /* White made time control */\r
12765         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12766         /* [HGM] time odds: correct new time quota for time odds! */\r
12767                                             / WhitePlayer()->timeOdds;\r
12768       else\r
12769         /* Black made time control */\r
12770         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12771                                             / WhitePlayer()->other->timeOdds;\r
12772 }\r
12773 \r
12774 void\r
12775 DisplayBothClocks()\r
12776 {\r
12777     int wom = gameMode == EditPosition ?\r
12778       !blackPlaysFirst : WhiteOnMove(currentMove);\r
12779     DisplayWhiteClock(whiteTimeRemaining, wom);\r
12780     DisplayBlackClock(blackTimeRemaining, !wom);\r
12781 }\r
12782 \r
12783 \r
12784 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
12785    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
12786    to use other calls if you don't.  Clocks will be less accurate if\r
12787    you have neither ftime nor gettimeofday.\r
12788 */\r
12789 \r
12790 /* Get the current time as a TimeMark */\r
12791 void\r
12792 GetTimeMark(tm)\r
12793      TimeMark *tm;\r
12794 {\r
12795 #if HAVE_GETTIMEOFDAY\r
12796 \r
12797     struct timeval timeVal;\r
12798     struct timezone timeZone;\r
12799 \r
12800     gettimeofday(&timeVal, &timeZone);\r
12801     tm->sec = (long) timeVal.tv_sec; \r
12802     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
12803 \r
12804 #else /*!HAVE_GETTIMEOFDAY*/\r
12805 #if HAVE_FTIME\r
12806 \r
12807 #include <sys/timeb.h>\r
12808     struct timeb timeB;\r
12809 \r
12810     ftime(&timeB);\r
12811     tm->sec = (long) timeB.time;\r
12812     tm->ms = (int) timeB.millitm;\r
12813 \r
12814 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
12815     tm->sec = (long) time(NULL);\r
12816     tm->ms = 0;\r
12817 #endif\r
12818 #endif\r
12819 }\r
12820 \r
12821 /* Return the difference in milliseconds between two\r
12822    time marks.  We assume the difference will fit in a long!\r
12823 */\r
12824 long\r
12825 SubtractTimeMarks(tm2, tm1)\r
12826      TimeMark *tm2, *tm1;\r
12827 {\r
12828     return 1000L*(tm2->sec - tm1->sec) +\r
12829            (long) (tm2->ms - tm1->ms);\r
12830 }\r
12831 \r
12832 \r
12833 /*\r
12834  * Code to manage the game clocks.\r
12835  *\r
12836  * In tournament play, black starts the clock and then white makes a move.\r
12837  * We give the human user a slight advantage if he is playing white---the\r
12838  * clocks don't run until he makes his first move, so it takes zero time.\r
12839  * Also, we don't account for network lag, so we could get out of sync\r
12840  * with GNU Chess's clock -- but then, referees are always right.  \r
12841  */\r
12842 \r
12843 static TimeMark tickStartTM;\r
12844 static long intendedTickLength;\r
12845 \r
12846 long\r
12847 NextTickLength(timeRemaining)\r
12848      long timeRemaining;\r
12849 {\r
12850     long nominalTickLength, nextTickLength;\r
12851 \r
12852     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
12853       nominalTickLength = 100L;\r
12854     else\r
12855       nominalTickLength = 1000L;\r
12856     nextTickLength = timeRemaining % nominalTickLength;\r
12857     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
12858 \r
12859     return nextTickLength;\r
12860 }\r
12861 \r
12862 /* Adjust clock one minute up or down */\r
12863 void\r
12864 AdjustClock(Boolean which, int dir)\r
12865 {\r
12866     if(which) blackTimeRemaining += 60000*dir;\r
12867     else      whiteTimeRemaining += 60000*dir;\r
12868     DisplayBothClocks();\r
12869 }\r
12870 \r
12871 /* Stop clocks and reset to a fresh time control */\r
12872 void\r
12873 ResetClocks() \r
12874 {\r
12875     (void) StopClockTimer();\r
12876     if (appData.icsActive) {\r
12877         whiteTimeRemaining = blackTimeRemaining = 0;\r
12878     } else { /* [HGM] correct new time quote for time odds */\r
12879         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
12880         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
12881     }\r
12882     if (whiteFlag || blackFlag) {\r
12883         DisplayTitle("");\r
12884         whiteFlag = blackFlag = FALSE;\r
12885     }\r
12886     DisplayBothClocks();\r
12887 }\r
12888 \r
12889 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
12890 \r
12891 /* Decrement running clock by amount of time that has passed */\r
12892 void\r
12893 DecrementClocks()\r
12894 {\r
12895     long timeRemaining;\r
12896     long lastTickLength, fudge;\r
12897     TimeMark now;\r
12898 \r
12899     if (!appData.clockMode) return;\r
12900     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
12901         \r
12902     GetTimeMark(&now);\r
12903 \r
12904     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
12905 \r
12906     /* Fudge if we woke up a little too soon */\r
12907     fudge = intendedTickLength - lastTickLength;\r
12908     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
12909 \r
12910     if (WhiteOnMove(forwardMostMove)) {\r
12911         if(whiteNPS >= 0) lastTickLength = 0;\r
12912         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
12913         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
12914                           WhiteOnMove(currentMove));\r
12915     } else {\r
12916         if(blackNPS >= 0) lastTickLength = 0;\r
12917         timeRemaining = blackTimeRemaining -= lastTickLength;\r
12918         DisplayBlackClock(blackTimeRemaining - fudge,\r
12919                           !WhiteOnMove(currentMove));\r
12920     }\r
12921 \r
12922     if (CheckFlags()) return;\r
12923         \r
12924     tickStartTM = now;\r
12925     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
12926     StartClockTimer(intendedTickLength);\r
12927 \r
12928     /* if the time remaining has fallen below the alarm threshold, sound the\r
12929      * alarm. if the alarm has sounded and (due to a takeback or time control\r
12930      * with increment) the time remaining has increased to a level above the\r
12931      * threshold, reset the alarm so it can sound again. \r
12932      */\r
12933     \r
12934     if (appData.icsActive && appData.icsAlarm) {\r
12935 \r
12936         /* make sure we are dealing with the user's clock */\r
12937         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
12938                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
12939            )) return;\r
12940 \r
12941         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
12942             alarmSounded = FALSE;\r
12943         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
12944             PlayAlarmSound();\r
12945             alarmSounded = TRUE;\r
12946         }\r
12947     }\r
12948 }\r
12949 \r
12950 \r
12951 /* A player has just moved, so stop the previously running\r
12952    clock and (if in clock mode) start the other one.\r
12953    We redisplay both clocks in case we're in ICS mode, because\r
12954    ICS gives us an update to both clocks after every move.\r
12955    Note that this routine is called *after* forwardMostMove\r
12956    is updated, so the last fractional tick must be subtracted\r
12957    from the color that is *not* on move now.\r
12958 */\r
12959 void\r
12960 SwitchClocks()\r
12961 {\r
12962     long lastTickLength;\r
12963     TimeMark now;\r
12964     int flagged = FALSE;\r
12965 \r
12966     GetTimeMark(&now);\r
12967 \r
12968     if (StopClockTimer() && appData.clockMode) {\r
12969         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
12970         if (WhiteOnMove(forwardMostMove)) {\r
12971             if(blackNPS >= 0) lastTickLength = 0;\r
12972             blackTimeRemaining -= lastTickLength;\r
12973            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
12974 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
12975                  pvInfoList[forwardMostMove-1].time =               // use GUI time\r
12976                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
12977         } else {\r
12978            if(whiteNPS >= 0) lastTickLength = 0;\r
12979            whiteTimeRemaining -= lastTickLength;\r
12980            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
12981 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
12982                  pvInfoList[forwardMostMove-1].time = \r
12983                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
12984         }\r
12985         flagged = CheckFlags();\r
12986     }\r
12987     CheckTimeControl();\r
12988 \r
12989     if (flagged || !appData.clockMode) return;\r
12990 \r
12991     switch (gameMode) {\r
12992       case MachinePlaysBlack:\r
12993       case MachinePlaysWhite:\r
12994       case BeginningOfGame:\r
12995         if (pausing) return;\r
12996         break;\r
12997 \r
12998       case EditGame:\r
12999       case PlayFromGameFile:\r
13000       case IcsExamining:\r
13001         return;\r
13002 \r
13003       default:\r
13004         break;\r
13005     }\r
13006 \r
13007     tickStartTM = now;\r
13008     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13009       whiteTimeRemaining : blackTimeRemaining);\r
13010     StartClockTimer(intendedTickLength);\r
13011 }\r
13012         \r
13013 \r
13014 /* Stop both clocks */\r
13015 void\r
13016 StopClocks()\r
13017 {       \r
13018     long lastTickLength;\r
13019     TimeMark now;\r
13020 \r
13021     if (!StopClockTimer()) return;\r
13022     if (!appData.clockMode) return;\r
13023 \r
13024     GetTimeMark(&now);\r
13025 \r
13026     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13027     if (WhiteOnMove(forwardMostMove)) {\r
13028         if(whiteNPS >= 0) lastTickLength = 0;\r
13029         whiteTimeRemaining -= lastTickLength;\r
13030         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
13031     } else {\r
13032         if(blackNPS >= 0) lastTickLength = 0;\r
13033         blackTimeRemaining -= lastTickLength;\r
13034         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
13035     }\r
13036     CheckFlags();\r
13037 }\r
13038         \r
13039 /* Start clock of player on move.  Time may have been reset, so\r
13040    if clock is already running, stop and restart it. */\r
13041 void\r
13042 StartClocks()\r
13043 {\r
13044     (void) StopClockTimer(); /* in case it was running already */\r
13045     DisplayBothClocks();\r
13046     if (CheckFlags()) return;\r
13047 \r
13048     if (!appData.clockMode) return;\r
13049     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
13050 \r
13051     GetTimeMark(&tickStartTM);\r
13052     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13053       whiteTimeRemaining : blackTimeRemaining);\r
13054 \r
13055    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
13056     whiteNPS = blackNPS = -1; \r
13057     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
13058        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
13059         whiteNPS = first.nps;\r
13060     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
13061        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
13062         blackNPS = first.nps;\r
13063     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
13064         whiteNPS = second.nps;\r
13065     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
13066         blackNPS = second.nps;\r
13067     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
13068 \r
13069     StartClockTimer(intendedTickLength);\r
13070 }\r
13071 \r
13072 char *\r
13073 TimeString(ms)\r
13074      long ms;\r
13075 {\r
13076     long second, minute, hour, day;\r
13077     char *sign = "";\r
13078     static char buf[32];\r
13079     \r
13080     if (ms > 0 && ms <= 9900) {\r
13081       /* convert milliseconds to tenths, rounding up */\r
13082       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
13083 \r
13084       sprintf(buf, " %03.1f ", tenths/10.0);\r
13085       return buf;\r
13086     }\r
13087 \r
13088     /* convert milliseconds to seconds, rounding up */\r
13089     /* use floating point to avoid strangeness of integer division\r
13090        with negative dividends on many machines */\r
13091     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
13092 \r
13093     if (second < 0) {\r
13094         sign = "-";\r
13095         second = -second;\r
13096     }\r
13097     \r
13098     day = second / (60 * 60 * 24);\r
13099     second = second % (60 * 60 * 24);\r
13100     hour = second / (60 * 60);\r
13101     second = second % (60 * 60);\r
13102     minute = second / 60;\r
13103     second = second % 60;\r
13104     \r
13105     if (day > 0)\r
13106       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
13107               sign, day, hour, minute, second);\r
13108     else if (hour > 0)\r
13109       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
13110     else\r
13111       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
13112     \r
13113     return buf;\r
13114 }\r
13115 \r
13116 \r
13117 /*\r
13118  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
13119  */\r
13120 char *\r
13121 StrStr(string, match)\r
13122      char *string, *match;\r
13123 {\r
13124     int i, length;\r
13125     \r
13126     length = strlen(match);\r
13127     \r
13128     for (i = strlen(string) - length; i >= 0; i--, string++)\r
13129       if (!strncmp(match, string, length))\r
13130         return string;\r
13131     \r
13132     return NULL;\r
13133 }\r
13134 \r
13135 char *\r
13136 StrCaseStr(string, match)\r
13137      char *string, *match;\r
13138 {\r
13139     int i, j, length;\r
13140     \r
13141     length = strlen(match);\r
13142     \r
13143     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
13144         for (j = 0; j < length; j++) {\r
13145             if (ToLower(match[j]) != ToLower(string[j]))\r
13146               break;\r
13147         }\r
13148         if (j == length) return string;\r
13149     }\r
13150 \r
13151     return NULL;\r
13152 }\r
13153 \r
13154 #ifndef _amigados\r
13155 int\r
13156 StrCaseCmp(s1, s2)\r
13157      char *s1, *s2;\r
13158 {\r
13159     char c1, c2;\r
13160     \r
13161     for (;;) {\r
13162         c1 = ToLower(*s1++);\r
13163         c2 = ToLower(*s2++);\r
13164         if (c1 > c2) return 1;\r
13165         if (c1 < c2) return -1;\r
13166         if (c1 == NULLCHAR) return 0;\r
13167     }\r
13168 }\r
13169 \r
13170 \r
13171 int\r
13172 ToLower(c)\r
13173      int c;\r
13174 {\r
13175     return isupper(c) ? tolower(c) : c;\r
13176 }\r
13177 \r
13178 \r
13179 int\r
13180 ToUpper(c)\r
13181      int c;\r
13182 {\r
13183     return islower(c) ? toupper(c) : c;\r
13184 }\r
13185 #endif /* !_amigados    */\r
13186 \r
13187 char *\r
13188 StrSave(s)\r
13189      char *s;\r
13190 {\r
13191     char *ret;\r
13192 \r
13193     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
13194         strcpy(ret, s);\r
13195     }\r
13196     return ret;\r
13197 }\r
13198 \r
13199 char *\r
13200 StrSavePtr(s, savePtr)\r
13201      char *s, **savePtr;\r
13202 {\r
13203     if (*savePtr) {\r
13204         free(*savePtr);\r
13205     }\r
13206     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
13207         strcpy(*savePtr, s);\r
13208     }\r
13209     return(*savePtr);\r
13210 }\r
13211 \r
13212 char *\r
13213 PGNDate()\r
13214 {\r
13215     time_t clock;\r
13216     struct tm *tm;\r
13217     char buf[MSG_SIZ];\r
13218 \r
13219     clock = time((time_t *)NULL);\r
13220     tm = localtime(&clock);\r
13221     sprintf(buf, "%04d.%02d.%02d",\r
13222             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
13223     return StrSave(buf);\r
13224 }\r
13225 \r
13226 \r
13227 char *\r
13228 PositionToFEN(move, useFEN960)\r
13229      int move;\r
13230      int useFEN960;\r
13231 {\r
13232     int i, j, fromX, fromY, toX, toY;\r
13233     int whiteToPlay;\r
13234     char buf[128];\r
13235     char *p, *q;\r
13236     int emptycount;\r
13237     ChessSquare piece;\r
13238 \r
13239     whiteToPlay = (gameMode == EditPosition) ?\r
13240       !blackPlaysFirst : (move % 2 == 0);\r
13241     p = buf;\r
13242 \r
13243     /* Piece placement data */\r
13244     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13245         emptycount = 0;\r
13246         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
13247             if (boards[move][i][j] == EmptySquare) {\r
13248                 emptycount++;\r
13249             } else { ChessSquare piece = boards[move][i][j];\r
13250                 if (emptycount > 0) {\r
13251                     if(emptycount<10) /* [HGM] can be >= 10 */\r
13252                         *p++ = '0' + emptycount;\r
13253                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13254                     emptycount = 0;\r
13255                 }\r
13256                 if(PieceToChar(piece) == '+') {\r
13257                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
13258                     *p++ = '+';\r
13259                     piece = (ChessSquare)(DEMOTED piece);\r
13260                 } \r
13261                 *p++ = PieceToChar(piece);\r
13262                 if(p[-1] == '~') {\r
13263                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
13264                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
13265                     *p++ = '~';\r
13266                 }\r
13267             }\r
13268         }\r
13269         if (emptycount > 0) {\r
13270             if(emptycount<10) /* [HGM] can be >= 10 */\r
13271                 *p++ = '0' + emptycount;\r
13272             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13273             emptycount = 0;\r
13274         }\r
13275         *p++ = '/';\r
13276     }\r
13277     *(p - 1) = ' ';\r
13278 \r
13279     /* [HGM] print Crazyhouse or Shogi holdings */\r
13280     if( gameInfo.holdingsWidth ) {\r
13281         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
13282         q = p;\r
13283         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
13284             piece = boards[move][i][BOARD_WIDTH-1];\r
13285             if( piece != EmptySquare )\r
13286               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
13287                   *p++ = PieceToChar(piece);\r
13288         }\r
13289         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
13290             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
13291             if( piece != EmptySquare )\r
13292               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
13293                   *p++ = PieceToChar(piece);\r
13294         }\r
13295 \r
13296         if( q == p ) *p++ = '-';\r
13297         *p++ = ']';\r
13298         *p++ = ' ';\r
13299     }\r
13300 \r
13301     /* Active color */\r
13302     *p++ = whiteToPlay ? 'w' : 'b';\r
13303     *p++ = ' ';\r
13304 \r
13305   if(nrCastlingRights) {\r
13306      q = p;\r
13307      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
13308        /* [HGM] write directly from rights */\r
13309            if(castlingRights[move][2] >= 0 &&\r
13310               castlingRights[move][0] >= 0   )\r
13311                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
13312            if(castlingRights[move][2] >= 0 &&\r
13313               castlingRights[move][1] >= 0   )\r
13314                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
13315            if(castlingRights[move][5] >= 0 &&\r
13316               castlingRights[move][3] >= 0   )\r
13317                 *p++ = castlingRights[move][3] + AAA;\r
13318            if(castlingRights[move][5] >= 0 &&\r
13319               castlingRights[move][4] >= 0   )\r
13320                 *p++ = castlingRights[move][4] + AAA;\r
13321      } else {\r
13322 \r
13323         /* [HGM] write true castling rights */\r
13324         if( nrCastlingRights == 6 ) {\r
13325             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
13326                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
13327             if(castlingRights[move][1] == BOARD_LEFT &&\r
13328                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
13329             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
13330                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
13331             if(castlingRights[move][4] == BOARD_LEFT &&\r
13332                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
13333         }\r
13334      }\r
13335      if (q == p) *p++ = '-'; /* No castling rights */\r
13336      *p++ = ' ';\r
13337   }\r
13338 \r
13339   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13340      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13341     /* En passant target square */\r
13342     if (move > backwardMostMove) {\r
13343         fromX = moveList[move - 1][0] - AAA;\r
13344         fromY = moveList[move - 1][1] - ONE;\r
13345         toX = moveList[move - 1][2] - AAA;\r
13346         toY = moveList[move - 1][3] - ONE;\r
13347         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
13348             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
13349             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
13350             fromX == toX) {\r
13351             /* 2-square pawn move just happened */\r
13352             *p++ = toX + AAA;\r
13353             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
13354         } else {\r
13355             *p++ = '-';\r
13356         }\r
13357     } else {\r
13358         *p++ = '-';\r
13359     }\r
13360     *p++ = ' ';\r
13361   }\r
13362 \r
13363     /* [HGM] find reversible plies */\r
13364     {   int i = 0, j=move;\r
13365 \r
13366         if (appData.debugMode) { int k;\r
13367             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
13368             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
13369                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
13370 \r
13371         }\r
13372 \r
13373         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
13374         if( j == backwardMostMove ) i += initialRulePlies;\r
13375         sprintf(p, "%d ", i);\r
13376         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
13377     }\r
13378     /* Fullmove number */\r
13379     sprintf(p, "%d", (move / 2) + 1);\r
13380     \r
13381     return StrSave(buf);\r
13382 }\r
13383 \r
13384 Boolean\r
13385 ParseFEN(board, blackPlaysFirst, fen)\r
13386     Board board;\r
13387      int *blackPlaysFirst;\r
13388      char *fen;\r
13389 {\r
13390     int i, j;\r
13391     char *p;\r
13392     int emptycount;\r
13393     ChessSquare piece;\r
13394 \r
13395     p = fen;\r
13396 \r
13397     /* [HGM] by default clear Crazyhouse holdings, if present */\r
13398     if(gameInfo.holdingsWidth) {\r
13399        for(i=0; i<BOARD_HEIGHT; i++) {\r
13400            board[i][0]             = EmptySquare; /* black holdings */\r
13401            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
13402            board[i][1]             = (ChessSquare) 0; /* black counts */\r
13403            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
13404        }\r
13405     }\r
13406 \r
13407     /* Piece placement data */\r
13408     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13409         j = 0;\r
13410         for (;;) {\r
13411             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
13412                 if (*p == '/') p++;\r
13413                 emptycount = gameInfo.boardWidth - j;\r
13414                 while (emptycount--)\r
13415                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13416                 break;\r
13417 #if(BOARD_SIZE >= 10)\r
13418             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
13419                 p++; emptycount=10;\r
13420                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13421                 while (emptycount--)\r
13422                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13423 #endif\r
13424             } else if (isdigit(*p)) {\r
13425                 emptycount = *p++ - '0';\r
13426                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
13427                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13428                 while (emptycount--)\r
13429                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13430             } else if (*p == '+' || isalpha(*p)) {\r
13431                 if (j >= gameInfo.boardWidth) return FALSE;\r
13432                 if(*p=='+') {\r
13433                     piece = CharToPiece(*++p);\r
13434                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
13435                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
13436                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
13437                 } else piece = CharToPiece(*p++);\r
13438 \r
13439                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
13440                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
13441                     piece = (ChessSquare) (PROMOTED piece);\r
13442                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
13443                     p++;\r
13444                 }\r
13445                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
13446             } else {\r
13447                 return FALSE;\r
13448             }\r
13449         }\r
13450     }\r
13451     while (*p == '/' || *p == ' ') p++;\r
13452 \r
13453     /* [HGM] look for Crazyhouse holdings here */\r
13454     while(*p==' ') p++;\r
13455     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
13456         if(*p == '[') p++;\r
13457         if(*p == '-' ) *p++; /* empty holdings */ else {\r
13458             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
13459             /* if we would allow FEN reading to set board size, we would   */\r
13460             /* have to add holdings and shift the board read so far here   */\r
13461             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
13462                 *p++;\r
13463                 if((int) piece >= (int) BlackPawn ) {\r
13464                     i = (int)piece - (int)BlackPawn;\r
13465                     i = PieceToNumber((ChessSquare)i);\r
13466                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13467                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
13468                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
13469                 } else {\r
13470                     i = (int)piece - (int)WhitePawn;\r
13471                     i = PieceToNumber((ChessSquare)i);\r
13472                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13473                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
13474                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
13475                 }\r
13476             }\r
13477         }\r
13478         if(*p == ']') *p++;\r
13479     }\r
13480 \r
13481     while(*p == ' ') p++;\r
13482 \r
13483     /* Active color */\r
13484     switch (*p++) {\r
13485       case 'w':\r
13486         *blackPlaysFirst = FALSE;\r
13487         break;\r
13488       case 'b': \r
13489         *blackPlaysFirst = TRUE;\r
13490         break;\r
13491       default:\r
13492         return FALSE;\r
13493     }\r
13494 \r
13495     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
13496     /* return the extra info in global variiables             */\r
13497 \r
13498     /* set defaults in case FEN is incomplete */\r
13499     FENepStatus = EP_UNKNOWN;\r
13500     for(i=0; i<nrCastlingRights; i++ ) {\r
13501         FENcastlingRights[i] =\r
13502             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
13503     }   /* assume possible unless obviously impossible */\r
13504     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
13505     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
13506     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
13507     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
13508     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
13509     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
13510     FENrulePlies = 0;\r
13511 \r
13512     while(*p==' ') p++;\r
13513     if(nrCastlingRights) {\r
13514       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
13515           /* castling indicator present, so default becomes no castlings */\r
13516           for(i=0; i<nrCastlingRights; i++ ) {\r
13517                  FENcastlingRights[i] = -1;\r
13518           }\r
13519       }\r
13520       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
13521              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
13522              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
13523              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
13524         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
13525 \r
13526         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
13527             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
13528             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
13529         }\r
13530         switch(c) {\r
13531           case'K':\r
13532               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
13533               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
13534               FENcastlingRights[2] = whiteKingFile;\r
13535               break;\r
13536           case'Q':\r
13537               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
13538               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
13539               FENcastlingRights[2] = whiteKingFile;\r
13540               break;\r
13541           case'k':\r
13542               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
13543               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
13544               FENcastlingRights[5] = blackKingFile;\r
13545               break;\r
13546           case'q':\r
13547               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
13548               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
13549               FENcastlingRights[5] = blackKingFile;\r
13550           case '-':\r
13551               break;\r
13552           default: /* FRC castlings */\r
13553               if(c >= 'a') { /* black rights */\r
13554                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13555                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
13556                   if(i == BOARD_RGHT) break;\r
13557                   FENcastlingRights[5] = i;\r
13558                   c -= AAA;\r
13559                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
13560                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
13561                   if(c > i)\r
13562                       FENcastlingRights[3] = c;\r
13563                   else\r
13564                       FENcastlingRights[4] = c;\r
13565               } else { /* white rights */\r
13566                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13567                     if(board[0][i] == WhiteKing) break;\r
13568                   if(i == BOARD_RGHT) break;\r
13569                   FENcastlingRights[2] = i;\r
13570                   c -= AAA - 'a' + 'A';\r
13571                   if(board[0][c] >= WhiteKing) break;\r
13572                   if(c > i)\r
13573                       FENcastlingRights[0] = c;\r
13574                   else\r
13575                       FENcastlingRights[1] = c;\r
13576               }\r
13577         }\r
13578       }\r
13579     if (appData.debugMode) {\r
13580         fprintf(debugFP, "FEN castling rights:");\r
13581         for(i=0; i<nrCastlingRights; i++)\r
13582         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
13583         fprintf(debugFP, "\n");\r
13584     }\r
13585 \r
13586       while(*p==' ') p++;\r
13587     }\r
13588 \r
13589     /* read e.p. field in games that know e.p. capture */\r
13590     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13591        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13592       if(*p=='-') {\r
13593         p++; FENepStatus = EP_NONE;\r
13594       } else {\r
13595          char c = *p++ - AAA;\r
13596 \r
13597          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
13598          if(*p >= '0' && *p <='9') *p++;\r
13599          FENepStatus = c;\r
13600       }\r
13601     }\r
13602 \r
13603 \r
13604     if(sscanf(p, "%d", &i) == 1) {\r
13605         FENrulePlies = i; /* 50-move ply counter */\r
13606         /* (The move number is still ignored)    */\r
13607     }\r
13608 \r
13609     return TRUE;\r
13610 }\r
13611       \r
13612 void\r
13613 EditPositionPasteFEN(char *fen)\r
13614 {\r
13615   if (fen != NULL) {\r
13616     Board initial_position;\r
13617 \r
13618     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
13619       DisplayError("Bad FEN position in clipboard", 0);\r
13620       return ;\r
13621     } else {\r
13622       int savedBlackPlaysFirst = blackPlaysFirst;\r
13623       EditPositionEvent();\r
13624       blackPlaysFirst = savedBlackPlaysFirst;\r
13625       CopyBoard(boards[0], initial_position);\r
13626           /* [HGM] copy FEN attributes as well */\r
13627           {   int i;\r
13628               initialRulePlies = FENrulePlies;\r
13629               epStatus[0] = FENepStatus;\r
13630               for( i=0; i<nrCastlingRights; i++ )\r
13631                   castlingRights[0][i] = FENcastlingRights[i];\r
13632           }\r
13633       EditPositionDone();\r
13634       DisplayBothClocks();\r
13635       DrawPosition(FALSE, boards[currentMove]);\r
13636     }\r
13637   }\r
13638 }\r