added firstOptions, secondOptions, noGUI, firstXBook, secondXBook
[xboard.git] / backend.c
1 /*\r
2  * backend.c -- Common back end for X and Windows NT versions of\r
3  * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $\r
4  *\r
5  * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.\r
6  * Enhancements Copyright 1992-2001 Free Software Foundation, Inc.\r
7  *\r
8  * The following terms apply to Digital Equipment Corporation's copyright\r
9  * interest in XBoard:\r
10  * ------------------------------------------------------------------------\r
11  * All Rights Reserved\r
12  *\r
13  * Permission to use, copy, modify, and distribute this software and its\r
14  * documentation for any purpose and without fee is hereby granted,\r
15  * provided that the above copyright notice appear in all copies and that\r
16  * both that copyright notice and this permission notice appear in\r
17  * supporting documentation, and that the name of Digital not be\r
18  * used in advertising or publicity pertaining to distribution of the\r
19  * software without specific, written prior permission.\r
20  *\r
21  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING\r
22  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL\r
23  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR\r
24  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,\r
25  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\r
26  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS\r
27  * SOFTWARE.\r
28  * ------------------------------------------------------------------------\r
29  *\r
30  * The following terms apply to the enhanced version of XBoard distributed\r
31  * by the Free Software Foundation:\r
32  * ------------------------------------------------------------------------\r
33  * This program is free software; you can redistribute it and/or modify\r
34  * it under the terms of the GNU General Public License as published by\r
35  * the Free Software Foundation; either version 2 of the License, or\r
36  * (at your option) any later version.\r
37  *\r
38  * This program is distributed in the hope that it will be useful,\r
39  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
40  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
41  * GNU General Public License for more details.\r
42  *\r
43  * You should have received a copy of the GNU General Public License\r
44  * along with this program; if not, write to the Free Software\r
45  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.\r
46  * ------------------------------------------------------------------------\r
47  *\r
48  * See the file ChangeLog for a revision history.  */\r
49 \r
50 /* [AS] Also useful here for debugging */\r
51 #ifdef WIN32\r
52 #include <windows.h>\r
53 \r
54 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );\r
55 \r
56 #else\r
57 \r
58 #define DoSleep( n ) if( (n) >= 0) sleep(n)\r
59 \r
60 #endif\r
61 \r
62 #include "config.h"\r
63 \r
64 #include <assert.h>\r
65 #include <stdio.h>\r
66 #include <ctype.h>\r
67 #include <errno.h>\r
68 #include <sys/types.h>\r
69 #include <sys/stat.h>\r
70 #include <math.h>\r
71 \r
72 #if STDC_HEADERS\r
73 # include <stdlib.h>\r
74 # include <string.h>\r
75 #else /* not STDC_HEADERS */\r
76 # if HAVE_STRING_H\r
77 #  include <string.h>\r
78 # else /* not HAVE_STRING_H */\r
79 #  include <strings.h>\r
80 # endif /* not HAVE_STRING_H */\r
81 #endif /* not STDC_HEADERS */\r
82 \r
83 #if HAVE_SYS_FCNTL_H\r
84 # include <sys/fcntl.h>\r
85 #else /* not HAVE_SYS_FCNTL_H */\r
86 # if HAVE_FCNTL_H\r
87 #  include <fcntl.h>\r
88 # endif /* HAVE_FCNTL_H */\r
89 #endif /* not HAVE_SYS_FCNTL_H */\r
90 \r
91 #if TIME_WITH_SYS_TIME\r
92 # include <sys/time.h>\r
93 # include <time.h>\r
94 #else\r
95 # if HAVE_SYS_TIME_H\r
96 #  include <sys/time.h>\r
97 # else\r
98 #  include <time.h>\r
99 # endif\r
100 #endif\r
101 \r
102 #if defined(_amigados) && !defined(__GNUC__)\r
103 struct timezone {\r
104     int tz_minuteswest;\r
105     int tz_dsttime;\r
106 };\r
107 extern int gettimeofday(struct timeval *, struct timezone *);\r
108 #endif\r
109 \r
110 #if HAVE_UNISTD_H\r
111 # include <unistd.h>\r
112 #endif\r
113 \r
114 #include "common.h"\r
115 #include "frontend.h"\r
116 #include "backend.h"\r
117 #include "parser.h"\r
118 #include "moves.h"\r
119 #if ZIPPY\r
120 # include "zippy.h"\r
121 #endif\r
122 #include "backendz.h"\r
123 #include "gettext.h" \r
124  \r
125 #ifdef ENABLE_NLS \r
126 # define _(s) gettext (s) \r
127 # define N_(s) gettext_noop (s) \r
128 #else \r
129 # define _(s) (s) \r
130 # define N_(s) s \r
131 #endif \r
132 \r
133 \r
134 /* A point in time */\r
135 typedef struct {\r
136     long sec;  /* Assuming this is >= 32 bits */\r
137     int ms;    /* Assuming this is >= 16 bits */\r
138 } TimeMark;\r
139 \r
140 int establish P((void));\r
141 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,\r
142                          char *buf, int count, int error));\r
143 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,\r
144                       char *buf, int count, int error));\r
145 void SendToICS P((char *s));\r
146 void SendToICSDelayed P((char *s, long msdelay));\r
147 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,\r
148                       int toX, int toY));\r
149 void InitPosition P((int redraw));\r
150 void HandleMachineMove P((char *message, ChessProgramState *cps));\r
151 int AutoPlayOneMove P((void));\r
152 int LoadGameOneMove P((ChessMove readAhead));\r
153 int LoadGameFromFile P((char *filename, int n, char *title, int useList));\r
154 int LoadPositionFromFile P((char *filename, int n, char *title));\r
155 int SavePositionToFile P((char *filename));\r
156 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,\r
157                   Board board));\r
158 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));\r
159 void ShowMove P((int fromX, int fromY, int toX, int toY));\r
160 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,\r
161                    /*char*/int promoChar));\r
162 void BackwardInner P((int target));\r
163 void ForwardInner P((int target));\r
164 void GameEnds P((ChessMove result, char *resultDetails, int whosays));\r
165 void EditPositionDone P((void));\r
166 void PrintOpponents P((FILE *fp));\r
167 void PrintPosition P((FILE *fp, int move));\r
168 void StartChessProgram P((ChessProgramState *cps));\r
169 void SendToProgram P((char *message, ChessProgramState *cps));\r
170 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));\r
171 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,\r
172                            char *buf, int count, int error));\r
173 void SendTimeControl P((ChessProgramState *cps,\r
174                         int mps, long tc, int inc, int sd, int st));\r
175 char *TimeControlTagValue P((void));\r
176 void Attention P((ChessProgramState *cps));\r
177 void FeedMovesToProgram P((ChessProgramState *cps, int upto));\r
178 void ResurrectChessProgram P((void));\r
179 void DisplayComment P((int moveNumber, char *text));\r
180 void DisplayMove P((int moveNumber));\r
181 void DisplayAnalysis P((void));\r
182 \r
183 void ParseGameHistory P((char *game));\r
184 void ParseBoard12 P((char *string));\r
185 void StartClocks P((void));\r
186 void SwitchClocks P((void));\r
187 void StopClocks P((void));\r
188 void ResetClocks P((void));\r
189 char *PGNDate P((void));\r
190 void SetGameInfo P((void));\r
191 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));\r
192 int RegisterMove P((void));\r
193 void MakeRegisteredMove P((void));\r
194 void TruncateGame P((void));\r
195 int looking_at P((char *, int *, char *));\r
196 void CopyPlayerNameIntoFileName P((char **, char *));\r
197 char *SavePart P((char *));\r
198 int SaveGameOldStyle P((FILE *));\r
199 int SaveGamePGN P((FILE *));\r
200 void GetTimeMark P((TimeMark *));\r
201 long SubtractTimeMarks P((TimeMark *, TimeMark *));\r
202 int CheckFlags P((void));\r
203 long NextTickLength P((long));\r
204 void CheckTimeControl P((void));\r
205 void show_bytes P((FILE *, char *, int));\r
206 int string_to_rating P((char *str));\r
207 void ParseFeatures P((char* args, ChessProgramState *cps));\r
208 void InitBackEnd3 P((void));\r
209 void FeatureDone P((ChessProgramState* cps, int val));\r
210 void InitChessProgram P((ChessProgramState *cps, int setup));\r
211 \r
212 #ifdef WIN32\r
213        extern void ConsoleCreate();\r
214 #endif\r
215 \r
216 ChessProgramState *WhitePlayer();\r
217 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c\r
218 int VerifyDisplayMode P(());\r
219 \r
220 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment\r
221 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c\r
222 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move\r
223 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book\r
224 extern char installDir[MSG_SIZ];\r
225 \r
226 extern int tinyLayout, smallLayout;\r
227 ChessProgramStats programStats;\r
228 static int exiting = 0; /* [HGM] moved to top */\r
229 static int setboardSpoiledMachineBlack = 0, errorExitFlag = 0;\r
230 extern int startedFromPositionFile;\r
231 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */\r
232 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */\r
233 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */\r
234 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */\r
235 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */\r
236 int opponentKibitzes;\r
237 \r
238 /* States for ics_getting_history */\r
239 #define H_FALSE 0\r
240 #define H_REQUESTED 1\r
241 #define H_GOT_REQ_HEADER 2\r
242 #define H_GOT_UNREQ_HEADER 3\r
243 #define H_GETTING_MOVES 4\r
244 #define H_GOT_UNWANTED_HEADER 5\r
245 \r
246 /* whosays values for GameEnds */\r
247 #define GE_ICS 0\r
248 #define GE_ENGINE 1\r
249 #define GE_PLAYER 2\r
250 #define GE_FILE 3\r
251 #define GE_XBOARD 4\r
252 #define GE_ENGINE1 5\r
253 #define GE_ENGINE2 6\r
254 \r
255 /* Maximum number of games in a cmail message */\r
256 #define CMAIL_MAX_GAMES 20\r
257 \r
258 /* Different types of move when calling RegisterMove */\r
259 #define CMAIL_MOVE   0\r
260 #define CMAIL_RESIGN 1\r
261 #define CMAIL_DRAW   2\r
262 #define CMAIL_ACCEPT 3\r
263 \r
264 /* Different types of result to remember for each game */\r
265 #define CMAIL_NOT_RESULT 0\r
266 #define CMAIL_OLD_RESULT 1\r
267 #define CMAIL_NEW_RESULT 2\r
268 \r
269 /* Telnet protocol constants */\r
270 #define TN_WILL 0373\r
271 #define TN_WONT 0374\r
272 #define TN_DO   0375\r
273 #define TN_DONT 0376\r
274 #define TN_IAC  0377\r
275 #define TN_ECHO 0001\r
276 #define TN_SGA  0003\r
277 #define TN_PORT 23\r
278 \r
279 /* [AS] */\r
280 static char * safeStrCpy( char * dst, const char * src, size_t count )\r
281 {\r
282     assert( dst != NULL );\r
283     assert( src != NULL );\r
284     assert( count > 0 );\r
285 \r
286     strncpy( dst, src, count );\r
287     dst[ count-1 ] = '\0';\r
288     return dst;\r
289 }\r
290 \r
291 static char * safeStrCat( char * dst, const char * src, size_t count )\r
292 {\r
293     size_t  dst_len;\r
294 \r
295     assert( dst != NULL );\r
296     assert( src != NULL );\r
297     assert( count > 0 );\r
298 \r
299     dst_len = strlen(dst);\r
300 \r
301     assert( count > dst_len ); /* Buffer size must be greater than current length */\r
302 \r
303     safeStrCpy( dst + dst_len, src, count - dst_len );\r
304 \r
305     return dst;\r
306 }\r
307 \r
308 /* Some compiler can't cast u64 to double\r
309  * This function do the job for us:\r
310 \r
311  * We use the highest bit for cast, this only\r
312  * works if the highest bit is not\r
313  * in use (This should not happen)\r
314  *\r
315  * We used this for all compiler\r
316  */\r
317 double\r
318 u64ToDouble(u64 value)\r
319 {\r
320   double r;\r
321   u64 tmp = value & u64Const(0x7fffffffffffffff);\r
322   r = (double)(s64)tmp;\r
323   if (value & u64Const(0x8000000000000000))\r
324        r +=  9.2233720368547758080e18; /* 2^63 */\r
325  return r;\r
326 }\r
327 \r
328 /* Fake up flags for now, as we aren't keeping track of castling\r
329    availability yet. [HGM] Change of logic: the flag now only\r
330    indicates the type of castlings allowed by the rule of the game.\r
331    The actual rights themselves are maintained in the array\r
332    castlingRights, as part of the game history, and are not probed\r
333    by this function.\r
334  */\r
335 int\r
336 PosFlags(index)\r
337 {\r
338   int flags = F_ALL_CASTLE_OK;\r
339   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;\r
340   switch (gameInfo.variant) {\r
341   case VariantSuicide:\r
342     flags &= ~F_ALL_CASTLE_OK;\r
343   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!\r
344     flags |= F_IGNORE_CHECK;\r
345     break;\r
346   case VariantAtomic:\r
347     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;\r
348     break;\r
349   case VariantKriegspiel:\r
350     flags |= F_KRIEGSPIEL_CAPTURE;\r
351     break;\r
352   case VariantCapaRandom: \r
353   case VariantFischeRandom:\r
354     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */\r
355   case VariantNoCastle:\r
356   case VariantShatranj:\r
357   case VariantCourier:\r
358     flags &= ~F_ALL_CASTLE_OK;\r
359     break;\r
360   default:\r
361     break;\r
362   }\r
363   return flags;\r
364 }\r
365 \r
366 FILE *gameFileFP, *debugFP;\r
367 \r
368 /* \r
369     [AS] Note: sometimes, the sscanf() function is used to parse the input\r
370     into a fixed-size buffer. Because of this, we must be prepared to\r
371     receive strings as long as the size of the input buffer, which is currently\r
372     set to 4K for Windows and 8K for the rest.\r
373     So, we must either allocate sufficiently large buffers here, or\r
374     reduce the size of the input buffer in the input reading part.\r
375 */\r
376 \r
377 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];\r
378 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];\r
379 char thinkOutput1[MSG_SIZ*10];\r
380 \r
381 ChessProgramState first, second;\r
382 \r
383 /* premove variables */\r
384 int premoveToX = 0;\r
385 int premoveToY = 0;\r
386 int premoveFromX = 0;\r
387 int premoveFromY = 0;\r
388 int premovePromoChar = 0;\r
389 int gotPremove = 0;\r
390 Boolean alarmSounded;\r
391 /* end premove variables */\r
392 \r
393 char *ics_prefix = "$";\r
394 int ics_type = ICS_GENERIC;\r
395 \r
396 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;\r
397 int pauseExamForwardMostMove = 0;\r
398 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;\r
399 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];\r
400 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;\r
401 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;\r
402 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;\r
403 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;\r
404 int whiteFlag = FALSE, blackFlag = FALSE;\r
405 int userOfferedDraw = FALSE;\r
406 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;\r
407 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;\r
408 int cmailMoveType[CMAIL_MAX_GAMES];\r
409 long ics_clock_paused = 0;\r
410 ProcRef icsPR = NoProc, cmailPR = NoProc;\r
411 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;\r
412 GameMode gameMode = BeginningOfGame;\r
413 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];\r
414 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];\r
415 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */\r
416 int hiddenThinkOutputState = 0; /* [AS] */\r
417 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */\r
418 int adjudicateLossPlies = 6;\r
419 char white_holding[64], black_holding[64];\r
420 TimeMark lastNodeCountTime;\r
421 long lastNodeCount=0;\r
422 int have_sent_ICS_logon = 0;\r
423 int movesPerSession;\r
424 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;\r
425 long timeControl_2; /* [AS] Allow separate time controls */\r
426 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */\r
427 long timeRemaining[2][MAX_MOVES];\r
428 int matchGame = 0;\r
429 TimeMark programStartTime;\r
430 char ics_handle[MSG_SIZ];\r
431 int have_set_title = 0;\r
432 \r
433 /* animateTraining preserves the state of appData.animate\r
434  * when Training mode is activated. This allows the\r
435  * response to be animated when appData.animate == TRUE and\r
436  * appData.animateDragging == TRUE.\r
437  */\r
438 Boolean animateTraining;\r
439 \r
440 GameInfo gameInfo;\r
441 \r
442 AppData appData;\r
443 \r
444 Board boards[MAX_MOVES];\r
445 /* [HGM] Following 7 needed for accurate legality tests: */\r
446 char  epStatus[MAX_MOVES];\r
447 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1\r
448 char  castlingRank[BOARD_SIZE]; // and corresponding ranks\r
449 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];\r
450 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status\r
451 int   initialRulePlies, FENrulePlies;\r
452 char  FENepStatus;\r
453 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)\r
454 int loadFlag = 0; \r
455 int shuffleOpenings;\r
456 \r
457 ChessSquare  FIDEArray[2][BOARD_SIZE] = {\r
458     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
459         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
460     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
461         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
462 };\r
463 \r
464 ChessSquare twoKingsArray[2][BOARD_SIZE] = {\r
465     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
466         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },\r
467     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
468         BlackKing, BlackKing, BlackKnight, BlackRook }\r
469 };\r
470 \r
471 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {\r
472     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,\r
473         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },\r
474     { BlackRook, BlackMan, BlackBishop, BlackQueen,\r
475         BlackUnicorn, BlackBishop, BlackMan, BlackRook }\r
476 };\r
477 \r
478 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */\r
479     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,\r
480         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
481     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,\r
482         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
483 };\r
484 \r
485 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */\r
486     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,\r
487         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
488     { BlackRook, BlackKnight, BlackAlfil, BlackKing,\r
489         BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
490 };\r
491 \r
492 \r
493 #if (BOARD_SIZE>=10)\r
494 ChessSquare ShogiArray[2][BOARD_SIZE] = {\r
495     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,\r
496         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },\r
497     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,\r
498         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }\r
499 };\r
500 \r
501 ChessSquare XiangqiArray[2][BOARD_SIZE] = {\r
502     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,\r
503         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
504     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,\r
505         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
506 };\r
507 \r
508 ChessSquare CapablancaArray[2][BOARD_SIZE] = {\r
509     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, \r
510         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },\r
511     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, \r
512         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
513 };\r
514 \r
515 ChessSquare GreatArray[2][BOARD_SIZE] = {\r
516     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, \r
517         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },\r
518     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, \r
519         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },\r
520 };\r
521 \r
522 ChessSquare JanusArray[2][BOARD_SIZE] = {\r
523     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, \r
524         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },\r
525     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, \r
526         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }\r
527 };\r
528 \r
529 #ifdef GOTHIC\r
530 ChessSquare GothicArray[2][BOARD_SIZE] = {\r
531     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, \r
532         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },\r
533     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, \r
534         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }\r
535 };\r
536 #else // !GOTHIC\r
537 #define GothicArray CapablancaArray\r
538 #endif // !GOTHIC\r
539 \r
540 #ifdef FALCON\r
541 ChessSquare FalconArray[2][BOARD_SIZE] = {\r
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, \r
543         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },\r
544     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, \r
545         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }\r
546 };\r
547 #else // !FALCON\r
548 #define FalconArray CapablancaArray\r
549 #endif // !FALCON\r
550 \r
551 #else // !(BOARD_SIZE>=10)\r
552 #define XiangqiPosition FIDEArray\r
553 #define CapablancaArray FIDEArray\r
554 #define GothicArray FIDEArray\r
555 #define GreatArray FIDEArray\r
556 #endif // !(BOARD_SIZE>=10)\r
557 \r
558 #if (BOARD_SIZE>=12)\r
559 ChessSquare CourierArray[2][BOARD_SIZE] = {\r
560     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,\r
561         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },\r
562     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,\r
563         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }\r
564 };\r
565 #else // !(BOARD_SIZE>=12)\r
566 #define CourierArray CapablancaArray\r
567 #endif // !(BOARD_SIZE>=12)\r
568 \r
569 \r
570 Board initialPosition;\r
571 \r
572 \r
573 /* Convert str to a rating. Checks for special cases of "----",\r
574 \r
575    "++++", etc. Also strips ()'s */\r
576 int\r
577 string_to_rating(str)\r
578   char *str;\r
579 {\r
580   while(*str && !isdigit(*str)) ++str;\r
581   if (!*str)\r
582     return 0;   /* One of the special "no rating" cases */\r
583   else\r
584     return atoi(str);\r
585 }\r
586 \r
587 void\r
588 ClearProgramStats()\r
589 {\r
590     /* Init programStats */\r
591     programStats.movelist[0] = 0;\r
592     programStats.depth = 0;\r
593     programStats.nr_moves = 0;\r
594     programStats.moves_left = 0;\r
595     programStats.nodes = 0;\r
596     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output\r
597     programStats.score = 0;\r
598     programStats.got_only_move = 0;\r
599     programStats.got_fail = 0;\r
600     programStats.line_is_book = 0;\r
601 }\r
602 \r
603 void\r
604 InitBackEnd1()\r
605 {\r
606     int matched, min, sec;\r
607 \r
608     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options\r
609 \r
610     GetTimeMark(&programStartTime);\r
611 \r
612     ClearProgramStats();\r
613     programStats.ok_to_send = 1;\r
614     programStats.seen_stat = 0;\r
615 \r
616     /*\r
617      * Initialize game list\r
618      */\r
619     ListNew(&gameList);\r
620 \r
621 \r
622     /*\r
623      * Internet chess server status\r
624      */\r
625     if (appData.icsActive) {\r
626         appData.matchMode = FALSE;\r
627         appData.matchGames = 0;\r
628 #if ZIPPY       \r
629         appData.noChessProgram = !appData.zippyPlay;\r
630 #else\r
631         appData.zippyPlay = FALSE;\r
632         appData.zippyTalk = FALSE;\r
633         appData.noChessProgram = TRUE;\r
634 #endif\r
635         if (*appData.icsHelper != NULLCHAR) {\r
636             appData.useTelnet = TRUE;\r
637             appData.telnetProgram = appData.icsHelper;\r
638         }\r
639     } else {\r
640         appData.zippyTalk = appData.zippyPlay = FALSE;\r
641     }\r
642 \r
643     /* [AS] Initialize pv info list [HGM] and game state */\r
644     {\r
645         int i, j;\r
646 \r
647         for( i=0; i<MAX_MOVES; i++ ) {\r
648             pvInfoList[i].depth = -1;\r
649             epStatus[i]=EP_NONE;\r
650             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
651         }\r
652     }\r
653 \r
654     /*\r
655      * Parse timeControl resource\r
656      */\r
657     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,\r
658                           appData.movesPerSession)) {\r
659         char buf[MSG_SIZ];\r
660         sprintf(buf, _("bad timeControl option %s"), appData.timeControl);\r
661         DisplayFatalError(buf, 0, 2);\r
662     }\r
663 \r
664     /*\r
665      * Parse searchTime resource\r
666      */\r
667     if (*appData.searchTime != NULLCHAR) {\r
668         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);\r
669         if (matched == 1) {\r
670             searchTime = min * 60;\r
671         } else if (matched == 2) {\r
672             searchTime = min * 60 + sec;\r
673         } else {\r
674             char buf[MSG_SIZ];\r
675             sprintf(buf, _("bad searchTime option %s"), appData.searchTime);\r
676             DisplayFatalError(buf, 0, 2);\r
677         }\r
678     }\r
679 \r
680     /* [AS] Adjudication threshold */\r
681     adjudicateLossThreshold = appData.adjudicateLossThreshold;\r
682     \r
683     first.which = "first";\r
684     second.which = "second";\r
685     first.maybeThinking = second.maybeThinking = FALSE;\r
686     first.pr = second.pr = NoProc;\r
687     first.isr = second.isr = NULL;\r
688     first.sendTime = second.sendTime = 2;\r
689     first.sendDrawOffers = 1;\r
690     if (appData.firstPlaysBlack) {\r
691         first.twoMachinesColor = "black\n";\r
692         second.twoMachinesColor = "white\n";\r
693     } else {\r
694         first.twoMachinesColor = "white\n";\r
695         second.twoMachinesColor = "black\n";\r
696     }\r
697     first.program = appData.firstChessProgram;\r
698     second.program = appData.secondChessProgram;\r
699     first.host = appData.firstHost;\r
700     second.host = appData.secondHost;\r
701     first.dir = appData.firstDirectory;\r
702     second.dir = appData.secondDirectory;\r
703     first.other = &second;\r
704     second.other = &first;\r
705     first.initString = appData.initString;\r
706     second.initString = appData.secondInitString;\r
707     first.computerString = appData.firstComputerString;\r
708     second.computerString = appData.secondComputerString;\r
709     first.useSigint = second.useSigint = TRUE;\r
710     first.useSigterm = second.useSigterm = TRUE;\r
711     first.reuse = appData.reuseFirst;\r
712     second.reuse = appData.reuseSecond;\r
713     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second\r
714     second.nps = appData.secondNPS;\r
715     first.useSetboard = second.useSetboard = FALSE;\r
716     first.useSAN = second.useSAN = FALSE;\r
717     first.usePing = second.usePing = FALSE;\r
718     first.lastPing = second.lastPing = 0;\r
719     first.lastPong = second.lastPong = 0;\r
720     first.usePlayother = second.usePlayother = FALSE;\r
721     first.useColors = second.useColors = TRUE;\r
722     first.useUsermove = second.useUsermove = FALSE;\r
723     first.sendICS = second.sendICS = FALSE;\r
724     first.sendName = second.sendName = appData.icsActive;\r
725     first.sdKludge = second.sdKludge = FALSE;\r
726     first.stKludge = second.stKludge = FALSE;\r
727     TidyProgramName(first.program, first.host, first.tidy);\r
728     TidyProgramName(second.program, second.host, second.tidy);\r
729     first.matchWins = second.matchWins = 0;\r
730     strcpy(first.variants, appData.variant);\r
731     strcpy(second.variants, appData.variant);\r
732     first.analysisSupport = second.analysisSupport = 2; /* detect */\r
733     first.analyzing = second.analyzing = FALSE;\r
734     first.initDone = second.initDone = FALSE;\r
735 \r
736     /* New features added by Tord: */\r
737     first.useFEN960 = FALSE; second.useFEN960 = FALSE;\r
738     first.useOOCastle = TRUE; second.useOOCastle = TRUE;\r
739     /* End of new features added by Tord. */\r
740 \r
741     /* [HGM] time odds: set factor for each machine */\r
742     first.timeOdds  = appData.firstTimeOdds;\r
743     second.timeOdds = appData.secondTimeOdds;\r
744     { int norm = 1;\r
745         if(appData.timeOddsMode) {\r
746             norm = first.timeOdds;\r
747             if(norm > second.timeOdds) norm = second.timeOdds;\r
748         }\r
749         first.timeOdds /= norm;\r
750         second.timeOdds /= norm;\r
751     }\r
752 \r
753     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/\r
754     first.accumulateTC = appData.firstAccumulateTC;\r
755     second.accumulateTC = appData.secondAccumulateTC;\r
756     first.maxNrOfSessions = second.maxNrOfSessions = 1;\r
757 \r
758     /* [HGM] debug */\r
759     first.debug = second.debug = FALSE;\r
760     first.supportsNPS = second.supportsNPS = UNKNOWN;\r
761 \r
762     /* [HGM] options */\r
763     first.optionSettings  = appData.firstOptions;\r
764     second.optionSettings = appData.secondOptions;\r
765 \r
766     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
767     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
768     first.isUCI = appData.firstIsUCI; /* [AS] */\r
769     second.isUCI = appData.secondIsUCI; /* [AS] */\r
770     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
771     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
772 \r
773     if (appData.firstProtocolVersion > PROTOVER ||\r
774         appData.firstProtocolVersion < 1) {\r
775       char buf[MSG_SIZ];\r
776       sprintf(buf, _("protocol version %d not supported"),\r
777               appData.firstProtocolVersion);\r
778       DisplayFatalError(buf, 0, 2);\r
779     } else {\r
780       first.protocolVersion = appData.firstProtocolVersion;\r
781     }\r
782 \r
783     if (appData.secondProtocolVersion > PROTOVER ||\r
784         appData.secondProtocolVersion < 1) {\r
785       char buf[MSG_SIZ];\r
786       sprintf(buf, _("protocol version %d not supported"),\r
787               appData.secondProtocolVersion);\r
788       DisplayFatalError(buf, 0, 2);\r
789     } else {\r
790       second.protocolVersion = appData.secondProtocolVersion;\r
791     }\r
792 \r
793     if (appData.icsActive) {\r
794         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */\r
795     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
796         appData.clockMode = FALSE;\r
797         first.sendTime = second.sendTime = 0;\r
798     }\r
799     \r
800 #if ZIPPY\r
801     /* Override some settings from environment variables, for backward\r
802        compatibility.  Unfortunately it's not feasible to have the env\r
803        vars just set defaults, at least in xboard.  Ugh.\r
804     */\r
805     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
806       ZippyInit();\r
807     }\r
808 #endif\r
809     \r
810     if (appData.noChessProgram) {\r
811         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
812                                         + strlen(PATCHLEVEL));\r
813         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
814     } else {\r
815 #if 0\r
816         char *p, *q;\r
817         q = first.program;\r
818         while (*q != ' ' && *q != NULLCHAR) q++;\r
819         p = q;\r
820         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */\r
821         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
822                                         + strlen(PATCHLEVEL) + (q - p));\r
823         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
824         strncat(programVersion, p, q - p);\r
825 #else\r
826         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */\r
827         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
828                                         + strlen(PATCHLEVEL) + strlen(first.tidy));\r
829         sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);\r
830 #endif\r
831     }\r
832 \r
833     if (!appData.icsActive) {\r
834       char buf[MSG_SIZ];\r
835       /* Check for variants that are supported only in ICS mode,\r
836          or not at all.  Some that are accepted here nevertheless\r
837          have bugs; see comments below.\r
838       */\r
839       VariantClass variant = StringToVariant(appData.variant);\r
840       switch (variant) {\r
841       case VariantBughouse:     /* need four players and two boards */\r
842       case VariantKriegspiel:   /* need to hide pieces and move details */\r
843       /* case VariantFischeRandom: (Fabien: moved below) */\r
844         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);\r
845         DisplayFatalError(buf, 0, 2);\r
846         return;\r
847 \r
848       case VariantUnknown:\r
849       case VariantLoadable:\r
850       case Variant29:\r
851       case Variant30:\r
852       case Variant31:\r
853       case Variant32:\r
854       case Variant33:\r
855       case Variant34:\r
856       case Variant35:\r
857       case Variant36:\r
858       default:\r
859         sprintf(buf, _("Unknown variant name %s"), appData.variant);\r
860         DisplayFatalError(buf, 0, 2);\r
861         return;\r
862 \r
863       case VariantXiangqi:    /* [HGM] repetition rules not implemented */\r
864       case VariantFairy:      /* [HGM] TestLegality definitely off! */\r
865       case VariantGothic:     /* [HGM] should work */\r
866       case VariantCapablanca: /* [HGM] should work */\r
867       case VariantCourier:    /* [HGM] initial forced moves not implemented */\r
868       case VariantShogi:      /* [HGM] drops not tested for legality */\r
869       case VariantKnightmate: /* [HGM] should work */\r
870       case VariantCylinder:   /* [HGM] untested */\r
871       case VariantFalcon:     /* [HGM] untested */\r
872       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)\r
873                                  offboard interposition not understood */\r
874       case VariantNormal:     /* definitely works! */\r
875       case VariantWildCastle: /* pieces not automatically shuffled */\r
876       case VariantNoCastle:   /* pieces not automatically shuffled */\r
877       case VariantFischeRandom: /* [HGM] works and shuffles pieces */\r
878       case VariantLosers:     /* should work except for win condition,\r
879                                  and doesn't know captures are mandatory */\r
880       case VariantSuicide:    /* should work except for win condition,\r
881                                  and doesn't know captures are mandatory */\r
882       case VariantGiveaway:   /* should work except for win condition,\r
883                                  and doesn't know captures are mandatory */\r
884       case VariantTwoKings:   /* should work */\r
885       case VariantAtomic:     /* should work except for win condition */\r
886       case Variant3Check:     /* should work except for win condition */\r
887       case VariantShatranj:   /* should work except for all win conditions */\r
888       case VariantBerolina:   /* might work if TestLegality is off */\r
889       case VariantCapaRandom: /* should work */\r
890       case VariantJanus:      /* should work */\r
891       case VariantSuper:      /* experimental */\r
892       case VariantGreat:      /* experimental, requires legality testing to be off */\r
893         break;\r
894       }\r
895     }\r
896 \r
897     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard\r
898     InitEngineUCI( installDir, &second );\r
899 }\r
900 \r
901 int NextIntegerFromString( char ** str, long * value )\r
902 {\r
903     int result = -1;\r
904     char * s = *str;\r
905 \r
906     while( *s == ' ' || *s == '\t' ) {\r
907         s++;\r
908     }\r
909 \r
910     *value = 0;\r
911 \r
912     if( *s >= '0' && *s <= '9' ) {\r
913         while( *s >= '0' && *s <= '9' ) {\r
914             *value = *value * 10 + (*s - '0');\r
915             s++;\r
916         }\r
917 \r
918         result = 0;\r
919     }\r
920 \r
921     *str = s;\r
922 \r
923     return result;\r
924 }\r
925 \r
926 int NextTimeControlFromString( char ** str, long * value )\r
927 {\r
928     long temp;\r
929     int result = NextIntegerFromString( str, &temp );\r
930 \r
931     if( result == 0 ) {\r
932         *value = temp * 60; /* Minutes */\r
933         if( **str == ':' ) {\r
934             (*str)++;\r
935             result = NextIntegerFromString( str, &temp );\r
936             *value += temp; /* Seconds */\r
937         }\r
938     }\r
939 \r
940     return result;\r
941 }\r
942 \r
943 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)\r
944 {   /* [HGM] routine added to read '+moves/time' for secondary time control */\r
945     int result = -1; long temp, temp2;\r
946 \r
947     if(**str != '+') return -1; // old params remain in force!\r
948     (*str)++;\r
949     if( NextTimeControlFromString( str, &temp ) ) return -1;\r
950 \r
951     if(**str != '/') {\r
952         /* time only: incremental or sudden-death time control */\r
953         if(**str == '+') { /* increment follows; read it */\r
954             (*str)++;\r
955             if(result = NextIntegerFromString( str, &temp2)) return -1;\r
956             *inc = temp2 * 1000;\r
957         } else *inc = 0;\r
958         *moves = 0; *tc = temp * 1000; \r
959         return 0;\r
960     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */\r
961 \r
962     (*str)++; /* classical time control */\r
963     result = NextTimeControlFromString( str, &temp2);\r
964     if(result == 0) {\r
965         *moves = temp/60;\r
966         *tc    = temp2 * 1000;\r
967         *inc   = 0;\r
968     }\r
969     return result;\r
970 }\r
971 \r
972 int GetTimeQuota(int movenr)\r
973 {   /* [HGM] get time to add from the multi-session time-control string */\r
974     int moves=1; /* kludge to force reading of first session */\r
975     long time, increment;\r
976     char *s = fullTimeControlString;\r
977 \r
978     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);\r
979     do {\r
980         if(moves) NextSessionFromString(&s, &moves, &time, &increment);\r
981         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);\r
982         if(movenr == -1) return time;    /* last move before new session     */\r
983         if(!moves) return increment;     /* current session is incremental   */\r
984         if(movenr >= 0) movenr -= moves; /* we already finished this session */\r
985     } while(movenr >= -1);               /* try again for next session       */\r
986 \r
987     return 0; // no new time quota on this move\r
988 }\r
989 \r
990 int\r
991 ParseTimeControl(tc, ti, mps)\r
992      char *tc;\r
993      int ti;\r
994      int mps;\r
995 {\r
996 #if 0\r
997     int matched, min, sec;\r
998 \r
999     matched = sscanf(tc, "%d:%d", &min, &sec);\r
1000     if (matched == 1) {\r
1001         timeControl = min * 60 * 1000;\r
1002     } else if (matched == 2) {\r
1003         timeControl = (min * 60 + sec) * 1000;\r
1004     } else {\r
1005         return FALSE;\r
1006     }\r
1007 #else\r
1008     long tc1;\r
1009     long tc2;\r
1010     char buf[MSG_SIZ];\r
1011 \r
1012     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;\r
1013     if(ti > 0) {\r
1014         if(mps)\r
1015              sprintf(buf, "+%d/%s+%d", mps, tc, ti);\r
1016         else sprintf(buf, "+%s+%d", tc, ti);\r
1017     } else {\r
1018         if(mps)\r
1019              sprintf(buf, "+%d/%s", mps, tc);\r
1020         else sprintf(buf, "+%s", tc);\r
1021     }\r
1022     fullTimeControlString = StrSave(buf);\r
1023 \r
1024     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
1025         return FALSE;\r
1026     }\r
1027 \r
1028     if( *tc == '/' ) {\r
1029         /* Parse second time control */\r
1030         tc++;\r
1031 \r
1032         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
1033             return FALSE;\r
1034         }\r
1035 \r
1036         if( tc2 == 0 ) {\r
1037             return FALSE;\r
1038         }\r
1039 \r
1040         timeControl_2 = tc2 * 1000;\r
1041     }\r
1042     else {\r
1043         timeControl_2 = 0;\r
1044     }\r
1045 \r
1046     if( tc1 == 0 ) {\r
1047         return FALSE;\r
1048     }\r
1049 \r
1050     timeControl = tc1 * 1000;\r
1051 #endif\r
1052 \r
1053     if (ti >= 0) {\r
1054         timeIncrement = ti * 1000;  /* convert to ms */\r
1055         movesPerSession = 0;\r
1056     } else {\r
1057         timeIncrement = 0;\r
1058         movesPerSession = mps;\r
1059     }\r
1060     return TRUE;\r
1061 }\r
1062 \r
1063 void\r
1064 InitBackEnd2()\r
1065 {\r
1066     if (appData.debugMode) {\r
1067         fprintf(debugFP, "%s\n", programVersion);\r
1068     }\r
1069 \r
1070     if (appData.matchGames > 0) {\r
1071         appData.matchMode = TRUE;\r
1072     } else if (appData.matchMode) {\r
1073         appData.matchGames = 1;\r
1074     }\r
1075     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */\r
1076         appData.matchGames = appData.sameColorGames;\r
1077     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */\r
1078         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;\r
1079         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;\r
1080     }\r
1081     Reset(TRUE, FALSE);\r
1082     if (appData.noChessProgram || first.protocolVersion == 1) {\r
1083       InitBackEnd3();\r
1084     } else {\r
1085       /* kludge: allow timeout for initial "feature" commands */\r
1086       FreezeUI();\r
1087       DisplayMessage("", _("Starting chess program"));\r
1088       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
1089     }\r
1090 }\r
1091 \r
1092 void\r
1093 InitBackEnd3 P((void))\r
1094 {\r
1095     GameMode initialMode;\r
1096     char buf[MSG_SIZ];\r
1097     int err;\r
1098 \r
1099     InitChessProgram(&first, startedFromSetupPosition);\r
1100 \r
1101 \r
1102     if (appData.icsActive) {\r
1103 #ifdef WIN32\r
1104         /* [DM] Make a console window if needed [HGM] merged ifs */\r
1105         ConsoleCreate(); \r
1106 #endif\r
1107         err = establish();\r
1108         if (err != 0) {\r
1109             if (*appData.icsCommPort != NULLCHAR) {\r
1110                 sprintf(buf, _("Could not open comm port %s"),  \r
1111                         appData.icsCommPort);\r
1112             } else {\r
1113                 sprintf(buf, _("Could not connect to host %s, port %s"),  \r
1114                         appData.icsHost, appData.icsPort);\r
1115             }\r
1116             DisplayFatalError(buf, err, 1);\r
1117             return;\r
1118         }\r
1119         SetICSMode();\r
1120         telnetISR =\r
1121           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
1122         fromUserISR =\r
1123           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
1124     } else if (appData.noChessProgram) {\r
1125         SetNCPMode();\r
1126     } else {\r
1127         SetGNUMode();\r
1128     }\r
1129 \r
1130     if (*appData.cmailGameName != NULLCHAR) {\r
1131         SetCmailMode();\r
1132         OpenLoopback(&cmailPR);\r
1133         cmailISR =\r
1134           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
1135     }\r
1136     \r
1137     ThawUI();\r
1138     DisplayMessage("", "");\r
1139     if (StrCaseCmp(appData.initialMode, "") == 0) {\r
1140       initialMode = BeginningOfGame;\r
1141     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
1142       initialMode = TwoMachinesPlay;\r
1143     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
1144       initialMode = AnalyzeFile; \r
1145     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
1146       initialMode = AnalyzeMode;\r
1147     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
1148       initialMode = MachinePlaysWhite;\r
1149     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
1150       initialMode = MachinePlaysBlack;\r
1151     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
1152       initialMode = EditGame;\r
1153     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
1154       initialMode = EditPosition;\r
1155     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
1156       initialMode = Training;\r
1157     } else {\r
1158       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);\r
1159       DisplayFatalError(buf, 0, 2);\r
1160       return;\r
1161     }\r
1162 \r
1163     if (appData.matchMode) {\r
1164         /* Set up machine vs. machine match */\r
1165         if (appData.noChessProgram) {\r
1166             DisplayFatalError(_("Can't have a match with no chess programs"),\r
1167                               0, 2);\r
1168             return;\r
1169         }\r
1170         matchMode = TRUE;\r
1171         matchGame = 1;\r
1172         if (*appData.loadGameFile != NULLCHAR) {\r
1173             int index = appData.loadGameIndex; // [HGM] autoinc\r
1174             if(index<0) lastIndex = index = 1;\r
1175             if (!LoadGameFromFile(appData.loadGameFile,\r
1176                                   index,\r
1177                                   appData.loadGameFile, FALSE)) {\r
1178                 DisplayFatalError(_("Bad game file"), 0, 1);\r
1179                 return;\r
1180             }\r
1181         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1182             int index = appData.loadPositionIndex; // [HGM] autoinc\r
1183             if(index<0) lastIndex = index = 1;\r
1184             if (!LoadPositionFromFile(appData.loadPositionFile,\r
1185                                       index,\r
1186                                       appData.loadPositionFile)) {\r
1187                 DisplayFatalError(_("Bad position file"), 0, 1);\r
1188                 return;\r
1189             }\r
1190         }\r
1191         TwoMachinesEvent();\r
1192     } else if (*appData.cmailGameName != NULLCHAR) {\r
1193         /* Set up cmail mode */\r
1194         ReloadCmailMsgEvent(TRUE);\r
1195     } else {\r
1196         /* Set up other modes */\r
1197         if (initialMode == AnalyzeFile) {\r
1198           if (*appData.loadGameFile == NULLCHAR) {\r
1199             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);\r
1200             return;\r
1201           }\r
1202         }\r
1203         if (*appData.loadGameFile != NULLCHAR) {\r
1204             (void) LoadGameFromFile(appData.loadGameFile,\r
1205                                     appData.loadGameIndex,\r
1206                                     appData.loadGameFile, TRUE);\r
1207         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1208             (void) LoadPositionFromFile(appData.loadPositionFile,\r
1209                                         appData.loadPositionIndex,\r
1210                                         appData.loadPositionFile);\r
1211             /* [HGM] try to make self-starting even after FEN load */\r
1212             /* to allow automatic setup of fairy variants with wtm */\r
1213             if(initialMode == BeginningOfGame && !blackPlaysFirst) {\r
1214                 gameMode = BeginningOfGame;\r
1215                 setboardSpoiledMachineBlack = 1;\r
1216             }\r
1217             /* [HGM] loadPos: make that every new game uses the setup */\r
1218             /* from file as long as we do not switch variant          */\r
1219             if(!blackPlaysFirst) { int i;\r
1220                 startedFromPositionFile = TRUE;\r
1221                 CopyBoard(filePosition, boards[0]);\r
1222                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];\r
1223             }\r
1224         }\r
1225         if (initialMode == AnalyzeMode) {\r
1226           if (appData.noChessProgram) {\r
1227             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);\r
1228             return;\r
1229           }\r
1230           if (appData.icsActive) {\r
1231             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);\r
1232             return;\r
1233           }\r
1234           AnalyzeModeEvent();\r
1235         } else if (initialMode == AnalyzeFile) {\r
1236           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent\r
1237           ShowThinkingEvent();\r
1238           AnalyzeFileEvent();\r
1239           AnalysisPeriodicEvent(1);\r
1240         } else if (initialMode == MachinePlaysWhite) {\r
1241           if (appData.noChessProgram) {\r
1242             DisplayFatalError(_("MachineWhite mode requires a chess engine"),\r
1243                               0, 2);\r
1244             return;\r
1245           }\r
1246           if (appData.icsActive) {\r
1247             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),\r
1248                               0, 2);\r
1249             return;\r
1250           }\r
1251           MachineWhiteEvent();\r
1252         } else if (initialMode == MachinePlaysBlack) {\r
1253           if (appData.noChessProgram) {\r
1254             DisplayFatalError(_("MachineBlack mode requires a chess engine"),\r
1255                               0, 2);\r
1256             return;\r
1257           }\r
1258           if (appData.icsActive) {\r
1259             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),\r
1260                               0, 2);\r
1261             return;\r
1262           }\r
1263           MachineBlackEvent();\r
1264         } else if (initialMode == TwoMachinesPlay) {\r
1265           if (appData.noChessProgram) {\r
1266             DisplayFatalError(_("TwoMachines mode requires a chess engine"),\r
1267                               0, 2);\r
1268             return;\r
1269           }\r
1270           if (appData.icsActive) {\r
1271             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),\r
1272                               0, 2);\r
1273             return;\r
1274           }\r
1275           TwoMachinesEvent();\r
1276         } else if (initialMode == EditGame) {\r
1277           EditGameEvent();\r
1278         } else if (initialMode == EditPosition) {\r
1279           EditPositionEvent();\r
1280         } else if (initialMode == Training) {\r
1281           if (*appData.loadGameFile == NULLCHAR) {\r
1282             DisplayFatalError(_("Training mode requires a game file"), 0, 2);\r
1283             return;\r
1284           }\r
1285           TrainingEvent();\r
1286         }\r
1287     }\r
1288 }\r
1289 \r
1290 /*\r
1291  * Establish will establish a contact to a remote host.port.\r
1292  * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
1293  *  used to talk to the host.\r
1294  * Returns 0 if okay, error code if not.\r
1295  */\r
1296 int\r
1297 establish()\r
1298 {\r
1299     char buf[MSG_SIZ];\r
1300 \r
1301     if (*appData.icsCommPort != NULLCHAR) {\r
1302         /* Talk to the host through a serial comm port */\r
1303         return OpenCommPort(appData.icsCommPort, &icsPR);\r
1304 \r
1305     } else if (*appData.gateway != NULLCHAR) {\r
1306         if (*appData.remoteShell == NULLCHAR) {\r
1307             /* Use the rcmd protocol to run telnet program on a gateway host */\r
1308             sprintf(buf, "%s %s %s",\r
1309                     appData.telnetProgram, appData.icsHost, appData.icsPort);\r
1310             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
1311 \r
1312         } else {\r
1313             /* Use the rsh program to run telnet program on a gateway host */\r
1314             if (*appData.remoteUser == NULLCHAR) {\r
1315                 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,\r
1316                         appData.gateway, appData.telnetProgram,\r
1317                         appData.icsHost, appData.icsPort);\r
1318             } else {\r
1319                 sprintf(buf, "%s %s -l %s %s %s %s",\r
1320                         appData.remoteShell, appData.gateway, \r
1321                         appData.remoteUser, appData.telnetProgram,\r
1322                         appData.icsHost, appData.icsPort);\r
1323             }\r
1324             return StartChildProcess(buf, "", &icsPR);\r
1325 \r
1326         }\r
1327     } else if (appData.useTelnet) {\r
1328         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
1329 \r
1330     } else {\r
1331         /* TCP socket interface differs somewhat between\r
1332            Unix and NT; handle details in the front end.\r
1333            */\r
1334         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
1335     }\r
1336 }\r
1337 \r
1338 void\r
1339 show_bytes(fp, buf, count)\r
1340      FILE *fp;\r
1341      char *buf;\r
1342      int count;\r
1343 {\r
1344     while (count--) {\r
1345         if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
1346             fprintf(fp, "\\%03o", *buf & 0xff);\r
1347         } else {\r
1348             putc(*buf, fp);\r
1349         }\r
1350         buf++;\r
1351     }\r
1352     fflush(fp);\r
1353 }\r
1354 \r
1355 /* Returns an errno value */\r
1356 int\r
1357 OutputMaybeTelnet(pr, message, count, outError)\r
1358      ProcRef pr;\r
1359      char *message;\r
1360      int count;\r
1361      int *outError;\r
1362 {\r
1363     char buf[8192], *p, *q, *buflim;\r
1364     int left, newcount, outcount;\r
1365 \r
1366     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
1367         *appData.gateway != NULLCHAR) {\r
1368         if (appData.debugMode) {\r
1369             fprintf(debugFP, ">ICS: ");\r
1370             show_bytes(debugFP, message, count);\r
1371             fprintf(debugFP, "\n");\r
1372         }\r
1373         return OutputToProcess(pr, message, count, outError);\r
1374     }\r
1375 \r
1376     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
1377     p = message;\r
1378     q = buf;\r
1379     left = count;\r
1380     newcount = 0;\r
1381     while (left) {\r
1382         if (q >= buflim) {\r
1383             if (appData.debugMode) {\r
1384                 fprintf(debugFP, ">ICS: ");\r
1385                 show_bytes(debugFP, buf, newcount);\r
1386                 fprintf(debugFP, "\n");\r
1387             }\r
1388             outcount = OutputToProcess(pr, buf, newcount, outError);\r
1389             if (outcount < newcount) return -1; /* to be sure */\r
1390             q = buf;\r
1391             newcount = 0;\r
1392         }\r
1393         if (*p == '\n') {\r
1394             *q++ = '\r';\r
1395             newcount++;\r
1396         } else if (((unsigned char) *p) == TN_IAC) {\r
1397             *q++ = (char) TN_IAC;\r
1398             newcount ++;\r
1399         }\r
1400         *q++ = *p++;\r
1401         newcount++;\r
1402         left--;\r
1403     }\r
1404     if (appData.debugMode) {\r
1405         fprintf(debugFP, ">ICS: ");\r
1406         show_bytes(debugFP, buf, newcount);\r
1407         fprintf(debugFP, "\n");\r
1408     }\r
1409     outcount = OutputToProcess(pr, buf, newcount, outError);\r
1410     if (outcount < newcount) return -1; /* to be sure */\r
1411     return count;\r
1412 }\r
1413 \r
1414 void\r
1415 read_from_player(isr, closure, message, count, error)\r
1416      InputSourceRef isr;\r
1417      VOIDSTAR closure;\r
1418      char *message;\r
1419      int count;\r
1420      int error;\r
1421 {\r
1422     int outError, outCount;\r
1423     static int gotEof = 0;\r
1424 \r
1425     /* Pass data read from player on to ICS */\r
1426     if (count > 0) {\r
1427         gotEof = 0;\r
1428         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
1429         if (outCount < count) {\r
1430             DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1431         }\r
1432     } else if (count < 0) {\r
1433         RemoveInputSource(isr);\r
1434         DisplayFatalError(_("Error reading from keyboard"), error, 1);\r
1435     } else if (gotEof++ > 0) {\r
1436         RemoveInputSource(isr);\r
1437         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);\r
1438     }\r
1439 }\r
1440 \r
1441 void\r
1442 SendToICS(s)\r
1443      char *s;\r
1444 {\r
1445     int count, outCount, outError;\r
1446 \r
1447     if (icsPR == NULL) return;\r
1448 \r
1449     count = strlen(s);\r
1450     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
1451     if (outCount < count) {\r
1452         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1453     }\r
1454 }\r
1455 \r
1456 /* This is used for sending logon scripts to the ICS. Sending\r
1457    without a delay causes problems when using timestamp on ICC\r
1458    (at least on my machine). */\r
1459 void\r
1460 SendToICSDelayed(s,msdelay)\r
1461      char *s;\r
1462      long msdelay;\r
1463 {\r
1464     int count, outCount, outError;\r
1465 \r
1466     if (icsPR == NULL) return;\r
1467 \r
1468     count = strlen(s);\r
1469     if (appData.debugMode) {\r
1470         fprintf(debugFP, ">ICS: ");\r
1471         show_bytes(debugFP, s, count);\r
1472         fprintf(debugFP, "\n");\r
1473     }\r
1474     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
1475                                       msdelay);\r
1476     if (outCount < count) {\r
1477         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1478     }\r
1479 }\r
1480 \r
1481 \r
1482 /* Remove all highlighting escape sequences in s\r
1483    Also deletes any suffix starting with '(' \r
1484    */\r
1485 char *\r
1486 StripHighlightAndTitle(s)\r
1487      char *s;\r
1488 {\r
1489     static char retbuf[MSG_SIZ];\r
1490     char *p = retbuf;\r
1491 \r
1492     while (*s != NULLCHAR) {\r
1493         while (*s == '\033') {\r
1494             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1495             if (*s != NULLCHAR) s++;\r
1496         }\r
1497         while (*s != NULLCHAR && *s != '\033') {\r
1498             if (*s == '(' || *s == '[') {\r
1499                 *p = NULLCHAR;\r
1500                 return retbuf;\r
1501             }\r
1502             *p++ = *s++;\r
1503         }\r
1504     }\r
1505     *p = NULLCHAR;\r
1506     return retbuf;\r
1507 }\r
1508 \r
1509 /* Remove all highlighting escape sequences in s */\r
1510 char *\r
1511 StripHighlight(s)\r
1512      char *s;\r
1513 {\r
1514     static char retbuf[MSG_SIZ];\r
1515     char *p = retbuf;\r
1516 \r
1517     while (*s != NULLCHAR) {\r
1518         while (*s == '\033') {\r
1519             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1520             if (*s != NULLCHAR) s++;\r
1521         }\r
1522         while (*s != NULLCHAR && *s != '\033') {\r
1523             *p++ = *s++;\r
1524         }\r
1525     }\r
1526     *p = NULLCHAR;\r
1527     return retbuf;\r
1528 }\r
1529 \r
1530 char *variantNames[] = VARIANT_NAMES;\r
1531 char *\r
1532 VariantName(v)\r
1533      VariantClass v;\r
1534 {\r
1535     return variantNames[v];\r
1536 }\r
1537 \r
1538 \r
1539 /* Identify a variant from the strings the chess servers use or the\r
1540    PGN Variant tag names we use. */\r
1541 VariantClass\r
1542 StringToVariant(e)\r
1543      char *e;\r
1544 {\r
1545     char *p;\r
1546     int wnum = -1;\r
1547     VariantClass v = VariantNormal;\r
1548     int i, found = FALSE;\r
1549     char buf[MSG_SIZ];\r
1550 \r
1551     if (!e) return v;\r
1552 \r
1553     /* [HGM] skip over optional board-size prefixes */\r
1554     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||\r
1555         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
1556         while( *e++ != '_');\r
1557     }\r
1558 \r
1559     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1560       if (StrCaseStr(e, variantNames[i])) {\r
1561         v = (VariantClass) i;\r
1562         found = TRUE;\r
1563         break;\r
1564       }\r
1565     }\r
1566 \r
1567     if (!found) {\r
1568       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1569           || StrCaseStr(e, "wild/fr") \r
1570           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {\r
1571         v = VariantFischeRandom;\r
1572       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1573                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1574         p += i;\r
1575         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1576         if (isdigit(*p)) {\r
1577           wnum = atoi(p);\r
1578         } else {\r
1579           wnum = -1;\r
1580         }\r
1581         switch (wnum) {\r
1582         case 0: /* FICS only, actually */\r
1583         case 1:\r
1584           /* Castling legal even if K starts on d-file */\r
1585           v = VariantWildCastle;\r
1586           break;\r
1587         case 2:\r
1588         case 3:\r
1589         case 4:\r
1590           /* Castling illegal even if K & R happen to start in\r
1591              normal positions. */\r
1592           v = VariantNoCastle;\r
1593           break;\r
1594         case 5:\r
1595         case 7:\r
1596         case 8:\r
1597         case 10:\r
1598         case 11:\r
1599         case 12:\r
1600         case 13:\r
1601         case 14:\r
1602         case 15:\r
1603         case 18:\r
1604         case 19:\r
1605           /* Castling legal iff K & R start in normal positions */\r
1606           v = VariantNormal;\r
1607           break;\r
1608         case 6:\r
1609         case 20:\r
1610         case 21:\r
1611           /* Special wilds for position setup; unclear what to do here */\r
1612           v = VariantLoadable;\r
1613           break;\r
1614         case 9:\r
1615           /* Bizarre ICC game */\r
1616           v = VariantTwoKings;\r
1617           break;\r
1618         case 16:\r
1619           v = VariantKriegspiel;\r
1620           break;\r
1621         case 17:\r
1622           v = VariantLosers;\r
1623           break;\r
1624         case 22:\r
1625           v = VariantFischeRandom;\r
1626           break;\r
1627         case 23:\r
1628           v = VariantCrazyhouse;\r
1629           break;\r
1630         case 24:\r
1631           v = VariantBughouse;\r
1632           break;\r
1633         case 25:\r
1634           v = Variant3Check;\r
1635           break;\r
1636         case 26:\r
1637           /* Not quite the same as FICS suicide! */\r
1638           v = VariantGiveaway;\r
1639           break;\r
1640         case 27:\r
1641           v = VariantAtomic;\r
1642           break;\r
1643         case 28:\r
1644           v = VariantShatranj;\r
1645           break;\r
1646 \r
1647         /* Temporary names for future ICC types.  The name *will* change in \r
1648            the next xboard/WinBoard release after ICC defines it. */\r
1649         case 29:\r
1650           v = Variant29;\r
1651           break;\r
1652         case 30:\r
1653           v = Variant30;\r
1654           break;\r
1655         case 31:\r
1656           v = Variant31;\r
1657           break;\r
1658         case 32:\r
1659           v = Variant32;\r
1660           break;\r
1661         case 33:\r
1662           v = Variant33;\r
1663           break;\r
1664         case 34:\r
1665           v = Variant34;\r
1666           break;\r
1667         case 35:\r
1668           v = Variant35;\r
1669           break;\r
1670         case 36:\r
1671           v = Variant36;\r
1672           break;\r
1673         case 37:\r
1674           v = VariantShogi;\r
1675           break;\r
1676         case 38:\r
1677           v = VariantXiangqi;\r
1678           break;\r
1679         case 39:\r
1680           v = VariantCourier;\r
1681           break;\r
1682         case 40:\r
1683           v = VariantGothic;\r
1684           break;\r
1685         case 41:\r
1686           v = VariantCapablanca;\r
1687           break;\r
1688         case 42:\r
1689           v = VariantKnightmate;\r
1690           break;\r
1691         case 43:\r
1692           v = VariantFairy;\r
1693           break;\r
1694         case 44:\r
1695           v = VariantCylinder;\r
1696           break;\r
1697         case 45:\r
1698           v = VariantFalcon;\r
1699           break;\r
1700         case 46:\r
1701           v = VariantCapaRandom;\r
1702           break;\r
1703         case 47:\r
1704           v = VariantBerolina;\r
1705           break;\r
1706         case 48:\r
1707           v = VariantJanus;\r
1708           break;\r
1709         case 49:\r
1710           v = VariantSuper;\r
1711           break;\r
1712         case 50:\r
1713           v = VariantGreat;\r
1714           break;\r
1715         case -1:\r
1716           /* Found "wild" or "w" in the string but no number;\r
1717              must assume it's normal chess. */\r
1718           v = VariantNormal;\r
1719           break;\r
1720         default:\r
1721           sprintf(buf, _("Unknown wild type %d"), wnum);\r
1722           DisplayError(buf, 0);\r
1723           v = VariantUnknown;\r
1724           break;\r
1725         }\r
1726       }\r
1727     }\r
1728     if (appData.debugMode) {\r
1729       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),\r
1730               e, wnum, VariantName(v));\r
1731     }\r
1732     return v;\r
1733 }\r
1734 \r
1735 static int leftover_start = 0, leftover_len = 0;\r
1736 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1737 \r
1738 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1739    advance *index beyond it, and set leftover_start to the new value of\r
1740    *index; else return FALSE.  If pattern contains the character '*', it\r
1741    matches any sequence of characters not containing '\r', '\n', or the\r
1742    character following the '*' (if any), and the matched sequence(s) are\r
1743    copied into star_match.\r
1744    */\r
1745 int\r
1746 looking_at(buf, index, pattern)\r
1747      char *buf;\r
1748      int *index;\r
1749      char *pattern;\r
1750 {\r
1751     char *bufp = &buf[*index], *patternp = pattern;\r
1752     int star_count = 0;\r
1753     char *matchp = star_match[0];\r
1754     \r
1755     for (;;) {\r
1756         if (*patternp == NULLCHAR) {\r
1757             *index = leftover_start = bufp - buf;\r
1758             *matchp = NULLCHAR;\r
1759             return TRUE;\r
1760         }\r
1761         if (*bufp == NULLCHAR) return FALSE;\r
1762         if (*patternp == '*') {\r
1763             if (*bufp == *(patternp + 1)) {\r
1764                 *matchp = NULLCHAR;\r
1765                 matchp = star_match[++star_count];\r
1766                 patternp += 2;\r
1767                 bufp++;\r
1768                 continue;\r
1769             } else if (*bufp == '\n' || *bufp == '\r') {\r
1770                 patternp++;\r
1771                 if (*patternp == NULLCHAR)\r
1772                   continue;\r
1773                 else\r
1774                   return FALSE;\r
1775             } else {\r
1776                 *matchp++ = *bufp++;\r
1777                 continue;\r
1778             }\r
1779         }\r
1780         if (*patternp != *bufp) return FALSE;\r
1781         patternp++;\r
1782         bufp++;\r
1783     }\r
1784 }\r
1785 \r
1786 void\r
1787 SendToPlayer(data, length)\r
1788      char *data;\r
1789      int length;\r
1790 {\r
1791     int error, outCount;\r
1792     outCount = OutputToProcess(NoProc, data, length, &error);\r
1793     if (outCount < length) {\r
1794         DisplayFatalError(_("Error writing to display"), error, 1);\r
1795     }\r
1796 }\r
1797 \r
1798 void\r
1799 PackHolding(packed, holding)\r
1800      char packed[];\r
1801      char *holding;\r
1802 {\r
1803     char *p = holding;\r
1804     char *q = packed;\r
1805     int runlength = 0;\r
1806     int curr = 9999;\r
1807     do {\r
1808         if (*p == curr) {\r
1809             runlength++;\r
1810         } else {\r
1811             switch (runlength) {\r
1812               case 0:\r
1813                 break;\r
1814               case 1:\r
1815                 *q++ = curr;\r
1816                 break;\r
1817               case 2:\r
1818                 *q++ = curr;\r
1819                 *q++ = curr;\r
1820                 break;\r
1821               default:\r
1822                 sprintf(q, "%d", runlength);\r
1823                 while (*q) q++;\r
1824                 *q++ = curr;\r
1825                 break;\r
1826             }\r
1827             runlength = 1;\r
1828             curr = *p;\r
1829         }\r
1830     } while (*p++);\r
1831     *q = NULLCHAR;\r
1832 }\r
1833 \r
1834 /* Telnet protocol requests from the front end */\r
1835 void\r
1836 TelnetRequest(ddww, option)\r
1837      unsigned char ddww, option;\r
1838 {\r
1839     unsigned char msg[3];\r
1840     int outCount, outError;\r
1841 \r
1842     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1843 \r
1844     if (appData.debugMode) {\r
1845         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1846         switch (ddww) {\r
1847           case TN_DO:\r
1848             ddwwStr = "DO";\r
1849             break;\r
1850           case TN_DONT:\r
1851             ddwwStr = "DONT";\r
1852             break;\r
1853           case TN_WILL:\r
1854             ddwwStr = "WILL";\r
1855             break;\r
1856           case TN_WONT:\r
1857             ddwwStr = "WONT";\r
1858             break;\r
1859           default:\r
1860             ddwwStr = buf1;\r
1861             sprintf(buf1, "%d", ddww);\r
1862             break;\r
1863         }\r
1864         switch (option) {\r
1865           case TN_ECHO:\r
1866             optionStr = "ECHO";\r
1867             break;\r
1868           default:\r
1869             optionStr = buf2;\r
1870             sprintf(buf2, "%d", option);\r
1871             break;\r
1872         }\r
1873         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1874     }\r
1875     msg[0] = TN_IAC;\r
1876     msg[1] = ddww;\r
1877     msg[2] = option;\r
1878     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1879     if (outCount < 3) {\r
1880         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1881     }\r
1882 }\r
1883 \r
1884 void\r
1885 DoEcho()\r
1886 {\r
1887     if (!appData.icsActive) return;\r
1888     TelnetRequest(TN_DO, TN_ECHO);\r
1889 }\r
1890 \r
1891 void\r
1892 DontEcho()\r
1893 {\r
1894     if (!appData.icsActive) return;\r
1895     TelnetRequest(TN_DONT, TN_ECHO);\r
1896 }\r
1897 \r
1898 void\r
1899 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
1900 {\r
1901     /* put the holdings sent to us by the server on the board holdings area */\r
1902     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
1903     char p;\r
1904     ChessSquare piece;\r
1905 \r
1906     if(gameInfo.holdingsWidth < 2)  return;\r
1907 \r
1908     if( (int)lowestPiece >= BlackPawn ) {\r
1909         holdingsColumn = 0;\r
1910         countsColumn = 1;\r
1911         holdingsStartRow = BOARD_HEIGHT-1;\r
1912         direction = -1;\r
1913     } else {\r
1914         holdingsColumn = BOARD_WIDTH-1;\r
1915         countsColumn = BOARD_WIDTH-2;\r
1916         holdingsStartRow = 0;\r
1917         direction = 1;\r
1918     }\r
1919 \r
1920     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
1921         board[i][holdingsColumn] = EmptySquare;\r
1922         board[i][countsColumn]   = (ChessSquare) 0;\r
1923     }\r
1924     while( (p=*holdings++) != NULLCHAR ) {\r
1925         piece = CharToPiece( ToUpper(p) );\r
1926         if(piece == EmptySquare) continue;\r
1927         /*j = (int) piece - (int) WhitePawn;*/\r
1928         j = PieceToNumber(piece);\r
1929         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1930         if(j < 0) continue;               /* should not happen */\r
1931         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );\r
1932         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
1933         board[holdingsStartRow+j*direction][countsColumn]++;\r
1934     }\r
1935 \r
1936 }\r
1937 \r
1938 \r
1939 void\r
1940 VariantSwitch(Board board, VariantClass newVariant)\r
1941 {\r
1942    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;\r
1943    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;\r
1944    Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;\r
1945 \r
1946    startedFromPositionFile = FALSE;\r
1947    if(gameInfo.variant == newVariant) return;\r
1948 \r
1949    /* [HGM] This routine is called each time an assignment is made to\r
1950     * gameInfo.variant during a game, to make sure the board sizes\r
1951     * are set to match the new variant. If that means adding or deleting\r
1952     * holdings, we shift the playing board accordingly\r
1953     * This kludge is needed because in ICS observe mode, we get boards\r
1954     * of an ongoing game without knowing the variant, and learn about the\r
1955     * latter only later. This can be because of the move list we requested,\r
1956     * in which case the game history is refilled from the beginning anyway,\r
1957     * but also when receiving holdings of a crazyhouse game. In the latter\r
1958     * case we want to add those holdings to the already received position.\r
1959     */\r
1960 \r
1961 \r
1962   if (appData.debugMode) {\r
1963     fprintf(debugFP, "Switch board from %s to %s\n",\r
1964                VariantName(gameInfo.variant), VariantName(newVariant));\r
1965     setbuf(debugFP, NULL);\r
1966   }\r
1967     shuffleOpenings = 0;       /* [HGM] shuffle */\r
1968     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
1969     switch(newVariant) {\r
1970             case VariantShogi:\r
1971               newWidth = 9;  newHeight = 9;\r
1972               gameInfo.holdingsSize = 7;\r
1973             case VariantBughouse:\r
1974             case VariantCrazyhouse:\r
1975               newHoldingsWidth = 2; break;\r
1976             default:\r
1977               newHoldingsWidth = gameInfo.holdingsSize = 0;\r
1978     }\r
1979 \r
1980     if(newWidth  != gameInfo.boardWidth  ||\r
1981        newHeight != gameInfo.boardHeight ||\r
1982        newHoldingsWidth != gameInfo.holdingsWidth ) {\r
1983 \r
1984         /* shift position to new playing area, if needed */\r
1985         if(newHoldingsWidth > gameInfo.holdingsWidth) {\r
1986            for(i=0; i<BOARD_HEIGHT; i++) \r
1987                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)\r
1988                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
1989                                                      board[i][j];\r
1990            for(i=0; i<newHeight; i++) {\r
1991                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;\r
1992                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;\r
1993            }\r
1994         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {\r
1995            for(i=0; i<BOARD_HEIGHT; i++)\r
1996                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
1997                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
1998                                                  board[i][j];\r
1999         }\r
2000 \r
2001         gameInfo.boardWidth  = newWidth;\r
2002         gameInfo.boardHeight = newHeight;\r
2003         gameInfo.holdingsWidth = newHoldingsWidth;\r
2004         gameInfo.variant = newVariant;\r
2005         InitDrawingSizes(-2, 0);\r
2006 \r
2007         /* [HGM] The following should definitely be solved in a better way */\r
2008 #if 0\r
2009         CopyBoard(board, tempBoard); /* save position in case it is board[0] */\r
2010         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];\r
2011         saveEP = epStatus[0];\r
2012 #endif\r
2013         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */\r
2014 #if 0\r
2015         epStatus[0] = saveEP;\r
2016         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];\r
2017         CopyBoard(tempBoard, board); /* restore position received from ICS   */\r
2018 #endif\r
2019     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }\r
2020 \r
2021     forwardMostMove = oldForwardMostMove;\r
2022     backwardMostMove = oldBackwardMostMove;\r
2023     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */\r
2024 }\r
2025 \r
2026 static int loggedOn = FALSE;\r
2027 \r
2028 /*-- Game start info cache: --*/\r
2029 int gs_gamenum;\r
2030 char gs_kind[MSG_SIZ];\r
2031 static char player1Name[128] = "";\r
2032 static char player2Name[128] = "";\r
2033 static int player1Rating = -1;\r
2034 static int player2Rating = -1;\r
2035 /*----------------------------*/\r
2036 \r
2037 ColorClass curColor = ColorNormal;\r
2038 int suppressKibitz = 0;\r
2039 \r
2040 void\r
2041 read_from_ics(isr, closure, data, count, error)\r
2042      InputSourceRef isr;\r
2043      VOIDSTAR closure;\r
2044      char *data;\r
2045      int count;\r
2046      int error;\r
2047 {\r
2048 #define BUF_SIZE 8192\r
2049 #define STARTED_NONE 0\r
2050 #define STARTED_MOVES 1\r
2051 #define STARTED_BOARD 2\r
2052 #define STARTED_OBSERVE 3\r
2053 #define STARTED_HOLDINGS 4\r
2054 #define STARTED_CHATTER 5\r
2055 #define STARTED_COMMENT 6\r
2056 #define STARTED_MOVES_NOHIDE 7\r
2057     \r
2058     static int started = STARTED_NONE;\r
2059     static char parse[20000];\r
2060     static int parse_pos = 0;\r
2061     static char buf[BUF_SIZE + 1];\r
2062     static int firstTime = TRUE, intfSet = FALSE;\r
2063     static ColorClass prevColor = ColorNormal;\r
2064     static int savingComment = FALSE;\r
2065     char str[500];\r
2066     int i, oldi;\r
2067     int buf_len;\r
2068     int next_out;\r
2069     int tkind;\r
2070     int backup;    /* [DM] For zippy color lines */\r
2071     char *p;\r
2072 \r
2073     if (appData.debugMode) {\r
2074       if (!error) {\r
2075         fprintf(debugFP, "<ICS: ");\r
2076         show_bytes(debugFP, data, count);\r
2077         fprintf(debugFP, "\n");\r
2078       }\r
2079     }\r
2080 \r
2081     if (appData.debugMode) { int f = forwardMostMove;\r
2082         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,\r
2083                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
2084     }\r
2085     if (count > 0) {\r
2086         /* If last read ended with a partial line that we couldn't parse,\r
2087            prepend it to the new read and try again. */\r
2088         if (leftover_len > 0) {\r
2089             for (i=0; i<leftover_len; i++)\r
2090               buf[i] = buf[leftover_start + i];\r
2091         }\r
2092 \r
2093         /* Copy in new characters, removing nulls and \r's */\r
2094         buf_len = leftover_len;\r
2095         for (i = 0; i < count; i++) {\r
2096             if (data[i] != NULLCHAR && data[i] != '\r')\r
2097               buf[buf_len++] = data[i];\r
2098             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && \r
2099                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') \r
2100                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous\r
2101         }\r
2102 \r
2103         buf[buf_len] = NULLCHAR;\r
2104         next_out = leftover_len;\r
2105         leftover_start = 0;\r
2106         \r
2107         i = 0;\r
2108         while (i < buf_len) {\r
2109             /* Deal with part of the TELNET option negotiation\r
2110                protocol.  We refuse to do anything beyond the\r
2111                defaults, except that we allow the WILL ECHO option,\r
2112                which ICS uses to turn off password echoing when we are\r
2113                directly connected to it.  We reject this option\r
2114                if localLineEditing mode is on (always on in xboard)\r
2115                and we are talking to port 23, which might be a real\r
2116                telnet server that will try to keep WILL ECHO on permanently.\r
2117              */\r
2118             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
2119                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
2120                 unsigned char option;\r
2121                 oldi = i;\r
2122                 switch ((unsigned char) buf[++i]) {\r
2123                   case TN_WILL:\r
2124                     if (appData.debugMode)\r
2125                       fprintf(debugFP, "\n<WILL ");\r
2126                     switch (option = (unsigned char) buf[++i]) {\r
2127                       case TN_ECHO:\r
2128                         if (appData.debugMode)\r
2129                           fprintf(debugFP, "ECHO ");\r
2130                         /* Reply only if this is a change, according\r
2131                            to the protocol rules. */\r
2132                         if (remoteEchoOption) break;\r
2133                         if (appData.localLineEditing &&\r
2134                             atoi(appData.icsPort) == TN_PORT) {\r
2135                             TelnetRequest(TN_DONT, TN_ECHO);\r
2136                         } else {\r
2137                             EchoOff();\r
2138                             TelnetRequest(TN_DO, TN_ECHO);\r
2139                             remoteEchoOption = TRUE;\r
2140                         }\r
2141                         break;\r
2142                       default:\r
2143                         if (appData.debugMode)\r
2144                           fprintf(debugFP, "%d ", option);\r
2145                         /* Whatever this is, we don't want it. */\r
2146                         TelnetRequest(TN_DONT, option);\r
2147                         break;\r
2148                     }\r
2149                     break;\r
2150                   case TN_WONT:\r
2151                     if (appData.debugMode)\r
2152                       fprintf(debugFP, "\n<WONT ");\r
2153                     switch (option = (unsigned char) buf[++i]) {\r
2154                       case TN_ECHO:\r
2155                         if (appData.debugMode)\r
2156                           fprintf(debugFP, "ECHO ");\r
2157                         /* Reply only if this is a change, according\r
2158                            to the protocol rules. */\r
2159                         if (!remoteEchoOption) break;\r
2160                         EchoOn();\r
2161                         TelnetRequest(TN_DONT, TN_ECHO);\r
2162                         remoteEchoOption = FALSE;\r
2163                         break;\r
2164                       default:\r
2165                         if (appData.debugMode)\r
2166                           fprintf(debugFP, "%d ", (unsigned char) option);\r
2167                         /* Whatever this is, it must already be turned\r
2168                            off, because we never agree to turn on\r
2169                            anything non-default, so according to the\r
2170                            protocol rules, we don't reply. */\r
2171                         break;\r
2172                     }\r
2173                     break;\r
2174                   case TN_DO:\r
2175                     if (appData.debugMode)\r
2176                       fprintf(debugFP, "\n<DO ");\r
2177                     switch (option = (unsigned char) buf[++i]) {\r
2178                       default:\r
2179                         /* Whatever this is, we refuse to do it. */\r
2180                         if (appData.debugMode)\r
2181                           fprintf(debugFP, "%d ", option);\r
2182                         TelnetRequest(TN_WONT, option);\r
2183                         break;\r
2184                     }\r
2185                     break;\r
2186                   case TN_DONT:\r
2187                     if (appData.debugMode)\r
2188                       fprintf(debugFP, "\n<DONT ");\r
2189                     switch (option = (unsigned char) buf[++i]) {\r
2190                       default:\r
2191                         if (appData.debugMode)\r
2192                           fprintf(debugFP, "%d ", option);\r
2193                         /* Whatever this is, we are already not doing\r
2194                            it, because we never agree to do anything\r
2195                            non-default, so according to the protocol\r
2196                            rules, we don't reply. */\r
2197                         break;\r
2198                     }\r
2199                     break;\r
2200                   case TN_IAC:\r
2201                     if (appData.debugMode)\r
2202                       fprintf(debugFP, "\n<IAC ");\r
2203                     /* Doubled IAC; pass it through */\r
2204                     i--;\r
2205                     break;\r
2206                   default:\r
2207                     if (appData.debugMode)\r
2208                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
2209                     /* Drop all other telnet commands on the floor */\r
2210                     break;\r
2211                 }\r
2212                 if (oldi > next_out)\r
2213                   SendToPlayer(&buf[next_out], oldi - next_out);\r
2214                 if (++i > next_out)\r
2215                   next_out = i;\r
2216                 continue;\r
2217             }\r
2218                 \r
2219             /* OK, this at least will *usually* work */\r
2220             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
2221                 loggedOn = TRUE;\r
2222             }\r
2223             \r
2224             if (loggedOn && !intfSet) {\r
2225                 if (ics_type == ICS_ICC) {\r
2226                   sprintf(str,\r
2227                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
2228                           programVersion);\r
2229 \r
2230                 } else if (ics_type == ICS_CHESSNET) {\r
2231                   sprintf(str, "/style 12\n");\r
2232                 } else {\r
2233                   strcpy(str, "alias $ @\n$set interface ");\r
2234                   strcat(str, programVersion);\r
2235                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
2236 #ifdef WIN32\r
2237                   strcat(str, "$iset nohighlight 1\n");\r
2238 #endif\r
2239                   strcat(str, "$iset lock 1\n$style 12\n");\r
2240                 }\r
2241                 SendToICS(str);\r
2242                 intfSet = TRUE;\r
2243             }\r
2244 \r
2245             if (started == STARTED_COMMENT) {\r
2246                 /* Accumulate characters in comment */\r
2247                 parse[parse_pos++] = buf[i];\r
2248                 if (buf[i] == '\n') {\r
2249                     parse[parse_pos] = NULLCHAR;\r
2250                     if(!suppressKibitz) // [HGM] kibitz\r
2251                         AppendComment(forwardMostMove, StripHighlight(parse));\r
2252                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window\r
2253                         int nrDigit = 0, nrAlph = 0, i;\r
2254                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input\r
2255                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }\r
2256                         parse[parse_pos] = NULLCHAR;\r
2257                         // try to be smart: if it does not look like search info, it should go to\r
2258                         // ICS interaction window after all, not to engine-output window.\r
2259                         for(i=0; i<parse_pos; i++) { // count letters and digits\r
2260                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');\r
2261                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');\r
2262                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');\r
2263                         }\r
2264                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info\r
2265                             OutputKibitz(suppressKibitz, parse);\r
2266                         } else {\r
2267                             char tmp[MSG_SIZ];\r
2268                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);\r
2269                             SendToPlayer(tmp, strlen(tmp));\r
2270                         }\r
2271                     }\r
2272                     started = STARTED_NONE;\r
2273                 } else {\r
2274                     /* Don't match patterns against characters in chatter */\r
2275                     i++;\r
2276                     continue;\r
2277                 }\r
2278             }\r
2279             if (started == STARTED_CHATTER) {\r
2280                 if (buf[i] != '\n') {\r
2281                     /* Don't match patterns against characters in chatter */\r
2282                     i++;\r
2283                     continue;\r
2284                 }\r
2285                 started = STARTED_NONE;\r
2286             }\r
2287 \r
2288             /* Kludge to deal with rcmd protocol */\r
2289             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2290                 DisplayFatalError(&buf[1], 0, 1);\r
2291                 continue;\r
2292             } else {\r
2293                 firstTime = FALSE;\r
2294             }\r
2295 \r
2296             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2297                 ics_type = ICS_ICC;\r
2298                 ics_prefix = "/";\r
2299                 if (appData.debugMode)\r
2300                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2301                 continue;\r
2302             }\r
2303             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2304                 ics_type = ICS_FICS;\r
2305                 ics_prefix = "$";\r
2306                 if (appData.debugMode)\r
2307                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2308                 continue;\r
2309             }\r
2310             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2311                 ics_type = ICS_CHESSNET;\r
2312                 ics_prefix = "/";\r
2313                 if (appData.debugMode)\r
2314                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2315                 continue;\r
2316             }\r
2317 \r
2318             if (!loggedOn &&\r
2319                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2320                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2321                  looking_at(buf, &i, "will be \"*\""))) {\r
2322               strcpy(ics_handle, star_match[0]);\r
2323               continue;\r
2324             }\r
2325 \r
2326             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2327               char buf[MSG_SIZ];\r
2328               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2329               DisplayIcsInteractionTitle(buf);\r
2330               have_set_title = TRUE;\r
2331             }\r
2332 \r
2333             /* skip finger notes */\r
2334             if (started == STARTED_NONE &&\r
2335                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2336                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2337                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2338               started = STARTED_CHATTER;\r
2339               i += 3;\r
2340               continue;\r
2341             }\r
2342 \r
2343             /* skip formula vars */\r
2344             if (started == STARTED_NONE &&\r
2345                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2346               started = STARTED_CHATTER;\r
2347               i += 3;\r
2348               continue;\r
2349             }\r
2350 \r
2351             oldi = i;\r
2352             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window\r
2353             if (appData.autoKibitz && started == STARTED_NONE && \r
2354                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze\r
2355                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {\r
2356                 if(looking_at(buf, &i, "* kibitzes: ") &&\r
2357                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || \r
2358                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent\r
2359                         suppressKibitz = TRUE;\r
2360                         if((StrStr(star_match[0], gameInfo.white) == star_match[0])\r
2361                                 && (gameMode == IcsPlayingWhite) ||\r
2362                            (StrStr(star_match[0], gameInfo.black) == star_match[0])\r
2363                                 && (gameMode == IcsPlayingBlack)   ) // opponent kibitz\r
2364                             started = STARTED_CHATTER; // own kibitz we simply discard\r
2365                         else {\r
2366                             started = STARTED_COMMENT; // make sure it will be collected in parse[]\r
2367                             parse_pos = 0; parse[0] = NULLCHAR;\r
2368                             savingComment = TRUE;\r
2369                             suppressKibitz = gameMode != IcsObserving ? 2 :\r
2370                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;\r
2371                         } \r
2372                         continue;\r
2373                 } else\r
2374                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz\r
2375                     started = STARTED_CHATTER;\r
2376                     suppressKibitz = TRUE;\r
2377                 }\r
2378             } // [HGM] kibitz: end of patch\r
2379 \r
2380             if (appData.zippyTalk || appData.zippyPlay) {\r
2381                 /* [DM] Backup address for color zippy lines */\r
2382                 backup = i;\r
2383 #if ZIPPY\r
2384        #ifdef WIN32\r
2385                if (loggedOn == TRUE)\r
2386                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||\r
2387                           (appData.zippyPlay && ZippyMatch(buf, &backup)));\r
2388        #else\r
2389                 if (ZippyControl(buf, &i) ||\r
2390                     ZippyConverse(buf, &i) ||\r
2391                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2392                       loggedOn = TRUE;\r
2393                       if (!appData.colorize) continue;\r
2394                 }\r
2395        #endif\r
2396 #endif\r
2397             } // [DM] 'else { ' deleted\r
2398                 if (/* Don't color "message" or "messages" output */\r
2399                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2400                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2401                     looking_at(buf, &i, "--* (*:*): ") ||\r
2402                     /* Regular tells and says */\r
2403                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2404                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2405                     looking_at(buf, &i, "* says: ") ||\r
2406                     /* Message notifications (same color as tells) */\r
2407                     looking_at(buf, &i, "* has left a message ") ||\r
2408                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2409                     /* Whispers and kibitzes */\r
2410                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2411                     looking_at(buf, &i, "* kibitzes: ") ||\r
2412                     /* Channel tells */\r
2413                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2414 \r
2415                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2416                       /* Avoid "tells you:" spoofs in channels */\r
2417                      tkind = 3;\r
2418                   }\r
2419                   if (star_match[0][0] == NULLCHAR ||\r
2420                       strchr(star_match[0], ' ') ||\r
2421                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2422                     /* Reject bogus matches */\r
2423                     i = oldi;\r
2424                   } else {\r
2425                     if (appData.colorize) {\r
2426                       if (oldi > next_out) {\r
2427                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2428                         next_out = oldi;\r
2429                       }\r
2430                       switch (tkind) {\r
2431                       case 1:\r
2432                         Colorize(ColorTell, FALSE);\r
2433                         curColor = ColorTell;\r
2434                         break;\r
2435                       case 2:\r
2436                         Colorize(ColorKibitz, FALSE);\r
2437                         curColor = ColorKibitz;\r
2438                         break;\r
2439                       case 3:\r
2440                         p = strrchr(star_match[1], '(');\r
2441                         if (p == NULL) {\r
2442                           p = star_match[1];\r
2443                         } else {\r
2444                           p++;\r
2445                         }\r
2446                         if (atoi(p) == 1) {\r
2447                           Colorize(ColorChannel1, FALSE);\r
2448                           curColor = ColorChannel1;\r
2449                         } else {\r
2450                           Colorize(ColorChannel, FALSE);\r
2451                           curColor = ColorChannel;\r
2452                         }\r
2453                         break;\r
2454                       case 5:\r
2455                         curColor = ColorNormal;\r
2456                         break;\r
2457                       }\r
2458                     }\r
2459                     if (started == STARTED_NONE && appData.autoComment &&\r
2460                         (gameMode == IcsObserving ||\r
2461                          gameMode == IcsPlayingWhite ||\r
2462                          gameMode == IcsPlayingBlack)) {\r
2463                       parse_pos = i - oldi;\r
2464                       memcpy(parse, &buf[oldi], parse_pos);\r
2465                       parse[parse_pos] = NULLCHAR;\r
2466                       started = STARTED_COMMENT;\r
2467                       savingComment = TRUE;\r
2468                     } else {\r
2469                       started = STARTED_CHATTER;\r
2470                       savingComment = FALSE;\r
2471                     }\r
2472                     loggedOn = TRUE;\r
2473                     continue;\r
2474                   }\r
2475                 }\r
2476 \r
2477                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2478                     looking_at(buf, &i, "* c-shouts: ")) {\r
2479                     if (appData.colorize) {\r
2480                         if (oldi > next_out) {\r
2481                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2482                             next_out = oldi;\r
2483                         }\r
2484                         Colorize(ColorSShout, FALSE);\r
2485                         curColor = ColorSShout;\r
2486                     }\r
2487                     loggedOn = TRUE;\r
2488                     started = STARTED_CHATTER;\r
2489                     continue;\r
2490                 }\r
2491 \r
2492                 if (looking_at(buf, &i, "--->")) {\r
2493                     loggedOn = TRUE;\r
2494                     continue;\r
2495                 }\r
2496 \r
2497                 if (looking_at(buf, &i, "* shouts: ") ||\r
2498                     looking_at(buf, &i, "--> ")) {\r
2499                     if (appData.colorize) {\r
2500                         if (oldi > next_out) {\r
2501                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2502                             next_out = oldi;\r
2503                         }\r
2504                         Colorize(ColorShout, FALSE);\r
2505                         curColor = ColorShout;\r
2506                     }\r
2507                     loggedOn = TRUE;\r
2508                     started = STARTED_CHATTER;\r
2509                     continue;\r
2510                 }\r
2511 \r
2512                 if (looking_at( buf, &i, "Challenge:")) {\r
2513                     if (appData.colorize) {\r
2514                         if (oldi > next_out) {\r
2515                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2516                             next_out = oldi;\r
2517                         }\r
2518                         Colorize(ColorChallenge, FALSE);\r
2519                         curColor = ColorChallenge;\r
2520                     }\r
2521                     loggedOn = TRUE;\r
2522                     continue;\r
2523                 }\r
2524 \r
2525                 if (looking_at(buf, &i, "* offers you") ||\r
2526                     looking_at(buf, &i, "* offers to be") ||\r
2527                     looking_at(buf, &i, "* would like to") ||\r
2528                     looking_at(buf, &i, "* requests to") ||\r
2529                     looking_at(buf, &i, "Your opponent offers") ||\r
2530                     looking_at(buf, &i, "Your opponent requests")) {\r
2531 \r
2532                     if (appData.colorize) {\r
2533                         if (oldi > next_out) {\r
2534                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2535                             next_out = oldi;\r
2536                         }\r
2537                         Colorize(ColorRequest, FALSE);\r
2538                         curColor = ColorRequest;\r
2539                     }\r
2540                     continue;\r
2541                 }\r
2542 \r
2543                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2544                     if (appData.colorize) {\r
2545                         if (oldi > next_out) {\r
2546                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2547                             next_out = oldi;\r
2548                         }\r
2549                         Colorize(ColorSeek, FALSE);\r
2550                         curColor = ColorSeek;\r
2551                     }\r
2552                     continue;\r
2553             }\r
2554 \r
2555             if (looking_at(buf, &i, "\\   ")) {\r
2556                 if (prevColor != ColorNormal) {\r
2557                     if (oldi > next_out) {\r
2558                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2559                         next_out = oldi;\r
2560                     }\r
2561                     Colorize(prevColor, TRUE);\r
2562                     curColor = prevColor;\r
2563                 }\r
2564                 if (savingComment) {\r
2565                     parse_pos = i - oldi;\r
2566                     memcpy(parse, &buf[oldi], parse_pos);\r
2567                     parse[parse_pos] = NULLCHAR;\r
2568                     started = STARTED_COMMENT;\r
2569                 } else {\r
2570                     started = STARTED_CHATTER;\r
2571                 }\r
2572                 continue;\r
2573             }\r
2574 \r
2575             if (looking_at(buf, &i, "Black Strength :") ||\r
2576                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2577                 looking_at(buf, &i, "<10>") ||\r
2578                 looking_at(buf, &i, "#@#")) {\r
2579                 /* Wrong board style */\r
2580                 loggedOn = TRUE;\r
2581                 SendToICS(ics_prefix);\r
2582                 SendToICS("set style 12\n");\r
2583                 SendToICS(ics_prefix);\r
2584                 SendToICS("refresh\n");\r
2585                 continue;\r
2586             }\r
2587             \r
2588             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2589                 ICSInitScript();\r
2590                 have_sent_ICS_logon = 1;\r
2591                 continue;\r
2592             }\r
2593               \r
2594             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2595                 (looking_at(buf, &i, "\n<12> ") ||\r
2596                  looking_at(buf, &i, "<12> "))) {\r
2597                 loggedOn = TRUE;\r
2598                 if (oldi > next_out) {\r
2599                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2600                 }\r
2601                 next_out = i;\r
2602                 started = STARTED_BOARD;\r
2603                 parse_pos = 0;\r
2604                 continue;\r
2605             }\r
2606 \r
2607             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2608                 looking_at(buf, &i, "<b1> ")) {\r
2609                 if (oldi > next_out) {\r
2610                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2611                 }\r
2612                 next_out = i;\r
2613                 started = STARTED_HOLDINGS;\r
2614                 parse_pos = 0;\r
2615                 continue;\r
2616             }\r
2617 \r
2618             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2619                 loggedOn = TRUE;\r
2620                 /* Header for a move list -- first line */\r
2621 \r
2622                 switch (ics_getting_history) {\r
2623                   case H_FALSE:\r
2624                     switch (gameMode) {\r
2625                       case IcsIdle:\r
2626                       case BeginningOfGame:\r
2627                         /* User typed "moves" or "oldmoves" while we\r
2628                            were idle.  Pretend we asked for these\r
2629                            moves and soak them up so user can step\r
2630                            through them and/or save them.\r
2631                            */\r
2632                         Reset(FALSE, TRUE);\r
2633                         gameMode = IcsObserving;\r
2634                         ModeHighlight();\r
2635                         ics_gamenum = -1;\r
2636                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2637                         break;\r
2638                       case EditGame: /*?*/\r
2639                       case EditPosition: /*?*/\r
2640                         /* Should above feature work in these modes too? */\r
2641                         /* For now it doesn't */\r
2642                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2643                         break;\r
2644                       default:\r
2645                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2646                         break;\r
2647                     }\r
2648                     break;\r
2649                   case H_REQUESTED:\r
2650                     /* Is this the right one? */\r
2651                     if (gameInfo.white && gameInfo.black &&\r
2652                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2653                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2654                         /* All is well */\r
2655                         ics_getting_history = H_GOT_REQ_HEADER;\r
2656                     }\r
2657                     break;\r
2658                   case H_GOT_REQ_HEADER:\r
2659                   case H_GOT_UNREQ_HEADER:\r
2660                   case H_GOT_UNWANTED_HEADER:\r
2661                   case H_GETTING_MOVES:\r
2662                     /* Should not happen */\r
2663                     DisplayError(_("Error gathering move list: two headers"), 0);\r
2664                     ics_getting_history = H_FALSE;\r
2665                     break;\r
2666                 }\r
2667 \r
2668                 /* Save player ratings into gameInfo if needed */\r
2669                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2670                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2671                     (gameInfo.whiteRating == -1 ||\r
2672                      gameInfo.blackRating == -1)) {\r
2673 \r
2674                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2675                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2676                     if (appData.debugMode)\r
2677                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), \r
2678                               gameInfo.whiteRating, gameInfo.blackRating);\r
2679                 }\r
2680                 continue;\r
2681             }\r
2682 \r
2683             if (looking_at(buf, &i,\r
2684               "* * match, initial time: * minute*, increment: * second")) {\r
2685                 /* Header for a move list -- second line */\r
2686                 /* Initial board will follow if this is a wild game */\r
2687                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2688                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2689                 gameInfo.event = StrSave(str);\r
2690                 /* [HGM] we switched variant. Translate boards if needed. */\r
2691                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2692                 continue;\r
2693             }\r
2694 \r
2695             if (looking_at(buf, &i, "Move  ")) {\r
2696                 /* Beginning of a move list */\r
2697                 switch (ics_getting_history) {\r
2698                   case H_FALSE:\r
2699                     /* Normally should not happen */\r
2700                     /* Maybe user hit reset while we were parsing */\r
2701                     break;\r
2702                   case H_REQUESTED:\r
2703                     /* Happens if we are ignoring a move list that is not\r
2704                      * the one we just requested.  Common if the user\r
2705                      * tries to observe two games without turning off\r
2706                      * getMoveList */\r
2707                     break;\r
2708                   case H_GETTING_MOVES:\r
2709                     /* Should not happen */\r
2710                     DisplayError(_("Error gathering move list: nested"), 0);\r
2711                     ics_getting_history = H_FALSE;\r
2712                     break;\r
2713                   case H_GOT_REQ_HEADER:\r
2714                     ics_getting_history = H_GETTING_MOVES;\r
2715                     started = STARTED_MOVES;\r
2716                     parse_pos = 0;\r
2717                     if (oldi > next_out) {\r
2718                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2719                     }\r
2720                     break;\r
2721                   case H_GOT_UNREQ_HEADER:\r
2722                     ics_getting_history = H_GETTING_MOVES;\r
2723                     started = STARTED_MOVES_NOHIDE;\r
2724                     parse_pos = 0;\r
2725                     break;\r
2726                   case H_GOT_UNWANTED_HEADER:\r
2727                     ics_getting_history = H_FALSE;\r
2728                     break;\r
2729                 }\r
2730                 continue;\r
2731             }                           \r
2732             \r
2733             if (looking_at(buf, &i, "% ") ||\r
2734                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2735                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book\r
2736                 savingComment = FALSE;\r
2737                 switch (started) {\r
2738                   case STARTED_MOVES:\r
2739                   case STARTED_MOVES_NOHIDE:\r
2740                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2741                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2742                     ParseGameHistory(parse);\r
2743 #if ZIPPY\r
2744                     if (appData.zippyPlay && first.initDone) {\r
2745                         FeedMovesToProgram(&first, forwardMostMove);\r
2746                         if (gameMode == IcsPlayingWhite) {\r
2747                             if (WhiteOnMove(forwardMostMove)) {\r
2748                                 if (first.sendTime) {\r
2749                                   if (first.useColors) {\r
2750                                     SendToProgram("black\n", &first); \r
2751                                   }\r
2752                                   SendTimeRemaining(&first, TRUE);\r
2753                                 }\r
2754 #if 0\r
2755                                 if (first.useColors) {\r
2756                                   SendToProgram("white\ngo\n", &first);\r
2757                                 } else {\r
2758                                   SendToProgram("go\n", &first);\r
2759                                 }\r
2760 #else\r
2761                                 if (first.useColors) {\r
2762                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent\r
2763                                 }\r
2764                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos\r
2765 #endif\r
2766                                 first.maybeThinking = TRUE;\r
2767                             } else {\r
2768                                 if (first.usePlayother) {\r
2769                                   if (first.sendTime) {\r
2770                                     SendTimeRemaining(&first, TRUE);\r
2771                                   }\r
2772                                   SendToProgram("playother\n", &first);\r
2773                                   firstMove = FALSE;\r
2774                                 } else {\r
2775                                   firstMove = TRUE;\r
2776                                 }\r
2777                             }\r
2778                         } else if (gameMode == IcsPlayingBlack) {\r
2779                             if (!WhiteOnMove(forwardMostMove)) {\r
2780                                 if (first.sendTime) {\r
2781                                   if (first.useColors) {\r
2782                                     SendToProgram("white\n", &first);\r
2783                                   }\r
2784                                   SendTimeRemaining(&first, FALSE);\r
2785                                 }\r
2786 #if 0\r
2787                                 if (first.useColors) {\r
2788                                   SendToProgram("black\ngo\n", &first);\r
2789                                 } else {\r
2790                                   SendToProgram("go\n", &first);\r
2791                                 }\r
2792 #else\r
2793                                 if (first.useColors) {\r
2794                                   SendToProgram("black\n", &first);\r
2795                                 }\r
2796                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);\r
2797 #endif\r
2798                                 first.maybeThinking = TRUE;\r
2799                             } else {\r
2800                                 if (first.usePlayother) {\r
2801                                   if (first.sendTime) {\r
2802                                     SendTimeRemaining(&first, FALSE);\r
2803                                   }\r
2804                                   SendToProgram("playother\n", &first);\r
2805                                   firstMove = FALSE;\r
2806                                 } else {\r
2807                                   firstMove = TRUE;\r
2808                                 }\r
2809                             }\r
2810                         }                       \r
2811                     }\r
2812 #endif\r
2813                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2814                         /* Moves came from oldmoves or moves command\r
2815                            while we weren't doing anything else.\r
2816                            */\r
2817                         currentMove = forwardMostMove;\r
2818                         ClearHighlights();/*!!could figure this out*/\r
2819                         flipView = appData.flipView;\r
2820                         DrawPosition(FALSE, boards[currentMove]);\r
2821                         DisplayBothClocks();\r
2822                         sprintf(str, "%s vs. %s",\r
2823                                 gameInfo.white, gameInfo.black);\r
2824                         DisplayTitle(str);\r
2825                         gameMode = IcsIdle;\r
2826                     } else {\r
2827                         /* Moves were history of an active game */\r
2828                         if (gameInfo.resultDetails != NULL) {\r
2829                             free(gameInfo.resultDetails);\r
2830                             gameInfo.resultDetails = NULL;\r
2831                         }\r
2832                     }\r
2833                     HistorySet(parseList, backwardMostMove,\r
2834                                forwardMostMove, currentMove-1);\r
2835                     DisplayMove(currentMove - 1);\r
2836                     if (started == STARTED_MOVES) next_out = i;\r
2837                     started = STARTED_NONE;\r
2838                     ics_getting_history = H_FALSE;\r
2839                     break;\r
2840 \r
2841                   case STARTED_OBSERVE:\r
2842                     started = STARTED_NONE;\r
2843                     SendToICS(ics_prefix);\r
2844                     SendToICS("refresh\n");\r
2845                     break;\r
2846 \r
2847                   default:\r
2848                     break;\r
2849                 }\r
2850                 if(bookHit) { // [HGM] book: simulate book reply\r
2851                     static char bookMove[MSG_SIZ]; // a bit generous?\r
2852 \r
2853                     programStats.depth = programStats.nodes = programStats.time = \r
2854                     programStats.score = programStats.got_only_move = 0;\r
2855                     sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
2856 \r
2857                     strcpy(bookMove, "move ");\r
2858                     strcat(bookMove, bookHit);\r
2859                     HandleMachineMove(bookMove, &first);\r
2860                 }\r
2861                 continue;\r
2862             }\r
2863             \r
2864             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2865                  started == STARTED_HOLDINGS ||\r
2866                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2867                 /* Accumulate characters in move list or board */\r
2868                 parse[parse_pos++] = buf[i];\r
2869             }\r
2870             \r
2871             /* Start of game messages.  Mostly we detect start of game\r
2872                when the first board image arrives.  On some versions\r
2873                of the ICS, though, we need to do a "refresh" after starting\r
2874                to observe in order to get the current board right away. */\r
2875             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2876                 started = STARTED_OBSERVE;\r
2877                 continue;\r
2878             }\r
2879 \r
2880             /* Handle auto-observe */\r
2881             if (appData.autoObserve &&\r
2882                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2883                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2884                 char *player;\r
2885                 /* Choose the player that was highlighted, if any. */\r
2886                 if (star_match[0][0] == '\033' ||\r
2887                     star_match[1][0] != '\033') {\r
2888                     player = star_match[0];\r
2889                 } else {\r
2890                     player = star_match[2];\r
2891                 }\r
2892                 sprintf(str, "%sobserve %s\n",\r
2893                         ics_prefix, StripHighlightAndTitle(player));\r
2894                 SendToICS(str);\r
2895 \r
2896                 /* Save ratings from notify string */\r
2897                 strcpy(player1Name, star_match[0]);\r
2898                 player1Rating = string_to_rating(star_match[1]);\r
2899                 strcpy(player2Name, star_match[2]);\r
2900                 player2Rating = string_to_rating(star_match[3]);\r
2901 \r
2902                 if (appData.debugMode)\r
2903                   fprintf(debugFP, \r
2904                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2905                           player1Name, player1Rating,\r
2906                           player2Name, player2Rating);\r
2907 \r
2908                 continue;\r
2909             }\r
2910 \r
2911             /* Deal with automatic examine mode after a game,\r
2912                and with IcsObserving -> IcsExamining transition */\r
2913             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2914                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2915 \r
2916                 int gamenum = atoi(star_match[0]);\r
2917                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2918                     gamenum == ics_gamenum) {\r
2919                     /* We were already playing or observing this game;\r
2920                        no need to refetch history */\r
2921                     gameMode = IcsExamining;\r
2922                     if (pausing) {\r
2923                         pauseExamForwardMostMove = forwardMostMove;\r
2924                     } else if (currentMove < forwardMostMove) {\r
2925                         ForwardInner(forwardMostMove);\r
2926                     }\r
2927                 } else {\r
2928                     /* I don't think this case really can happen */\r
2929                     SendToICS(ics_prefix);\r
2930                     SendToICS("refresh\n");\r
2931                 }\r
2932                 continue;\r
2933             }    \r
2934             \r
2935             /* Error messages */\r
2936             if (ics_user_moved) {\r
2937                 if (looking_at(buf, &i, "Illegal move") ||\r
2938                     looking_at(buf, &i, "Not a legal move") ||\r
2939                     looking_at(buf, &i, "Your king is in check") ||\r
2940                     looking_at(buf, &i, "It isn't your turn") ||\r
2941                     looking_at(buf, &i, "It is not your move")) {\r
2942                     /* Illegal move */\r
2943                     ics_user_moved = 0;\r
2944                     if (forwardMostMove > backwardMostMove) {\r
2945                         currentMove = --forwardMostMove;\r
2946                         DisplayMove(currentMove - 1); /* before DMError */\r
2947                         DisplayMoveError(_("Illegal move (rejected by ICS)"));\r
2948                         DrawPosition(FALSE, boards[currentMove]);\r
2949                         SwitchClocks();\r
2950                         DisplayBothClocks();\r
2951                     }\r
2952                     continue;\r
2953                 }\r
2954             }\r
2955 \r
2956             if (looking_at(buf, &i, "still have time") ||\r
2957                 looking_at(buf, &i, "not out of time") ||\r
2958                 looking_at(buf, &i, "either player is out of time") ||\r
2959                 looking_at(buf, &i, "has timeseal; checking")) {\r
2960                 /* We must have called his flag a little too soon */\r
2961                 whiteFlag = blackFlag = FALSE;\r
2962                 continue;\r
2963             }\r
2964 \r
2965             if (looking_at(buf, &i, "added * seconds to") ||\r
2966                 looking_at(buf, &i, "seconds were added to")) {\r
2967                 /* Update the clocks */\r
2968                 SendToICS(ics_prefix);\r
2969                 SendToICS("refresh\n");\r
2970                 continue;\r
2971             }\r
2972 \r
2973             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2974                 ics_clock_paused = TRUE;\r
2975                 StopClocks();\r
2976                 continue;\r
2977             }\r
2978 \r
2979             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2980                 ics_clock_paused = FALSE;\r
2981                 StartClocks();\r
2982                 continue;\r
2983             }\r
2984 \r
2985             /* Grab player ratings from the Creating: message.\r
2986                Note we have to check for the special case when\r
2987                the ICS inserts things like [white] or [black]. */\r
2988             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
2989                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
2990                 /* star_matches:\r
2991                    0    player 1 name (not necessarily white)\r
2992                    1    player 1 rating\r
2993                    2    empty, white, or black (IGNORED)\r
2994                    3    player 2 name (not necessarily black)\r
2995                    4    player 2 rating\r
2996                    \r
2997                    The names/ratings are sorted out when the game\r
2998                    actually starts (below).\r
2999                 */\r
3000                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
3001                 player1Rating = string_to_rating(star_match[1]);\r
3002                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
3003                 player2Rating = string_to_rating(star_match[4]);\r
3004 \r
3005                 if (appData.debugMode)\r
3006                   fprintf(debugFP, \r
3007                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
3008                           player1Name, player1Rating,\r
3009                           player2Name, player2Rating);\r
3010 \r
3011                 continue;\r
3012             }\r
3013             \r
3014             /* Improved generic start/end-of-game messages */\r
3015             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
3016                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
3017                 /* If tkind == 0: */\r
3018                 /* star_match[0] is the game number */\r
3019                 /*           [1] is the white player's name */\r
3020                 /*           [2] is the black player's name */\r
3021                 /* For end-of-game: */\r
3022                 /*           [3] is the reason for the game end */\r
3023                 /*           [4] is a PGN end game-token, preceded by " " */\r
3024                 /* For start-of-game: */\r
3025                 /*           [3] begins with "Creating" or "Continuing" */\r
3026                 /*           [4] is " *" or empty (don't care). */\r
3027                 int gamenum = atoi(star_match[0]);\r
3028                 char *whitename, *blackname, *why, *endtoken;\r
3029                 ChessMove endtype = (ChessMove) 0;\r
3030 \r
3031                 if (tkind == 0) {\r
3032                   whitename = star_match[1];\r
3033                   blackname = star_match[2];\r
3034                   why = star_match[3];\r
3035                   endtoken = star_match[4];\r
3036                 } else {\r
3037                   whitename = star_match[1];\r
3038                   blackname = star_match[3];\r
3039                   why = star_match[5];\r
3040                   endtoken = star_match[6];\r
3041                 }\r
3042 \r
3043                 /* Game start messages */\r
3044                 if (strncmp(why, "Creating ", 9) == 0 ||\r
3045                     strncmp(why, "Continuing ", 11) == 0) {\r
3046                     gs_gamenum = gamenum;\r
3047                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
3048 #if ZIPPY\r
3049                     if (appData.zippyPlay) {\r
3050                         ZippyGameStart(whitename, blackname);\r
3051                     }\r
3052 #endif /*ZIPPY*/\r
3053                     continue;\r
3054                 }\r
3055 \r
3056                 /* Game end messages */\r
3057                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
3058                     ics_gamenum != gamenum) {\r
3059                     continue;\r
3060                 }\r
3061                 while (endtoken[0] == ' ') endtoken++;\r
3062                 switch (endtoken[0]) {\r
3063                   case '*':\r
3064                   default:\r
3065                     endtype = GameUnfinished;\r
3066                     break;\r
3067                   case '0':\r
3068                     endtype = BlackWins;\r
3069                     break;\r
3070                   case '1':\r
3071                     if (endtoken[1] == '/')\r
3072                       endtype = GameIsDrawn;\r
3073                     else\r
3074                       endtype = WhiteWins;\r
3075                     break;\r
3076                 }\r
3077                 GameEnds(endtype, why, GE_ICS);\r
3078 #if ZIPPY\r
3079                 if (appData.zippyPlay && first.initDone) {\r
3080                     ZippyGameEnd(endtype, why);\r
3081                     if (first.pr == NULL) {\r
3082                       /* Start the next process early so that we'll\r
3083                          be ready for the next challenge */\r
3084                       StartChessProgram(&first);\r
3085                     }\r
3086                     /* Send "new" early, in case this command takes\r
3087                        a long time to finish, so that we'll be ready\r
3088                        for the next challenge. */\r
3089                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'\r
3090                     Reset(TRUE, TRUE);\r
3091                 }\r
3092 #endif /*ZIPPY*/\r
3093                 continue;\r
3094             }\r
3095 \r
3096             if (looking_at(buf, &i, "Removing game * from observation") ||\r
3097                 looking_at(buf, &i, "no longer observing game *") ||\r
3098                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
3099                 if (gameMode == IcsObserving &&\r
3100                     atoi(star_match[0]) == ics_gamenum)\r
3101                   {\r
3102                       /* icsEngineAnalyze */\r
3103                       if (appData.icsEngineAnalyze) {\r
3104                             ExitAnalyzeMode();\r
3105                             ModeHighlight();\r
3106                       }\r
3107                       StopClocks();\r
3108                       gameMode = IcsIdle;\r
3109                       ics_gamenum = -1;\r
3110                       ics_user_moved = FALSE;\r
3111                   }\r
3112                 continue;\r
3113             }\r
3114 \r
3115             if (looking_at(buf, &i, "no longer examining game *")) {\r
3116                 if (gameMode == IcsExamining &&\r
3117                     atoi(star_match[0]) == ics_gamenum)\r
3118                   {\r
3119                       gameMode = IcsIdle;\r
3120                       ics_gamenum = -1;\r
3121                       ics_user_moved = FALSE;\r
3122                   }\r
3123                 continue;\r
3124             }\r
3125 \r
3126             /* Advance leftover_start past any newlines we find,\r
3127                so only partial lines can get reparsed */\r
3128             if (looking_at(buf, &i, "\n")) {\r
3129                 prevColor = curColor;\r
3130                 if (curColor != ColorNormal) {\r
3131                     if (oldi > next_out) {\r
3132                         SendToPlayer(&buf[next_out], oldi - next_out);\r
3133                         next_out = oldi;\r
3134                     }\r
3135                     Colorize(ColorNormal, FALSE);\r
3136                     curColor = ColorNormal;\r
3137                 }\r
3138                 if (started == STARTED_BOARD) {\r
3139                     started = STARTED_NONE;\r
3140                     parse[parse_pos] = NULLCHAR;\r
3141                     ParseBoard12(parse);\r
3142                     ics_user_moved = 0;\r
3143 \r
3144                     /* Send premove here */\r
3145                     if (appData.premove) {\r
3146                       char str[MSG_SIZ];\r
3147                       if (currentMove == 0 &&\r
3148                           gameMode == IcsPlayingWhite &&\r
3149                           appData.premoveWhite) {\r
3150                         sprintf(str, "%s%s\n", ics_prefix,\r
3151                                 appData.premoveWhiteText);\r
3152                         if (appData.debugMode)\r
3153                           fprintf(debugFP, "Sending premove:\n");\r
3154                         SendToICS(str);\r
3155                       } else if (currentMove == 1 &&\r
3156                                  gameMode == IcsPlayingBlack &&\r
3157                                  appData.premoveBlack) {\r
3158                         sprintf(str, "%s%s\n", ics_prefix,\r
3159                                 appData.premoveBlackText);\r
3160                         if (appData.debugMode)\r
3161                           fprintf(debugFP, "Sending premove:\n");\r
3162                         SendToICS(str);\r
3163                       } else if (gotPremove) {\r
3164                         gotPremove = 0;\r
3165                         ClearPremoveHighlights();\r
3166                         if (appData.debugMode)\r
3167                           fprintf(debugFP, "Sending premove:\n");\r
3168                           UserMoveEvent(premoveFromX, premoveFromY, \r
3169                                         premoveToX, premoveToY, \r
3170                                         premovePromoChar);\r
3171                       }\r
3172                     }\r
3173 \r
3174                     /* Usually suppress following prompt */\r
3175                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
3176                         if (looking_at(buf, &i, "*% ")) {\r
3177                             savingComment = FALSE;\r
3178                         }\r
3179                     }\r
3180                     next_out = i;\r
3181                 } else if (started == STARTED_HOLDINGS) {\r
3182                     int gamenum;\r
3183                     char new_piece[MSG_SIZ];\r
3184                     started = STARTED_NONE;\r
3185                     parse[parse_pos] = NULLCHAR;\r
3186                     if (appData.debugMode)\r
3187                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
3188                                                         parse, currentMove);\r
3189                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
3190                         gamenum == ics_gamenum) {\r
3191                         if (gameInfo.variant == VariantNormal) {\r
3192                           /* [HGM] We seem to switch variant during a game!\r
3193                            * Presumably no holdings were displayed, so we have\r
3194                            * to move the position two files to the right to\r
3195                            * create room for them!\r
3196                            */\r
3197                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
3198                           /* Get a move list just to see the header, which\r
3199                              will tell us whether this is really bug or zh */\r
3200                           if (ics_getting_history == H_FALSE) {\r
3201                             ics_getting_history = H_REQUESTED;\r
3202                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3203                             SendToICS(str);\r
3204                           }\r
3205                         }\r
3206                         new_piece[0] = NULLCHAR;\r
3207                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
3208                                &gamenum, white_holding, black_holding,\r
3209                                new_piece);\r
3210                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
3211                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
3212                         /* [HGM] copy holdings to board holdings area */\r
3213                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
3214                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
3215 #if ZIPPY\r
3216                         if (appData.zippyPlay && first.initDone) {\r
3217                             ZippyHoldings(white_holding, black_holding,\r
3218                                           new_piece);\r
3219                         }\r
3220 #endif /*ZIPPY*/\r
3221                         if (tinyLayout || smallLayout) {\r
3222                             char wh[16], bh[16];\r
3223                             PackHolding(wh, white_holding);\r
3224                             PackHolding(bh, black_holding);\r
3225                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
3226                                     gameInfo.white, gameInfo.black);\r
3227                         } else {\r
3228                             sprintf(str, "%s [%s] vs. %s [%s]",\r
3229                                     gameInfo.white, white_holding,\r
3230                                     gameInfo.black, black_holding);\r
3231                         }\r
3232 \r
3233                         DrawPosition(FALSE, boards[currentMove]);\r
3234                         DisplayTitle(str);\r
3235                     }\r
3236                     /* Suppress following prompt */\r
3237                     if (looking_at(buf, &i, "*% ")) {\r
3238                         savingComment = FALSE;\r
3239                     }\r
3240                     next_out = i;\r
3241                 }\r
3242                 continue;\r
3243             }\r
3244 \r
3245             i++;                /* skip unparsed character and loop back */\r
3246         }\r
3247         \r
3248         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window\r
3249             started != STARTED_HOLDINGS && i > next_out) {\r
3250             SendToPlayer(&buf[next_out], i - next_out);\r
3251             next_out = i;\r
3252         }\r
3253         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above\r
3254         \r
3255         leftover_len = buf_len - leftover_start;\r
3256         /* if buffer ends with something we couldn't parse,\r
3257            reparse it after appending the next read */\r
3258         \r
3259     } else if (count == 0) {\r
3260         RemoveInputSource(isr);\r
3261         DisplayFatalError(_("Connection closed by ICS"), 0, 0);\r
3262     } else {\r
3263         DisplayFatalError(_("Error reading from ICS"), error, 1);\r
3264     }\r
3265 }\r
3266 \r
3267 \r
3268 /* Board style 12 looks like this:\r
3269    \r
3270    <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
3271    \r
3272  * The "<12> " is stripped before it gets to this routine.  The two\r
3273  * trailing 0's (flip state and clock ticking) are later addition, and\r
3274  * some chess servers may not have them, or may have only the first.\r
3275  * Additional trailing fields may be added in the future.  \r
3276  */\r
3277 \r
3278 #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
3279 \r
3280 #define RELATION_OBSERVING_PLAYED    0\r
3281 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
3282 #define RELATION_PLAYING_MYMOVE      1\r
3283 #define RELATION_PLAYING_NOTMYMOVE  -1\r
3284 #define RELATION_EXAMINING           2\r
3285 #define RELATION_ISOLATED_BOARD     -3\r
3286 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
3287 \r
3288 void\r
3289 ParseBoard12(string)\r
3290      char *string;\r
3291\r
3292     GameMode newGameMode;\r
3293     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;\r
3294     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;\r
3295     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
3296     char to_play, board_chars[200];\r
3297     char move_str[500], str[500], elapsed_time[500];\r
3298     char black[32], white[32];\r
3299     Board board;\r
3300     int prevMove = currentMove;\r
3301     int ticking = 2;\r
3302     ChessMove moveType;\r
3303     int fromX, fromY, toX, toY;\r
3304     char promoChar;\r
3305     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */\r
3306     char *bookHit = NULL; // [HGM] book\r
3307 \r
3308     fromX = fromY = toX = toY = -1;\r
3309     \r
3310     newGame = FALSE;\r
3311 \r
3312     if (appData.debugMode)\r
3313       fprintf(debugFP, _("Parsing board: %s\n"), string);\r
3314 \r
3315     move_str[0] = NULLCHAR;\r
3316     elapsed_time[0] = NULLCHAR;\r
3317     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */\r
3318         int  i = 0, j;\r
3319         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {\r
3320             if(string[i] == ' ') { ranks++; files = 0; }\r
3321             else files++;\r
3322             i++;\r
3323         }\r
3324         for(j = 0; j <i; j++) board_chars[j] = string[j];\r
3325         board_chars[i] = '\0';\r
3326         string += i + 1;\r
3327     }\r
3328     n = sscanf(string, PATTERN, &to_play, &double_push,\r
3329                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
3330                &gamenum, white, black, &relation, &basetime, &increment,\r
3331                &white_stren, &black_stren, &white_time, &black_time,\r
3332                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
3333                &ticking);\r
3334 \r
3335     if (n < 21) {\r
3336         sprintf(str, _("Failed to parse board string:\n\"%s\""), string);\r
3337         DisplayError(str, 0);\r
3338         return;\r
3339     }\r
3340 \r
3341     /* Convert the move number to internal form */\r
3342     moveNum = (moveNum - 1) * 2;\r
3343     if (to_play == 'B') moveNum++;\r
3344     if (moveNum >= MAX_MOVES) {\r
3345       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
3346                         0, 1);\r
3347       return;\r
3348     }\r
3349     \r
3350     switch (relation) {\r
3351       case RELATION_OBSERVING_PLAYED:\r
3352       case RELATION_OBSERVING_STATIC:\r
3353         if (gamenum == -1) {\r
3354             /* Old ICC buglet */\r
3355             relation = RELATION_OBSERVING_STATIC;\r
3356         }\r
3357         newGameMode = IcsObserving;\r
3358         break;\r
3359       case RELATION_PLAYING_MYMOVE:\r
3360       case RELATION_PLAYING_NOTMYMOVE:\r
3361         newGameMode =\r
3362           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3363             IcsPlayingWhite : IcsPlayingBlack;\r
3364         break;\r
3365       case RELATION_EXAMINING:\r
3366         newGameMode = IcsExamining;\r
3367         break;\r
3368       case RELATION_ISOLATED_BOARD:\r
3369       default:\r
3370         /* Just display this board.  If user was doing something else,\r
3371            we will forget about it until the next board comes. */ \r
3372         newGameMode = IcsIdle;\r
3373         break;\r
3374       case RELATION_STARTING_POSITION:\r
3375         newGameMode = gameMode;\r
3376         break;\r
3377     }\r
3378     \r
3379     /* Modify behavior for initial board display on move listing\r
3380        of wild games.\r
3381        */\r
3382     switch (ics_getting_history) {\r
3383       case H_FALSE:\r
3384       case H_REQUESTED:\r
3385         break;\r
3386       case H_GOT_REQ_HEADER:\r
3387       case H_GOT_UNREQ_HEADER:\r
3388         /* This is the initial position of the current game */\r
3389         gamenum = ics_gamenum;\r
3390         moveNum = 0;            /* old ICS bug workaround */\r
3391         if (to_play == 'B') {\r
3392           startedFromSetupPosition = TRUE;\r
3393           blackPlaysFirst = TRUE;\r
3394           moveNum = 1;\r
3395           if (forwardMostMove == 0) forwardMostMove = 1;\r
3396           if (backwardMostMove == 0) backwardMostMove = 1;\r
3397           if (currentMove == 0) currentMove = 1;\r
3398         }\r
3399         newGameMode = gameMode;\r
3400         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3401         break;\r
3402       case H_GOT_UNWANTED_HEADER:\r
3403         /* This is an initial board that we don't want */\r
3404         return;\r
3405       case H_GETTING_MOVES:\r
3406         /* Should not happen */\r
3407         DisplayError(_("Error gathering move list: extra board"), 0);\r
3408         ics_getting_history = H_FALSE;\r
3409         return;\r
3410     }\r
3411     \r
3412     /* Take action if this is the first board of a new game, or of a\r
3413        different game than is currently being displayed.  */\r
3414     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3415         relation == RELATION_ISOLATED_BOARD) {\r
3416         \r
3417         /* Forget the old game and get the history (if any) of the new one */\r
3418         if (gameMode != BeginningOfGame) {\r
3419           Reset(FALSE, TRUE);\r
3420         }\r
3421         newGame = TRUE;\r
3422         if (appData.autoRaiseBoard) BoardToTop();\r
3423         prevMove = -3;\r
3424         if (gamenum == -1) {\r
3425             newGameMode = IcsIdle;\r
3426         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3427                    appData.getMoveList) {\r
3428             /* Need to get game history */\r
3429             ics_getting_history = H_REQUESTED;\r
3430             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3431             SendToICS(str);\r
3432         }\r
3433         \r
3434         /* Initially flip the board to have black on the bottom if playing\r
3435            black or if the ICS flip flag is set, but let the user change\r
3436            it with the Flip View button. */\r
3437         flipView = appData.autoFlipView ? \r
3438           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3439           appData.flipView;\r
3440         \r
3441         /* Done with values from previous mode; copy in new ones */\r
3442         gameMode = newGameMode;\r
3443         ModeHighlight();\r
3444         ics_gamenum = gamenum;\r
3445         if (gamenum == gs_gamenum) {\r
3446             int klen = strlen(gs_kind);\r
3447             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3448             sprintf(str, "ICS %s", gs_kind);\r
3449             gameInfo.event = StrSave(str);\r
3450         } else {\r
3451             gameInfo.event = StrSave("ICS game");\r
3452         }\r
3453         gameInfo.site = StrSave(appData.icsHost);\r
3454         gameInfo.date = PGNDate();\r
3455         gameInfo.round = StrSave("-");\r
3456         gameInfo.white = StrSave(white);\r
3457         gameInfo.black = StrSave(black);\r
3458         timeControl = basetime * 60 * 1000;\r
3459         timeControl_2 = 0;\r
3460         timeIncrement = increment * 1000;\r
3461         movesPerSession = 0;\r
3462         gameInfo.timeControl = TimeControlTagValue();\r
3463         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3464   if (appData.debugMode) {\r
3465     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3466     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3467     setbuf(debugFP, NULL);\r
3468   }\r
3469 \r
3470         gameInfo.outOfBook = NULL;\r
3471         \r
3472         /* Do we have the ratings? */\r
3473         if (strcmp(player1Name, white) == 0 &&\r
3474             strcmp(player2Name, black) == 0) {\r
3475             if (appData.debugMode)\r
3476               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3477                       player1Rating, player2Rating);\r
3478             gameInfo.whiteRating = player1Rating;\r
3479             gameInfo.blackRating = player2Rating;\r
3480         } else if (strcmp(player2Name, white) == 0 &&\r
3481                    strcmp(player1Name, black) == 0) {\r
3482             if (appData.debugMode)\r
3483               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3484                       player2Rating, player1Rating);\r
3485             gameInfo.whiteRating = player2Rating;\r
3486             gameInfo.blackRating = player1Rating;\r
3487         }\r
3488         player1Name[0] = player2Name[0] = NULLCHAR;\r
3489 \r
3490         /* Silence shouts if requested */\r
3491         if (appData.quietPlay &&\r
3492             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3493             SendToICS(ics_prefix);\r
3494             SendToICS("set shout 0\n");\r
3495         }\r
3496     }\r
3497     \r
3498     /* Deal with midgame name changes */\r
3499     if (!newGame) {\r
3500         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3501             if (gameInfo.white) free(gameInfo.white);\r
3502             gameInfo.white = StrSave(white);\r
3503         }\r
3504         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3505             if (gameInfo.black) free(gameInfo.black);\r
3506             gameInfo.black = StrSave(black);\r
3507         }\r
3508     }\r
3509     \r
3510     /* Throw away game result if anything actually changes in examine mode */\r
3511     if (gameMode == IcsExamining && !newGame) {\r
3512         gameInfo.result = GameUnfinished;\r
3513         if (gameInfo.resultDetails != NULL) {\r
3514             free(gameInfo.resultDetails);\r
3515             gameInfo.resultDetails = NULL;\r
3516         }\r
3517     }\r
3518     \r
3519     /* In pausing && IcsExamining mode, we ignore boards coming\r
3520        in if they are in a different variation than we are. */\r
3521     if (pauseExamInvalid) return;\r
3522     if (pausing && gameMode == IcsExamining) {\r
3523         if (moveNum <= pauseExamForwardMostMove) {\r
3524             pauseExamInvalid = TRUE;\r
3525             forwardMostMove = pauseExamForwardMostMove;\r
3526             return;\r
3527         }\r
3528     }\r
3529     \r
3530   if (appData.debugMode) {\r
3531     fprintf(debugFP, "load %dx%d board\n", files, ranks);\r
3532   }\r
3533     /* Parse the board */\r
3534     for (k = 0; k < ranks; k++) {\r
3535       for (j = 0; j < files; j++)\r
3536         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);\r
3537       if(gameInfo.holdingsWidth > 1) {\r
3538            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3539            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3540       }\r
3541     }\r
3542     CopyBoard(boards[moveNum], board);\r
3543     if (moveNum == 0) {\r
3544         startedFromSetupPosition =\r
3545           !CompareBoards(board, initialPosition);\r
3546         if(startedFromSetupPosition)\r
3547             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */\r
3548     }\r
3549 \r
3550     /* [HGM] Set castling rights. Take the outermost Rooks,\r
3551        to make it also work for FRC opening positions. Note that board12\r
3552        is really defective for later FRC positions, as it has no way to\r
3553        indicate which Rook can castle if they are on the same side of King.\r
3554        For the initial position we grant rights to the outermost Rooks,\r
3555        and remember thos rights, and we then copy them on positions\r
3556        later in an FRC game. This means WB might not recognize castlings with\r
3557        Rooks that have moved back to their original position as illegal,\r
3558        but in ICS mode that is not its job anyway.\r
3559     */\r
3560     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)\r
3561     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;\r
3562 \r
3563         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3564             if(board[0][i] == WhiteRook) j = i;\r
3565         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3566         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3567             if(board[0][i] == WhiteRook) j = i;\r
3568         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3569         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3570             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3571         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3572         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3573             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3574         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3575 \r
3576         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }\r
3577         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3578             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;\r
3579         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3580             if(board[BOARD_HEIGHT-1][k] == bKing)\r
3581                 initialRights[5] = castlingRights[moveNum][5] = k;\r
3582     } else { int r;\r
3583         r = castlingRights[moveNum][0] = initialRights[0];\r
3584         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;\r
3585         r = castlingRights[moveNum][1] = initialRights[1];\r
3586         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;\r
3587         r = castlingRights[moveNum][3] = initialRights[3];\r
3588         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;\r
3589         r = castlingRights[moveNum][4] = initialRights[4];\r
3590         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;\r
3591         /* wildcastle kludge: always assume King has rights */\r
3592         r = castlingRights[moveNum][2] = initialRights[2];\r
3593         r = castlingRights[moveNum][5] = initialRights[5];\r
3594     }\r
3595     /* [HGM] e.p. rights. Assume that ICS sends file number here? */\r
3596     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;\r
3597 \r
3598     \r
3599     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3600         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3601         /* This was an initial position from a move list, not\r
3602            the current position */\r
3603         return;\r
3604     }\r
3605     \r
3606     /* Update currentMove and known move number limits */\r
3607     newMove = newGame || moveNum > forwardMostMove;\r
3608 \r
3609     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */\r
3610     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {\r
3611         takeback = forwardMostMove - moveNum;\r
3612         for (i = 0; i < takeback; i++) {\r
3613              if (appData.debugMode) fprintf(debugFP, "take back move\n");\r
3614              SendToProgram("undo\n", &first);\r
3615         }\r
3616     }\r
3617 \r
3618     if (newGame) {\r
3619         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3620         if (gameMode == IcsExamining && moveNum == 0) {\r
3621           /* Workaround for ICS limitation: we are not told the wild\r
3622              type when starting to examine a game.  But if we ask for\r
3623              the move list, the move list header will tell us */\r
3624             ics_getting_history = H_REQUESTED;\r
3625             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3626             SendToICS(str);\r
3627         }\r
3628     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3629                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3630         forwardMostMove = moveNum;\r
3631         if (!pausing || currentMove > forwardMostMove)\r
3632           currentMove = forwardMostMove;\r
3633     } else {\r
3634         /* New part of history that is not contiguous with old part */ \r
3635         if (pausing && gameMode == IcsExamining) {\r
3636             pauseExamInvalid = TRUE;\r
3637             forwardMostMove = pauseExamForwardMostMove;\r
3638             return;\r
3639         }\r
3640         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3641         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3642             ics_getting_history = H_REQUESTED;\r
3643             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3644             SendToICS(str);\r
3645         }\r
3646     }\r
3647     \r
3648     /* Update the clocks */\r
3649     if (strchr(elapsed_time, '.')) {\r
3650       /* Time is in ms */\r
3651       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3652       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3653     } else {\r
3654       /* Time is in seconds */\r
3655       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3656       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3657     }\r
3658       \r
3659 \r
3660 #if ZIPPY\r
3661     if (appData.zippyPlay && newGame &&\r
3662         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3663         gameMode != IcsExamining)\r
3664       ZippyFirstBoard(moveNum, basetime, increment);\r
3665 #endif\r
3666     \r
3667     /* Put the move on the move list, first converting\r
3668        to canonical algebraic form. */\r
3669     if (moveNum > 0) {\r
3670   if (appData.debugMode) {\r
3671     if (appData.debugMode) { int f = forwardMostMove;\r
3672         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,\r
3673                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
3674     }\r
3675     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3676     fprintf(debugFP, "moveNum = %d\n", moveNum);\r
3677     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3678     setbuf(debugFP, NULL);\r
3679   }\r
3680         if (moveNum <= backwardMostMove) {\r
3681             /* We don't know what the board looked like before\r
3682                this move.  Punt. */\r
3683             strcpy(parseList[moveNum - 1], move_str);\r
3684             strcat(parseList[moveNum - 1], " ");\r
3685             strcat(parseList[moveNum - 1], elapsed_time);\r
3686             moveList[moveNum - 1][0] = NULLCHAR;\r
3687         } else if (strcmp(move_str, "none") == 0) {\r
3688             // [HGM] long SAN: swapped order; test for 'none' before parsing move\r
3689             /* Again, we don't know what the board looked like;\r
3690                this is really the start of the game. */\r
3691             parseList[moveNum - 1][0] = NULLCHAR;\r
3692             moveList[moveNum - 1][0] = NULLCHAR;\r
3693             backwardMostMove = moveNum;\r
3694             startedFromSetupPosition = TRUE;\r
3695             fromX = fromY = toX = toY = -1;\r
3696         } else {\r
3697           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. \r
3698           //                 So we parse the long-algebraic move string in stead of the SAN move\r
3699           int valid; char buf[MSG_SIZ], *prom;\r
3700 \r
3701           // str looks something like "Q/a1-a2"; kill the slash\r
3702           if(str[1] == '/') \r
3703                 sprintf(buf, "%c%s", str[0], str+2);\r
3704           else  strcpy(buf, str); // might be castling\r
3705           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) \r
3706                 strcat(buf, prom); // long move lacks promo specification!\r
3707           if(!appData.testLegality) {\r
3708                 if(appData.debugMode) \r
3709                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);\r
3710                 strcpy(move_str, buf);\r
3711           }\r
3712           valid = ParseOneMove(move_str, moveNum - 1, &moveType,\r
3713                                 &fromX, &fromY, &toX, &toY, &promoChar)\r
3714                || ParseOneMove(buf, moveNum - 1, &moveType,\r
3715                                 &fromX, &fromY, &toX, &toY, &promoChar);\r
3716           // end of long SAN patch\r
3717           if (valid) {\r
3718             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3719                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3720                                      fromY, fromX, toY, toX, promoChar,\r
3721                                      parseList[moveNum-1]);\r
3722             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3723                              castlingRights[moveNum]) ) {\r
3724               case MT_NONE:\r
3725               case MT_STALEMATE:\r
3726               default:\r
3727                 break;\r
3728               case MT_CHECK:\r
3729                 if(gameInfo.variant != VariantShogi)\r
3730                     strcat(parseList[moveNum - 1], "+");\r
3731                 break;\r
3732               case MT_CHECKMATE:\r
3733                 strcat(parseList[moveNum - 1], "#");\r
3734                 break;\r
3735             }\r
3736             strcat(parseList[moveNum - 1], " ");\r
3737             strcat(parseList[moveNum - 1], elapsed_time);\r
3738             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3739             strcpy(moveList[moveNum - 1], currentMoveString);\r
3740             strcat(moveList[moveNum - 1], "\n");\r
3741           } else {\r
3742             /* Move from ICS was illegal!?  Punt. */\r
3743   if (appData.debugMode) {\r
3744     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3745     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3746   }\r
3747 #if 0\r
3748             if (appData.testLegality && appData.debugMode) {\r
3749                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3750                 DisplayError(str, 0);\r
3751             }\r
3752 #endif\r
3753             strcpy(parseList[moveNum - 1], move_str);\r
3754             strcat(parseList[moveNum - 1], " ");\r
3755             strcat(parseList[moveNum - 1], elapsed_time);\r
3756             moveList[moveNum - 1][0] = NULLCHAR;\r
3757             fromX = fromY = toX = toY = -1;\r
3758           }\r
3759         }\r
3760   if (appData.debugMode) {\r
3761     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3762     setbuf(debugFP, NULL);\r
3763   }\r
3764 \r
3765 #if ZIPPY\r
3766         /* Send move to chess program (BEFORE animating it). */\r
3767         if (appData.zippyPlay && !newGame && newMove && \r
3768            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3769 \r
3770             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3771                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3772                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3773                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),\r
3774                             move_str);\r
3775                     DisplayError(str, 0);\r
3776                 } else {\r
3777                     if (first.sendTime) {\r
3778                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3779                     }\r
3780                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book\r
3781                     if (firstMove && !bookHit) {\r
3782                         firstMove = FALSE;\r
3783                         if (first.useColors) {\r
3784                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3785                                         "white\ngo\n" :\r
3786                                         "black\ngo\n", &first);\r
3787                         } else {\r
3788                           SendToProgram("go\n", &first);\r
3789                         }\r
3790                         first.maybeThinking = TRUE;\r
3791                     }\r
3792                 }\r
3793             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3794               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3795                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);\r
3796                 DisplayError(str, 0);\r
3797               } else {\r
3798                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!\r
3799                 SendMoveToProgram(moveNum - 1, &first);\r
3800               }\r
3801             }\r
3802         }\r
3803 #endif\r
3804     }\r
3805 \r
3806     if (moveNum > 0 && !gotPremove) {\r
3807         /* If move comes from a remote source, animate it.  If it\r
3808            isn't remote, it will have already been animated. */\r
3809         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3810             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3811         }\r
3812         if (!pausing && appData.highlightLastMove) {\r
3813             SetHighlights(fromX, fromY, toX, toY);\r
3814         }\r
3815     }\r
3816     \r
3817     /* Start the clocks */\r
3818     whiteFlag = blackFlag = FALSE;\r
3819     appData.clockMode = !(basetime == 0 && increment == 0);\r
3820     if (ticking == 0) {\r
3821       ics_clock_paused = TRUE;\r
3822       StopClocks();\r
3823     } else if (ticking == 1) {\r
3824       ics_clock_paused = FALSE;\r
3825     }\r
3826     if (gameMode == IcsIdle ||\r
3827         relation == RELATION_OBSERVING_STATIC ||\r
3828         relation == RELATION_EXAMINING ||\r
3829         ics_clock_paused)\r
3830       DisplayBothClocks();\r
3831     else\r
3832       StartClocks();\r
3833     \r
3834     /* Display opponents and material strengths */\r
3835     if (gameInfo.variant != VariantBughouse &&\r
3836         gameInfo.variant != VariantCrazyhouse) {\r
3837         if (tinyLayout || smallLayout) {\r
3838             if(gameInfo.variant == VariantNormal)\r
3839                 sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3840                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3841                     basetime, increment);\r
3842             else\r
3843                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", \r
3844                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3845                     basetime, increment, (int) gameInfo.variant);\r
3846         } else {\r
3847             if(gameInfo.variant == VariantNormal)\r
3848                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3849                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3850                     basetime, increment);\r
3851             else\r
3852                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", \r
3853                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3854                     basetime, increment, VariantName(gameInfo.variant));\r
3855         }\r
3856         DisplayTitle(str);\r
3857   if (appData.debugMode) {\r
3858     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);\r
3859   }\r
3860     }\r
3861 \r
3862    \r
3863     /* Display the board */\r
3864     if (!pausing) {\r
3865       \r
3866       if (appData.premove)\r
3867           if (!gotPremove || \r
3868              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3869              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3870               ClearPremoveHighlights();\r
3871 \r
3872       DrawPosition(FALSE, boards[currentMove]);\r
3873       DisplayMove(moveNum - 1);\r
3874       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3875         RingBell();\r
3876     }\r
3877 \r
3878     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3879 #if ZIPPY\r
3880     if(bookHit) { // [HGM] book: simulate book reply\r
3881         static char bookMove[MSG_SIZ]; // a bit generous?\r
3882 \r
3883         programStats.depth = programStats.nodes = programStats.time = \r
3884         programStats.score = programStats.got_only_move = 0;\r
3885         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
3886 \r
3887         strcpy(bookMove, "move ");\r
3888         strcat(bookMove, bookHit);\r
3889         HandleMachineMove(bookMove, &first);\r
3890     }\r
3891 #endif\r
3892 }\r
3893 \r
3894 void\r
3895 GetMoveListEvent()\r
3896 {\r
3897     char buf[MSG_SIZ];\r
3898     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3899         ics_getting_history = H_REQUESTED;\r
3900         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3901         SendToICS(buf);\r
3902     }\r
3903 }\r
3904 \r
3905 void\r
3906 AnalysisPeriodicEvent(force)\r
3907      int force;\r
3908 {\r
3909     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3910          && !force) || !appData.periodicUpdates)\r
3911       return;\r
3912 \r
3913     /* Send . command to Crafty to collect stats */\r
3914     SendToProgram(".\n", &first);\r
3915 \r
3916     /* Don't send another until we get a response (this makes\r
3917        us stop sending to old Crafty's which don't understand\r
3918        the "." command (sending illegal cmds resets node count & time,\r
3919        which looks bad)) */\r
3920     programStats.ok_to_send = 0;\r
3921 }\r
3922 \r
3923 void\r
3924 SendMoveToProgram(moveNum, cps)\r
3925      int moveNum;\r
3926      ChessProgramState *cps;\r
3927 {\r
3928     char buf[MSG_SIZ];\r
3929 \r
3930     if (cps->useUsermove) {\r
3931       SendToProgram("usermove ", cps);\r
3932     }\r
3933     if (cps->useSAN) {\r
3934       char *space;\r
3935       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3936         int len = space - parseList[moveNum];\r
3937         memcpy(buf, parseList[moveNum], len);\r
3938         buf[len++] = '\n';\r
3939         buf[len] = NULLCHAR;\r
3940       } else {\r
3941         sprintf(buf, "%s\n", parseList[moveNum]);\r
3942       }\r
3943       SendToProgram(buf, cps);\r
3944     } else {\r
3945       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */\r
3946         AlphaRank(moveList[moveNum], 4);\r
3947         SendToProgram(moveList[moveNum], cps);\r
3948         AlphaRank(moveList[moveNum], 4); // and back\r
3949       } else\r
3950       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3951        * the engine. It would be nice to have a better way to identify castle \r
3952        * moves here. */\r
3953       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)\r
3954                                                                          && cps->useOOCastle) {\r
3955         int fromX = moveList[moveNum][0] - AAA; \r
3956         int fromY = moveList[moveNum][1] - ONE;\r
3957         int toX = moveList[moveNum][2] - AAA; \r
3958         int toY = moveList[moveNum][3] - ONE;\r
3959         if((boards[moveNum][fromY][fromX] == WhiteKing \r
3960             && boards[moveNum][toY][toX] == WhiteRook)\r
3961            || (boards[moveNum][fromY][fromX] == BlackKing \r
3962                && boards[moveNum][toY][toX] == BlackRook)) {\r
3963           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3964           else SendToProgram("O-O-O\n", cps);\r
3965         }\r
3966         else SendToProgram(moveList[moveNum], cps);\r
3967       }\r
3968       else SendToProgram(moveList[moveNum], cps);\r
3969       /* End of additions by Tord */\r
3970     }\r
3971 \r
3972     /* [HGM] setting up the opening has brought engine in force mode! */\r
3973     /*       Send 'go' if we are in a mode where machine should play. */\r
3974     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
3975         (gameMode == TwoMachinesPlay   ||\r
3976 #ifdef ZIPPY\r
3977          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||\r
3978 #endif\r
3979          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
3980         SendToProgram("go\n", cps);\r
3981   if (appData.debugMode) {\r
3982     fprintf(debugFP, "(extra)\n");\r
3983   }\r
3984     }\r
3985     setboardSpoiledMachineBlack = 0;\r
3986 }\r
3987 \r
3988 void\r
3989 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
3990      ChessMove moveType;\r
3991      int fromX, fromY, toX, toY;\r
3992 {\r
3993     char user_move[MSG_SIZ];\r
3994 \r
3995     switch (moveType) {\r
3996       default:\r
3997         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),\r
3998                 (int)moveType, fromX, fromY, toX, toY);\r
3999         DisplayError(user_move + strlen("say "), 0);\r
4000         break;\r
4001       case WhiteKingSideCastle:\r
4002       case BlackKingSideCastle:\r
4003       case WhiteQueenSideCastleWild:\r
4004       case BlackQueenSideCastleWild:\r
4005       /* PUSH Fabien */\r
4006       case WhiteHSideCastleFR:\r
4007       case BlackHSideCastleFR:\r
4008       /* POP Fabien */\r
4009         sprintf(user_move, "o-o\n");\r
4010         break;\r
4011       case WhiteQueenSideCastle:\r
4012       case BlackQueenSideCastle:\r
4013       case WhiteKingSideCastleWild:\r
4014       case BlackKingSideCastleWild:\r
4015       /* PUSH Fabien */\r
4016       case WhiteASideCastleFR:\r
4017       case BlackASideCastleFR:\r
4018       /* POP Fabien */\r
4019         sprintf(user_move, "o-o-o\n");\r
4020         break;\r
4021       case WhitePromotionQueen:\r
4022       case BlackPromotionQueen:\r
4023       case WhitePromotionRook:\r
4024       case BlackPromotionRook:\r
4025       case WhitePromotionBishop:\r
4026       case BlackPromotionBishop:\r
4027       case WhitePromotionKnight:\r
4028       case BlackPromotionKnight:\r
4029       case WhitePromotionKing:\r
4030       case BlackPromotionKing:\r
4031       case WhitePromotionChancellor:\r
4032       case BlackPromotionChancellor:\r
4033       case WhitePromotionArchbishop:\r
4034       case BlackPromotionArchbishop:\r
4035         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)\r
4036             sprintf(user_move, "%c%c%c%c=%c\n",\r
4037                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4038                 PieceToChar(WhiteFerz));\r
4039         else if(gameInfo.variant == VariantGreat)\r
4040             sprintf(user_move, "%c%c%c%c=%c\n",\r
4041                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4042                 PieceToChar(WhiteMan));\r
4043         else\r
4044             sprintf(user_move, "%c%c%c%c=%c\n",\r
4045                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4046                 PieceToChar(PromoPiece(moveType)));\r
4047         break;\r
4048       case WhiteDrop:\r
4049       case BlackDrop:\r
4050         sprintf(user_move, "%c@%c%c\n",\r
4051                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
4052                 AAA + toX, ONE + toY);\r
4053         break;\r
4054       case NormalMove:\r
4055       case WhiteCapturesEnPassant:\r
4056       case BlackCapturesEnPassant:\r
4057       case IllegalMove:  /* could be a variant we don't quite understand */\r
4058         sprintf(user_move, "%c%c%c%c\n",\r
4059                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
4060         break;\r
4061     }\r
4062     SendToICS(user_move);\r
4063 }\r
4064 \r
4065 void\r
4066 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
4067      int rf, ff, rt, ft;\r
4068      char promoChar;\r
4069      char move[7];\r
4070 {\r
4071     if (rf == DROP_RANK) {\r
4072         sprintf(move, "%c@%c%c\n",\r
4073                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
4074     } else {\r
4075         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
4076             sprintf(move, "%c%c%c%c\n",\r
4077                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
4078         } else {\r
4079             sprintf(move, "%c%c%c%c%c\n",\r
4080                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
4081         }\r
4082     }\r
4083 }\r
4084 \r
4085 void\r
4086 ProcessICSInitScript(f)\r
4087      FILE *f;\r
4088 {\r
4089     char buf[MSG_SIZ];\r
4090 \r
4091     while (fgets(buf, MSG_SIZ, f)) {\r
4092         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
4093     }\r
4094 \r
4095     fclose(f);\r
4096 }\r
4097 \r
4098 \r
4099 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
4100 void\r
4101 AlphaRank(char *move, int n)\r
4102 {\r
4103     char *p = move, c; int x, y;\r
4104 \r
4105     if (appData.debugMode) {\r
4106         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
4107     }\r
4108 \r
4109     if(move[1]=='*' && \r
4110        move[2]>='0' && move[2]<='9' &&\r
4111        move[3]>='a' && move[3]<='x'    ) {\r
4112         move[1] = '@';\r
4113         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4114         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4115     } else\r
4116     if(move[0]>='0' && move[0]<='9' &&\r
4117        move[1]>='a' && move[1]<='x' &&\r
4118        move[2]>='0' && move[2]<='9' &&\r
4119        move[3]>='a' && move[3]<='x'    ) {\r
4120         /* input move, Shogi -> normal */\r
4121         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;\r
4122         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\r
4123         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4124         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4125     } else\r
4126     if(move[1]=='@' &&\r
4127        move[3]>='0' && move[3]<='9' &&\r
4128        move[2]>='a' && move[2]<='x'    ) {\r
4129         move[1] = '*';\r
4130         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4131         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4132     } else\r
4133     if(\r
4134        move[0]>='a' && move[0]<='x' &&\r
4135        move[3]>='0' && move[3]<='9' &&\r
4136        move[2]>='a' && move[2]<='x'    ) {\r
4137          /* output move, normal -> Shogi */\r
4138         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
4139         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
4140         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4141         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4142         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
4143     }\r
4144     if (appData.debugMode) {\r
4145         fprintf(debugFP, "   out = '%s'\n", move);\r
4146     }\r
4147 }\r
4148 \r
4149 /* Parser for moves from gnuchess, ICS, or user typein box */\r
4150 Boolean\r
4151 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
4152      char *move;\r
4153      int moveNum;\r
4154      ChessMove *moveType;\r
4155      int *fromX, *fromY, *toX, *toY;\r
4156      char *promoChar;\r
4157 {       \r
4158     if (appData.debugMode) {\r
4159         fprintf(debugFP, "move to parse: %s\n", move);\r
4160     }\r
4161     *moveType = yylexstr(moveNum, move);\r
4162 \r
4163     switch (*moveType) {\r
4164       case WhitePromotionChancellor:\r
4165       case BlackPromotionChancellor:\r
4166       case WhitePromotionArchbishop:\r
4167       case BlackPromotionArchbishop:\r
4168       case WhitePromotionQueen:\r
4169       case BlackPromotionQueen:\r
4170       case WhitePromotionRook:\r
4171       case BlackPromotionRook:\r
4172       case WhitePromotionBishop:\r
4173       case BlackPromotionBishop:\r
4174       case WhitePromotionKnight:\r
4175       case BlackPromotionKnight:\r
4176       case WhitePromotionKing:\r
4177       case BlackPromotionKing:\r
4178       case NormalMove:\r
4179       case WhiteCapturesEnPassant:\r
4180       case BlackCapturesEnPassant:\r
4181       case WhiteKingSideCastle:\r
4182       case WhiteQueenSideCastle:\r
4183       case BlackKingSideCastle:\r
4184       case BlackQueenSideCastle:\r
4185       case WhiteKingSideCastleWild:\r
4186       case WhiteQueenSideCastleWild:\r
4187       case BlackKingSideCastleWild:\r
4188       case BlackQueenSideCastleWild:\r
4189       /* Code added by Tord: */\r
4190       case WhiteHSideCastleFR:\r
4191       case WhiteASideCastleFR:\r
4192       case BlackHSideCastleFR:\r
4193       case BlackASideCastleFR:\r
4194       /* End of code added by Tord */\r
4195       case IllegalMove:         /* bug or odd chess variant */\r
4196         *fromX = currentMoveString[0] - AAA;\r
4197         *fromY = currentMoveString[1] - ONE;\r
4198         *toX = currentMoveString[2] - AAA;\r
4199         *toY = currentMoveString[3] - ONE;\r
4200         *promoChar = currentMoveString[4];\r
4201         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
4202             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
4203     if (appData.debugMode) {\r
4204         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
4205     }\r
4206             *fromX = *fromY = *toX = *toY = 0;\r
4207             return FALSE;\r
4208         }\r
4209         if (appData.testLegality) {\r
4210           return (*moveType != IllegalMove);\r
4211         } else {\r
4212           return !(fromX == fromY && toX == toY);\r
4213         }\r
4214 \r
4215       case WhiteDrop:\r
4216       case BlackDrop:\r
4217         *fromX = *moveType == WhiteDrop ?\r
4218           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
4219           (int) CharToPiece(ToLower(currentMoveString[0]));\r
4220         *fromY = DROP_RANK;\r
4221         *toX = currentMoveString[2] - AAA;\r
4222         *toY = currentMoveString[3] - ONE;\r
4223         *promoChar = NULLCHAR;\r
4224         return TRUE;\r
4225 \r
4226       case AmbiguousMove:\r
4227       case ImpossibleMove:\r
4228       case (ChessMove) 0:       /* end of file */\r
4229       case ElapsedTime:\r
4230       case Comment:\r
4231       case PGNTag:\r
4232       case NAG:\r
4233       case WhiteWins:\r
4234       case BlackWins:\r
4235       case GameIsDrawn:\r
4236       default:\r
4237     if (appData.debugMode) {\r
4238         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
4239     }\r
4240         /* bug? */\r
4241         *fromX = *fromY = *toX = *toY = 0;\r
4242         *promoChar = NULLCHAR;\r
4243         return FALSE;\r
4244     }\r
4245 }\r
4246 \r
4247 /* [AS] FRC game initialization */\r
4248 static int FindEmptySquare( Board board, int n )\r
4249 {\r
4250     int i = 0;\r
4251 \r
4252     while( 1 ) {\r
4253         while( board[0][i] != EmptySquare ) i++;\r
4254         if( n == 0 )\r
4255             break;\r
4256         n--;\r
4257         i++;\r
4258     }\r
4259 \r
4260     return i;\r
4261 }\r
4262 \r
4263 #if 0\r
4264 static void ShuffleFRC( Board board )\r
4265 {\r
4266     int i;\r
4267 \r
4268     srand( time(0) );\r
4269     \r
4270     for( i=0; i<8; i++ ) {\r
4271         board[0][i] = EmptySquare;\r
4272     }\r
4273 \r
4274     board[0][(rand() % 4)*2  ] = WhiteBishop; /* On dark square */\r
4275     board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */\r
4276     board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;\r
4277     board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;\r
4278     board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;\r
4279     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4280     initialRights[1]  = initialRights[4]  =\r
4281     castlingRights[0][1] = castlingRights[0][4] = i;\r
4282     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4283     initialRights[2]  = initialRights[5]  =\r
4284     castlingRights[0][2] = castlingRights[0][5] = i;\r
4285     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4286     initialRights[0]  = initialRights[3]  =\r
4287     castlingRights[0][0] = castlingRights[0][3] = i;\r
4288 \r
4289     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4290         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4291     }\r
4292 }\r
4293 \r
4294 static unsigned char FRC_KnightTable[10] = {\r
4295     0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33\r
4296 };\r
4297 \r
4298 static void SetupFRC( Board board, int pos_index )\r
4299 {\r
4300     int i;\r
4301     unsigned char knights;\r
4302 \r
4303     /* Bring the position index into a safe range (just in case...) */\r
4304     if( pos_index < 0 ) pos_index = 0;\r
4305 \r
4306     pos_index %= 960;\r
4307 \r
4308     /* Clear the board */\r
4309     for( i=0; i<8; i++ ) {\r
4310         board[0][i] = EmptySquare;\r
4311     }\r
4312 \r
4313     /* Place bishops and queen */\r
4314     board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */\r
4315     pos_index /= 4;\r
4316     \r
4317     board[0][ (pos_index % 4)*2     ] = WhiteBishop; /* On dark square */\r
4318     pos_index /= 4;\r
4319 \r
4320     board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;\r
4321     pos_index /= 6;\r
4322 \r
4323     /* Place knigths */\r
4324     knights = FRC_KnightTable[ pos_index ];\r
4325 \r
4326     board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;\r
4327     board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;\r
4328 \r
4329     /* Place rooks and king */\r
4330     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4331     initialRights[1]  = initialRights[4]  =\r
4332     castlingRights[0][1] = castlingRights[0][4] = i;\r
4333     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4334     initialRights[2]  = initialRights[5]  =\r
4335     castlingRights[0][2] = castlingRights[0][5] = i;\r
4336     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4337     initialRights[0]  = initialRights[3]  =\r
4338     castlingRights[0][0] = castlingRights[0][3] = i;\r
4339 \r
4340     /* Mirror piece placement for black */\r
4341     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4342         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4343     }\r
4344 }\r
4345 #else\r
4346 // [HGM] shuffle: a more general way to suffle opening setups, applicable to arbitrry variants.\r
4347 // All positions will have equal probability, but the current method will not provide a unique\r
4348 // numbering scheme for arrays that contain 3 or more pieces of the same kind.\r
4349 #define DARK 1\r
4350 #define LITE 2\r
4351 #define ANY 3\r
4352 \r
4353 int squaresLeft[4];\r
4354 int piecesLeft[(int)BlackPawn];\r
4355 long long int seed, nrOfShuffles;\r
4356 \r
4357 void GetPositionNumber()\r
4358 {       // sets global variable seed\r
4359         int i;\r
4360 \r
4361         seed = appData.defaultFrcPosition;\r
4362         if(seed < 0) { // randomize based on time for negative FRC position numbers\r
4363                 srandom(time(0)); \r
4364                 for(i=0; i<50; i++) seed += random();\r
4365                 seed = random() ^ random() >> 8 ^ random() << 8;\r
4366                 if(seed<0) seed = -seed;\r
4367         }\r
4368 }\r
4369 \r
4370 int put(Board board, int pieceType, int rank, int n, int shade)\r
4371 // put the piece on the (n-1)-th empty squares of the given shade\r
4372 {\r
4373         int i;\r
4374 \r
4375         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
4376                 if( ((i-BOARD_LEFT)&1)+1 & shade && board[rank][i] == EmptySquare && n-- == 0) {\r
4377                         board[rank][i] = (ChessSquare) pieceType;\r
4378                         squaresLeft[(i-BOARD_LEFT&1) + 1]--;\r
4379                         squaresLeft[ANY]--;\r
4380                         piecesLeft[pieceType]--; \r
4381                         return i;\r
4382                 }\r
4383         }\r
4384         return -1;\r
4385 }\r
4386 \r
4387 \r
4388 void AddOnePiece(Board board, int pieceType, int rank, int shade)\r
4389 // calculate where the next piece goes, (any empty square), and put it there\r
4390 {\r
4391         int i;\r
4392 \r
4393         i = seed % squaresLeft[shade];\r
4394         nrOfShuffles *= squaresLeft[shade];\r
4395         seed /= squaresLeft[shade];\r
4396         put(board, pieceType, rank, i, shade);\r
4397 }\r
4398 \r
4399 void AddTwoPieces(Board board, int pieceType, int rank)\r
4400 // calculate where the next 2 identical pieces go, (any empty square), and put it there\r
4401 {\r
4402         int i, n=squaresLeft[ANY], j=n-1, k;\r
4403 \r
4404         k = n*(n-1)/2; // nr of possibilities, not counting permutations\r
4405         i = seed % k;  // pick one\r
4406         nrOfShuffles *= k;\r
4407         seed /= k;\r
4408         while(i >= j) i -= j--;\r
4409         j = n - 1 - j; i += j;\r
4410         put(board, pieceType, rank, j, ANY);\r
4411         put(board, pieceType, rank, i, ANY);\r
4412 }\r
4413 \r
4414 void SetUpShuffle(Board board, int number)\r
4415 {\r
4416         int i, p, first=1;\r
4417 \r
4418         GetPositionNumber(); nrOfShuffles = 1;\r
4419 \r
4420         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;\r
4421         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;\r
4422         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];\r
4423 \r
4424         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;\r
4425 \r
4426         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board\r
4427             p = (int) board[0][i];\r
4428             if(p < (int) BlackPawn) piecesLeft[p] ++;\r
4429             board[0][i] = EmptySquare;\r
4430         }\r
4431 \r
4432         if(PosFlags(0) & F_ALL_CASTLE_OK) {\r
4433             // shuffles restricted to allow normal castling put KRR first\r
4434             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle\r
4435                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4436             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles\r
4437                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4438             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling\r
4439                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);\r
4440             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling\r
4441                 put(board, WhiteRook, 0, 0, ANY);\r
4442             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle\r
4443         }\r
4444 \r
4445         if((BOARD_RGHT-BOARD_LEFT & 1) == 0)\r
4446             // only for even boards make effort to put pairs of colorbound pieces on opposite colors\r
4447             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {\r
4448                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;\r
4449                 while(piecesLeft[p] >= 2) {\r
4450                     AddOnePiece(board, p, 0, LITE);\r
4451                     AddOnePiece(board, p, 0, DARK);\r
4452                 }\r
4453                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)\r
4454             }\r
4455 \r
4456         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {\r
4457             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere\r
4458             // but we leave King and Rooks for last, to possibly obey FRC restriction\r
4459             if(p == (int)WhiteRook) continue;\r
4460             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations\r
4461             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece\r
4462         }\r
4463 \r
4464         // now everything is placed, except perhaps King (Unicorn) and Rooks\r
4465 \r
4466         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {\r
4467             // Last King gets castling rights\r
4468             while(piecesLeft[(int)WhiteUnicorn]) {\r
4469                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4470                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4471             }\r
4472 \r
4473             while(piecesLeft[(int)WhiteKing]) {\r
4474                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4475                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4476             }\r
4477 \r
4478 \r
4479         } else {\r
4480             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);\r
4481             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);\r
4482         }\r
4483 \r
4484         // Only Rooks can be left; simply place them all\r
4485         while(piecesLeft[(int)WhiteRook]) {\r
4486                 i = put(board, WhiteRook, 0, 0, ANY);\r
4487                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights\r
4488                         if(first) {\r
4489                                 first=0;\r
4490                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;\r
4491                         }\r
4492                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;\r
4493                 }\r
4494         }\r
4495         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white\r
4496             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;\r
4497         }\r
4498 \r
4499         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize\r
4500 }\r
4501 \r
4502 #endif\r
4503 \r
4504 int SetCharTable( char *table, const char * map )\r
4505 /* [HGM] moved here from winboard.c because of its general usefulness */\r
4506 /*       Basically a safe strcpy that uses the last character as King */\r
4507 {\r
4508     int result = FALSE; int NrPieces;\r
4509 \r
4510     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
4511                     && NrPieces >= 12 && !(NrPieces&1)) {\r
4512         int i; /* [HGM] Accept even length from 12 to 34 */\r
4513 \r
4514         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
4515         for( i=0; i<NrPieces/2-1; i++ ) {\r
4516             table[i] = map[i];\r
4517             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
4518         }\r
4519         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
4520         table[(int) BlackKing]  = map[NrPieces-1];\r
4521 \r
4522         result = TRUE;\r
4523     }\r
4524 \r
4525     return result;\r
4526 }\r
4527 \r
4528 void Prelude(Board board)\r
4529 {       // [HGM] superchess: random selection of exo-pieces\r
4530         int i, j, k; ChessSquare p; \r
4531         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };\r
4532 \r
4533         GetPositionNumber(); // use FRC position number\r
4534 \r
4535         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table\r
4536             SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4537             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) \r
4538                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;\r
4539         }\r
4540 \r
4541         j = seed%4;                 seed /= 4; \r
4542         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4543         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4544         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4545         j = seed%3 + (seed%3 >= j); seed /= 3; \r
4546         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4547         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4548         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4549         j = seed%3;                 seed /= 3; \r
4550         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4551         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4552         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4553         j = seed%2 + (seed%2 >= j); seed /= 2; \r
4554         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4555         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4556         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4557         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);\r
4558         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);\r
4559         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);\r
4560         put(board, exoPieces[0],    0, 0, ANY);\r
4561         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];\r
4562 }\r
4563 \r
4564 void\r
4565 InitPosition(redraw)\r
4566      int redraw;\r
4567 {\r
4568     ChessSquare (* pieces)[BOARD_SIZE];\r
4569     int i, j, pawnRow, overrule,\r
4570     oldx = gameInfo.boardWidth,\r
4571     oldy = gameInfo.boardHeight,\r
4572     oldh = gameInfo.holdingsWidth,\r
4573     oldv = gameInfo.variant;\r
4574 \r
4575     currentMove = forwardMostMove = backwardMostMove = 0;\r
4576     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request\r
4577 \r
4578     /* [AS] Initialize pv info list [HGM] and game status */\r
4579     {\r
4580         for( i=0; i<MAX_MOVES; i++ ) {\r
4581             pvInfoList[i].depth = 0;\r
4582             epStatus[i]=EP_NONE;\r
4583             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
4584         }\r
4585 \r
4586         initialRulePlies = 0; /* 50-move counter start */\r
4587 \r
4588         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4589         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4590     }\r
4591 \r
4592     \r
4593     /* [HGM] logic here is completely changed. In stead of full positions */\r
4594     /* the initialized data only consist of the two backranks. The switch */\r
4595     /* selects which one we will use, which is than copied to the Board   */\r
4596     /* initialPosition, which for the rest is initialized by Pawns and    */\r
4597     /* empty squares. This initial position is then copied to boards[0],  */\r
4598     /* possibly after shuffling, so that it remains available.            */\r
4599 \r
4600     gameInfo.holdingsWidth = 0; /* default board sizes */\r
4601     gameInfo.boardWidth    = 8;\r
4602     gameInfo.boardHeight   = 8;\r
4603     gameInfo.holdingsSize  = 0;\r
4604     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
4605     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
4606     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
4607 \r
4608     switch (gameInfo.variant) {\r
4609     case VariantFischeRandom:\r
4610       shuffleOpenings = TRUE;\r
4611     default:\r
4612       pieces = FIDEArray;\r
4613       break;\r
4614     case VariantShatranj:\r
4615       pieces = ShatranjArray;\r
4616       nrCastlingRights = 0;\r
4617       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
4618       break;\r
4619     case VariantTwoKings:\r
4620       pieces = twoKingsArray;\r
4621       nrCastlingRights = 8;                 /* add rights for second King */\r
4622       castlingRights[0][6] = initialRights[2] = 5;\r
4623       castlingRights[0][7] = initialRights[5] = 5;\r
4624       castlingRank[6] = 0;\r
4625       castlingRank[7] = BOARD_HEIGHT-1;\r
4626       break;\r
4627     case VariantCapaRandom:\r
4628       shuffleOpenings = TRUE;\r
4629     case VariantCapablanca:\r
4630       pieces = CapablancaArray;\r
4631       gameInfo.boardWidth = 10;\r
4632       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4633       break;\r
4634     case VariantGothic:\r
4635       pieces = GothicArray;\r
4636       gameInfo.boardWidth = 10;\r
4637       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4638       break;\r
4639     case VariantJanus:\r
4640       pieces = JanusArray;\r
4641       gameInfo.boardWidth = 10;\r
4642       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
4643       nrCastlingRights = 6;\r
4644         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4645         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4646         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH-1>>1;\r
4647         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4648         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4649         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH-1>>1;\r
4650       break;\r
4651     case VariantFalcon:\r
4652       pieces = FalconArray;\r
4653       gameInfo.boardWidth = 10;\r
4654       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4655       break;\r
4656     case VariantXiangqi:\r
4657       pieces = XiangqiArray;\r
4658       gameInfo.boardWidth  = 9;\r
4659       gameInfo.boardHeight = 10;\r
4660       nrCastlingRights = 0;\r
4661       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4662       break;\r
4663     case VariantShogi:\r
4664       pieces = ShogiArray;\r
4665       gameInfo.boardWidth  = 9;\r
4666       gameInfo.boardHeight = 9;\r
4667       gameInfo.holdingsSize = 7;\r
4668       nrCastlingRights = 0;\r
4669       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4670       break;\r
4671     case VariantCourier:\r
4672       pieces = CourierArray;\r
4673       gameInfo.boardWidth  = 12;\r
4674       nrCastlingRights = 0;\r
4675       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4676       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4677       break;\r
4678     case VariantKnightmate:\r
4679       pieces = KnightmateArray;\r
4680       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4681       break;\r
4682     case VariantFairy:\r
4683       pieces = fairyArray;\r
4684       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4685       break;\r
4686     case VariantGreat:\r
4687       pieces = GreatArray;\r
4688       gameInfo.boardWidth = 10;\r
4689       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");\r
4690       gameInfo.holdingsSize = 8;\r
4691       break;\r
4692     case VariantSuper:\r
4693       pieces = FIDEArray;\r
4694       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
4695       gameInfo.holdingsSize = 8;\r
4696       startedFromSetupPosition = TRUE;\r
4697       break;\r
4698     case VariantCrazyhouse:\r
4699     case VariantBughouse:\r
4700       pieces = FIDEArray;\r
4701       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4702       gameInfo.holdingsSize = 5;\r
4703       break;\r
4704     case VariantWildCastle:\r
4705       pieces = FIDEArray;\r
4706       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4707       shuffleOpenings = 1;\r
4708       break;\r
4709     case VariantNoCastle:\r
4710       pieces = FIDEArray;\r
4711       nrCastlingRights = 0;\r
4712       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4713       /* !!?unconstrained back-rank shuffle */\r
4714       shuffleOpenings = 1;\r
4715       break;\r
4716     }\r
4717 \r
4718     overrule = 0;\r
4719     if(appData.NrFiles >= 0) {\r
4720         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4721         gameInfo.boardWidth = appData.NrFiles;\r
4722     }\r
4723     if(appData.NrRanks >= 0) {\r
4724         gameInfo.boardHeight = appData.NrRanks;\r
4725     }\r
4726     if(appData.holdingsSize >= 0) {\r
4727         i = appData.holdingsSize;\r
4728         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4729         gameInfo.holdingsSize = i;\r
4730     }\r
4731     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4732     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4733         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);\r
4734 \r
4735     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4736     if(pawnRow < 1) pawnRow = 1;\r
4737 \r
4738     /* User pieceToChar list overrules defaults */\r
4739     if(appData.pieceToCharTable != NULL)\r
4740         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4741 \r
4742     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4743 \r
4744         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4745             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4746         for( i=0; i<BOARD_HEIGHT; i++ )\r
4747             initialPosition[i][j] = s;\r
4748 \r
4749         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4750         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4751         initialPosition[pawnRow][j] = WhitePawn;\r
4752         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4753         if(gameInfo.variant == VariantXiangqi) {\r
4754             if(j&1) {\r
4755                 initialPosition[pawnRow][j] = \r
4756                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4757                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4758                    initialPosition[2][j] = WhiteCannon;\r
4759                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4760                 }\r
4761             }\r
4762         }\r
4763         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4764     }\r
4765     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4766 \r
4767             j=BOARD_LEFT+1;\r
4768             initialPosition[1][j] = WhiteBishop;\r
4769             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4770             j=BOARD_RGHT-2;\r
4771             initialPosition[1][j] = WhiteRook;\r
4772             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4773     }\r
4774 \r
4775     if( nrCastlingRights == -1) {\r
4776         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4777         /*       This sets default castling rights from none to normal corners   */\r
4778         /* Variants with other castling rights must set them themselves above    */\r
4779         nrCastlingRights = 6;\r
4780        \r
4781         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4782         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4783         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4784         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4785         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4786         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4787      }\r
4788 \r
4789      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
4790      if(gameInfo.variant == VariantGreat) { // promotion commoners\r
4791         initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-1] = WhiteMan;\r
4792         initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-2] = 9;\r
4793         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;\r
4794         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;\r
4795      }\r
4796 #if 0\r
4797     if(gameInfo.variant == VariantFischeRandom) {\r
4798       if( appData.defaultFrcPosition < 0 ) {\r
4799         ShuffleFRC( initialPosition );\r
4800       }\r
4801       else {\r
4802         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4803       }\r
4804       startedFromSetupPosition = TRUE;\r
4805     } else \r
4806 #else\r
4807   if (appData.debugMode) {\r
4808     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
4809   }\r
4810     if(shuffleOpenings) {\r
4811         SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
4812         startedFromSetupPosition = TRUE;\r
4813     }\r
4814 #endif\r
4815     if(startedFromPositionFile) {\r
4816       /* [HGM] loadPos: use PositionFile for every new game */\r
4817       CopyBoard(initialPosition, filePosition);\r
4818       for(i=0; i<nrCastlingRights; i++)\r
4819           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4820       startedFromSetupPosition = TRUE;\r
4821     }\r
4822 \r
4823     CopyBoard(boards[0], initialPosition);\r
4824 \r
4825     if(oldx != gameInfo.boardWidth ||\r
4826        oldy != gameInfo.boardHeight ||\r
4827        oldh != gameInfo.holdingsWidth\r
4828 #ifdef GOTHIC\r
4829        || oldv == VariantGothic ||        // For licensing popups\r
4830        gameInfo.variant == VariantGothic\r
4831 #endif\r
4832 #ifdef FALCON\r
4833        || oldv == VariantFalcon ||\r
4834        gameInfo.variant == VariantFalcon\r
4835 #endif\r
4836                                          )\r
4837             InitDrawingSizes(-2 ,0);\r
4838 \r
4839     if (redraw)\r
4840       DrawPosition(TRUE, boards[currentMove]);\r
4841 }\r
4842 \r
4843 void\r
4844 SendBoard(cps, moveNum)\r
4845      ChessProgramState *cps;\r
4846      int moveNum;\r
4847 {\r
4848     char message[MSG_SIZ];\r
4849     \r
4850     if (cps->useSetboard) {\r
4851       char* fen = PositionToFEN(moveNum, cps->useFEN960);\r
4852       sprintf(message, "setboard %s\n", fen);\r
4853       SendToProgram(message, cps);\r
4854       free(fen);\r
4855 \r
4856     } else {\r
4857       ChessSquare *bp;\r
4858       int i, j;\r
4859       /* Kludge to set black to move, avoiding the troublesome and now\r
4860        * deprecated "black" command.\r
4861        */\r
4862       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4863 \r
4864       SendToProgram("edit\n", cps);\r
4865       SendToProgram("#\n", cps);\r
4866       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4867         bp = &boards[moveNum][i][BOARD_LEFT];\r
4868         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4869           if ((int) *bp < (int) BlackPawn) {\r
4870             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4871                     AAA + j, ONE + i);\r
4872             if(message[0] == '+' || message[0] == '~') {\r
4873                 sprintf(message, "%c%c%c+\n",\r
4874                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4875                         AAA + j, ONE + i);\r
4876             }\r
4877             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4878                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4879                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4880             }\r
4881             SendToProgram(message, cps);\r
4882           }\r
4883         }\r
4884       }\r
4885     \r
4886       SendToProgram("c\n", cps);\r
4887       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4888         bp = &boards[moveNum][i][BOARD_LEFT];\r
4889         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4890           if (((int) *bp != (int) EmptySquare)\r
4891               && ((int) *bp >= (int) BlackPawn)) {\r
4892             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4893                     AAA + j, ONE + i);\r
4894             if(message[0] == '+' || message[0] == '~') {\r
4895                 sprintf(message, "%c%c%c+\n",\r
4896                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4897                         AAA + j, ONE + i);\r
4898             }\r
4899             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4900                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4901                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4902             }\r
4903             SendToProgram(message, cps);\r
4904           }\r
4905         }\r
4906       }\r
4907     \r
4908       SendToProgram(".\n", cps);\r
4909     }\r
4910     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4911 }\r
4912 \r
4913 int\r
4914 IsPromotion(fromX, fromY, toX, toY)\r
4915      int fromX, fromY, toX, toY;\r
4916 {\r
4917     /* [HGM] add Shogi promotions */\r
4918     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4919     ChessSquare piece;\r
4920 \r
4921     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4922       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4923    /* [HGM] Note to self: line above also weeds out drops */\r
4924     piece = boards[currentMove][fromY][fromX];\r
4925     if(gameInfo.variant == VariantShogi) {\r
4926         promotionZoneSize = 3;\r
4927         highestPromotingPiece = (int)WhiteKing;\r
4928         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4929            and if in normal chess we then allow promotion to King, why not\r
4930            allow promotion of other piece in Shogi?                         */\r
4931     }\r
4932     if((int)piece >= BlackPawn) {\r
4933         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4934              return FALSE;\r
4935         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4936     } else {\r
4937         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4938            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4939     }\r
4940     return ( (int)piece <= highestPromotingPiece );\r
4941 }\r
4942 \r
4943 int\r
4944 InPalace(row, column)\r
4945      int row, column;\r
4946 {   /* [HGM] for Xiangqi */\r
4947     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4948          column < (BOARD_WIDTH + 4)/2 &&\r
4949          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4950     return FALSE;\r
4951 }\r
4952 \r
4953 int\r
4954 PieceForSquare (x, y)\r
4955      int x;\r
4956      int y;\r
4957 {\r
4958   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
4959      return -1;\r
4960   else\r
4961      return boards[currentMove][y][x];\r
4962 }\r
4963 \r
4964 int\r
4965 OKToStartUserMove(x, y)\r
4966      int x, y;\r
4967 {\r
4968     ChessSquare from_piece;\r
4969     int white_piece;\r
4970 \r
4971     if (matchMode) return FALSE;\r
4972     if (gameMode == EditPosition) return TRUE;\r
4973 \r
4974     if (x >= 0 && y >= 0)\r
4975       from_piece = boards[currentMove][y][x];\r
4976     else\r
4977       from_piece = EmptySquare;\r
4978 \r
4979     if (from_piece == EmptySquare) return FALSE;\r
4980 \r
4981     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4982       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4983 \r
4984     switch (gameMode) {\r
4985       case PlayFromGameFile:\r
4986       case AnalyzeFile:\r
4987       case TwoMachinesPlay:\r
4988       case EndOfGame:\r
4989         return FALSE;\r
4990 \r
4991       case IcsObserving:\r
4992       case IcsIdle:\r
4993         return FALSE;\r
4994 \r
4995       case MachinePlaysWhite:\r
4996       case IcsPlayingBlack:\r
4997         if (appData.zippyPlay) return FALSE;\r
4998         if (white_piece) {\r
4999             DisplayMoveError(_("You are playing Black"));\r
5000             return FALSE;\r
5001         }\r
5002         break;\r
5003 \r
5004       case MachinePlaysBlack:\r
5005       case IcsPlayingWhite:\r
5006         if (appData.zippyPlay) return FALSE;\r
5007         if (!white_piece) {\r
5008             DisplayMoveError(_("You are playing White"));\r
5009             return FALSE;\r
5010         }\r
5011         break;\r
5012 \r
5013       case EditGame:\r
5014         if (!white_piece && WhiteOnMove(currentMove)) {\r
5015             DisplayMoveError(_("It is White's turn"));\r
5016             return FALSE;\r
5017         }           \r
5018         if (white_piece && !WhiteOnMove(currentMove)) {\r
5019             DisplayMoveError(_("It is Black's turn"));\r
5020             return FALSE;\r
5021         }           \r
5022         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
5023             /* Editing correspondence game history */\r
5024             /* Could disallow this or prompt for confirmation */\r
5025             cmailOldMove = -1;\r
5026         }\r
5027         if (currentMove < forwardMostMove) {\r
5028             /* Discarding moves */\r
5029             /* Could prompt for confirmation here,\r
5030                but I don't think that's such a good idea */\r
5031             forwardMostMove = currentMove;\r
5032         }\r
5033         break;\r
5034 \r
5035       case BeginningOfGame:\r
5036         if (appData.icsActive) return FALSE;\r
5037         if (!appData.noChessProgram) {\r
5038             if (!white_piece) {\r
5039                 DisplayMoveError(_("You are playing White"));\r
5040                 return FALSE;\r
5041             }\r
5042         }\r
5043         break;\r
5044         \r
5045       case Training:\r
5046         if (!white_piece && WhiteOnMove(currentMove)) {\r
5047             DisplayMoveError(_("It is White's turn"));\r
5048             return FALSE;\r
5049         }           \r
5050         if (white_piece && !WhiteOnMove(currentMove)) {\r
5051             DisplayMoveError(_("It is Black's turn"));\r
5052             return FALSE;\r
5053         }           \r
5054         break;\r
5055 \r
5056       default:\r
5057       case IcsExamining:\r
5058         break;\r
5059     }\r
5060     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
5061         && gameMode != AnalyzeFile && gameMode != Training) {\r
5062         DisplayMoveError(_("Displayed position is not current"));\r
5063         return FALSE;\r
5064     }\r
5065     return TRUE;\r
5066 }\r
5067 \r
5068 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
5069 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
5070 int lastLoadGameUseList = FALSE;\r
5071 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
5072 ChessMove lastLoadGameStart = (ChessMove) 0;\r
5073 \r
5074 \r
5075 ChessMove\r
5076 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
5077      int fromX, fromY, toX, toY;\r
5078      int promoChar;\r
5079 {\r
5080     ChessMove moveType;\r
5081     ChessSquare pdown, pup;\r
5082 \r
5083     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
5084     if ((fromX == toX) && (fromY == toY)) {\r
5085         return ImpossibleMove;\r
5086     }\r
5087 \r
5088     /* [HGM] suppress all moves into holdings area and guard band */\r
5089     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
5090             return ImpossibleMove;\r
5091 \r
5092     /* [HGM] <sameColor> moved to here from winboard.c */\r
5093     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
5094     pdown = boards[currentMove][fromY][fromX];\r
5095     pup = boards[currentMove][toY][toX];\r
5096     if (    gameMode != EditPosition &&\r
5097             (WhitePawn <= pdown && pdown < BlackPawn &&\r
5098              WhitePawn <= pup && pup < BlackPawn  ||\r
5099              BlackPawn <= pdown && pdown < EmptySquare &&\r
5100              BlackPawn <= pup && pup < EmptySquare \r
5101             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
5102                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
5103                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
5104         )           )\r
5105          return ImpossibleMove;\r
5106 \r
5107     /* Check if the user is playing in turn.  This is complicated because we\r
5108        let the user "pick up" a piece before it is his turn.  So the piece he\r
5109        tried to pick up may have been captured by the time he puts it down!\r
5110        Therefore we use the color the user is supposed to be playing in this\r
5111        test, not the color of the piece that is currently on the starting\r
5112        square---except in EditGame mode, where the user is playing both\r
5113        sides; fortunately there the capture race can't happen.  (It can\r
5114        now happen in IcsExamining mode, but that's just too bad.  The user\r
5115        will get a somewhat confusing message in that case.)\r
5116        */\r
5117 \r
5118     switch (gameMode) {\r
5119       case PlayFromGameFile:\r
5120       case AnalyzeFile:\r
5121       case TwoMachinesPlay:\r
5122       case EndOfGame:\r
5123       case IcsObserving:\r
5124       case IcsIdle:\r
5125         /* We switched into a game mode where moves are not accepted,\r
5126            perhaps while the mouse button was down. */\r
5127         return ImpossibleMove;\r
5128 \r
5129       case MachinePlaysWhite:\r
5130         /* User is moving for Black */\r
5131         if (WhiteOnMove(currentMove)) {\r
5132             DisplayMoveError(_("It is White's turn"));\r
5133             return ImpossibleMove;\r
5134         }\r
5135         break;\r
5136 \r
5137       case MachinePlaysBlack:\r
5138         /* User is moving for White */\r
5139         if (!WhiteOnMove(currentMove)) {\r
5140             DisplayMoveError(_("It is Black's turn"));\r
5141             return ImpossibleMove;\r
5142         }\r
5143         break;\r
5144 \r
5145       case EditGame:\r
5146       case IcsExamining:\r
5147       case BeginningOfGame:\r
5148       case AnalyzeMode:\r
5149       case Training:\r
5150         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
5151             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
5152             /* User is moving for Black */\r
5153             if (WhiteOnMove(currentMove)) {\r
5154                 DisplayMoveError(_("It is White's turn"));\r
5155                 return ImpossibleMove;\r
5156             }\r
5157         } else {\r
5158             /* User is moving for White */\r
5159             if (!WhiteOnMove(currentMove)) {\r
5160                 DisplayMoveError(_("It is Black's turn"));\r
5161                 return ImpossibleMove;\r
5162             }\r
5163         }\r
5164         break;\r
5165 \r
5166       case IcsPlayingBlack:\r
5167         /* User is moving for Black */\r
5168         if (WhiteOnMove(currentMove)) {\r
5169             if (!appData.premove) {\r
5170                 DisplayMoveError(_("It is White's turn"));\r
5171             } else if (toX >= 0 && toY >= 0) {\r
5172                 premoveToX = toX;\r
5173                 premoveToY = toY;\r
5174                 premoveFromX = fromX;\r
5175                 premoveFromY = fromY;\r
5176                 premovePromoChar = promoChar;\r
5177                 gotPremove = 1;\r
5178                 if (appData.debugMode) \r
5179                     fprintf(debugFP, "Got premove: fromX %d,"\r
5180                             "fromY %d, toX %d, toY %d\n",\r
5181                             fromX, fromY, toX, toY);\r
5182             }\r
5183             return ImpossibleMove;\r
5184         }\r
5185         break;\r
5186 \r
5187       case IcsPlayingWhite:\r
5188         /* User is moving for White */\r
5189         if (!WhiteOnMove(currentMove)) {\r
5190             if (!appData.premove) {\r
5191                 DisplayMoveError(_("It is Black's turn"));\r
5192             } else if (toX >= 0 && toY >= 0) {\r
5193                 premoveToX = toX;\r
5194                 premoveToY = toY;\r
5195                 premoveFromX = fromX;\r
5196                 premoveFromY = fromY;\r
5197                 premovePromoChar = promoChar;\r
5198                 gotPremove = 1;\r
5199                 if (appData.debugMode) \r
5200                     fprintf(debugFP, "Got premove: fromX %d,"\r
5201                             "fromY %d, toX %d, toY %d\n",\r
5202                             fromX, fromY, toX, toY);\r
5203             }\r
5204             return ImpossibleMove;\r
5205         }\r
5206         break;\r
5207 \r
5208       default:\r
5209         break;\r
5210 \r
5211       case EditPosition:\r
5212         /* EditPosition, empty square, or different color piece;\r
5213            click-click move is possible */\r
5214         if (toX == -2 || toY == -2) {\r
5215             boards[0][fromY][fromX] = EmptySquare;\r
5216             return AmbiguousMove;\r
5217         } else if (toX >= 0 && toY >= 0) {\r
5218             boards[0][toY][toX] = boards[0][fromY][fromX];\r
5219             boards[0][fromY][fromX] = EmptySquare;\r
5220             return AmbiguousMove;\r
5221         }\r
5222         return ImpossibleMove;\r
5223     }\r
5224 \r
5225     /* [HGM] If move started in holdings, it means a drop */\r
5226     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
5227          if( pup != EmptySquare ) return ImpossibleMove;\r
5228          if(appData.testLegality) {\r
5229              /* it would be more logical if LegalityTest() also figured out\r
5230               * which drops are legal. For now we forbid pawns on back rank.\r
5231               * Shogi is on its own here...\r
5232               */\r
5233              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
5234                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
5235                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
5236          }\r
5237          return WhiteDrop; /* Not needed to specify white or black yet */\r
5238     }\r
5239 \r
5240     userOfferedDraw = FALSE;\r
5241         \r
5242     /* [HGM] always test for legality, to get promotion info */\r
5243     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
5244                           epStatus[currentMove], castlingRights[currentMove],\r
5245                                          fromY, fromX, toY, toX, promoChar);\r
5246 \r
5247     /* [HGM] but possibly ignore an IllegalMove result */\r
5248     if (appData.testLegality) {\r
5249         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
5250             DisplayMoveError(_("Illegal move"));\r
5251             return ImpossibleMove;\r
5252         }\r
5253     }\r
5254 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);\r
5255     return moveType;\r
5256     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
5257        function is made into one that returns an OK move type if FinishMove\r
5258        should be called. This to give the calling driver routine the\r
5259        opportunity to finish the userMove input with a promotion popup,\r
5260        without bothering the user with this for invalid or illegal moves */\r
5261 \r
5262 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
5263 }\r
5264 \r
5265 /* Common tail of UserMoveEvent and DropMenuEvent */\r
5266 int\r
5267 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
5268      ChessMove moveType;\r
5269      int fromX, fromY, toX, toY;\r
5270      /*char*/int promoChar;\r
5271 {\r
5272     char *bookHit = 0;\r
5273 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);\r
5274     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { \r
5275         // [HGM] superchess: suppress promotions to non-available piece\r
5276         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5277         if(WhiteOnMove(currentMove)) {\r
5278             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;\r
5279         } else {\r
5280             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;\r
5281         }\r
5282     }\r
5283 \r
5284     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion\r
5285        move type in caller when we know the move is a legal promotion */\r
5286     if(moveType == NormalMove && promoChar)\r
5287         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
5288 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);\r
5289     /* [HGM] convert drag-and-drop piece drops to standard form */\r
5290     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
5291          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
5292          fromX = boards[currentMove][fromY][fromX];\r
5293          fromY = DROP_RANK;\r
5294     }\r
5295 \r
5296     /* [HGM] <popupFix> The following if has been moved here from\r
5297        UserMoveEvent(). Because it seemed to belon here (why not allow\r
5298        piece drops in training games?), and because it can only be\r
5299        performed after it is known to what we promote. */\r
5300     if (gameMode == Training) {\r
5301       /* compare the move played on the board to the next move in the\r
5302        * game. If they match, display the move and the opponent's response. \r
5303        * If they don't match, display an error message.\r
5304        */\r
5305       int saveAnimate;\r
5306       Board testBoard;\r
5307       CopyBoard(testBoard, boards[currentMove]);\r
5308       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
5309 \r
5310       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
5311         ForwardInner(currentMove+1);\r
5312 \r
5313         /* Autoplay the opponent's response.\r
5314          * if appData.animate was TRUE when Training mode was entered,\r
5315          * the response will be animated.\r
5316          */\r
5317         saveAnimate = appData.animate;\r
5318         appData.animate = animateTraining;\r
5319         ForwardInner(currentMove+1);\r
5320         appData.animate = saveAnimate;\r
5321 \r
5322         /* check for the end of the game */\r
5323         if (currentMove >= forwardMostMove) {\r
5324           gameMode = PlayFromGameFile;\r
5325           ModeHighlight();\r
5326           SetTrainingModeOff();\r
5327           DisplayInformation(_("End of game"));\r
5328         }\r
5329       } else {\r
5330         DisplayError(_("Incorrect move"), 0);\r
5331       }\r
5332       return 1;\r
5333     }\r
5334 \r
5335   /* Ok, now we know that the move is good, so we can kill\r
5336      the previous line in Analysis Mode */\r
5337   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
5338     forwardMostMove = currentMove;\r
5339   }\r
5340 \r
5341   /* If we need the chess program but it's dead, restart it */\r
5342   ResurrectChessProgram();\r
5343 \r
5344   /* A user move restarts a paused game*/\r
5345   if (pausing)\r
5346     PauseEvent();\r
5347 \r
5348   thinkOutput[0] = NULLCHAR;\r
5349 \r
5350   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
5351 \r
5352     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) \r
5353                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
5354         // [HGM] superchess: take promotion piece out of holdings\r
5355         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5356         if(WhiteOnMove(forwardMostMove-1)) {\r
5357             if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])\r
5358                 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;\r
5359         } else {\r
5360             if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])\r
5361                 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;\r
5362         }\r
5363     }\r
5364 \r
5365   if (gameMode == BeginningOfGame) {\r
5366     if (appData.noChessProgram) {\r
5367       gameMode = EditGame;\r
5368       SetGameInfo();\r
5369     } else {\r
5370       char buf[MSG_SIZ];\r
5371       gameMode = MachinePlaysBlack;\r
5372       StartClocks();\r
5373       SetGameInfo();\r
5374       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
5375       DisplayTitle(buf);\r
5376       if (first.sendName) {\r
5377         sprintf(buf, "name %s\n", gameInfo.white);\r
5378         SendToProgram(buf, &first);\r
5379       }\r
5380       StartClocks();\r
5381     }\r
5382     ModeHighlight();\r
5383   }\r
5384 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);\r
5385   /* Relay move to ICS or chess engine */\r
5386   if (appData.icsActive) {\r
5387     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
5388         gameMode == IcsExamining) {\r
5389       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5390       ics_user_moved = 1;\r
5391     }\r
5392   } else {\r
5393     if (first.sendTime && (gameMode == BeginningOfGame ||\r
5394                            gameMode == MachinePlaysWhite ||\r
5395                            gameMode == MachinePlaysBlack)) {\r
5396       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
5397     }\r
5398     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
5399          // [HGM] book: if program might be playing, let it use book\r
5400         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);\r
5401         first.maybeThinking = TRUE;\r
5402     } else SendMoveToProgram(forwardMostMove-1, &first);\r
5403     if (currentMove == cmailOldMove + 1) {\r
5404       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
5405     }\r
5406   }\r
5407 \r
5408   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5409 \r
5410   switch (gameMode) {\r
5411   case EditGame:\r
5412     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
5413                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
5414     case MT_NONE:\r
5415     case MT_CHECK:\r
5416       break;\r
5417     case MT_CHECKMATE:\r
5418       if (WhiteOnMove(currentMove)) {\r
5419         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
5420       } else {\r
5421         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
5422       }\r
5423       break;\r
5424     case MT_STALEMATE:\r
5425       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
5426       break;\r
5427     }\r
5428     break;\r
5429     \r
5430   case MachinePlaysBlack:\r
5431   case MachinePlaysWhite:\r
5432     /* disable certain menu options while machine is thinking */\r
5433     SetMachineThinkingEnables();\r
5434     break;\r
5435 \r
5436   default:\r
5437     break;\r
5438   }\r
5439 \r
5440   if(bookHit) { // [HGM] book: simulate book reply\r
5441         static char bookMove[MSG_SIZ]; // a bit generous?\r
5442 \r
5443         programStats.depth = programStats.nodes = programStats.time = \r
5444         programStats.score = programStats.got_only_move = 0;\r
5445         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
5446 \r
5447         strcpy(bookMove, "move ");\r
5448         strcat(bookMove, bookHit);\r
5449         HandleMachineMove(bookMove, &first);\r
5450   }\r
5451   return 1;\r
5452 }\r
5453 \r
5454 void\r
5455 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
5456      int fromX, fromY, toX, toY;\r
5457      int promoChar;\r
5458 {\r
5459     /* [HGM] This routine was added to allow calling of its two logical\r
5460        parts from other modules in the old way. Before, UserMoveEvent()\r
5461        automatically called FinishMove() if the move was OK, and returned\r
5462        otherwise. I separated the two, in order to make it possible to\r
5463        slip a promotion popup in between. But that it always needs two\r
5464        calls, to the first part, (now called UserMoveTest() ), and to\r
5465        FinishMove if the first part succeeded. Calls that do not need\r
5466        to do anything in between, can call this routine the old way. \r
5467     */\r
5468     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
5469 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);\r
5470     if(moveType != ImpossibleMove)\r
5471         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
5472 }\r
5473 \r
5474 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
5475 {\r
5476     char * hint = lastHint;\r
5477     FrontEndProgramStats stats;\r
5478 \r
5479     stats.which = cps == &first ? 0 : 1;\r
5480     stats.depth = cpstats->depth;\r
5481     stats.nodes = cpstats->nodes;\r
5482     stats.score = cpstats->score;\r
5483     stats.time = cpstats->time;\r
5484     stats.pv = cpstats->movelist;\r
5485     stats.hint = lastHint;\r
5486     stats.an_move_index = 0;\r
5487     stats.an_move_count = 0;\r
5488 \r
5489     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
5490         stats.hint = cpstats->move_name;\r
5491         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
5492         stats.an_move_count = cpstats->nr_moves;\r
5493     }\r
5494 \r
5495     SetProgramStats( &stats );\r
5496 }\r
5497 \r
5498 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)\r
5499 {   // [HGM] book: this routine intercepts moves to simulate book replies\r
5500     char *bookHit = NULL;\r
5501 \r
5502     //first determine if the incoming move brings opponent into his book\r
5503     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
5504         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
5505     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");\r
5506     if(bookHit != NULL && !cps->bookSuspend) {\r
5507         // make sure opponent is not going to reply after receiving move to book position\r
5508         SendToProgram("force\n", cps);\r
5509         cps->bookSuspend = TRUE; // flag indicating it has to be restarted\r
5510     }\r
5511     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move\r
5512     // now arrange restart after book miss\r
5513     if(bookHit) {\r
5514         // after a book hit we never send 'go', and the code after the call to this routine\r
5515         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').\r
5516         char buf[MSG_SIZ];\r
5517         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(\r
5518         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it\r
5519         SendToProgram(buf, cps);\r
5520         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'\r
5521     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine\r
5522         SendToProgram("go\n", cps);\r
5523         cps->bookSuspend = FALSE; // after a 'go' we are never suspended\r
5524     } else { // 'go' might be sent based on 'firstMove' after this routine returns\r
5525         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return\r
5526             SendToProgram("go\n", cps); \r
5527         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss\r
5528     }\r
5529     return bookHit; // notify caller of hit, so it can take action to send move to opponent\r
5530 }\r
5531 \r
5532 char *savedMessage;\r
5533 ChessProgramState *savedState;\r
5534 void DeferredBookMove(void)\r
5535 {\r
5536         if(savedState->lastPing != savedState->lastPong)\r
5537                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
5538         else\r
5539         HandleMachineMove(savedMessage, savedState);\r
5540 }\r
5541 \r
5542 void\r
5543 HandleMachineMove(message, cps)\r
5544      char *message;\r
5545      ChessProgramState *cps;\r
5546 {\r
5547     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
5548     char realname[MSG_SIZ];\r
5549     int fromX, fromY, toX, toY;\r
5550     ChessMove moveType;\r
5551     char promoChar;\r
5552     char *p;\r
5553     int machineWhite;\r
5554     char *bookHit;\r
5555 \r
5556 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit\r
5557     /*\r
5558      * Kludge to ignore BEL characters\r
5559      */\r
5560     while (*message == '\007') message++;\r
5561 \r
5562     /*\r
5563      * [HGM] engine debug message: ignore lines starting with '#' character\r
5564      */\r
5565     if(cps->debug && *message == '#') return;\r
5566 \r
5567     /*\r
5568      * Look for book output\r
5569      */\r
5570     if (cps == &first && bookRequested) {\r
5571         if (message[0] == '\t' || message[0] == ' ') {\r
5572             /* Part of the book output is here; append it */\r
5573             strcat(bookOutput, message);\r
5574             strcat(bookOutput, "  \n");\r
5575             return;\r
5576         } else if (bookOutput[0] != NULLCHAR) {\r
5577             /* All of book output has arrived; display it */\r
5578             char *p = bookOutput;\r
5579             while (*p != NULLCHAR) {\r
5580                 if (*p == '\t') *p = ' ';\r
5581                 p++;\r
5582             }\r
5583             DisplayInformation(bookOutput);\r
5584             bookRequested = FALSE;\r
5585             /* Fall through to parse the current output */\r
5586         }\r
5587     }\r
5588 \r
5589     /*\r
5590      * Look for machine move.\r
5591      */\r
5592     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
5593         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
5594     {\r
5595         /* This method is only useful on engines that support ping */\r
5596         if (cps->lastPing != cps->lastPong) {\r
5597           if (gameMode == BeginningOfGame) {\r
5598             /* Extra move from before last new; ignore */\r
5599             if (appData.debugMode) {\r
5600                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5601             }\r
5602           } else {\r
5603             if (appData.debugMode) {\r
5604                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5605                         cps->which, gameMode);\r
5606             }\r
5607 \r
5608             SendToProgram("undo\n", cps);\r
5609           }\r
5610           return;\r
5611         }\r
5612 \r
5613         switch (gameMode) {\r
5614           case BeginningOfGame:\r
5615             /* Extra move from before last reset; ignore */\r
5616             if (appData.debugMode) {\r
5617                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5618             }\r
5619             return;\r
5620 \r
5621           case EndOfGame:\r
5622           case IcsIdle:\r
5623           default:\r
5624             /* Extra move after we tried to stop.  The mode test is\r
5625                not a reliable way of detecting this problem, but it's\r
5626                the best we can do on engines that don't support ping.\r
5627             */\r
5628             if (appData.debugMode) {\r
5629                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5630                         cps->which, gameMode);\r
5631             }\r
5632             SendToProgram("undo\n", cps);\r
5633             return;\r
5634 \r
5635           case MachinePlaysWhite:\r
5636           case IcsPlayingWhite:\r
5637             machineWhite = TRUE;\r
5638             break;\r
5639 \r
5640           case MachinePlaysBlack:\r
5641           case IcsPlayingBlack:\r
5642             machineWhite = FALSE;\r
5643             break;\r
5644 \r
5645           case TwoMachinesPlay:\r
5646             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5647             break;\r
5648         }\r
5649         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5650             if (appData.debugMode) {\r
5651                 fprintf(debugFP,\r
5652                         "Ignoring move out of turn by %s, gameMode %d"\r
5653                         ", forwardMost %d\n",\r
5654                         cps->which, gameMode, forwardMostMove);\r
5655             }\r
5656             return;\r
5657         }\r
5658 \r
5659     if (appData.debugMode) { int f = forwardMostMove;\r
5660         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5661                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5662     }\r
5663         if(cps->alphaRank) AlphaRank(machineMove, 4);\r
5664         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5665                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5666             /* Machine move could not be parsed; ignore it. */\r
5667             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),\r
5668                     machineMove, cps->which);\r
5669             DisplayError(buf1, 0);\r
5670             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",\r
5671                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5672             if (gameMode == TwoMachinesPlay) {\r
5673               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5674                        buf1, GE_XBOARD);\r
5675             }\r
5676             return;\r
5677         }\r
5678 \r
5679         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5680         /* So we have to redo legality test with true e.p. status here,  */\r
5681         /* to make sure an illegal e.p. capture does not slip through,   */\r
5682         /* to cause a forfeit on a justified illegal-move complaint      */\r
5683         /* of the opponent.                                              */\r
5684         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5685             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5686                                                               ) {\r
5687            ChessMove moveType;\r
5688            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5689                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5690                              fromY, fromX, toY, toX, promoChar);\r
5691             if (appData.debugMode) {\r
5692                 int i;\r
5693                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5694                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5695                 fprintf(debugFP, "castling rights\n");\r
5696             }\r
5697             if(moveType == IllegalMove) {\r
5698                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5699                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5700                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5701                            buf1, GE_XBOARD);\r
5702            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
5703            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5704               when they shouldn't (like TSCP-Gothic) */\r
5705            switch(moveType) {\r
5706              case WhiteASideCastleFR:\r
5707              case BlackASideCastleFR:\r
5708                toX+=2;\r
5709                currentMoveString[2]++;\r
5710                break;\r
5711              case WhiteHSideCastleFR:\r
5712              case BlackHSideCastleFR:\r
5713                toX--;\r
5714                currentMoveString[2]--;\r
5715                break;\r
5716            }\r
5717         }\r
5718         hintRequested = FALSE;\r
5719         lastHint[0] = NULLCHAR;\r
5720         bookRequested = FALSE;\r
5721         /* Program may be pondering now */\r
5722         cps->maybeThinking = TRUE;\r
5723         if (cps->sendTime == 2) cps->sendTime = 1;\r
5724         if (cps->offeredDraw) cps->offeredDraw--;\r
5725 \r
5726 #if ZIPPY\r
5727         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5728             first.initDone) {\r
5729           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5730           ics_user_moved = 1;\r
5731           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
5732                 char buf[3*MSG_SIZ];\r
5733 \r
5734                 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",\r
5735                         programStats.depth,\r
5736                         programStats.score / 100.,\r
5737                         programStats.time / 100.,\r
5738                         (double) programStats.nodes,\r
5739                         programStats.nodes / (10*abs(programStats.time) + 1.),\r
5740                         programStats.movelist);\r
5741                 SendToICS(buf);\r
5742           }\r
5743         }\r
5744 #endif\r
5745         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5746         strcpy(machineMove, currentMoveString);\r
5747         strcat(machineMove, "\n");\r
5748         strcpy(moveList[forwardMostMove], machineMove);\r
5749 \r
5750         /* [AS] Save move info and clear stats for next move */\r
5751         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5752         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5753         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5754         ClearProgramStats();\r
5755         thinkOutput[0] = NULLCHAR;\r
5756         hiddenThinkOutputState = 0;\r
5757 \r
5758         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5759 \r
5760         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5761         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5762             int count = 0;\r
5763 \r
5764             while( count < adjudicateLossPlies ) {\r
5765                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5766 \r
5767                 if( count & 1 ) {\r
5768                     score = -score; /* Flip score for winning side */\r
5769                 }\r
5770 \r
5771                 if( score > adjudicateLossThreshold ) {\r
5772                     break;\r
5773                 }\r
5774 \r
5775                 count++;\r
5776             }\r
5777 \r
5778             if( count >= adjudicateLossPlies ) {\r
5779                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5780 \r
5781                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5782                     "Xboard adjudication", \r
5783                     GE_XBOARD );\r
5784 \r
5785                 return;\r
5786             }\r
5787         }\r
5788 \r
5789         if( gameMode == TwoMachinesPlay ) {\r
5790           // [HGM] some adjudications useful with buggy engines\r
5791             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
5792           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {\r
5793 \r
5794             if(appData.testLegality)\r
5795             // don't wait for engine to announce game end if we can judge ourselves\r
5796             switch (MateTest(boards[forwardMostMove],\r
5797                                  PosFlags(forwardMostMove), epFile,\r
5798                                        castlingRights[forwardMostMove]) ) {\r
5799               case MT_NONE:\r
5800               case MT_CHECK:\r
5801               default:\r
5802                 break;\r
5803               case MT_STALEMATE:\r
5804                 epStatus[forwardMostMove] = EP_STALEMATE;\r
5805                 if(appData.checkMates) {\r
5806                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5807                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5808                     GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",\r
5809                         GE_XBOARD );\r
5810                 }\r
5811                 break;\r
5812               case MT_CHECKMATE:\r
5813                 epStatus[forwardMostMove] = EP_CHECKMATE;\r
5814                 if(appData.checkMates) {\r
5815                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5816                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5817                     GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5818                     "Xboard adjudication: Checkmate", \r
5819                     GE_XBOARD );\r
5820                 }\r
5821                 break;\r
5822             }\r
5823 \r
5824             if( appData.testLegality )\r
5825             {   /* [HGM] Some more adjudications for obstinate engines */\r
5826                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5827                     NrWQ=0, NrBQ=0, NrW=0, bishopsColor = 0,\r
5828                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;\r
5829                 static int moveCount = 6;\r
5830 \r
5831                 /* First absolutely insufficient mating material. Count what is on board. */\r
5832                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5833                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5834                     int m=i;\r
5835 \r
5836                     switch((int) p)\r
5837                     {   /* count B,N,R and other of each side */\r
5838                         case WhiteKnight:\r
5839                              NrWN++; break;\r
5840                         case WhiteBishop:\r
5841                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5842                              bishopsColor |= 1 << ((i^j)&1);\r
5843                              NrWB++; break;\r
5844                         case BlackKnight:\r
5845                              NrBN++; break;\r
5846                         case BlackBishop:\r
5847                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5848                              bishopsColor |= 1 << ((i^j)&1);\r
5849                              NrBB++; break;\r
5850                         case WhiteRook:\r
5851                              NrWR++; break;\r
5852                         case BlackRook:\r
5853                              NrBR++; break;\r
5854                         case WhiteQueen:\r
5855                              NrWQ++; break;\r
5856                         case BlackQueen:\r
5857                              NrBQ++; break;\r
5858                         case EmptySquare: \r
5859                              break;\r
5860                         case BlackPawn:\r
5861                              m = 7-i;\r
5862                         case WhitePawn:\r
5863                              PawnAdvance += m; NrPawns++;\r
5864                     }\r
5865                     NrPieces += (p != EmptySquare);\r
5866                     NrW += ((int)p < (int)BlackPawn);\r
5867                     if(gameInfo.variant == VariantXiangqi && \r
5868                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {\r
5869                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces\r
5870                         NrW -= ((int)p < (int)BlackPawn);\r
5871                     }\r
5872                 }\r
5873 \r
5874                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&\r
5875                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||\r
5876                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color\r
5877                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5878 \r
5879                      /* always flag draws, for judging claims */\r
5880                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5881 \r
5882                      if(appData.materialDraws) {\r
5883                          /* but only adjudicate them if adjudication enabled */\r
5884                          SendToProgram("force\n", cps->other); // suppress reply\r
5885                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */\r
5886                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5887                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5888                          return;\r
5889                      }\r
5890                 }\r
5891 \r
5892                 /* Shatranj baring rule */\r
5893                 if( gameInfo.variant == VariantShatranj && (NrW == 1 || NrPieces - NrW == 1) )\r
5894                 {    /* bare King */\r
5895 \r
5896                      if(--bare < 0 && appData.checkMates) {\r
5897                          /* but only adjudicate them if adjudication enabled */\r
5898                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5899                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5900                          GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
5901                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5902                          return;\r
5903                      }\r
5904                 } else bare = 1;\r
5905 \r
5906                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5907                 if(NrPieces == 4 && \r
5908                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5909                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5910                    || NrWN==2 || NrBN==2     /* KNNK */\r
5911                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5912                   ) ) {\r
5913                      if(--moveCount < 0 && appData.trivialDraws)\r
5914                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5915                           SendToProgram("force\n", cps->other); // suppress reply\r
5916                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5917                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5918                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5919                           return;\r
5920                      }\r
5921                 } else moveCount = 6;\r
5922             }\r
5923           }\r
5924 #if 1\r
5925     if (appData.debugMode) { int i;\r
5926       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5927               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5928               appData.drawRepeats);\r
5929       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5930            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5931 \r
5932     }\r
5933 #endif\r
5934                 /* Check for rep-draws */\r
5935                 count = 0;\r
5936                 for(k = forwardMostMove-2;\r
5937                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5938                         epStatus[k] < EP_UNKNOWN &&\r
5939                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5940                     k-=2)\r
5941                 {   int rights=0;\r
5942 #if 0\r
5943     if (appData.debugMode) {\r
5944       fprintf(debugFP, " loop\n");\r
5945     }\r
5946 #endif\r
5947                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5948 #if 0\r
5949     if (appData.debugMode) {\r
5950       fprintf(debugFP, "match\n");\r
5951     }\r
5952 #endif\r
5953                         /* compare castling rights */\r
5954                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5955                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5956                                 rights++; /* King lost rights, while rook still had them */\r
5957                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5958                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5959                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5960                                    rights++; /* but at least one rook lost them */\r
5961                         }\r
5962                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5963                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5964                                 rights++; \r
5965                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5966                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5967                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5968                                    rights++;\r
5969                         }\r
5970 #if 0\r
5971     if (appData.debugMode) {\r
5972       for(i=0; i<nrCastlingRights; i++)\r
5973       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5974     }\r
5975 \r
5976     if (appData.debugMode) {\r
5977       fprintf(debugFP, " %d %d\n", rights, k);\r
5978     }\r
5979 #endif\r
5980                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5981                             && appData.drawRepeats > 1) {\r
5982                              /* adjudicate after user-specified nr of repeats */\r
5983                              SendToProgram("force\n", cps->other); // suppress reply\r
5984                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5985                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5986                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { \r
5987                                 // [HGM] xiangqi: check for forbidden perpetuals\r
5988                                 int m, ourPerpetual = 1, hisPerpetual = 1;\r
5989                                 for(m=forwardMostMove; m>k; m-=2) {\r
5990                                     if(MateTest(boards[m], PosFlags(m), \r
5991                                                         EP_NONE, castlingRights[m]) != MT_CHECK)\r
5992                                         ourPerpetual = 0; // the current mover did not always check\r
5993                                     if(MateTest(boards[m-1], PosFlags(m-1), \r
5994                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)\r
5995                                         hisPerpetual = 0; // the opponent did not always check\r
5996                                 }\r
5997                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
5998                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5999                                            "Xboard adjudication: perpetual checking", GE_XBOARD );\r
6000                                     return;\r
6001                                 }\r
6002                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet\r
6003                                     break; // (or we would have caught him before). Abort repetition-checking loop.\r
6004                                 // if neither of us is checking all the time, or both are, it is draw\r
6005                                 // (illegal-chase forfeits not implemented yet!)\r
6006                              }\r
6007                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
6008                              return;\r
6009                         }\r
6010                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
6011                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
6012                     }\r
6013                 }\r
6014 \r
6015                 /* Now we test for 50-move draws. Determine ply count */\r
6016                 count = forwardMostMove;\r
6017                 /* look for last irreversble move */\r
6018                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
6019                     count--;\r
6020                 /* if we hit starting position, add initial plies */\r
6021                 if( count == backwardMostMove )\r
6022                     count -= initialRulePlies;\r
6023                 count = forwardMostMove - count; \r
6024                 if( count >= 100)\r
6025                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
6026                          /* this is used to judge if draw claims are legal */\r
6027                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
6028                          SendToProgram("force\n", cps->other); // suppress reply\r
6029                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6030                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6031                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
6032                          return;\r
6033                 }\r
6034 \r
6035                 /* if draw offer is pending, treat it as a draw claim\r
6036                  * when draw condition present, to allow engines a way to\r
6037                  * claim draws before making their move to avoid a race\r
6038                  * condition occurring after their move\r
6039                  */\r
6040                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
6041                          char *p = NULL;\r
6042                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
6043                              p = "Draw claim: 50-move rule";\r
6044                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
6045                              p = "Draw claim: 3-fold repetition";\r
6046                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
6047                              p = "Draw claim: insufficient mating material";\r
6048                          if( p != NULL ) {\r
6049                              SendToProgram("force\n", cps->other); // suppress reply\r
6050                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6051                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
6052                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6053                              return;\r
6054                          }\r
6055                 }\r
6056 \r
6057 \r
6058                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
6059                     SendToProgram("force\n", cps->other); // suppress reply\r
6060                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6061                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6062 \r
6063                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
6064 \r
6065                     return;\r
6066                 }\r
6067         }\r
6068 \r
6069         bookHit = NULL;\r
6070         if (gameMode == TwoMachinesPlay) {\r
6071             /* [HGM] relaying draw offers moved to after reception of move */\r
6072             /* and interpreting offer as claim if it brings draw condition */\r
6073             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
6074                 SendToProgram("draw\n", cps->other);\r
6075             }\r
6076             if (cps->other->sendTime) {\r
6077                 SendTimeRemaining(cps->other,\r
6078                                   cps->other->twoMachinesColor[0] == 'w');\r
6079             }\r
6080             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);\r
6081             if (firstMove && !bookHit) {\r
6082                 firstMove = FALSE;\r
6083                 if (cps->other->useColors) {\r
6084                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
6085                 }\r
6086                 SendToProgram("go\n", cps->other);\r
6087             }\r
6088             cps->other->maybeThinking = TRUE;\r
6089         }\r
6090 \r
6091         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6092         \r
6093         if (!pausing && appData.ringBellAfterMoves) {\r
6094             RingBell();\r
6095         }\r
6096 \r
6097         /* \r
6098          * Reenable menu items that were disabled while\r
6099          * machine was thinking\r
6100          */\r
6101         if (gameMode != TwoMachinesPlay)\r
6102             SetUserThinkingEnables();\r
6103 \r
6104         // [HGM] book: after book hit opponent has received move and is now in force mode\r
6105         // force the book reply into it, and then fake that it outputted this move by jumping\r
6106         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move\r
6107         if(bookHit) {\r
6108                 static char bookMove[MSG_SIZ]; // a bit generous?\r
6109 \r
6110                 strcpy(bookMove, "move ");\r
6111                 strcat(bookMove, bookHit);\r
6112                 message = bookMove;\r
6113                 cps = cps->other;\r
6114                 programStats.depth = programStats.nodes = programStats.time = \r
6115                 programStats.score = programStats.got_only_move = 0;\r
6116                 sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
6117 \r
6118                 if(cps->lastPing != cps->lastPong) {\r
6119                     savedMessage = message; // args for deferred call\r
6120                     savedState = cps;\r
6121                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
6122                     return;\r
6123                 }\r
6124                 goto FakeBookMove;\r
6125         }\r
6126 \r
6127         return;\r
6128     }\r
6129 \r
6130     /* Set special modes for chess engines.  Later something general\r
6131      *  could be added here; for now there is just one kludge feature,\r
6132      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
6133      *  when "xboard" is given as an interactive command.\r
6134      */\r
6135     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
6136         cps->useSigint = FALSE;\r
6137         cps->useSigterm = FALSE;\r
6138     }\r
6139 \r
6140     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
6141      * want this, I was asked to put it in, and obliged.\r
6142      */\r
6143     if (!strncmp(message, "setboard ", 9)) {\r
6144         Board initial_position; int i;\r
6145 \r
6146         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
6147 \r
6148         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
6149             DisplayError(_("Bad FEN received from engine"), 0);\r
6150             return ;\r
6151         } else {\r
6152            Reset(FALSE, FALSE);\r
6153            CopyBoard(boards[0], initial_position);\r
6154            initialRulePlies = FENrulePlies;\r
6155            epStatus[0] = FENepStatus;\r
6156            for( i=0; i<nrCastlingRights; i++ )\r
6157                 castlingRights[0][i] = FENcastlingRights[i];\r
6158            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
6159            else gameMode = MachinePlaysBlack;                 \r
6160            DrawPosition(FALSE, boards[currentMove]);\r
6161         }\r
6162         return;\r
6163     }\r
6164 \r
6165     /*\r
6166      * Look for communication commands\r
6167      */\r
6168     if (!strncmp(message, "telluser ", 9)) {\r
6169         DisplayNote(message + 9);\r
6170         return;\r
6171     }\r
6172     if (!strncmp(message, "tellusererror ", 14)) {\r
6173         DisplayError(message + 14, 0);\r
6174         return;\r
6175     }\r
6176     if (!strncmp(message, "tellopponent ", 13)) {\r
6177       if (appData.icsActive) {\r
6178         if (loggedOn) {\r
6179           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
6180           SendToICS(buf1);\r
6181         }\r
6182       } else {\r
6183         DisplayNote(message + 13);\r
6184       }\r
6185       return;\r
6186     }\r
6187     if (!strncmp(message, "tellothers ", 11)) {\r
6188       if (appData.icsActive) {\r
6189         if (loggedOn) {\r
6190           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
6191           SendToICS(buf1);\r
6192         }\r
6193       }\r
6194       return;\r
6195     }\r
6196     if (!strncmp(message, "tellall ", 8)) {\r
6197       if (appData.icsActive) {\r
6198         if (loggedOn) {\r
6199           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
6200           SendToICS(buf1);\r
6201         }\r
6202       } else {\r
6203         DisplayNote(message + 8);\r
6204       }\r
6205       return;\r
6206     }\r
6207     if (strncmp(message, "warning", 7) == 0) {\r
6208         /* Undocumented feature, use tellusererror in new code */\r
6209         DisplayError(message, 0);\r
6210         return;\r
6211     }\r
6212     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
6213         strcpy(realname, cps->tidy);\r
6214         strcat(realname, " query");\r
6215         AskQuestion(realname, buf2, buf1, cps->pr);\r
6216         return;\r
6217     }\r
6218     /* Commands from the engine directly to ICS.  We don't allow these to be \r
6219      *  sent until we are logged on. Crafty kibitzes have been known to \r
6220      *  interfere with the login process.\r
6221      */\r
6222     if (loggedOn) {\r
6223         if (!strncmp(message, "tellics ", 8)) {\r
6224             SendToICS(message + 8);\r
6225             SendToICS("\n");\r
6226             return;\r
6227         }\r
6228         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
6229             SendToICS(ics_prefix);\r
6230             SendToICS(message + 15);\r
6231             SendToICS("\n");\r
6232             return;\r
6233         }\r
6234         /* The following are for backward compatibility only */\r
6235         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
6236             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
6237             SendToICS(ics_prefix);\r
6238             SendToICS(message);\r
6239             SendToICS("\n");\r
6240             return;\r
6241         }\r
6242     }\r
6243     if (strncmp(message, "feature ", 8) == 0) {\r
6244       ParseFeatures(message+8, cps);\r
6245     }\r
6246     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
6247         return;\r
6248     }\r
6249     /*\r
6250      * If the move is illegal, cancel it and redraw the board.\r
6251      * Also deal with other error cases.  Matching is rather loose\r
6252      * here to accommodate engines written before the spec.\r
6253      */\r
6254     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
6255         strncmp(message, "Error", 5) == 0) {\r
6256         if (StrStr(message, "name") || \r
6257             StrStr(message, "rating") || StrStr(message, "?") ||\r
6258             StrStr(message, "result") || StrStr(message, "board") ||\r
6259             StrStr(message, "bk") || StrStr(message, "computer") ||\r
6260             StrStr(message, "variant") || StrStr(message, "hint") ||\r
6261             StrStr(message, "random") || StrStr(message, "depth") ||\r
6262             StrStr(message, "accepted")) {\r
6263             return;\r
6264         }\r
6265         if (StrStr(message, "protover")) {\r
6266           /* Program is responding to input, so it's apparently done\r
6267              initializing, and this error message indicates it is\r
6268              protocol version 1.  So we don't need to wait any longer\r
6269              for it to initialize and send feature commands. */\r
6270           FeatureDone(cps, 1);\r
6271           cps->protocolVersion = 1;\r
6272           return;\r
6273         }\r
6274         cps->maybeThinking = FALSE;\r
6275 \r
6276         if (StrStr(message, "draw")) {\r
6277             /* Program doesn't have "draw" command */\r
6278             cps->sendDrawOffers = 0;\r
6279             return;\r
6280         }\r
6281         if (cps->sendTime != 1 &&\r
6282             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
6283           /* Program apparently doesn't have "time" or "otim" command */\r
6284           cps->sendTime = 0;\r
6285           return;\r
6286         }\r
6287         if (StrStr(message, "analyze")) {\r
6288             cps->analysisSupport = FALSE;\r
6289             cps->analyzing = FALSE;\r
6290             Reset(FALSE, TRUE);\r
6291             sprintf(buf2, _("%s does not support analysis"), cps->tidy);\r
6292             DisplayError(buf2, 0);\r
6293             return;\r
6294         }\r
6295         if (StrStr(message, "(no matching move)st")) {\r
6296           /* Special kludge for GNU Chess 4 only */\r
6297           cps->stKludge = TRUE;\r
6298           SendTimeControl(cps, movesPerSession, timeControl,\r
6299                           timeIncrement, appData.searchDepth,\r
6300                           searchTime);\r
6301           return;\r
6302         }\r
6303         if (StrStr(message, "(no matching move)sd")) {\r
6304           /* Special kludge for GNU Chess 4 only */\r
6305           cps->sdKludge = TRUE;\r
6306           SendTimeControl(cps, movesPerSession, timeControl,\r
6307                           timeIncrement, appData.searchDepth,\r
6308                           searchTime);\r
6309           return;\r
6310         }\r
6311         if (!StrStr(message, "llegal")) {\r
6312             return;\r
6313         }\r
6314         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6315             gameMode == IcsIdle) return;\r
6316         if (forwardMostMove <= backwardMostMove) return;\r
6317 #if 0\r
6318         /* Following removed: it caused a bug where a real illegal move\r
6319            message in analyze mored would be ignored. */\r
6320         if (cps == &first && programStats.ok_to_send == 0) {\r
6321             /* Bogus message from Crafty responding to "."  This filtering\r
6322                can miss some of the bad messages, but fortunately the bug \r
6323                is fixed in current Crafty versions, so it doesn't matter. */\r
6324             return;\r
6325         }\r
6326 #endif\r
6327         if (pausing) PauseEvent();\r
6328         if (gameMode == PlayFromGameFile) {\r
6329             /* Stop reading this game file */\r
6330             gameMode = EditGame;\r
6331             ModeHighlight();\r
6332         }\r
6333         currentMove = --forwardMostMove;\r
6334         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
6335         SwitchClocks();\r
6336         DisplayBothClocks();\r
6337         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),\r
6338                 parseList[currentMove], cps->which);\r
6339         DisplayMoveError(buf1);\r
6340         DrawPosition(FALSE, boards[currentMove]);\r
6341 \r
6342         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
6343         /* only passes fully legal moves                            */\r
6344         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
6345             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
6346                                 "False illegal-move claim", GE_XBOARD );\r
6347         }\r
6348         return;\r
6349     }\r
6350     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
6351         /* Program has a broken "time" command that\r
6352            outputs a string not ending in newline.\r
6353            Don't use it. */\r
6354         cps->sendTime = 0;\r
6355     }\r
6356     \r
6357     /*\r
6358      * If chess program startup fails, exit with an error message.\r
6359      * Attempts to recover here are futile.\r
6360      */\r
6361     if ((StrStr(message, "unknown host") != NULL)\r
6362         || (StrStr(message, "No remote directory") != NULL)\r
6363         || (StrStr(message, "not found") != NULL)\r
6364         || (StrStr(message, "No such file") != NULL)\r
6365         || (StrStr(message, "can't alloc") != NULL)\r
6366         || (StrStr(message, "Permission denied") != NULL)) {\r
6367 \r
6368         cps->maybeThinking = FALSE;\r
6369         sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),\r
6370                 cps->which, cps->program, cps->host, message);\r
6371         RemoveInputSource(cps->isr);\r
6372         DisplayFatalError(buf1, 0, 1);\r
6373         return;\r
6374     }\r
6375     \r
6376     /* \r
6377      * Look for hint output\r
6378      */\r
6379     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
6380         if (cps == &first && hintRequested) {\r
6381             hintRequested = FALSE;\r
6382             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
6383                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
6384                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
6385                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
6386                                     fromY, fromX, toY, toX, promoChar, buf1);\r
6387                 sprintf(buf2, _("Hint: %s"), buf1);\r
6388                 DisplayInformation(buf2);\r
6389             } else {\r
6390                 /* Hint move could not be parsed!? */\r
6391                 sprintf(buf2,\r
6392                         _("Illegal hint move \"%s\"\nfrom %s chess program"),\r
6393                         buf1, cps->which);\r
6394                 DisplayError(buf2, 0);\r
6395             }\r
6396         } else {\r
6397             strcpy(lastHint, buf1);\r
6398         }\r
6399         return;\r
6400     }\r
6401 \r
6402     /*\r
6403      * Ignore other messages if game is not in progress\r
6404      */\r
6405     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6406         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
6407 \r
6408     /*\r
6409      * look for win, lose, draw, or draw offer\r
6410      */\r
6411     if (strncmp(message, "1-0", 3) == 0) {\r
6412         char *p, *q, *r = "";\r
6413         p = strchr(message, '{');\r
6414         if (p) {\r
6415             q = strchr(p, '}');\r
6416             if (q) {\r
6417                 *q = NULLCHAR;\r
6418                 r = p + 1;\r
6419             }\r
6420         }\r
6421         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
6422         return;\r
6423     } else if (strncmp(message, "0-1", 3) == 0) {\r
6424         char *p, *q, *r = "";\r
6425         p = strchr(message, '{');\r
6426         if (p) {\r
6427             q = strchr(p, '}');\r
6428             if (q) {\r
6429                 *q = NULLCHAR;\r
6430                 r = p + 1;\r
6431             }\r
6432         }\r
6433         /* Kludge for Arasan 4.1 bug */\r
6434         if (strcmp(r, "Black resigns") == 0) {\r
6435             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
6436             return;\r
6437         }\r
6438         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
6439         return;\r
6440     } else if (strncmp(message, "1/2", 3) == 0) {\r
6441         char *p, *q, *r = "";\r
6442         p = strchr(message, '{');\r
6443         if (p) {\r
6444             q = strchr(p, '}');\r
6445             if (q) {\r
6446                 *q = NULLCHAR;\r
6447                 r = p + 1;\r
6448             }\r
6449         }\r
6450             \r
6451         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
6452         return;\r
6453 \r
6454     } else if (strncmp(message, "White resign", 12) == 0) {\r
6455         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6456         return;\r
6457     } else if (strncmp(message, "Black resign", 12) == 0) {\r
6458         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6459         return;\r
6460     } else if (strncmp(message, "White matches", 13) == 0 ||\r
6461                strncmp(message, "Black matches", 13) == 0   ) {\r
6462         /* [HGM] ignore GNUShogi noises */\r
6463         return;\r
6464     } else if (strncmp(message, "White", 5) == 0 &&\r
6465                message[5] != '(' &&\r
6466                StrStr(message, "Black") == NULL) {\r
6467         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6468         return;\r
6469     } else if (strncmp(message, "Black", 5) == 0 &&\r
6470                message[5] != '(') {\r
6471         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6472         return;\r
6473     } else if (strcmp(message, "resign") == 0 ||\r
6474                strcmp(message, "computer resigns") == 0) {\r
6475         switch (gameMode) {\r
6476           case MachinePlaysBlack:\r
6477           case IcsPlayingBlack:\r
6478             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
6479             break;\r
6480           case MachinePlaysWhite:\r
6481           case IcsPlayingWhite:\r
6482             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
6483             break;\r
6484           case TwoMachinesPlay:\r
6485             if (cps->twoMachinesColor[0] == 'w')\r
6486               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6487             else\r
6488               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6489             break;\r
6490           default:\r
6491             /* can't happen */\r
6492             break;\r
6493         }\r
6494         return;\r
6495     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
6496         switch (gameMode) {\r
6497           case MachinePlaysBlack:\r
6498           case IcsPlayingBlack:\r
6499             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6500             break;\r
6501           case MachinePlaysWhite:\r
6502           case IcsPlayingWhite:\r
6503             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
6504             break;\r
6505           case TwoMachinesPlay:\r
6506             if (cps->twoMachinesColor[0] == 'w')\r
6507               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6508             else\r
6509               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6510             break;\r
6511           default:\r
6512             /* can't happen */\r
6513             break;\r
6514         }\r
6515         return;\r
6516     } else if (strncmp(message, "computer mates", 14) == 0) {\r
6517         switch (gameMode) {\r
6518           case MachinePlaysBlack:\r
6519           case IcsPlayingBlack:\r
6520             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
6521             break;\r
6522           case MachinePlaysWhite:\r
6523           case IcsPlayingWhite:\r
6524             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6525             break;\r
6526           case TwoMachinesPlay:\r
6527             if (cps->twoMachinesColor[0] == 'w')\r
6528               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6529             else\r
6530               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6531             break;\r
6532           default:\r
6533             /* can't happen */\r
6534             break;\r
6535         }\r
6536         return;\r
6537     } else if (strncmp(message, "checkmate", 9) == 0) {\r
6538         if (WhiteOnMove(forwardMostMove)) {\r
6539             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6540         } else {\r
6541             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6542         }\r
6543         return;\r
6544     } else if (strstr(message, "Draw") != NULL ||\r
6545                strstr(message, "game is a draw") != NULL) {\r
6546         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
6547         return;\r
6548     } else if (strstr(message, "offer") != NULL &&\r
6549                strstr(message, "draw") != NULL) {\r
6550 #if ZIPPY\r
6551         if (appData.zippyPlay && first.initDone) {\r
6552             /* Relay offer to ICS */\r
6553             SendToICS(ics_prefix);\r
6554             SendToICS("draw\n");\r
6555         }\r
6556 #endif\r
6557         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
6558         if (gameMode == TwoMachinesPlay) {\r
6559             if (cps->other->offeredDraw) {\r
6560                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6561             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
6562             /* until after we also have move, to see if it is really claim */\r
6563             }\r
6564 #if 0\r
6565               else {\r
6566                 if (cps->other->sendDrawOffers) {\r
6567                     SendToProgram("draw\n", cps->other);\r
6568                 }\r
6569             }\r
6570 #endif\r
6571         } else if (gameMode == MachinePlaysWhite ||\r
6572                    gameMode == MachinePlaysBlack) {\r
6573           if (userOfferedDraw) {\r
6574             DisplayInformation(_("Machine accepts your draw offer"));\r
6575             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6576           } else {\r
6577             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));\r
6578           }\r
6579         }\r
6580     }\r
6581 \r
6582     \r
6583     /*\r
6584      * Look for thinking output\r
6585      */\r
6586     if ( appData.showThinking // [HGM] thinking: test all options that cause this output\r
6587           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
6588                                 ) {\r
6589         int plylev, mvleft, mvtot, curscore, time;\r
6590         char mvname[MOVE_LEN];\r
6591         u64 nodes; // [DM]\r
6592         char plyext;\r
6593         int ignore = FALSE;\r
6594         int prefixHint = FALSE;\r
6595         mvname[0] = NULLCHAR;\r
6596 \r
6597         switch (gameMode) {\r
6598           case MachinePlaysBlack:\r
6599           case IcsPlayingBlack:\r
6600             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6601             break;\r
6602           case MachinePlaysWhite:\r
6603           case IcsPlayingWhite:\r
6604             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6605             break;\r
6606           case AnalyzeMode:\r
6607           case AnalyzeFile:\r
6608             break;\r
6609           case IcsObserving: /* [DM] icsEngineAnalyze */\r
6610             if (!appData.icsEngineAnalyze) ignore = TRUE;\r
6611             break;\r
6612           case TwoMachinesPlay:\r
6613             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
6614                 ignore = TRUE;\r
6615             }\r
6616             break;\r
6617           default:\r
6618             ignore = TRUE;\r
6619             break;\r
6620         }\r
6621 \r
6622         if (!ignore) {\r
6623             buf1[0] = NULLCHAR;\r
6624             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6625                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
6626 \r
6627                 if (plyext != ' ' && plyext != '\t') {\r
6628                     time *= 100;\r
6629                 }\r
6630 \r
6631                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6632                 if( cps->scoreIsAbsolute && \r
6633                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
6634                 {\r
6635                     curscore = -curscore;\r
6636                 }\r
6637 \r
6638 \r
6639                 programStats.depth = plylev;\r
6640                 programStats.nodes = nodes;\r
6641                 programStats.time = time;\r
6642                 programStats.score = curscore;\r
6643                 programStats.got_only_move = 0;\r
6644 \r
6645                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
6646                         int ticklen;\r
6647 \r
6648                         if(cps->nps == 0) ticklen = 10*time;       // use engine reported time\r
6649                         else ticklen = (1000. * nodes) / cps->nps; // convert node count to time\r
6650                         if(WhiteOnMove(forwardMostMove)) \r
6651                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
6652                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
6653                 }\r
6654 \r
6655                 /* Buffer overflow protection */\r
6656                 if (buf1[0] != NULLCHAR) {\r
6657                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
6658                         && appData.debugMode) {\r
6659                         fprintf(debugFP,\r
6660                                 "PV is too long; using the first %d bytes.\n",\r
6661                                 sizeof(programStats.movelist) - 1);\r
6662                     }\r
6663 \r
6664                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
6665                 } else {\r
6666                     sprintf(programStats.movelist, " no PV\n");\r
6667                 }\r
6668 \r
6669                 if (programStats.seen_stat) {\r
6670                     programStats.ok_to_send = 1;\r
6671                 }\r
6672 \r
6673                 if (strchr(programStats.movelist, '(') != NULL) {\r
6674                     programStats.line_is_book = 1;\r
6675                     programStats.nr_moves = 0;\r
6676                     programStats.moves_left = 0;\r
6677                 } else {\r
6678                     programStats.line_is_book = 0;\r
6679                 }\r
6680 \r
6681                 SendProgramStatsToFrontend( cps, &programStats );\r
6682 \r
6683                 /* \r
6684                     [AS] Protect the thinkOutput buffer from overflow... this\r
6685                     is only useful if buf1 hasn't overflowed first!\r
6686                 */\r
6687                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
6688                         plylev, \r
6689                         (gameMode == TwoMachinesPlay ?\r
6690                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
6691                         ((double) curscore) / 100.0,\r
6692                         prefixHint ? lastHint : "",\r
6693                         prefixHint ? " " : "" );\r
6694 \r
6695                 if( buf1[0] != NULLCHAR ) {\r
6696                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
6697 \r
6698                     if( strlen(buf1) > max_len ) {\r
6699                         if( appData.debugMode) {\r
6700                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
6701                         }\r
6702                         buf1[max_len+1] = '\0';\r
6703                     }\r
6704 \r
6705                     strcat( thinkOutput, buf1 );\r
6706                 }\r
6707 \r
6708                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode\r
6709                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6710                     DisplayMove(currentMove - 1);\r
6711                     DisplayAnalysis();\r
6712                 }\r
6713                 return;\r
6714 \r
6715             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6716                 /* crafty (9.25+) says "(only move) <move>"\r
6717                  * if there is only 1 legal move\r
6718                  */\r
6719                 sscanf(p, "(only move) %s", buf1);\r
6720                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6721                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6722                 programStats.depth = 1;\r
6723                 programStats.nr_moves = 1;\r
6724                 programStats.moves_left = 1;\r
6725                 programStats.nodes = 1;\r
6726                 programStats.time = 1;\r
6727                 programStats.got_only_move = 1;\r
6728 \r
6729                 /* Not really, but we also use this member to\r
6730                    mean "line isn't going to change" (Crafty\r
6731                    isn't searching, so stats won't change) */\r
6732                 programStats.line_is_book = 1;\r
6733 \r
6734                 SendProgramStatsToFrontend( cps, &programStats );\r
6735                 \r
6736                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || \r
6737                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6738                     DisplayMove(currentMove - 1);\r
6739                     DisplayAnalysis();\r
6740                 }\r
6741                 return;\r
6742             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",\r
6743                               &time, &nodes, &plylev, &mvleft,\r
6744                               &mvtot, mvname) >= 5) {\r
6745                 /* The stat01: line is from Crafty (9.29+) in response\r
6746                    to the "." command */\r
6747                 programStats.seen_stat = 1;\r
6748                 cps->maybeThinking = TRUE;\r
6749 \r
6750                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6751                   return;\r
6752 \r
6753                 programStats.depth = plylev;\r
6754                 programStats.time = time;\r
6755                 programStats.nodes = nodes;\r
6756                 programStats.moves_left = mvleft;\r
6757                 programStats.nr_moves = mvtot;\r
6758                 strcpy(programStats.move_name, mvname);\r
6759                 programStats.ok_to_send = 1;\r
6760                 programStats.movelist[0] = '\0';\r
6761 \r
6762                 SendProgramStatsToFrontend( cps, &programStats );\r
6763 \r
6764                 DisplayAnalysis();\r
6765                 return;\r
6766 \r
6767             } else if (strncmp(message,"++",2) == 0) {\r
6768                 /* Crafty 9.29+ outputs this */\r
6769                 programStats.got_fail = 2;\r
6770                 return;\r
6771 \r
6772             } else if (strncmp(message,"--",2) == 0) {\r
6773                 /* Crafty 9.29+ outputs this */\r
6774                 programStats.got_fail = 1;\r
6775                 return;\r
6776 \r
6777             } else if (thinkOutput[0] != NULLCHAR &&\r
6778                        strncmp(message, "    ", 4) == 0) {\r
6779                 unsigned message_len;\r
6780 \r
6781                 p = message;\r
6782                 while (*p && *p == ' ') p++;\r
6783 \r
6784                 message_len = strlen( p );\r
6785 \r
6786                 /* [AS] Avoid buffer overflow */\r
6787                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6788                     strcat(thinkOutput, " ");\r
6789                     strcat(thinkOutput, p);\r
6790                 }\r
6791 \r
6792                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6793                     strcat(programStats.movelist, " ");\r
6794                     strcat(programStats.movelist, p);\r
6795                 }\r
6796 \r
6797                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||\r
6798                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6799                     DisplayMove(currentMove - 1);\r
6800                     DisplayAnalysis();\r
6801                 }\r
6802                 return;\r
6803             }\r
6804         }\r
6805         else {\r
6806             buf1[0] = NULLCHAR;\r
6807 \r
6808             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
6809                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6810             {\r
6811                 ChessProgramStats cpstats;\r
6812 \r
6813                 if (plyext != ' ' && plyext != '\t') {\r
6814                     time *= 100;\r
6815                 }\r
6816 \r
6817                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6818                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6819                     curscore = -curscore;\r
6820                 }\r
6821 \r
6822                 cpstats.depth = plylev;\r
6823                 cpstats.nodes = nodes;\r
6824                 cpstats.time = time;\r
6825                 cpstats.score = curscore;\r
6826                 cpstats.got_only_move = 0;\r
6827                 cpstats.movelist[0] = '\0';\r
6828 \r
6829                 if (buf1[0] != NULLCHAR) {\r
6830                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6831                 }\r
6832 \r
6833                 cpstats.ok_to_send = 0;\r
6834                 cpstats.line_is_book = 0;\r
6835                 cpstats.nr_moves = 0;\r
6836                 cpstats.moves_left = 0;\r
6837 \r
6838                 SendProgramStatsToFrontend( cps, &cpstats );\r
6839             }\r
6840         }\r
6841     }\r
6842 }\r
6843 \r
6844 \r
6845 /* Parse a game score from the character string "game", and\r
6846    record it as the history of the current game.  The game\r
6847    score is NOT assumed to start from the standard position. \r
6848    The display is not updated in any way.\r
6849    */\r
6850 void\r
6851 ParseGameHistory(game)\r
6852      char *game;\r
6853 {\r
6854     ChessMove moveType;\r
6855     int fromX, fromY, toX, toY, boardIndex;\r
6856     char promoChar;\r
6857     char *p, *q;\r
6858     char buf[MSG_SIZ];\r
6859 \r
6860     if (appData.debugMode)\r
6861       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6862 \r
6863     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6864     gameInfo.site = StrSave(appData.icsHost);\r
6865     gameInfo.date = PGNDate();\r
6866     gameInfo.round = StrSave("-");\r
6867 \r
6868     /* Parse out names of players */\r
6869     while (*game == ' ') game++;\r
6870     p = buf;\r
6871     while (*game != ' ') *p++ = *game++;\r
6872     *p = NULLCHAR;\r
6873     gameInfo.white = StrSave(buf);\r
6874     while (*game == ' ') game++;\r
6875     p = buf;\r
6876     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6877     *p = NULLCHAR;\r
6878     gameInfo.black = StrSave(buf);\r
6879 \r
6880     /* Parse moves */\r
6881     boardIndex = blackPlaysFirst ? 1 : 0;\r
6882     yynewstr(game);\r
6883     for (;;) {\r
6884         yyboardindex = boardIndex;\r
6885         moveType = (ChessMove) yylex();\r
6886         switch (moveType) {\r
6887           case IllegalMove:             /* maybe suicide chess, etc. */\r
6888   if (appData.debugMode) {\r
6889     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6890     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6891     setbuf(debugFP, NULL);\r
6892   }\r
6893           case WhitePromotionChancellor:\r
6894           case BlackPromotionChancellor:\r
6895           case WhitePromotionArchbishop:\r
6896           case BlackPromotionArchbishop:\r
6897           case WhitePromotionQueen:\r
6898           case BlackPromotionQueen:\r
6899           case WhitePromotionRook:\r
6900           case BlackPromotionRook:\r
6901           case WhitePromotionBishop:\r
6902           case BlackPromotionBishop:\r
6903           case WhitePromotionKnight:\r
6904           case BlackPromotionKnight:\r
6905           case WhitePromotionKing:\r
6906           case BlackPromotionKing:\r
6907           case NormalMove:\r
6908           case WhiteCapturesEnPassant:\r
6909           case BlackCapturesEnPassant:\r
6910           case WhiteKingSideCastle:\r
6911           case WhiteQueenSideCastle:\r
6912           case BlackKingSideCastle:\r
6913           case BlackQueenSideCastle:\r
6914           case WhiteKingSideCastleWild:\r
6915           case WhiteQueenSideCastleWild:\r
6916           case BlackKingSideCastleWild:\r
6917           case BlackQueenSideCastleWild:\r
6918           /* PUSH Fabien */\r
6919           case WhiteHSideCastleFR:\r
6920           case WhiteASideCastleFR:\r
6921           case BlackHSideCastleFR:\r
6922           case BlackASideCastleFR:\r
6923           /* POP Fabien */\r
6924             fromX = currentMoveString[0] - AAA;\r
6925             fromY = currentMoveString[1] - ONE;\r
6926             toX = currentMoveString[2] - AAA;\r
6927             toY = currentMoveString[3] - ONE;\r
6928             promoChar = currentMoveString[4];\r
6929             break;\r
6930           case WhiteDrop:\r
6931           case BlackDrop:\r
6932             fromX = moveType == WhiteDrop ?\r
6933               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6934             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6935             fromY = DROP_RANK;\r
6936             toX = currentMoveString[2] - AAA;\r
6937             toY = currentMoveString[3] - ONE;\r
6938             promoChar = NULLCHAR;\r
6939             break;\r
6940           case AmbiguousMove:\r
6941             /* bug? */\r
6942             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);\r
6943   if (appData.debugMode) {\r
6944     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
6945     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6946     setbuf(debugFP, NULL);\r
6947   }\r
6948             DisplayError(buf, 0);\r
6949             return;\r
6950           case ImpossibleMove:\r
6951             /* bug? */\r
6952             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);\r
6953   if (appData.debugMode) {\r
6954     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
6955     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6956     setbuf(debugFP, NULL);\r
6957   }\r
6958             DisplayError(buf, 0);\r
6959             return;\r
6960           case (ChessMove) 0:   /* end of file */\r
6961             if (boardIndex < backwardMostMove) {\r
6962                 /* Oops, gap.  How did that happen? */\r
6963                 DisplayError(_("Gap in move list"), 0);\r
6964                 return;\r
6965             }\r
6966             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
6967             if (boardIndex > forwardMostMove) {\r
6968                 forwardMostMove = boardIndex;\r
6969             }\r
6970             return;\r
6971           case ElapsedTime:\r
6972             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
6973                 strcat(parseList[boardIndex-1], " ");\r
6974                 strcat(parseList[boardIndex-1], yy_text);\r
6975             }\r
6976             continue;\r
6977           case Comment:\r
6978           case PGNTag:\r
6979           case NAG:\r
6980           default:\r
6981             /* ignore */\r
6982             continue;\r
6983           case WhiteWins:\r
6984           case BlackWins:\r
6985           case GameIsDrawn:\r
6986           case GameUnfinished:\r
6987             if (gameMode == IcsExamining) {\r
6988                 if (boardIndex < backwardMostMove) {\r
6989                     /* Oops, gap.  How did that happen? */\r
6990                     return;\r
6991                 }\r
6992                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6993                 return;\r
6994             }\r
6995             gameInfo.result = moveType;\r
6996             p = strchr(yy_text, '{');\r
6997             if (p == NULL) p = strchr(yy_text, '(');\r
6998             if (p == NULL) {\r
6999                 p = yy_text;\r
7000                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
7001             } else {\r
7002                 q = strchr(p, *p == '{' ? '}' : ')');\r
7003                 if (q != NULL) *q = NULLCHAR;\r
7004                 p++;\r
7005             }\r
7006             gameInfo.resultDetails = StrSave(p);\r
7007             continue;\r
7008         }\r
7009         if (boardIndex >= forwardMostMove &&\r
7010             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
7011             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
7012             return;\r
7013         }\r
7014         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
7015                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
7016                                  parseList[boardIndex]);\r
7017         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
7018         /* currentMoveString is set as a side-effect of yylex */\r
7019         strcpy(moveList[boardIndex], currentMoveString);\r
7020         strcat(moveList[boardIndex], "\n");\r
7021         boardIndex++;\r
7022         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
7023         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
7024                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
7025           case MT_NONE:\r
7026           case MT_STALEMATE:\r
7027           default:\r
7028             break;\r
7029           case MT_CHECK:\r
7030             if(gameInfo.variant != VariantShogi)\r
7031                 strcat(parseList[boardIndex - 1], "+");\r
7032             break;\r
7033           case MT_CHECKMATE:\r
7034             strcat(parseList[boardIndex - 1], "#");\r
7035             break;\r
7036         }\r
7037     }\r
7038 }\r
7039 \r
7040 \r
7041 /* Apply a move to the given board  */\r
7042 void\r
7043 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
7044      int fromX, fromY, toX, toY;\r
7045      int promoChar;\r
7046      Board board;\r
7047 {\r
7048   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
7049 \r
7050     /* [HGM] compute & store e.p. status and castling rights for new position */\r
7051     /* if we are updating a board for which those exist (i.e. in boards[])    */\r
7052     if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)\r
7053     { int i, j;\r
7054 \r
7055       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
7056       oldEP = epStatus[p-1];\r
7057       epStatus[p] = EP_NONE;\r
7058 \r
7059       if( board[toY][toX] != EmptySquare ) \r
7060            epStatus[p] = EP_CAPTURE;  \r
7061 \r
7062       if( board[fromY][fromX] == WhitePawn ) {\r
7063            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7064                epStatus[p] = EP_PAWN_MOVE;\r
7065            if( toY-fromY==2) {\r
7066                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
7067                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7068                       epStatus[p] = toX | berolina;\r
7069                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
7070                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7071                       epStatus[p] = toX;\r
7072            }\r
7073       } else \r
7074       if( board[fromY][fromX] == BlackPawn ) {\r
7075            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7076                epStatus[p] = EP_PAWN_MOVE; \r
7077            if( toY-fromY== -2) {\r
7078                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
7079                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7080                       epStatus[p] = toX | berolina;\r
7081                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
7082                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7083                       epStatus[p] = toX;\r
7084            }\r
7085        }\r
7086 \r
7087        for(i=0; i<nrCastlingRights; i++) {\r
7088            castlingRights[p][i] = castlingRights[p-1][i];\r
7089            if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||\r
7090               castlingRights[p][i] == toX   && castlingRank[i] == toY   \r
7091              ) castlingRights[p][i] = -1; // revoke for moved or captured piece\r
7092        }\r
7093 \r
7094     }\r
7095 \r
7096   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
7097   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
7098        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
7099          \r
7100   if (fromX == toX && fromY == toY) return;\r
7101 \r
7102   if (fromY == DROP_RANK) {\r
7103         /* must be first */\r
7104         piece = board[toY][toX] = (ChessSquare) fromX;\r
7105   } else {\r
7106      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
7107      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
7108      if(gameInfo.variant == VariantKnightmate)\r
7109          king += (int) WhiteUnicorn - (int) WhiteKing;\r
7110 \r
7111     /* Code added by Tord: */\r
7112     /* FRC castling assumed when king captures friendly rook. */\r
7113     if (board[fromY][fromX] == WhiteKing &&\r
7114              board[toY][toX] == WhiteRook) {\r
7115       board[fromY][fromX] = EmptySquare;\r
7116       board[toY][toX] = EmptySquare;\r
7117       if(toX > fromX) {\r
7118         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
7119       } else {\r
7120         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
7121       }\r
7122     } else if (board[fromY][fromX] == BlackKing &&\r
7123                board[toY][toX] == BlackRook) {\r
7124       board[fromY][fromX] = EmptySquare;\r
7125       board[toY][toX] = EmptySquare;\r
7126       if(toX > fromX) {\r
7127         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
7128       } else {\r
7129         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
7130       }\r
7131     /* End of code added by Tord */\r
7132 \r
7133     } else if (board[fromY][fromX] == king\r
7134         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7135         && toY == fromY && toX > fromX+1) {\r
7136         board[fromY][fromX] = EmptySquare;\r
7137         board[toY][toX] = king;\r
7138         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7139         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7140     } else if (board[fromY][fromX] == king\r
7141         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7142                && toY == fromY && toX < fromX-1) {\r
7143         board[fromY][fromX] = EmptySquare;\r
7144         board[toY][toX] = king;\r
7145         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7146         board[fromY][BOARD_LEFT] = EmptySquare;\r
7147     } else if (board[fromY][fromX] == WhitePawn\r
7148                && toY == BOARD_HEIGHT-1\r
7149                && gameInfo.variant != VariantXiangqi\r
7150                ) {\r
7151         /* white pawn promotion */\r
7152         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
7153         if (board[toY][toX] == EmptySquare) {\r
7154             board[toY][toX] = WhiteQueen;\r
7155         }\r
7156         if(gameInfo.variant==VariantBughouse ||\r
7157            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7158             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7159         board[fromY][fromX] = EmptySquare;\r
7160     } else if ((fromY == BOARD_HEIGHT-4)\r
7161                && (toX != fromX)\r
7162                && gameInfo.variant != VariantXiangqi\r
7163                && gameInfo.variant != VariantBerolina\r
7164                && (board[fromY][fromX] == WhitePawn)\r
7165                && (board[toY][toX] == EmptySquare)) {\r
7166         board[fromY][fromX] = EmptySquare;\r
7167         board[toY][toX] = WhitePawn;\r
7168         captured = board[toY - 1][toX];\r
7169         board[toY - 1][toX] = EmptySquare;\r
7170     } else if ((fromY == BOARD_HEIGHT-4)\r
7171                && (toX == fromX)\r
7172                && gameInfo.variant == VariantBerolina\r
7173                && (board[fromY][fromX] == WhitePawn)\r
7174                && (board[toY][toX] == EmptySquare)) {\r
7175         board[fromY][fromX] = EmptySquare;\r
7176         board[toY][toX] = WhitePawn;\r
7177         if(oldEP & EP_BEROLIN_A) {\r
7178                 captured = board[fromY][fromX-1];\r
7179                 board[fromY][fromX-1] = EmptySquare;\r
7180         }else{  captured = board[fromY][fromX+1];\r
7181                 board[fromY][fromX+1] = EmptySquare;\r
7182         }\r
7183     } else if (board[fromY][fromX] == king\r
7184         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7185                && toY == fromY && toX > fromX+1) {\r
7186         board[fromY][fromX] = EmptySquare;\r
7187         board[toY][toX] = king;\r
7188         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7189         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7190     } else if (board[fromY][fromX] == king\r
7191         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7192                && toY == fromY && toX < fromX-1) {\r
7193         board[fromY][fromX] = EmptySquare;\r
7194         board[toY][toX] = king;\r
7195         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7196         board[fromY][BOARD_LEFT] = EmptySquare;\r
7197     } else if (fromY == 7 && fromX == 3\r
7198                && board[fromY][fromX] == BlackKing\r
7199                && toY == 7 && toX == 5) {\r
7200         board[fromY][fromX] = EmptySquare;\r
7201         board[toY][toX] = BlackKing;\r
7202         board[fromY][7] = EmptySquare;\r
7203         board[toY][4] = BlackRook;\r
7204     } else if (fromY == 7 && fromX == 3\r
7205                && board[fromY][fromX] == BlackKing\r
7206                && toY == 7 && toX == 1) {\r
7207         board[fromY][fromX] = EmptySquare;\r
7208         board[toY][toX] = BlackKing;\r
7209         board[fromY][0] = EmptySquare;\r
7210         board[toY][2] = BlackRook;\r
7211     } else if (board[fromY][fromX] == BlackPawn\r
7212                && toY == 0\r
7213                && gameInfo.variant != VariantXiangqi\r
7214                ) {\r
7215         /* black pawn promotion */\r
7216         board[0][toX] = CharToPiece(ToLower(promoChar));\r
7217         if (board[0][toX] == EmptySquare) {\r
7218             board[0][toX] = BlackQueen;\r
7219         }\r
7220         if(gameInfo.variant==VariantBughouse ||\r
7221            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7222             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7223         board[fromY][fromX] = EmptySquare;\r
7224     } else if ((fromY == 3)\r
7225                && (toX != fromX)\r
7226                && gameInfo.variant != VariantXiangqi\r
7227                && gameInfo.variant != VariantBerolina\r
7228                && (board[fromY][fromX] == BlackPawn)\r
7229                && (board[toY][toX] == EmptySquare)) {\r
7230         board[fromY][fromX] = EmptySquare;\r
7231         board[toY][toX] = BlackPawn;\r
7232         captured = board[toY + 1][toX];\r
7233         board[toY + 1][toX] = EmptySquare;\r
7234     } else if ((fromY == 3)\r
7235                && (toX == fromX)\r
7236                && gameInfo.variant == VariantBerolina\r
7237                && (board[fromY][fromX] == BlackPawn)\r
7238                && (board[toY][toX] == EmptySquare)) {\r
7239         board[fromY][fromX] = EmptySquare;\r
7240         board[toY][toX] = BlackPawn;\r
7241         if(oldEP & EP_BEROLIN_A) {\r
7242                 captured = board[fromY][fromX-1];\r
7243                 board[fromY][fromX-1] = EmptySquare;\r
7244         }else{  captured = board[fromY][fromX+1];\r
7245                 board[fromY][fromX+1] = EmptySquare;\r
7246         }\r
7247     } else {\r
7248         board[toY][toX] = board[fromY][fromX];\r
7249         board[fromY][fromX] = EmptySquare;\r
7250     }\r
7251 \r
7252     /* [HGM] now we promote for Shogi, if needed */\r
7253     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
7254         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7255   }\r
7256 \r
7257     if (gameInfo.holdingsWidth != 0) {\r
7258 \r
7259       /* !!A lot more code needs to be written to support holdings  */\r
7260       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
7261       /* penultimate board files, so they are automaticlly stored   */\r
7262       /* in the game history.                                       */\r
7263       if (fromY == DROP_RANK) {\r
7264         /* Delete from holdings, by decreasing count */\r
7265         /* and erasing image if necessary            */\r
7266         p = (int) fromX;\r
7267         if(p < (int) BlackPawn) { /* white drop */\r
7268              p -= (int)WhitePawn;\r
7269              if(p >= gameInfo.holdingsSize) p = 0;\r
7270              if(--board[p][BOARD_WIDTH-2] == 0)\r
7271                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
7272         } else {                  /* black drop */\r
7273              p -= (int)BlackPawn;\r
7274              if(p >= gameInfo.holdingsSize) p = 0;\r
7275              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
7276                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
7277         }\r
7278       }\r
7279       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
7280           && gameInfo.variant != VariantBughouse        ) {\r
7281         /* [HGM] holdings: Add to holdings, if holdings exist */\r
7282         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { \r
7283                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
7284                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
7285         }\r
7286         p = (int) captured;\r
7287         if (p >= (int) BlackPawn) {\r
7288           p -= (int)BlackPawn;\r
7289           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7290                   /* in Shogi restore piece to its original  first */\r
7291                   captured = (ChessSquare) (DEMOTED captured);\r
7292                   p = DEMOTED p;\r
7293           }\r
7294           p = PieceToNumber((ChessSquare)p);\r
7295           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
7296           board[p][BOARD_WIDTH-2]++;\r
7297           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;\r
7298         } else {\r
7299           p -= (int)WhitePawn;\r
7300           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7301                   captured = (ChessSquare) (DEMOTED captured);\r
7302                   p = DEMOTED p;\r
7303           }\r
7304           p = PieceToNumber((ChessSquare)p);\r
7305           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
7306           board[BOARD_HEIGHT-1-p][1]++;\r
7307           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;\r
7308         }\r
7309       }\r
7310 \r
7311     } else if (gameInfo.variant == VariantAtomic) {\r
7312       if (captured != EmptySquare) {\r
7313         int y, x;\r
7314         for (y = toY-1; y <= toY+1; y++) {\r
7315           for (x = toX-1; x <= toX+1; x++) {\r
7316             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
7317                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
7318               board[y][x] = EmptySquare;\r
7319             }\r
7320           }\r
7321         }\r
7322         board[toY][toX] = EmptySquare;\r
7323       }\r
7324     }\r
7325     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
7326         /* [HGM] Shogi promotions */\r
7327         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7328     }\r
7329 \r
7330 }\r
7331 \r
7332 /* Updates forwardMostMove */\r
7333 void\r
7334 MakeMove(fromX, fromY, toX, toY, promoChar)\r
7335      int fromX, fromY, toX, toY;\r
7336      int promoChar;\r
7337 {\r
7338 //    forwardMostMove++; // [HGM] bare: moved downstream\r
7339 \r
7340     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */\r
7341         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
7342         piece = boards[forwardMostMove][fromY][fromX];\r
7343         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
7344         if(gameInfo.variant == VariantKnightmate)\r
7345             king += (int) WhiteUnicorn - (int) WhiteKing;\r
7346         if(forwardMostMove == 0) {\r
7347             if(blackPlaysFirst) \r
7348                 fprintf(serverMoves, "%s;", second.tidy);\r
7349             fprintf(serverMoves, "%s;", first.tidy);\r
7350             if(!blackPlaysFirst) \r
7351                 fprintf(serverMoves, "%s;", second.tidy);\r
7352         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
7353         lastLoadFlag = loadFlag;\r
7354         // print base move\r
7355         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
7356         // print castling suffix\r
7357         if( toY == fromY && piece == king ) {\r
7358             if(toX-fromX > 1)\r
7359                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
7360             if(fromX-toX >1)\r
7361                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
7362         }\r
7363         // e.p. suffix\r
7364         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||\r
7365              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&\r
7366              boards[forwardMostMove][toY][toX] == EmptySquare\r
7367              && fromX != toX )\r
7368                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
7369         // promotion suffix\r
7370         if(promoChar != NULLCHAR)\r
7371                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
7372         if(!loadFlag) {\r
7373             fprintf(serverMoves, "/%d/%d",\r
7374                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);\r
7375             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;\r
7376             else                      timeLeft = blackTimeRemaining/1000;\r
7377             fprintf(serverMoves, "/%d", timeLeft);\r
7378         }\r
7379         fflush(serverMoves);\r
7380     }\r
7381 \r
7382     if (forwardMostMove+1 >= MAX_MOVES) {\r
7383       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
7384                         0, 1);\r
7385       return;\r
7386     }\r
7387     SwitchClocks();\r
7388     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;\r
7389     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;\r
7390     if (commentList[forwardMostMove+1] != NULL) {\r
7391         free(commentList[forwardMostMove+1]);\r
7392         commentList[forwardMostMove+1] = NULL;\r
7393     }\r
7394     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);\r
7395     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);\r
7396     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board\r
7397     gameInfo.result = GameUnfinished;\r
7398     if (gameInfo.resultDetails != NULL) {\r
7399         free(gameInfo.resultDetails);\r
7400         gameInfo.resultDetails = NULL;\r
7401     }\r
7402     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
7403                               moveList[forwardMostMove - 1]);\r
7404     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
7405                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
7406                              fromY, fromX, toY, toX, promoChar,\r
7407                              parseList[forwardMostMove - 1]);\r
7408     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
7409                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
7410                             castlingRights[forwardMostMove]) ) {\r
7411       case MT_NONE:\r
7412       case MT_STALEMATE:\r
7413       default:\r
7414         break;\r
7415       case MT_CHECK:\r
7416         if(gameInfo.variant != VariantShogi)\r
7417             strcat(parseList[forwardMostMove - 1], "+");\r
7418         break;\r
7419       case MT_CHECKMATE:\r
7420         strcat(parseList[forwardMostMove - 1], "#");\r
7421         break;\r
7422     }\r
7423     if (appData.debugMode) {\r
7424         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
7425     }\r
7426 \r
7427 }\r
7428 \r
7429 /* Updates currentMove if not pausing */\r
7430 void\r
7431 ShowMove(fromX, fromY, toX, toY)\r
7432 {\r
7433     int instant = (gameMode == PlayFromGameFile) ?\r
7434         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
7435     if(appData.noGUI) return;\r
7436     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
7437         if (!instant) {\r
7438             if (forwardMostMove == currentMove + 1) {\r
7439                 AnimateMove(boards[forwardMostMove - 1],\r
7440                             fromX, fromY, toX, toY);\r
7441             }\r
7442             if (appData.highlightLastMove) {\r
7443                 SetHighlights(fromX, fromY, toX, toY);\r
7444             }\r
7445         }\r
7446         currentMove = forwardMostMove;\r
7447     }\r
7448 \r
7449     if (instant) return;\r
7450 \r
7451     DisplayMove(currentMove - 1);\r
7452     DrawPosition(FALSE, boards[currentMove]);\r
7453     DisplayBothClocks();\r
7454     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
7455 }\r
7456 \r
7457 void SendEgtPath(ChessProgramState *cps)\r
7458 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */\r
7459         char buf[MSG_SIZ], name[MSG_SIZ], *p;\r
7460 \r
7461         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;\r
7462 \r
7463         while(*p) {\r
7464             char c, *q = name+1, *r, *s;\r
7465 \r
7466             name[0] = ','; // extract next format name from feature and copy with prefixed ','\r
7467             while(*p && *p != ',') *q++ = *p++;\r
7468             *q++ = ':'; *q = 0;\r
7469             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && \r
7470                 strcmp(name, ",nalimov:") == 0 ) {\r
7471                 // take nalimov path from the menu-changeable option first, if it is defined\r
7472                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);\r
7473                 SendToProgram(buf,cps);     // send egtbpath command for nalimov\r
7474             } else\r
7475             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||\r
7476                 (s = StrStr(appData.egtFormats, name)) != NULL) {\r
7477                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma\r
7478                 s = r = StrStr(s, ":") + 1; // beginning of path info\r
7479                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string\r
7480                 c = *r; *r = 0;             // temporarily null-terminate path info\r
7481                     *--q = 0;               // strip of trailig ':' from name\r
7482                     sprintf(buf, "egtbpath %s %s\n", name+1, s);\r
7483                 *r = c;\r
7484                 SendToProgram(buf,cps);     // send egtbpath command for this format\r
7485             }\r
7486             if(*p == ',') p++; // read away comma to position for next format name\r
7487         }\r
7488 }\r
7489 \r
7490 void\r
7491 InitChessProgram(cps, setup)\r
7492      ChessProgramState *cps;\r
7493      int setup; /* [HGM] needed to setup FRC opening position */\r
7494 {\r
7495     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
7496     if (appData.noChessProgram) return;\r
7497     hintRequested = FALSE;\r
7498     bookRequested = FALSE;\r
7499 \r
7500     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */\r
7501     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */\r
7502     if(cps->memSize) { /* [HGM] memory */\r
7503         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);\r
7504         SendToProgram(buf, cps);\r
7505     }\r
7506     SendEgtPath(cps); /* [HGM] EGT */\r
7507     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */\r
7508         sprintf(buf, "cores %d\n", appData.smpCores);\r
7509         SendToProgram(buf, cps);\r
7510     }\r
7511 \r
7512     SendToProgram(cps->initString, cps);\r
7513     if (gameInfo.variant != VariantNormal &&\r
7514         gameInfo.variant != VariantLoadable\r
7515         /* [HGM] also send variant if board size non-standard */\r
7516         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
7517                                             ) {\r
7518       char *v = VariantName(gameInfo.variant);\r
7519       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
7520         /* [HGM] in protocol 1 we have to assume all variants valid */\r
7521         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);\r
7522         DisplayFatalError(buf, 0, 1);\r
7523         return;\r
7524       }\r
7525 \r
7526       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
7527       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7528       if( gameInfo.variant == VariantXiangqi )\r
7529            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
7530       if( gameInfo.variant == VariantShogi )\r
7531            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
7532       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
7533            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
7534       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
7535                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
7536            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7537       if( gameInfo.variant == VariantCourier )\r
7538            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7539       if( gameInfo.variant == VariantSuper )\r
7540            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7541       if( gameInfo.variant == VariantGreat )\r
7542            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7543 \r
7544       if(overruled) {\r
7545            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
7546                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
7547            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
7548            if(StrStr(cps->variants, b) == NULL) { \r
7549                // specific sized variant not known, check if general sizing allowed\r
7550                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
7551                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
7552                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
7553                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
7554                        DisplayFatalError(buf, 0, 1);\r
7555                        return;\r
7556                    }\r
7557                    /* [HGM] here we really should compare with the maximum supported board size */\r
7558                }\r
7559            }\r
7560       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
7561       sprintf(buf, "variant %s\n", b);\r
7562       SendToProgram(buf, cps);\r
7563     }\r
7564     currentlyInitializedVariant = gameInfo.variant;\r
7565 \r
7566     /* [HGM] send opening position in FRC to first engine */\r
7567     if(setup) {\r
7568           SendToProgram("force\n", cps);\r
7569           SendBoard(cps, 0);\r
7570           /* engine is now in force mode! Set flag to wake it up after first move. */\r
7571           setboardSpoiledMachineBlack = 1;\r
7572     }\r
7573 \r
7574     if (cps->sendICS) {\r
7575       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
7576       SendToProgram(buf, cps);\r
7577     }\r
7578     cps->maybeThinking = FALSE;\r
7579     cps->offeredDraw = 0;\r
7580     if (!appData.icsActive) {\r
7581         SendTimeControl(cps, movesPerSession, timeControl,\r
7582                         timeIncrement, appData.searchDepth,\r
7583                         searchTime);\r
7584     }\r
7585     if (appData.showThinking \r
7586         // [HGM] thinking: four options require thinking output to be sent\r
7587         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
7588                                 ) {\r
7589         SendToProgram("post\n", cps);\r
7590     }\r
7591     SendToProgram("hard\n", cps);\r
7592     if (!appData.ponderNextMove) {\r
7593         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
7594            it without being sure what state we are in first.  "hard"\r
7595            is not a toggle, so that one is OK.\r
7596          */\r
7597         SendToProgram("easy\n", cps);\r
7598     }\r
7599     if (cps->usePing) {\r
7600       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
7601       SendToProgram(buf, cps);\r
7602     }\r
7603     cps->initDone = TRUE;\r
7604 }   \r
7605 \r
7606 \r
7607 void\r
7608 StartChessProgram(cps)\r
7609      ChessProgramState *cps;\r
7610 {\r
7611     char buf[MSG_SIZ];\r
7612     int err;\r
7613 \r
7614     if (appData.noChessProgram) return;\r
7615     cps->initDone = FALSE;\r
7616 \r
7617     if (strcmp(cps->host, "localhost") == 0) {\r
7618         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
7619     } else if (*appData.remoteShell == NULLCHAR) {\r
7620         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
7621     } else {\r
7622         if (*appData.remoteUser == NULLCHAR) {\r
7623             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
7624                     cps->program);\r
7625         } else {\r
7626             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
7627                     cps->host, appData.remoteUser, cps->program);\r
7628         }\r
7629         err = StartChildProcess(buf, "", &cps->pr);\r
7630     }\r
7631     \r
7632     if (err != 0) {\r
7633         sprintf(buf, _("Startup failure on '%s'"), cps->program);\r
7634         DisplayFatalError(buf, err, 1);\r
7635         cps->pr = NoProc;\r
7636         cps->isr = NULL;\r
7637         return;\r
7638     }\r
7639     \r
7640     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
7641     if (cps->protocolVersion > 1) {\r
7642       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
7643       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options\r
7644       cps->comboCnt = 0;  //                and values of combo boxes\r
7645       SendToProgram(buf, cps);\r
7646     } else {\r
7647       SendToProgram("xboard\n", cps);\r
7648     }\r
7649 }\r
7650 \r
7651 \r
7652 void\r
7653 TwoMachinesEventIfReady P((void))\r
7654 {\r
7655   if (first.lastPing != first.lastPong) {\r
7656     DisplayMessage("", _("Waiting for first chess program"));\r
7657     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7658     return;\r
7659   }\r
7660   if (second.lastPing != second.lastPong) {\r
7661     DisplayMessage("", _("Waiting for second chess program"));\r
7662     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7663     return;\r
7664   }\r
7665   ThawUI();\r
7666   TwoMachinesEvent();\r
7667 }\r
7668 \r
7669 void\r
7670 NextMatchGame P((void))\r
7671 {\r
7672     int index; /* [HGM] autoinc: step lod index during match */\r
7673     Reset(FALSE, TRUE);\r
7674     if (*appData.loadGameFile != NULLCHAR) {\r
7675         index = appData.loadGameIndex;\r
7676         if(index < 0) { // [HGM] autoinc\r
7677             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7678             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7679         } \r
7680         LoadGameFromFile(appData.loadGameFile,\r
7681                          index,\r
7682                          appData.loadGameFile, FALSE);\r
7683     } else if (*appData.loadPositionFile != NULLCHAR) {\r
7684         index = appData.loadPositionIndex;\r
7685         if(index < 0) { // [HGM] autoinc\r
7686             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7687             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7688         } \r
7689         LoadPositionFromFile(appData.loadPositionFile,\r
7690                              index,\r
7691                              appData.loadPositionFile);\r
7692     }\r
7693     TwoMachinesEventIfReady();\r
7694 }\r
7695 \r
7696 void UserAdjudicationEvent( int result )\r
7697 {\r
7698     ChessMove gameResult = GameIsDrawn;\r
7699 \r
7700     if( result > 0 ) {\r
7701         gameResult = WhiteWins;\r
7702     }\r
7703     else if( result < 0 ) {\r
7704         gameResult = BlackWins;\r
7705     }\r
7706 \r
7707     if( gameMode == TwoMachinesPlay ) {\r
7708         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
7709     }\r
7710 }\r
7711 \r
7712 \r
7713 void\r
7714 GameEnds(result, resultDetails, whosays)\r
7715      ChessMove result;\r
7716      char *resultDetails;\r
7717      int whosays;\r
7718 {\r
7719     GameMode nextGameMode;\r
7720     int isIcsGame;\r
7721     char buf[MSG_SIZ];\r
7722 \r
7723     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
7724     endingGame = 1;\r
7725 \r
7726     if (appData.debugMode) {\r
7727       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
7728               result, resultDetails ? resultDetails : "(null)", whosays);\r
7729     }\r
7730 \r
7731     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
7732         /* If we are playing on ICS, the server decides when the\r
7733            game is over, but the engine can offer to draw, claim \r
7734            a draw, or resign. \r
7735          */\r
7736 #if ZIPPY\r
7737         if (appData.zippyPlay && first.initDone) {\r
7738             if (result == GameIsDrawn) {\r
7739                 /* In case draw still needs to be claimed */\r
7740                 SendToICS(ics_prefix);\r
7741                 SendToICS("draw\n");\r
7742             } else if (StrCaseStr(resultDetails, "resign")) {\r
7743                 SendToICS(ics_prefix);\r
7744                 SendToICS("resign\n");\r
7745             }\r
7746         }\r
7747 #endif\r
7748         endingGame = 0; /* [HGM] crash */\r
7749         return;\r
7750     }\r
7751 \r
7752     /* If we're loading the game from a file, stop */\r
7753     if (whosays == GE_FILE) {\r
7754       (void) StopLoadGameTimer();\r
7755       gameFileFP = NULL;\r
7756     }\r
7757 \r
7758     /* Cancel draw offers */\r
7759     first.offeredDraw = second.offeredDraw = 0;\r
7760 \r
7761     /* If this is an ICS game, only ICS can really say it's done;\r
7762        if not, anyone can. */\r
7763     isIcsGame = (gameMode == IcsPlayingWhite || \r
7764                  gameMode == IcsPlayingBlack || \r
7765                  gameMode == IcsObserving    || \r
7766                  gameMode == IcsExamining);\r
7767 \r
7768     if (!isIcsGame || whosays == GE_ICS) {\r
7769         /* OK -- not an ICS game, or ICS said it was done */\r
7770         StopClocks();\r
7771         if (!isIcsGame && !appData.noChessProgram) \r
7772           SetUserThinkingEnables();\r
7773     \r
7774         /* [HGM] if a machine claims the game end we verify this claim */\r
7775         if(gameMode == TwoMachinesPlay && appData.testClaims) {\r
7776             if(appData.testLegality && whosays >= GE_ENGINE1 ) {\r
7777                 char claimer;\r
7778 \r
7779                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
7780                                             first.twoMachinesColor[0] :\r
7781                                             second.twoMachinesColor[0] ;\r
7782                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) &&\r
7783                     (result == WhiteWins && claimer == 'w' ||\r
7784                      result == BlackWins && claimer == 'b'   ) ) {\r
7785                 if (appData.debugMode) {\r
7786                      fprintf(debugFP, "result=%d sp=%d move=%d\n",\r
7787                         result, epStatus[forwardMostMove], forwardMostMove);\r
7788                 }\r
7789                       /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7790                       if(epStatus[forwardMostMove] != EP_CHECKMATE &&\r
7791                          result != (WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins)) {\r
7792                               sprintf(buf, "False win claim: '%s'", resultDetails);\r
7793                               result = claimer == 'w' ? BlackWins : WhiteWins;\r
7794                               resultDetails = buf;\r
7795                       }\r
7796                 } else\r
7797                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
7798                     && (forwardMostMove <= backwardMostMove ||\r
7799                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
7800                         (claimer=='b')==(forwardMostMove&1))\r
7801                                                                                   ) {\r
7802                       /* [HGM] verify: draws that were not flagged are false claims */\r
7803                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
7804                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7805                       resultDetails = buf;\r
7806                 }\r
7807                 /* (Claiming a loss is accepted no questions asked!) */\r
7808             }\r
7809             /* [HGM] bare: don't allow bare King to win */\r
7810             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)\r
7811                          && result != GameIsDrawn)\r
7812             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
7813                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
7814                         int p = (int)boards[forwardMostMove][i][j] - color;\r
7815                         if(p >= 0 && p <= (int)WhiteKing) k++;\r
7816                 }\r
7817                 if (appData.debugMode) {\r
7818                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",\r
7819                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);\r
7820                 }\r
7821                 if(k <= 1) {\r
7822                         result = GameIsDrawn;\r
7823                         sprintf(buf, "%s but bare king", resultDetails);\r
7824                         resultDetails = buf;\r
7825                 }\r
7826             }\r
7827         }\r
7828 \r
7829 \r
7830         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
7831             if(result==WhiteWins) c = '+';\r
7832             if(result==BlackWins) c = '-';\r
7833             if(resultDetails != NULL)\r
7834                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
7835         }\r
7836         if (resultDetails != NULL) {\r
7837             gameInfo.result = result;\r
7838             gameInfo.resultDetails = StrSave(resultDetails);\r
7839 \r
7840             /* display last move only if game was not loaded from file */\r
7841             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7842                 DisplayMove(currentMove - 1);\r
7843     \r
7844             if (forwardMostMove != 0) {\r
7845                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7846                     if (*appData.saveGameFile != NULLCHAR) {\r
7847                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7848                     } else if (appData.autoSaveGames) {\r
7849                         AutoSaveGame();\r
7850                     }\r
7851                     if (*appData.savePositionFile != NULLCHAR) {\r
7852                         SavePositionToFile(appData.savePositionFile);\r
7853                     }\r
7854                 }\r
7855             }\r
7856 \r
7857             /* Tell program how game ended in case it is learning */\r
7858             /* [HGM] Moved this to after saving the PGN, just in case */\r
7859             /* engine died and we got here through time loss. In that */\r
7860             /* case we will get a fatal error writing the pipe, which */\r
7861             /* would otherwise lose us the PGN.                       */\r
7862             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7863             /* output during GameEnds should never be fatal anymore   */\r
7864             if (gameMode == MachinePlaysWhite ||\r
7865                 gameMode == MachinePlaysBlack ||\r
7866                 gameMode == TwoMachinesPlay ||\r
7867                 gameMode == IcsPlayingWhite ||\r
7868                 gameMode == IcsPlayingBlack ||\r
7869                 gameMode == BeginningOfGame) {\r
7870                 char buf[MSG_SIZ];\r
7871                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7872                         resultDetails);\r
7873                 if (first.pr != NoProc) {\r
7874                     SendToProgram(buf, &first);\r
7875                 }\r
7876                 if (second.pr != NoProc &&\r
7877                     gameMode == TwoMachinesPlay) {\r
7878                     SendToProgram(buf, &second);\r
7879                 }\r
7880             }\r
7881         }\r
7882 \r
7883         if (appData.icsActive) {\r
7884             if (appData.quietPlay &&\r
7885                 (gameMode == IcsPlayingWhite ||\r
7886                  gameMode == IcsPlayingBlack)) {\r
7887                 SendToICS(ics_prefix);\r
7888                 SendToICS("set shout 1\n");\r
7889             }\r
7890             nextGameMode = IcsIdle;\r
7891             ics_user_moved = FALSE;\r
7892             /* clean up premove.  It's ugly when the game has ended and the\r
7893              * premove highlights are still on the board.\r
7894              */\r
7895             if (gotPremove) {\r
7896               gotPremove = FALSE;\r
7897               ClearPremoveHighlights();\r
7898               DrawPosition(FALSE, boards[currentMove]);\r
7899             }\r
7900             if (whosays == GE_ICS) {\r
7901                 switch (result) {\r
7902                 case WhiteWins:\r
7903                     if (gameMode == IcsPlayingWhite)\r
7904                         PlayIcsWinSound();\r
7905                     else if(gameMode == IcsPlayingBlack)\r
7906                         PlayIcsLossSound();\r
7907                     break;\r
7908                 case BlackWins:\r
7909                     if (gameMode == IcsPlayingBlack)\r
7910                         PlayIcsWinSound();\r
7911                     else if(gameMode == IcsPlayingWhite)\r
7912                         PlayIcsLossSound();\r
7913                     break;\r
7914                 case GameIsDrawn:\r
7915                     PlayIcsDrawSound();\r
7916                     break;\r
7917                 default:\r
7918                     PlayIcsUnfinishedSound();\r
7919                 }\r
7920             }\r
7921         } else if (gameMode == EditGame ||\r
7922                    gameMode == PlayFromGameFile || \r
7923                    gameMode == AnalyzeMode || \r
7924                    gameMode == AnalyzeFile) {\r
7925             nextGameMode = gameMode;\r
7926         } else {\r
7927             nextGameMode = EndOfGame;\r
7928         }\r
7929         pausing = FALSE;\r
7930         ModeHighlight();\r
7931     } else {\r
7932         nextGameMode = gameMode;\r
7933     }\r
7934 \r
7935     if (appData.noChessProgram) {\r
7936         gameMode = nextGameMode;\r
7937         ModeHighlight();\r
7938         endingGame = 0; /* [HGM] crash */\r
7939         return;\r
7940     }\r
7941 \r
7942     if (first.reuse) {\r
7943         /* Put first chess program into idle state */\r
7944         if (first.pr != NoProc &&\r
7945             (gameMode == MachinePlaysWhite ||\r
7946              gameMode == MachinePlaysBlack ||\r
7947              gameMode == TwoMachinesPlay ||\r
7948              gameMode == IcsPlayingWhite ||\r
7949              gameMode == IcsPlayingBlack ||\r
7950              gameMode == BeginningOfGame)) {\r
7951             SendToProgram("force\n", &first);\r
7952             if (first.usePing) {\r
7953               char buf[MSG_SIZ];\r
7954               sprintf(buf, "ping %d\n", ++first.lastPing);\r
7955               SendToProgram(buf, &first);\r
7956             }\r
7957         }\r
7958     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7959         /* Kill off first chess program */\r
7960         if (first.isr != NULL)\r
7961           RemoveInputSource(first.isr);\r
7962         first.isr = NULL;\r
7963     \r
7964         if (first.pr != NoProc) {\r
7965             ExitAnalyzeMode();\r
7966             DoSleep( appData.delayBeforeQuit );\r
7967             SendToProgram("quit\n", &first);\r
7968             DoSleep( appData.delayAfterQuit );\r
7969             DestroyChildProcess(first.pr, first.useSigterm);\r
7970         }\r
7971         first.pr = NoProc;\r
7972     }\r
7973     if (second.reuse) {\r
7974         /* Put second chess program into idle state */\r
7975         if (second.pr != NoProc &&\r
7976             gameMode == TwoMachinesPlay) {\r
7977             SendToProgram("force\n", &second);\r
7978             if (second.usePing) {\r
7979               char buf[MSG_SIZ];\r
7980               sprintf(buf, "ping %d\n", ++second.lastPing);\r
7981               SendToProgram(buf, &second);\r
7982             }\r
7983         }\r
7984     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7985         /* Kill off second chess program */\r
7986         if (second.isr != NULL)\r
7987           RemoveInputSource(second.isr);\r
7988         second.isr = NULL;\r
7989     \r
7990         if (second.pr != NoProc) {\r
7991             DoSleep( appData.delayBeforeQuit );\r
7992             SendToProgram("quit\n", &second);\r
7993             DoSleep( appData.delayAfterQuit );\r
7994             DestroyChildProcess(second.pr, second.useSigterm);\r
7995         }\r
7996         second.pr = NoProc;\r
7997     }\r
7998 \r
7999     if (matchMode && gameMode == TwoMachinesPlay) {\r
8000         switch (result) {\r
8001         case WhiteWins:\r
8002           if (first.twoMachinesColor[0] == 'w') {\r
8003             first.matchWins++;\r
8004           } else {\r
8005             second.matchWins++;\r
8006           }\r
8007           break;\r
8008         case BlackWins:\r
8009           if (first.twoMachinesColor[0] == 'b') {\r
8010             first.matchWins++;\r
8011           } else {\r
8012             second.matchWins++;\r
8013           }\r
8014           break;\r
8015         default:\r
8016           break;\r
8017         }\r
8018         if (matchGame < appData.matchGames) {\r
8019             char *tmp;\r
8020             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */\r
8021                 tmp = first.twoMachinesColor;\r
8022                 first.twoMachinesColor = second.twoMachinesColor;\r
8023                 second.twoMachinesColor = tmp;\r
8024             }\r
8025             gameMode = nextGameMode;\r
8026             matchGame++;\r
8027             if(appData.matchPause>10000 || appData.matchPause<10)\r
8028                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
8029             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
8030             endingGame = 0; /* [HGM] crash */\r
8031             return;\r
8032         } else {\r
8033             char buf[MSG_SIZ];\r
8034             gameMode = nextGameMode;\r
8035             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),\r
8036                     first.tidy, second.tidy,\r
8037                     first.matchWins, second.matchWins,\r
8038                     appData.matchGames - (first.matchWins + second.matchWins));\r
8039             DisplayFatalError(buf, 0, 0);\r
8040         }\r
8041     }\r
8042     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
8043         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
8044       ExitAnalyzeMode();\r
8045     gameMode = nextGameMode;\r
8046     ModeHighlight();\r
8047     endingGame = 0;  /* [HGM] crash */\r
8048 }\r
8049 \r
8050 /* Assumes program was just initialized (initString sent).\r
8051    Leaves program in force mode. */\r
8052 void\r
8053 FeedMovesToProgram(cps, upto) \r
8054      ChessProgramState *cps;\r
8055      int upto;\r
8056 {\r
8057     int i;\r
8058     \r
8059     if (appData.debugMode)\r
8060       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
8061               startedFromSetupPosition ? "position and " : "",\r
8062               backwardMostMove, upto, cps->which);\r
8063     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
8064         // [HGM] variantswitch: make engine aware of new variant\r
8065         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
8066                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
8067         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
8068         SendToProgram(buf, cps);\r
8069         currentlyInitializedVariant = gameInfo.variant;\r
8070     }\r
8071     SendToProgram("force\n", cps);\r
8072     if (startedFromSetupPosition) {\r
8073         SendBoard(cps, backwardMostMove);\r
8074     if (appData.debugMode) {\r
8075         fprintf(debugFP, "feedMoves\n");\r
8076     }\r
8077     }\r
8078     for (i = backwardMostMove; i < upto; i++) {\r
8079         SendMoveToProgram(i, cps);\r
8080     }\r
8081 }\r
8082 \r
8083 \r
8084 void\r
8085 ResurrectChessProgram()\r
8086 {\r
8087      /* The chess program may have exited.\r
8088         If so, restart it and feed it all the moves made so far. */\r
8089 \r
8090     if (appData.noChessProgram || first.pr != NoProc) return;\r
8091     \r
8092     StartChessProgram(&first);\r
8093     InitChessProgram(&first, FALSE);\r
8094     FeedMovesToProgram(&first, currentMove);\r
8095 \r
8096     if (!first.sendTime) {\r
8097         /* can't tell gnuchess what its clock should read,\r
8098            so we bow to its notion. */\r
8099         ResetClocks();\r
8100         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
8101         timeRemaining[1][currentMove] = blackTimeRemaining;\r
8102     }\r
8103 \r
8104     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||\r
8105                 appData.icsEngineAnalyze) && first.analysisSupport) {\r
8106       SendToProgram("analyze\n", &first);\r
8107       first.analyzing = TRUE;\r
8108     }\r
8109 }\r
8110 \r
8111 /*\r
8112  * Button procedures\r
8113  */\r
8114 void\r
8115 Reset(redraw, init)\r
8116      int redraw, init;\r
8117 {\r
8118     int i;\r
8119 \r
8120     if (appData.debugMode) {\r
8121         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
8122                 redraw, init, gameMode);\r
8123     }\r
8124     pausing = pauseExamInvalid = FALSE;\r
8125     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
8126     firstMove = TRUE;\r
8127     whiteFlag = blackFlag = FALSE;\r
8128     userOfferedDraw = FALSE;\r
8129     hintRequested = bookRequested = FALSE;\r
8130     first.maybeThinking = FALSE;\r
8131     second.maybeThinking = FALSE;\r
8132     first.bookSuspend = FALSE; // [HGM] book\r
8133     second.bookSuspend = FALSE;\r
8134     thinkOutput[0] = NULLCHAR;\r
8135     lastHint[0] = NULLCHAR;\r
8136     ClearGameInfo(&gameInfo);\r
8137     gameInfo.variant = StringToVariant(appData.variant);\r
8138     ics_user_moved = ics_clock_paused = FALSE;\r
8139     ics_getting_history = H_FALSE;\r
8140     ics_gamenum = -1;\r
8141     white_holding[0] = black_holding[0] = NULLCHAR;\r
8142     ClearProgramStats();\r
8143     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode\r
8144     \r
8145     ResetFrontEnd();\r
8146     ClearHighlights();\r
8147     flipView = appData.flipView;\r
8148     ClearPremoveHighlights();\r
8149     gotPremove = FALSE;\r
8150     alarmSounded = FALSE;\r
8151 \r
8152     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8153     if(appData.serverMovesName != NULL) {\r
8154         /* [HGM] prepare to make moves file for broadcasting */\r
8155         clock_t t = clock();\r
8156         if(serverMoves != NULL) fclose(serverMoves);\r
8157         serverMoves = fopen(appData.serverMovesName, "r");\r
8158         if(serverMoves != NULL) {\r
8159             fclose(serverMoves);\r
8160             /* delay 15 sec before overwriting, so all clients can see end */\r
8161             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
8162         }\r
8163         serverMoves = fopen(appData.serverMovesName, "w");\r
8164     }\r
8165 \r
8166     ExitAnalyzeMode();\r
8167     gameMode = BeginningOfGame;\r
8168     ModeHighlight();\r
8169     if(appData.icsActive) gameInfo.variant = VariantNormal;\r
8170     InitPosition(redraw);\r
8171     for (i = 0; i < MAX_MOVES; i++) {\r
8172         if (commentList[i] != NULL) {\r
8173             free(commentList[i]);\r
8174             commentList[i] = NULL;\r
8175         }\r
8176     }\r
8177     ResetClocks();\r
8178     timeRemaining[0][0] = whiteTimeRemaining;\r
8179     timeRemaining[1][0] = blackTimeRemaining;\r
8180     if (first.pr == NULL) {\r
8181         StartChessProgram(&first);\r
8182     }\r
8183     if (init) {\r
8184             InitChessProgram(&first, startedFromSetupPosition);\r
8185     }\r
8186     DisplayTitle("");\r
8187     DisplayMessage("", "");\r
8188     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8189 }\r
8190 \r
8191 void\r
8192 AutoPlayGameLoop()\r
8193 {\r
8194     for (;;) {\r
8195         if (!AutoPlayOneMove())\r
8196           return;\r
8197         if (matchMode || appData.timeDelay == 0)\r
8198           continue;\r
8199         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
8200           return;\r
8201         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
8202         break;\r
8203     }\r
8204 }\r
8205 \r
8206 \r
8207 int\r
8208 AutoPlayOneMove()\r
8209 {\r
8210     int fromX, fromY, toX, toY;\r
8211 \r
8212     if (appData.debugMode) {\r
8213       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
8214     }\r
8215 \r
8216     if (gameMode != PlayFromGameFile)\r
8217       return FALSE;\r
8218 \r
8219     if (currentMove >= forwardMostMove) {\r
8220       gameMode = EditGame;\r
8221       ModeHighlight();\r
8222 \r
8223       /* [AS] Clear current move marker at the end of a game */\r
8224       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
8225 \r
8226       return FALSE;\r
8227     }\r
8228     \r
8229     toX = moveList[currentMove][2] - AAA;\r
8230     toY = moveList[currentMove][3] - ONE;\r
8231 \r
8232     if (moveList[currentMove][1] == '@') {\r
8233         if (appData.highlightLastMove) {\r
8234             SetHighlights(-1, -1, toX, toY);\r
8235         }\r
8236     } else {\r
8237         fromX = moveList[currentMove][0] - AAA;\r
8238         fromY = moveList[currentMove][1] - ONE;\r
8239 \r
8240         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
8241 \r
8242         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
8243 \r
8244         if (appData.highlightLastMove) {\r
8245             SetHighlights(fromX, fromY, toX, toY);\r
8246         }\r
8247     }\r
8248     DisplayMove(currentMove);\r
8249     SendMoveToProgram(currentMove++, &first);\r
8250     DisplayBothClocks();\r
8251     DrawPosition(FALSE, boards[currentMove]);\r
8252     // [HGM] PV info: always display, routine tests if empty\r
8253     DisplayComment(currentMove - 1, commentList[currentMove]);\r
8254     return TRUE;\r
8255 }\r
8256 \r
8257 \r
8258 int\r
8259 LoadGameOneMove(readAhead)\r
8260      ChessMove readAhead;\r
8261 {\r
8262     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
8263     char promoChar = NULLCHAR;\r
8264     ChessMove moveType;\r
8265     char move[MSG_SIZ];\r
8266     char *p, *q;\r
8267     \r
8268     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
8269         gameMode != AnalyzeMode && gameMode != Training) {\r
8270         gameFileFP = NULL;\r
8271         return FALSE;\r
8272     }\r
8273     \r
8274     yyboardindex = forwardMostMove;\r
8275     if (readAhead != (ChessMove)0) {\r
8276       moveType = readAhead;\r
8277     } else {\r
8278       if (gameFileFP == NULL)\r
8279           return FALSE;\r
8280       moveType = (ChessMove) yylex();\r
8281     }\r
8282     \r
8283     done = FALSE;\r
8284     switch (moveType) {\r
8285       case Comment:\r
8286         if (appData.debugMode) \r
8287           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8288         p = yy_text;\r
8289         if (*p == '{' || *p == '[' || *p == '(') {\r
8290             p[strlen(p) - 1] = NULLCHAR;\r
8291             p++;\r
8292         }\r
8293 \r
8294         /* append the comment but don't display it */\r
8295         while (*p == '\n') p++;\r
8296         AppendComment(currentMove, p);\r
8297         return TRUE;\r
8298 \r
8299       case WhiteCapturesEnPassant:\r
8300       case BlackCapturesEnPassant:\r
8301       case WhitePromotionChancellor:\r
8302       case BlackPromotionChancellor:\r
8303       case WhitePromotionArchbishop:\r
8304       case BlackPromotionArchbishop:\r
8305       case WhitePromotionCentaur:\r
8306       case BlackPromotionCentaur:\r
8307       case WhitePromotionQueen:\r
8308       case BlackPromotionQueen:\r
8309       case WhitePromotionRook:\r
8310       case BlackPromotionRook:\r
8311       case WhitePromotionBishop:\r
8312       case BlackPromotionBishop:\r
8313       case WhitePromotionKnight:\r
8314       case BlackPromotionKnight:\r
8315       case WhitePromotionKing:\r
8316       case BlackPromotionKing:\r
8317       case NormalMove:\r
8318       case WhiteKingSideCastle:\r
8319       case WhiteQueenSideCastle:\r
8320       case BlackKingSideCastle:\r
8321       case BlackQueenSideCastle:\r
8322       case WhiteKingSideCastleWild:\r
8323       case WhiteQueenSideCastleWild:\r
8324       case BlackKingSideCastleWild:\r
8325       case BlackQueenSideCastleWild:\r
8326       /* PUSH Fabien */\r
8327       case WhiteHSideCastleFR:\r
8328       case WhiteASideCastleFR:\r
8329       case BlackHSideCastleFR:\r
8330       case BlackASideCastleFR:\r
8331       /* POP Fabien */\r
8332         if (appData.debugMode)\r
8333           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8334         fromX = currentMoveString[0] - AAA;\r
8335         fromY = currentMoveString[1] - ONE;\r
8336         toX = currentMoveString[2] - AAA;\r
8337         toY = currentMoveString[3] - ONE;\r
8338         promoChar = currentMoveString[4];\r
8339         break;\r
8340 \r
8341       case WhiteDrop:\r
8342       case BlackDrop:\r
8343         if (appData.debugMode)\r
8344           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8345         fromX = moveType == WhiteDrop ?\r
8346           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
8347         (int) CharToPiece(ToLower(currentMoveString[0]));\r
8348         fromY = DROP_RANK;\r
8349         toX = currentMoveString[2] - AAA;\r
8350         toY = currentMoveString[3] - ONE;\r
8351         break;\r
8352 \r
8353       case WhiteWins:\r
8354       case BlackWins:\r
8355       case GameIsDrawn:\r
8356       case GameUnfinished:\r
8357         if (appData.debugMode)\r
8358           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
8359         p = strchr(yy_text, '{');\r
8360         if (p == NULL) p = strchr(yy_text, '(');\r
8361         if (p == NULL) {\r
8362             p = yy_text;\r
8363             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
8364         } else {\r
8365             q = strchr(p, *p == '{' ? '}' : ')');\r
8366             if (q != NULL) *q = NULLCHAR;\r
8367             p++;\r
8368         }\r
8369         GameEnds(moveType, p, GE_FILE);\r
8370         done = TRUE;\r
8371         if (cmailMsgLoaded) {\r
8372             ClearHighlights();\r
8373             flipView = WhiteOnMove(currentMove);\r
8374             if (moveType == GameUnfinished) flipView = !flipView;\r
8375             if (appData.debugMode)\r
8376               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
8377         }\r
8378         break;\r
8379 \r
8380       case (ChessMove) 0:       /* end of file */\r
8381         if (appData.debugMode)\r
8382           fprintf(debugFP, "Parser hit end of file\n");\r
8383         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8384                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8385           case MT_NONE:\r
8386           case MT_CHECK:\r
8387             break;\r
8388           case MT_CHECKMATE:\r
8389             if (WhiteOnMove(currentMove)) {\r
8390                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8391             } else {\r
8392                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8393             }\r
8394             break;\r
8395           case MT_STALEMATE:\r
8396             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8397             break;\r
8398         }\r
8399         done = TRUE;\r
8400         break;\r
8401 \r
8402       case MoveNumberOne:\r
8403         if (lastLoadGameStart == GNUChessGame) {\r
8404             /* GNUChessGames have numbers, but they aren't move numbers */\r
8405             if (appData.debugMode)\r
8406               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8407                       yy_text, (int) moveType);\r
8408             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8409         }\r
8410         /* else fall thru */\r
8411 \r
8412       case XBoardGame:\r
8413       case GNUChessGame:\r
8414       case PGNTag:\r
8415         /* Reached start of next game in file */\r
8416         if (appData.debugMode)\r
8417           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
8418         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8419                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8420           case MT_NONE:\r
8421           case MT_CHECK:\r
8422             break;\r
8423           case MT_CHECKMATE:\r
8424             if (WhiteOnMove(currentMove)) {\r
8425                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8426             } else {\r
8427                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8428             }\r
8429             break;\r
8430           case MT_STALEMATE:\r
8431             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8432             break;\r
8433         }\r
8434         done = TRUE;\r
8435         break;\r
8436 \r
8437       case PositionDiagram:     /* should not happen; ignore */\r
8438       case ElapsedTime:         /* ignore */\r
8439       case NAG:                 /* ignore */\r
8440         if (appData.debugMode)\r
8441           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8442                   yy_text, (int) moveType);\r
8443         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8444 \r
8445       case IllegalMove:\r
8446         if (appData.testLegality) {\r
8447             if (appData.debugMode)\r
8448               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
8449             sprintf(move, _("Illegal move: %d.%s%s"),\r
8450                     (forwardMostMove / 2) + 1,\r
8451                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8452             DisplayError(move, 0);\r
8453             done = TRUE;\r
8454         } else {\r
8455             if (appData.debugMode)\r
8456               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
8457                       yy_text, currentMoveString);\r
8458             fromX = currentMoveString[0] - AAA;\r
8459             fromY = currentMoveString[1] - ONE;\r
8460             toX = currentMoveString[2] - AAA;\r
8461             toY = currentMoveString[3] - ONE;\r
8462             promoChar = currentMoveString[4];\r
8463         }\r
8464         break;\r
8465 \r
8466       case AmbiguousMove:\r
8467         if (appData.debugMode)\r
8468           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
8469         sprintf(move, _("Ambiguous move: %d.%s%s"),\r
8470                 (forwardMostMove / 2) + 1,\r
8471                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8472         DisplayError(move, 0);\r
8473         done = TRUE;\r
8474         break;\r
8475 \r
8476       default:\r
8477       case ImpossibleMove:\r
8478         if (appData.debugMode)\r
8479           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);\r
8480         sprintf(move, _("Illegal move: %d.%s%s"),\r
8481                 (forwardMostMove / 2) + 1,\r
8482                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8483         DisplayError(move, 0);\r
8484         done = TRUE;\r
8485         break;\r
8486     }\r
8487 \r
8488     if (done) {\r
8489         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
8490             DrawPosition(FALSE, boards[currentMove]);\r
8491             DisplayBothClocks();\r
8492             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
8493               DisplayComment(currentMove - 1, commentList[currentMove]);\r
8494         }\r
8495         (void) StopLoadGameTimer();\r
8496         gameFileFP = NULL;\r
8497         cmailOldMove = forwardMostMove;\r
8498         return FALSE;\r
8499     } else {\r
8500         /* currentMoveString is set as a side-effect of yylex */\r
8501         strcat(currentMoveString, "\n");\r
8502         strcpy(moveList[forwardMostMove], currentMoveString);\r
8503         \r
8504         thinkOutput[0] = NULLCHAR;\r
8505         MakeMove(fromX, fromY, toX, toY, promoChar);\r
8506         currentMove = forwardMostMove;\r
8507         return TRUE;\r
8508     }\r
8509 }\r
8510 \r
8511 /* Load the nth game from the given file */\r
8512 int\r
8513 LoadGameFromFile(filename, n, title, useList)\r
8514      char *filename;\r
8515      int n;\r
8516      char *title;\r
8517      /*Boolean*/ int useList;\r
8518 {\r
8519     FILE *f;\r
8520     char buf[MSG_SIZ];\r
8521 \r
8522     if (strcmp(filename, "-") == 0) {\r
8523         f = stdin;\r
8524         title = "stdin";\r
8525     } else {\r
8526         f = fopen(filename, "rb");\r
8527         if (f == NULL) {\r
8528             sprintf(buf, _("Can't open \"%s\""), filename);\r
8529             DisplayError(buf, errno);\r
8530             return FALSE;\r
8531         }\r
8532     }\r
8533     if (fseek(f, 0, 0) == -1) {\r
8534         /* f is not seekable; probably a pipe */\r
8535         useList = FALSE;\r
8536     }\r
8537     if (useList && n == 0) {\r
8538         int error = GameListBuild(f);\r
8539         if (error) {\r
8540             DisplayError(_("Cannot build game list"), error);\r
8541         } else if (!ListEmpty(&gameList) &&\r
8542                    ((ListGame *) gameList.tailPred)->number > 1) {\r
8543             GameListPopUp(f, title);\r
8544             return TRUE;\r
8545         }\r
8546         GameListDestroy();\r
8547         n = 1;\r
8548     }\r
8549     if (n == 0) n = 1;\r
8550     return LoadGame(f, n, title, FALSE);\r
8551 }\r
8552 \r
8553 \r
8554 void\r
8555 MakeRegisteredMove()\r
8556 {\r
8557     int fromX, fromY, toX, toY;\r
8558     char promoChar;\r
8559     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8560         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
8561           case CMAIL_MOVE:\r
8562           case CMAIL_DRAW:\r
8563             if (appData.debugMode)\r
8564               fprintf(debugFP, "Restoring %s for game %d\n",\r
8565                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8566     \r
8567             thinkOutput[0] = NULLCHAR;\r
8568             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
8569             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
8570             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
8571             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
8572             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
8573             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
8574             MakeMove(fromX, fromY, toX, toY, promoChar);\r
8575             ShowMove(fromX, fromY, toX, toY);\r
8576               \r
8577             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8578                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8579               case MT_NONE:\r
8580               case MT_CHECK:\r
8581                 break;\r
8582                 \r
8583               case MT_CHECKMATE:\r
8584                 if (WhiteOnMove(currentMove)) {\r
8585                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
8586                 } else {\r
8587                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
8588                 }\r
8589                 break;\r
8590                 \r
8591               case MT_STALEMATE:\r
8592                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
8593                 break;\r
8594             }\r
8595 \r
8596             break;\r
8597             \r
8598           case CMAIL_RESIGN:\r
8599             if (WhiteOnMove(currentMove)) {\r
8600                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
8601             } else {\r
8602                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
8603             }\r
8604             break;\r
8605             \r
8606           case CMAIL_ACCEPT:\r
8607             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
8608             break;\r
8609               \r
8610           default:\r
8611             break;\r
8612         }\r
8613     }\r
8614 \r
8615     return;\r
8616 }\r
8617 \r
8618 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
8619 int\r
8620 CmailLoadGame(f, gameNumber, title, useList)\r
8621      FILE *f;\r
8622      int gameNumber;\r
8623      char *title;\r
8624      int useList;\r
8625 {\r
8626     int retVal;\r
8627 \r
8628     if (gameNumber > nCmailGames) {\r
8629         DisplayError(_("No more games in this message"), 0);\r
8630         return FALSE;\r
8631     }\r
8632     if (f == lastLoadGameFP) {\r
8633         int offset = gameNumber - lastLoadGameNumber;\r
8634         if (offset == 0) {\r
8635             cmailMsg[0] = NULLCHAR;\r
8636             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8637                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8638                 nCmailMovesRegistered--;\r
8639             }\r
8640             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
8641             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
8642                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
8643             }\r
8644         } else {\r
8645             if (! RegisterMove()) return FALSE;\r
8646         }\r
8647     }\r
8648 \r
8649     retVal = LoadGame(f, gameNumber, title, useList);\r
8650 \r
8651     /* Make move registered during previous look at this game, if any */\r
8652     MakeRegisteredMove();\r
8653 \r
8654     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
8655         commentList[currentMove]\r
8656           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
8657         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8658     }\r
8659 \r
8660     return retVal;\r
8661 }\r
8662 \r
8663 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
8664 int\r
8665 ReloadGame(offset)\r
8666      int offset;\r
8667 {\r
8668     int gameNumber = lastLoadGameNumber + offset;\r
8669     if (lastLoadGameFP == NULL) {\r
8670         DisplayError(_("No game has been loaded yet"), 0);\r
8671         return FALSE;\r
8672     }\r
8673     if (gameNumber <= 0) {\r
8674         DisplayError(_("Can't back up any further"), 0);\r
8675         return FALSE;\r
8676     }\r
8677     if (cmailMsgLoaded) {\r
8678         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
8679                              lastLoadGameTitle, lastLoadGameUseList);\r
8680     } else {\r
8681         return LoadGame(lastLoadGameFP, gameNumber,\r
8682                         lastLoadGameTitle, lastLoadGameUseList);\r
8683     }\r
8684 }\r
8685 \r
8686 \r
8687 \r
8688 /* Load the nth game from open file f */\r
8689 int\r
8690 LoadGame(f, gameNumber, title, useList)\r
8691      FILE *f;\r
8692      int gameNumber;\r
8693      char *title;\r
8694      int useList;\r
8695 {\r
8696     ChessMove cm;\r
8697     char buf[MSG_SIZ];\r
8698     int gn = gameNumber;\r
8699     ListGame *lg = NULL;\r
8700     int numPGNTags = 0;\r
8701     int err;\r
8702     GameMode oldGameMode;\r
8703     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
8704 \r
8705     if (appData.debugMode) \r
8706         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
8707 \r
8708     if (gameMode == Training )\r
8709         SetTrainingModeOff();\r
8710 \r
8711     oldGameMode = gameMode;\r
8712     if (gameMode != BeginningOfGame) {\r
8713       Reset(FALSE, TRUE);\r
8714     }\r
8715 \r
8716     gameFileFP = f;\r
8717     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
8718         fclose(lastLoadGameFP);\r
8719     }\r
8720 \r
8721     if (useList) {\r
8722         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
8723         \r
8724         if (lg) {\r
8725             fseek(f, lg->offset, 0);\r
8726             GameListHighlight(gameNumber);\r
8727             gn = 1;\r
8728         }\r
8729         else {\r
8730             DisplayError(_("Game number out of range"), 0);\r
8731             return FALSE;\r
8732         }\r
8733     } else {\r
8734         GameListDestroy();\r
8735         if (fseek(f, 0, 0) == -1) {\r
8736             if (f == lastLoadGameFP ?\r
8737                 gameNumber == lastLoadGameNumber + 1 :\r
8738                 gameNumber == 1) {\r
8739                 gn = 1;\r
8740             } else {\r
8741                 DisplayError(_("Can't seek on game file"), 0);\r
8742                 return FALSE;\r
8743             }\r
8744         }\r
8745     }\r
8746     lastLoadGameFP = f;\r
8747     lastLoadGameNumber = gameNumber;\r
8748     strcpy(lastLoadGameTitle, title);\r
8749     lastLoadGameUseList = useList;\r
8750 \r
8751     yynewfile(f);\r
8752 \r
8753     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
8754         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
8755                 lg->gameInfo.black);\r
8756             DisplayTitle(buf);\r
8757     } else if (*title != NULLCHAR) {\r
8758         if (gameNumber > 1) {\r
8759             sprintf(buf, "%s %d", title, gameNumber);\r
8760             DisplayTitle(buf);\r
8761         } else {\r
8762             DisplayTitle(title);\r
8763         }\r
8764     }\r
8765 \r
8766     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
8767         gameMode = PlayFromGameFile;\r
8768         ModeHighlight();\r
8769     }\r
8770 \r
8771     currentMove = forwardMostMove = backwardMostMove = 0;\r
8772     CopyBoard(boards[0], initialPosition);\r
8773     StopClocks();\r
8774 \r
8775     /*\r
8776      * Skip the first gn-1 games in the file.\r
8777      * Also skip over anything that precedes an identifiable \r
8778      * start of game marker, to avoid being confused by \r
8779      * garbage at the start of the file.  Currently \r
8780      * recognized start of game markers are the move number "1",\r
8781      * the pattern "gnuchess .* game", the pattern\r
8782      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
8783      * A game that starts with one of the latter two patterns\r
8784      * will also have a move number 1, possibly\r
8785      * following a position diagram.\r
8786      * 5-4-02: Let's try being more lenient and allowing a game to\r
8787      * start with an unnumbered move.  Does that break anything?\r
8788      */\r
8789     cm = lastLoadGameStart = (ChessMove) 0;\r
8790     while (gn > 0) {\r
8791         yyboardindex = forwardMostMove;\r
8792         cm = (ChessMove) yylex();\r
8793         switch (cm) {\r
8794           case (ChessMove) 0:\r
8795             if (cmailMsgLoaded) {\r
8796                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
8797             } else {\r
8798                 Reset(TRUE, TRUE);\r
8799                 DisplayError(_("Game not found in file"), 0);\r
8800             }\r
8801             return FALSE;\r
8802 \r
8803           case GNUChessGame:\r
8804           case XBoardGame:\r
8805             gn--;\r
8806             lastLoadGameStart = cm;\r
8807             break;\r
8808             \r
8809           case MoveNumberOne:\r
8810             switch (lastLoadGameStart) {\r
8811               case GNUChessGame:\r
8812               case XBoardGame:\r
8813               case PGNTag:\r
8814                 break;\r
8815               case MoveNumberOne:\r
8816               case (ChessMove) 0:\r
8817                 gn--;           /* count this game */\r
8818                 lastLoadGameStart = cm;\r
8819                 break;\r
8820               default:\r
8821                 /* impossible */\r
8822                 break;\r
8823             }\r
8824             break;\r
8825 \r
8826           case PGNTag:\r
8827             switch (lastLoadGameStart) {\r
8828               case GNUChessGame:\r
8829               case PGNTag:\r
8830               case MoveNumberOne:\r
8831               case (ChessMove) 0:\r
8832                 gn--;           /* count this game */\r
8833                 lastLoadGameStart = cm;\r
8834                 break;\r
8835               case XBoardGame:\r
8836                 lastLoadGameStart = cm; /* game counted already */\r
8837                 break;\r
8838               default:\r
8839                 /* impossible */\r
8840                 break;\r
8841             }\r
8842             if (gn > 0) {\r
8843                 do {\r
8844                     yyboardindex = forwardMostMove;\r
8845                     cm = (ChessMove) yylex();\r
8846                 } while (cm == PGNTag || cm == Comment);\r
8847             }\r
8848             break;\r
8849 \r
8850           case WhiteWins:\r
8851           case BlackWins:\r
8852           case GameIsDrawn:\r
8853             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8854                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8855                     != CMAIL_OLD_RESULT) {\r
8856                     nCmailResults ++ ;\r
8857                     cmailResult[  CMAIL_MAX_GAMES\r
8858                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8859                 }\r
8860             }\r
8861             break;\r
8862 \r
8863           case NormalMove:\r
8864             /* Only a NormalMove can be at the start of a game\r
8865              * without a position diagram. */\r
8866             if (lastLoadGameStart == (ChessMove) 0) {\r
8867               gn--;\r
8868               lastLoadGameStart = MoveNumberOne;\r
8869             }\r
8870             break;\r
8871 \r
8872           default:\r
8873             break;\r
8874         }\r
8875     }\r
8876     \r
8877     if (appData.debugMode)\r
8878       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8879 \r
8880     if (cm == XBoardGame) {\r
8881         /* Skip any header junk before position diagram and/or move 1 */\r
8882         for (;;) {\r
8883             yyboardindex = forwardMostMove;\r
8884             cm = (ChessMove) yylex();\r
8885 \r
8886             if (cm == (ChessMove) 0 ||\r
8887                 cm == GNUChessGame || cm == XBoardGame) {\r
8888                 /* Empty game; pretend end-of-file and handle later */\r
8889                 cm = (ChessMove) 0;\r
8890                 break;\r
8891             }\r
8892 \r
8893             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8894                 cm == PGNTag || cm == Comment)\r
8895               break;\r
8896         }\r
8897     } else if (cm == GNUChessGame) {\r
8898         if (gameInfo.event != NULL) {\r
8899             free(gameInfo.event);\r
8900         }\r
8901         gameInfo.event = StrSave(yy_text);\r
8902     }   \r
8903 \r
8904     startedFromSetupPosition = FALSE;\r
8905     while (cm == PGNTag) {\r
8906         if (appData.debugMode) \r
8907           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8908         err = ParsePGNTag(yy_text, &gameInfo);\r
8909         if (!err) numPGNTags++;\r
8910 \r
8911         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8912         if(gameInfo.variant != oldVariant) {\r
8913             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8914             InitPosition(TRUE);\r
8915             oldVariant = gameInfo.variant;\r
8916             if (appData.debugMode) \r
8917               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8918         }\r
8919 \r
8920 \r
8921         if (gameInfo.fen != NULL) {\r
8922           Board initial_position;\r
8923           startedFromSetupPosition = TRUE;\r
8924           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8925             Reset(TRUE, TRUE);\r
8926             DisplayError(_("Bad FEN position in file"), 0);\r
8927             return FALSE;\r
8928           }\r
8929           CopyBoard(boards[0], initial_position);\r
8930           if (blackPlaysFirst) {\r
8931             currentMove = forwardMostMove = backwardMostMove = 1;\r
8932             CopyBoard(boards[1], initial_position);\r
8933             strcpy(moveList[0], "");\r
8934             strcpy(parseList[0], "");\r
8935             timeRemaining[0][1] = whiteTimeRemaining;\r
8936             timeRemaining[1][1] = blackTimeRemaining;\r
8937             if (commentList[0] != NULL) {\r
8938               commentList[1] = commentList[0];\r
8939               commentList[0] = NULL;\r
8940             }\r
8941           } else {\r
8942             currentMove = forwardMostMove = backwardMostMove = 0;\r
8943           }\r
8944           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */\r
8945           {   int i;\r
8946               initialRulePlies = FENrulePlies;\r
8947               epStatus[forwardMostMove] = FENepStatus;\r
8948               for( i=0; i< nrCastlingRights; i++ )\r
8949                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
8950           }\r
8951           yyboardindex = forwardMostMove;\r
8952           free(gameInfo.fen);\r
8953           gameInfo.fen = NULL;\r
8954         }\r
8955 \r
8956         yyboardindex = forwardMostMove;\r
8957         cm = (ChessMove) yylex();\r
8958 \r
8959         /* Handle comments interspersed among the tags */\r
8960         while (cm == Comment) {\r
8961             char *p;\r
8962             if (appData.debugMode) \r
8963               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8964             p = yy_text;\r
8965             if (*p == '{' || *p == '[' || *p == '(') {\r
8966                 p[strlen(p) - 1] = NULLCHAR;\r
8967                 p++;\r
8968             }\r
8969             while (*p == '\n') p++;\r
8970             AppendComment(currentMove, p);\r
8971             yyboardindex = forwardMostMove;\r
8972             cm = (ChessMove) yylex();\r
8973         }\r
8974     }\r
8975 \r
8976     /* don't rely on existence of Event tag since if game was\r
8977      * pasted from clipboard the Event tag may not exist\r
8978      */\r
8979     if (numPGNTags > 0){\r
8980         char *tags;\r
8981         if (gameInfo.variant == VariantNormal) {\r
8982           gameInfo.variant = StringToVariant(gameInfo.event);\r
8983         }\r
8984         if (!matchMode) {\r
8985           if( appData.autoDisplayTags ) {\r
8986             tags = PGNTags(&gameInfo);\r
8987             TagsPopUp(tags, CmailMsg());\r
8988             free(tags);\r
8989           }\r
8990         }\r
8991     } else {\r
8992         /* Make something up, but don't display it now */\r
8993         SetGameInfo();\r
8994         TagsPopDown();\r
8995     }\r
8996 \r
8997     if (cm == PositionDiagram) {\r
8998         int i, j;\r
8999         char *p;\r
9000         Board initial_position;\r
9001 \r
9002         if (appData.debugMode)\r
9003           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
9004 \r
9005         if (!startedFromSetupPosition) {\r
9006             p = yy_text;\r
9007             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
9008               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
9009                 switch (*p) {\r
9010                   case '[':\r
9011                   case '-':\r
9012                   case ' ':\r
9013                   case '\t':\r
9014                   case '\n':\r
9015                   case '\r':\r
9016                     break;\r
9017                   default:\r
9018                     initial_position[i][j++] = CharToPiece(*p);\r
9019                     break;\r
9020                 }\r
9021             while (*p == ' ' || *p == '\t' ||\r
9022                    *p == '\n' || *p == '\r') p++;\r
9023         \r
9024             if (strncmp(p, "black", strlen("black"))==0)\r
9025               blackPlaysFirst = TRUE;\r
9026             else\r
9027               blackPlaysFirst = FALSE;\r
9028             startedFromSetupPosition = TRUE;\r
9029         \r
9030             CopyBoard(boards[0], initial_position);\r
9031             if (blackPlaysFirst) {\r
9032                 currentMove = forwardMostMove = backwardMostMove = 1;\r
9033                 CopyBoard(boards[1], initial_position);\r
9034                 strcpy(moveList[0], "");\r
9035                 strcpy(parseList[0], "");\r
9036                 timeRemaining[0][1] = whiteTimeRemaining;\r
9037                 timeRemaining[1][1] = blackTimeRemaining;\r
9038                 if (commentList[0] != NULL) {\r
9039                     commentList[1] = commentList[0];\r
9040                     commentList[0] = NULL;\r
9041                 }\r
9042             } else {\r
9043                 currentMove = forwardMostMove = backwardMostMove = 0;\r
9044             }\r
9045         }\r
9046         yyboardindex = forwardMostMove;\r
9047         cm = (ChessMove) yylex();\r
9048     }\r
9049 \r
9050     if (first.pr == NoProc) {\r
9051         StartChessProgram(&first);\r
9052     }\r
9053     InitChessProgram(&first, FALSE);\r
9054     SendToProgram("force\n", &first);\r
9055     if (startedFromSetupPosition) {\r
9056         SendBoard(&first, forwardMostMove);\r
9057     if (appData.debugMode) {\r
9058         fprintf(debugFP, "Load Game\n");\r
9059     }\r
9060         DisplayBothClocks();\r
9061     }      \r
9062 \r
9063     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
9064     loadFlag = appData.suppressLoadMoves;\r
9065 \r
9066     while (cm == Comment) {\r
9067         char *p;\r
9068         if (appData.debugMode) \r
9069           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
9070         p = yy_text;\r
9071         if (*p == '{' || *p == '[' || *p == '(') {\r
9072             p[strlen(p) - 1] = NULLCHAR;\r
9073             p++;\r
9074         }\r
9075         while (*p == '\n') p++;\r
9076         AppendComment(currentMove, p);\r
9077         yyboardindex = forwardMostMove;\r
9078         cm = (ChessMove) yylex();\r
9079     }\r
9080 \r
9081     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
9082         cm == WhiteWins || cm == BlackWins ||\r
9083         cm == GameIsDrawn || cm == GameUnfinished) {\r
9084         DisplayMessage("", _("No moves in game"));\r
9085         if (cmailMsgLoaded) {\r
9086             if (appData.debugMode)\r
9087               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
9088             ClearHighlights();\r
9089             flipView = FALSE;\r
9090         }\r
9091         DrawPosition(FALSE, boards[currentMove]);\r
9092         DisplayBothClocks();\r
9093         gameMode = EditGame;\r
9094         ModeHighlight();\r
9095         gameFileFP = NULL;\r
9096         cmailOldMove = 0;\r
9097         return TRUE;\r
9098     }\r
9099 \r
9100     // [HGM] PV info: routine tests if comment empty\r
9101     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
9102         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9103     }\r
9104     if (!matchMode && appData.timeDelay != 0) \r
9105       DrawPosition(FALSE, boards[currentMove]);\r
9106 \r
9107     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
9108       programStats.ok_to_send = 1;\r
9109     }\r
9110 \r
9111     /* if the first token after the PGN tags is a move\r
9112      * and not move number 1, retrieve it from the parser \r
9113      */\r
9114     if (cm != MoveNumberOne)\r
9115         LoadGameOneMove(cm);\r
9116 \r
9117     /* load the remaining moves from the file */\r
9118     while (LoadGameOneMove((ChessMove)0)) {\r
9119       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9120       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9121     }\r
9122 \r
9123     /* rewind to the start of the game */\r
9124     currentMove = backwardMostMove;\r
9125 \r
9126     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9127 \r
9128     if (oldGameMode == AnalyzeFile ||\r
9129         oldGameMode == AnalyzeMode) {\r
9130       AnalyzeFileEvent();\r
9131     }\r
9132 \r
9133     if (matchMode || appData.timeDelay == 0) {\r
9134       ToEndEvent();\r
9135       gameMode = EditGame;\r
9136       ModeHighlight();\r
9137     } else if (appData.timeDelay > 0) {\r
9138       AutoPlayGameLoop();\r
9139     }\r
9140 \r
9141     if (appData.debugMode) \r
9142         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
9143 \r
9144     loadFlag = 0; /* [HGM] true game starts */\r
9145     return TRUE;\r
9146 }\r
9147 \r
9148 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
9149 int\r
9150 ReloadPosition(offset)\r
9151      int offset;\r
9152 {\r
9153     int positionNumber = lastLoadPositionNumber + offset;\r
9154     if (lastLoadPositionFP == NULL) {\r
9155         DisplayError(_("No position has been loaded yet"), 0);\r
9156         return FALSE;\r
9157     }\r
9158     if (positionNumber <= 0) {\r
9159         DisplayError(_("Can't back up any further"), 0);\r
9160         return FALSE;\r
9161     }\r
9162     return LoadPosition(lastLoadPositionFP, positionNumber,\r
9163                         lastLoadPositionTitle);\r
9164 }\r
9165 \r
9166 /* Load the nth position from the given file */\r
9167 int\r
9168 LoadPositionFromFile(filename, n, title)\r
9169      char *filename;\r
9170      int n;\r
9171      char *title;\r
9172 {\r
9173     FILE *f;\r
9174     char buf[MSG_SIZ];\r
9175 \r
9176     if (strcmp(filename, "-") == 0) {\r
9177         return LoadPosition(stdin, n, "stdin");\r
9178     } else {\r
9179         f = fopen(filename, "rb");\r
9180         if (f == NULL) {\r
9181             sprintf(buf, _("Can't open \"%s\""), filename);\r
9182             DisplayError(buf, errno);\r
9183             return FALSE;\r
9184         } else {\r
9185             return LoadPosition(f, n, title);\r
9186         }\r
9187     }\r
9188 }\r
9189 \r
9190 /* Load the nth position from the given open file, and close it */\r
9191 int\r
9192 LoadPosition(f, positionNumber, title)\r
9193      FILE *f;\r
9194      int positionNumber;\r
9195      char *title;\r
9196 {\r
9197     char *p, line[MSG_SIZ];\r
9198     Board initial_position;\r
9199     int i, j, fenMode, pn;\r
9200     \r
9201     if (gameMode == Training )\r
9202         SetTrainingModeOff();\r
9203 \r
9204     if (gameMode != BeginningOfGame) {\r
9205         Reset(FALSE, TRUE);\r
9206     }\r
9207     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
9208         fclose(lastLoadPositionFP);\r
9209     }\r
9210     if (positionNumber == 0) positionNumber = 1;\r
9211     lastLoadPositionFP = f;\r
9212     lastLoadPositionNumber = positionNumber;\r
9213     strcpy(lastLoadPositionTitle, title);\r
9214     if (first.pr == NoProc) {\r
9215       StartChessProgram(&first);\r
9216       InitChessProgram(&first, FALSE);\r
9217     }    \r
9218     pn = positionNumber;\r
9219     if (positionNumber < 0) {\r
9220         /* Negative position number means to seek to that byte offset */\r
9221         if (fseek(f, -positionNumber, 0) == -1) {\r
9222             DisplayError(_("Can't seek on position file"), 0);\r
9223             return FALSE;\r
9224         };\r
9225         pn = 1;\r
9226     } else {\r
9227         if (fseek(f, 0, 0) == -1) {\r
9228             if (f == lastLoadPositionFP ?\r
9229                 positionNumber == lastLoadPositionNumber + 1 :\r
9230                 positionNumber == 1) {\r
9231                 pn = 1;\r
9232             } else {\r
9233                 DisplayError(_("Can't seek on position file"), 0);\r
9234                 return FALSE;\r
9235             }\r
9236         }\r
9237     }\r
9238     /* See if this file is FEN or old-style xboard */\r
9239     if (fgets(line, MSG_SIZ, f) == NULL) {\r
9240         DisplayError(_("Position not found in file"), 0);\r
9241         return FALSE;\r
9242     }\r
9243 #if 0\r
9244     switch (line[0]) {\r
9245       case '#':  case 'x':\r
9246       default:\r
9247         fenMode = FALSE;\r
9248         break;\r
9249       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
9250       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
9251       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
9252       case '7':  case '8':  case '9':\r
9253       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
9254       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
9255       case 'C':  case 'W':             case 'c':  case 'w': \r
9256         fenMode = TRUE;\r
9257         break;\r
9258     }\r
9259 #else\r
9260     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
9261     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
9262 #endif\r
9263 \r
9264     if (pn >= 2) {\r
9265         if (fenMode || line[0] == '#') pn--;\r
9266         while (pn > 0) {\r
9267             /* skip positions before number pn */\r
9268             if (fgets(line, MSG_SIZ, f) == NULL) {\r
9269                 Reset(TRUE, TRUE);\r
9270                 DisplayError(_("Position not found in file"), 0);\r
9271                 return FALSE;\r
9272             }\r
9273             if (fenMode || line[0] == '#') pn--;\r
9274         }\r
9275     }\r
9276 \r
9277     if (fenMode) {\r
9278         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
9279             DisplayError(_("Bad FEN position in file"), 0);\r
9280             return FALSE;\r
9281         }\r
9282     } else {\r
9283         (void) fgets(line, MSG_SIZ, f);\r
9284         (void) fgets(line, MSG_SIZ, f);\r
9285     \r
9286         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
9287             (void) fgets(line, MSG_SIZ, f);\r
9288             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
9289                 if (*p == ' ')\r
9290                   continue;\r
9291                 initial_position[i][j++] = CharToPiece(*p);\r
9292             }\r
9293         }\r
9294     \r
9295         blackPlaysFirst = FALSE;\r
9296         if (!feof(f)) {\r
9297             (void) fgets(line, MSG_SIZ, f);\r
9298             if (strncmp(line, "black", strlen("black"))==0)\r
9299               blackPlaysFirst = TRUE;\r
9300         }\r
9301     }\r
9302     startedFromSetupPosition = TRUE;\r
9303     \r
9304     SendToProgram("force\n", &first);\r
9305     CopyBoard(boards[0], initial_position);\r
9306     if (blackPlaysFirst) {\r
9307         currentMove = forwardMostMove = backwardMostMove = 1;\r
9308         strcpy(moveList[0], "");\r
9309         strcpy(parseList[0], "");\r
9310         CopyBoard(boards[1], initial_position);\r
9311         DisplayMessage("", _("Black to play"));\r
9312     } else {\r
9313         currentMove = forwardMostMove = backwardMostMove = 0;\r
9314         DisplayMessage("", _("White to play"));\r
9315     }\r
9316           /* [HGM] copy FEN attributes as well */\r
9317           {   int i;\r
9318               initialRulePlies = FENrulePlies;\r
9319               epStatus[forwardMostMove] = FENepStatus;\r
9320               for( i=0; i< nrCastlingRights; i++ )\r
9321                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
9322           }\r
9323     SendBoard(&first, forwardMostMove);\r
9324     if (appData.debugMode) {\r
9325 int i, j;\r
9326   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}\r
9327   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");\r
9328         fprintf(debugFP, "Load Position\n");\r
9329     }\r
9330 \r
9331     if (positionNumber > 1) {\r
9332         sprintf(line, "%s %d", title, positionNumber);\r
9333         DisplayTitle(line);\r
9334     } else {\r
9335         DisplayTitle(title);\r
9336     }\r
9337     gameMode = EditGame;\r
9338     ModeHighlight();\r
9339     ResetClocks();\r
9340     timeRemaining[0][1] = whiteTimeRemaining;\r
9341     timeRemaining[1][1] = blackTimeRemaining;\r
9342     DrawPosition(FALSE, boards[currentMove]);\r
9343    \r
9344     return TRUE;\r
9345 }\r
9346 \r
9347 \r
9348 void\r
9349 CopyPlayerNameIntoFileName(dest, src)\r
9350      char **dest, *src;\r
9351 {\r
9352     while (*src != NULLCHAR && *src != ',') {\r
9353         if (*src == ' ') {\r
9354             *(*dest)++ = '_';\r
9355             src++;\r
9356         } else {\r
9357             *(*dest)++ = *src++;\r
9358         }\r
9359     }\r
9360 }\r
9361 \r
9362 char *DefaultFileName(ext)\r
9363      char *ext;\r
9364 {\r
9365     static char def[MSG_SIZ];\r
9366     char *p;\r
9367 \r
9368     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
9369         p = def;\r
9370         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
9371         *p++ = '-';\r
9372         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
9373         *p++ = '.';\r
9374         strcpy(p, ext);\r
9375     } else {\r
9376         def[0] = NULLCHAR;\r
9377     }\r
9378     return def;\r
9379 }\r
9380 \r
9381 /* Save the current game to the given file */\r
9382 int\r
9383 SaveGameToFile(filename, append)\r
9384      char *filename;\r
9385      int append;\r
9386 {\r
9387     FILE *f;\r
9388     char buf[MSG_SIZ];\r
9389 \r
9390     if (strcmp(filename, "-") == 0) {\r
9391         return SaveGame(stdout, 0, NULL);\r
9392     } else {\r
9393         f = fopen(filename, append ? "a" : "w");\r
9394         if (f == NULL) {\r
9395             sprintf(buf, _("Can't open \"%s\""), filename);\r
9396             DisplayError(buf, errno);\r
9397             return FALSE;\r
9398         } else {\r
9399             return SaveGame(f, 0, NULL);\r
9400         }\r
9401     }\r
9402 }\r
9403 \r
9404 char *\r
9405 SavePart(str)\r
9406      char *str;\r
9407 {\r
9408     static char buf[MSG_SIZ];\r
9409     char *p;\r
9410     \r
9411     p = strchr(str, ' ');\r
9412     if (p == NULL) return str;\r
9413     strncpy(buf, str, p - str);\r
9414     buf[p - str] = NULLCHAR;\r
9415     return buf;\r
9416 }\r
9417 \r
9418 #define PGN_MAX_LINE 75\r
9419 \r
9420 #define PGN_SIDE_WHITE  0\r
9421 #define PGN_SIDE_BLACK  1\r
9422 \r
9423 /* [AS] */\r
9424 static int FindFirstMoveOutOfBook( int side )\r
9425 {\r
9426     int result = -1;\r
9427 \r
9428     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
9429         int index = backwardMostMove;\r
9430         int has_book_hit = 0;\r
9431 \r
9432         if( (index % 2) != side ) {\r
9433             index++;\r
9434         }\r
9435 \r
9436         while( index < forwardMostMove ) {\r
9437             /* Check to see if engine is in book */\r
9438             int depth = pvInfoList[index].depth;\r
9439             int score = pvInfoList[index].score;\r
9440             int in_book = 0;\r
9441 \r
9442             if( depth <= 2 ) {\r
9443                 in_book = 1;\r
9444             }\r
9445             else if( score == 0 && depth == 63 ) {\r
9446                 in_book = 1; /* Zappa */\r
9447             }\r
9448             else if( score == 2 && depth == 99 ) {\r
9449                 in_book = 1; /* Abrok */\r
9450             }\r
9451 \r
9452             has_book_hit += in_book;\r
9453 \r
9454             if( ! in_book ) {\r
9455                 result = index;\r
9456 \r
9457                 break;\r
9458             }\r
9459 \r
9460             index += 2;\r
9461         }\r
9462     }\r
9463 \r
9464     return result;\r
9465 }\r
9466 \r
9467 /* [AS] */\r
9468 void GetOutOfBookInfo( char * buf )\r
9469 {\r
9470     int oob[2];\r
9471     int i;\r
9472     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9473 \r
9474     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
9475     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
9476 \r
9477     *buf = '\0';\r
9478 \r
9479     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
9480         for( i=0; i<2; i++ ) {\r
9481             int idx = oob[i];\r
9482 \r
9483             if( idx >= 0 ) {\r
9484                 if( i > 0 && oob[0] >= 0 ) {\r
9485                     strcat( buf, "   " );\r
9486                 }\r
9487 \r
9488                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
9489                 sprintf( buf+strlen(buf), "%s%.2f", \r
9490                     pvInfoList[idx].score >= 0 ? "+" : "",\r
9491                     pvInfoList[idx].score / 100.0 );\r
9492             }\r
9493         }\r
9494     }\r
9495 }\r
9496 \r
9497 /* Save game in PGN style and close the file */\r
9498 int\r
9499 SaveGamePGN(f)\r
9500      FILE *f;\r
9501 {\r
9502     int i, offset, linelen, newblock;\r
9503     time_t tm;\r
9504     char *movetext;\r
9505     char numtext[32];\r
9506     int movelen, numlen, blank;\r
9507     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
9508 \r
9509     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9510     \r
9511     tm = time((time_t *) NULL);\r
9512     \r
9513     PrintPGNTags(f, &gameInfo);\r
9514     \r
9515     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9516         char *fen = PositionToFEN(backwardMostMove, 1);\r
9517         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
9518         fprintf(f, "\n{--------------\n");\r
9519         PrintPosition(f, backwardMostMove);\r
9520         fprintf(f, "--------------}\n");\r
9521         free(fen);\r
9522     }\r
9523     else {\r
9524         /* [AS] Out of book annotation */\r
9525         if( appData.saveOutOfBookInfo ) {\r
9526             char buf[64];\r
9527 \r
9528             GetOutOfBookInfo( buf );\r
9529 \r
9530             if( buf[0] != '\0' ) {\r
9531                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
9532             }\r
9533         }\r
9534 \r
9535         fprintf(f, "\n");\r
9536     }\r
9537 \r
9538     i = backwardMostMove;\r
9539     linelen = 0;\r
9540     newblock = TRUE;\r
9541 \r
9542     while (i < forwardMostMove) {\r
9543         /* Print comments preceding this move */\r
9544         if (commentList[i] != NULL) {\r
9545             if (linelen > 0) fprintf(f, "\n");\r
9546             fprintf(f, "{\n%s}\n", commentList[i]);\r
9547             linelen = 0;\r
9548             newblock = TRUE;\r
9549         }\r
9550 \r
9551         /* Format move number */\r
9552         if ((i % 2) == 0) {\r
9553             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
9554         } else {\r
9555             if (newblock) {\r
9556                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
9557             } else {\r
9558                 numtext[0] = NULLCHAR;\r
9559             }\r
9560         }\r
9561         numlen = strlen(numtext);\r
9562         newblock = FALSE;\r
9563 \r
9564         /* Print move number */\r
9565         blank = linelen > 0 && numlen > 0;\r
9566         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
9567             fprintf(f, "\n");\r
9568             linelen = 0;\r
9569             blank = 0;\r
9570         }\r
9571         if (blank) {\r
9572             fprintf(f, " ");\r
9573             linelen++;\r
9574         }\r
9575         fprintf(f, numtext);\r
9576         linelen += numlen;\r
9577 \r
9578         /* Get move */\r
9579         movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */\r
9580 \r
9581         /* Print move */\r
9582         blank = linelen > 0 && movelen > 0;\r
9583         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9584             fprintf(f, "\n");\r
9585             linelen = 0;\r
9586             blank = 0;\r
9587         }\r
9588         if (blank) {\r
9589             fprintf(f, " ");\r
9590             linelen++;\r
9591         }\r
9592         fprintf(f, parseList[i]);\r
9593         linelen += movelen;\r
9594 \r
9595         /* [AS] Add PV info if present */\r
9596         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9597             /* [HGM] add time */\r
9598             char buf[MSG_SIZ]; int seconds = 0;\r
9599 \r
9600 #if 0\r
9601             if(i >= backwardMostMove) {\r
9602                 if(WhiteOnMove(i))\r
9603                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]\r
9604                                   + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;\r
9605                 else\r
9606                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]\r
9607                                   + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;\r
9608             }\r
9609             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest\r
9610 #else\r
9611             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time\r
9612 #endif\r
9613     if (appData.debugMode,0) {\r
9614         fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",\r
9615                 timeRemaining[0][i+1], timeRemaining[0][i],\r
9616                      timeRemaining[1][i+1], timeRemaining[1][i], seconds\r
9617         );\r
9618     }\r
9619 \r
9620             if( seconds <= 0) buf[0] = 0; else\r
9621             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {\r
9622                 seconds = (seconds + 4)/10; // round to full seconds\r
9623                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else\r
9624                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
9625             }\r
9626 \r
9627             sprintf( move_buffer, "{%s%.2f/%d%s}", \r
9628                 pvInfoList[i].score >= 0 ? "+" : "",\r
9629                 pvInfoList[i].score / 100.0,\r
9630                 pvInfoList[i].depth,\r
9631                 buf );\r
9632 \r
9633             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */\r
9634 \r
9635             /* Print score/depth */\r
9636             blank = linelen > 0 && movelen > 0;\r
9637             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9638                 fprintf(f, "\n");\r
9639                 linelen = 0;\r
9640                 blank = 0;\r
9641             }\r
9642             if (blank) {\r
9643                 fprintf(f, " ");\r
9644                 linelen++;\r
9645             }\r
9646             fprintf(f, move_buffer);\r
9647             linelen += movelen;\r
9648         }\r
9649 \r
9650         i++;\r
9651     }\r
9652     \r
9653     /* Start a new line */\r
9654     if (linelen > 0) fprintf(f, "\n");\r
9655 \r
9656     /* Print comments after last move */\r
9657     if (commentList[i] != NULL) {\r
9658         fprintf(f, "{\n%s}\n", commentList[i]);\r
9659     }\r
9660 \r
9661     /* Print result */\r
9662     if (gameInfo.resultDetails != NULL &&\r
9663         gameInfo.resultDetails[0] != NULLCHAR) {\r
9664         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
9665                 PGNResult(gameInfo.result));\r
9666     } else {\r
9667         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9668     }\r
9669 \r
9670     fclose(f);\r
9671     return TRUE;\r
9672 }\r
9673 \r
9674 /* Save game in old style and close the file */\r
9675 int\r
9676 SaveGameOldStyle(f)\r
9677      FILE *f;\r
9678 {\r
9679     int i, offset;\r
9680     time_t tm;\r
9681     \r
9682     tm = time((time_t *) NULL);\r
9683     \r
9684     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
9685     PrintOpponents(f);\r
9686     \r
9687     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9688         fprintf(f, "\n[--------------\n");\r
9689         PrintPosition(f, backwardMostMove);\r
9690         fprintf(f, "--------------]\n");\r
9691     } else {\r
9692         fprintf(f, "\n");\r
9693     }\r
9694 \r
9695     i = backwardMostMove;\r
9696     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9697 \r
9698     while (i < forwardMostMove) {\r
9699         if (commentList[i] != NULL) {\r
9700             fprintf(f, "[%s]\n", commentList[i]);\r
9701         }\r
9702 \r
9703         if ((i % 2) == 1) {\r
9704             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
9705             i++;\r
9706         } else {\r
9707             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
9708             i++;\r
9709             if (commentList[i] != NULL) {\r
9710                 fprintf(f, "\n");\r
9711                 continue;\r
9712             }\r
9713             if (i >= forwardMostMove) {\r
9714                 fprintf(f, "\n");\r
9715                 break;\r
9716             }\r
9717             fprintf(f, "%s\n", parseList[i]);\r
9718             i++;\r
9719         }\r
9720     }\r
9721     \r
9722     if (commentList[i] != NULL) {\r
9723         fprintf(f, "[%s]\n", commentList[i]);\r
9724     }\r
9725 \r
9726     /* This isn't really the old style, but it's close enough */\r
9727     if (gameInfo.resultDetails != NULL &&\r
9728         gameInfo.resultDetails[0] != NULLCHAR) {\r
9729         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
9730                 gameInfo.resultDetails);\r
9731     } else {\r
9732         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9733     }\r
9734 \r
9735     fclose(f);\r
9736     return TRUE;\r
9737 }\r
9738 \r
9739 /* Save the current game to open file f and close the file */\r
9740 int\r
9741 SaveGame(f, dummy, dummy2)\r
9742      FILE *f;\r
9743      int dummy;\r
9744      char *dummy2;\r
9745 {\r
9746     if (gameMode == EditPosition) EditPositionDone();\r
9747     if (appData.oldSaveStyle)\r
9748       return SaveGameOldStyle(f);\r
9749     else\r
9750       return SaveGamePGN(f);\r
9751 }\r
9752 \r
9753 /* Save the current position to the given file */\r
9754 int\r
9755 SavePositionToFile(filename)\r
9756      char *filename;\r
9757 {\r
9758     FILE *f;\r
9759     char buf[MSG_SIZ];\r
9760 \r
9761     if (strcmp(filename, "-") == 0) {\r
9762         return SavePosition(stdout, 0, NULL);\r
9763     } else {\r
9764         f = fopen(filename, "a");\r
9765         if (f == NULL) {\r
9766             sprintf(buf, _("Can't open \"%s\""), filename);\r
9767             DisplayError(buf, errno);\r
9768             return FALSE;\r
9769         } else {\r
9770             SavePosition(f, 0, NULL);\r
9771             return TRUE;\r
9772         }\r
9773     }\r
9774 }\r
9775 \r
9776 /* Save the current position to the given open file and close the file */\r
9777 int\r
9778 SavePosition(f, dummy, dummy2)\r
9779      FILE *f;\r
9780      int dummy;\r
9781      char *dummy2;\r
9782 {\r
9783     time_t tm;\r
9784     char *fen;\r
9785     \r
9786     if (appData.oldSaveStyle) {\r
9787         tm = time((time_t *) NULL);\r
9788     \r
9789         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
9790         PrintOpponents(f);\r
9791         fprintf(f, "[--------------\n");\r
9792         PrintPosition(f, currentMove);\r
9793         fprintf(f, "--------------]\n");\r
9794     } else {\r
9795         fen = PositionToFEN(currentMove, 1);\r
9796         fprintf(f, "%s\n", fen);\r
9797         free(fen);\r
9798     }\r
9799     fclose(f);\r
9800     return TRUE;\r
9801 }\r
9802 \r
9803 void\r
9804 ReloadCmailMsgEvent(unregister)\r
9805      int unregister;\r
9806 {\r
9807 #if !WIN32\r
9808     static char *inFilename = NULL;\r
9809     static char *outFilename;\r
9810     int i;\r
9811     struct stat inbuf, outbuf;\r
9812     int status;\r
9813     \r
9814     /* Any registered moves are unregistered if unregister is set, */\r
9815     /* i.e. invoked by the signal handler */\r
9816     if (unregister) {\r
9817         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9818             cmailMoveRegistered[i] = FALSE;\r
9819             if (cmailCommentList[i] != NULL) {\r
9820                 free(cmailCommentList[i]);\r
9821                 cmailCommentList[i] = NULL;\r
9822             }\r
9823         }\r
9824         nCmailMovesRegistered = 0;\r
9825     }\r
9826 \r
9827     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9828         cmailResult[i] = CMAIL_NOT_RESULT;\r
9829     }\r
9830     nCmailResults = 0;\r
9831 \r
9832     if (inFilename == NULL) {\r
9833         /* Because the filenames are static they only get malloced once  */\r
9834         /* and they never get freed                                      */\r
9835         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
9836         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
9837 \r
9838         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
9839         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
9840     }\r
9841     \r
9842     status = stat(outFilename, &outbuf);\r
9843     if (status < 0) {\r
9844         cmailMailedMove = FALSE;\r
9845     } else {\r
9846         status = stat(inFilename, &inbuf);\r
9847         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
9848     }\r
9849     \r
9850     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
9851        counts the games, notes how each one terminated, etc.\r
9852        \r
9853        It would be nice to remove this kludge and instead gather all\r
9854        the information while building the game list.  (And to keep it\r
9855        in the game list nodes instead of having a bunch of fixed-size\r
9856        parallel arrays.)  Note this will require getting each game's\r
9857        termination from the PGN tags, as the game list builder does\r
9858        not process the game moves.  --mann\r
9859        */\r
9860     cmailMsgLoaded = TRUE;\r
9861     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
9862     \r
9863     /* Load first game in the file or popup game menu */\r
9864     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
9865 \r
9866 #endif /* !WIN32 */\r
9867     return;\r
9868 }\r
9869 \r
9870 int\r
9871 RegisterMove()\r
9872 {\r
9873     FILE *f;\r
9874     char string[MSG_SIZ];\r
9875 \r
9876     if (   cmailMailedMove\r
9877         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9878         return TRUE;            /* Allow free viewing  */\r
9879     }\r
9880 \r
9881     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9882     /* with the move registered when the conditions for registering no   */\r
9883     /* longer hold                                                       */\r
9884     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9885         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9886         nCmailMovesRegistered --;\r
9887 \r
9888         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9889           {\r
9890               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9891               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9892           }\r
9893     }\r
9894 \r
9895     if (cmailOldMove == -1) {\r
9896         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);\r
9897         return FALSE;\r
9898     }\r
9899 \r
9900     if (currentMove > cmailOldMove + 1) {\r
9901         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);\r
9902         return FALSE;\r
9903     }\r
9904 \r
9905     if (currentMove < cmailOldMove) {\r
9906         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);\r
9907         return FALSE;\r
9908     }\r
9909 \r
9910     if (forwardMostMove > currentMove) {\r
9911         /* Silently truncate extra moves */\r
9912         TruncateGame();\r
9913     }\r
9914 \r
9915     if (   (currentMove == cmailOldMove + 1)\r
9916         || (   (currentMove == cmailOldMove)\r
9917             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9918                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9919         if (gameInfo.result != GameUnfinished) {\r
9920             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9921         }\r
9922 \r
9923         if (commentList[currentMove] != NULL) {\r
9924             cmailCommentList[lastLoadGameNumber - 1]\r
9925               = StrSave(commentList[currentMove]);\r
9926         }\r
9927         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9928 \r
9929         if (appData.debugMode)\r
9930           fprintf(debugFP, "Saving %s for game %d\n",\r
9931                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9932 \r
9933         sprintf(string,\r
9934                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
9935         \r
9936         f = fopen(string, "w");\r
9937         if (appData.oldSaveStyle) {\r
9938             SaveGameOldStyle(f); /* also closes the file */\r
9939             \r
9940             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
9941             f = fopen(string, "w");\r
9942             SavePosition(f, 0, NULL); /* also closes the file */\r
9943         } else {\r
9944             fprintf(f, "{--------------\n");\r
9945             PrintPosition(f, currentMove);\r
9946             fprintf(f, "--------------}\n\n");\r
9947             \r
9948             SaveGame(f, 0, NULL); /* also closes the file*/\r
9949         }\r
9950         \r
9951         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
9952         nCmailMovesRegistered ++;\r
9953     } else if (nCmailGames == 1) {\r
9954         DisplayError(_("You have not made a move yet"), 0);\r
9955         return FALSE;\r
9956     }\r
9957 \r
9958     return TRUE;\r
9959 }\r
9960 \r
9961 void\r
9962 MailMoveEvent()\r
9963 {\r
9964 #if !WIN32\r
9965     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
9966     FILE *commandOutput;\r
9967     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
9968     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
9969     int nBuffers;\r
9970     int i;\r
9971     int archived;\r
9972     char *arcDir;\r
9973 \r
9974     if (! cmailMsgLoaded) {\r
9975         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);\r
9976         return;\r
9977     }\r
9978 \r
9979     if (nCmailGames == nCmailResults) {\r
9980         DisplayError(_("No unfinished games"), 0);\r
9981         return;\r
9982     }\r
9983 \r
9984 #if CMAIL_PROHIBIT_REMAIL\r
9985     if (cmailMailedMove) {\r
9986         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
9987         DisplayError(msg, 0);\r
9988         return;\r
9989     }\r
9990 #endif\r
9991 \r
9992     if (! (cmailMailedMove || RegisterMove())) return;\r
9993     \r
9994     if (   cmailMailedMove\r
9995         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
9996         sprintf(string, partCommandString,\r
9997                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
9998         commandOutput = popen(string, "r");\r
9999 \r
10000         if (commandOutput == NULL) {\r
10001             DisplayError(_("Failed to invoke cmail"), 0);\r
10002         } else {\r
10003             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
10004                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
10005             }\r
10006             if (nBuffers > 1) {\r
10007                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
10008                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
10009                 nBytes = MSG_SIZ - 1;\r
10010             } else {\r
10011                 (void) memcpy(msg, buffer, nBytes);\r
10012             }\r
10013             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
10014 \r
10015             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
10016                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
10017 \r
10018                 archived = TRUE;\r
10019                 for (i = 0; i < nCmailGames; i ++) {\r
10020                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
10021                         archived = FALSE;\r
10022                     }\r
10023                 }\r
10024                 if (   archived\r
10025                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
10026                         != NULL)) {\r
10027                     sprintf(buffer, "%s/%s.%s.archive",\r
10028                             arcDir,\r
10029                             appData.cmailGameName,\r
10030                             gameInfo.date);\r
10031                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
10032                     cmailMsgLoaded = FALSE;\r
10033                 }\r
10034             }\r
10035 \r
10036             DisplayInformation(msg);\r
10037             pclose(commandOutput);\r
10038         }\r
10039     } else {\r
10040         if ((*cmailMsg) != '\0') {\r
10041             DisplayInformation(cmailMsg);\r
10042         }\r
10043     }\r
10044 \r
10045     return;\r
10046 #endif /* !WIN32 */\r
10047 }\r
10048 \r
10049 char *\r
10050 CmailMsg()\r
10051 {\r
10052 #if WIN32\r
10053     return NULL;\r
10054 #else\r
10055     int  prependComma = 0;\r
10056     char number[5];\r
10057     char string[MSG_SIZ];       /* Space for game-list */\r
10058     int  i;\r
10059     \r
10060     if (!cmailMsgLoaded) return "";\r
10061 \r
10062     if (cmailMailedMove) {\r
10063         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));\r
10064     } else {\r
10065         /* Create a list of games left */\r
10066         sprintf(string, "[");\r
10067         for (i = 0; i < nCmailGames; i ++) {\r
10068             if (! (   cmailMoveRegistered[i]\r
10069                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
10070                 if (prependComma) {\r
10071                     sprintf(number, ",%d", i + 1);\r
10072                 } else {\r
10073                     sprintf(number, "%d", i + 1);\r
10074                     prependComma = 1;\r
10075                 }\r
10076                 \r
10077                 strcat(string, number);\r
10078             }\r
10079         }\r
10080         strcat(string, "]");\r
10081 \r
10082         if (nCmailMovesRegistered + nCmailResults == 0) {\r
10083             switch (nCmailGames) {\r
10084               case 1:\r
10085                 sprintf(cmailMsg,\r
10086                         _("Still need to make move for game\n"));\r
10087                 break;\r
10088                 \r
10089               case 2:\r
10090                 sprintf(cmailMsg,\r
10091                         _("Still need to make moves for both games\n"));\r
10092                 break;\r
10093                 \r
10094               default:\r
10095                 sprintf(cmailMsg,\r
10096                         _("Still need to make moves for all %d games\n"),\r
10097                         nCmailGames);\r
10098                 break;\r
10099             }\r
10100         } else {\r
10101             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
10102               case 1:\r
10103                 sprintf(cmailMsg,\r
10104                         _("Still need to make a move for game %s\n"),\r
10105                         string);\r
10106                 break;\r
10107                 \r
10108               case 0:\r
10109                 if (nCmailResults == nCmailGames) {\r
10110                     sprintf(cmailMsg, _("No unfinished games\n"));\r
10111                 } else {\r
10112                     sprintf(cmailMsg, _("Ready to send mail\n"));\r
10113                 }\r
10114                 break;\r
10115                 \r
10116               default:\r
10117                 sprintf(cmailMsg,\r
10118                         _("Still need to make moves for games %s\n"),\r
10119                         string);\r
10120             }\r
10121         }\r
10122     }\r
10123     return cmailMsg;\r
10124 #endif /* WIN32 */\r
10125 }\r
10126 \r
10127 void\r
10128 ResetGameEvent()\r
10129 {\r
10130     if (gameMode == Training)\r
10131       SetTrainingModeOff();\r
10132 \r
10133     Reset(TRUE, TRUE);\r
10134     cmailMsgLoaded = FALSE;\r
10135     if (appData.icsActive) {\r
10136       SendToICS(ics_prefix);\r
10137       SendToICS("refresh\n");\r
10138     }\r
10139 }\r
10140 \r
10141 void\r
10142 ExitEvent(status)\r
10143      int status;\r
10144 {\r
10145     exiting++;\r
10146     if (exiting > 2) {\r
10147       /* Give up on clean exit */\r
10148       exit(status);\r
10149     }\r
10150     if (exiting > 1) {\r
10151       /* Keep trying for clean exit */\r
10152       return;\r
10153     }\r
10154 \r
10155     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
10156 \r
10157     if (telnetISR != NULL) {\r
10158       RemoveInputSource(telnetISR);\r
10159     }\r
10160     if (icsPR != NoProc) {\r
10161       DestroyChildProcess(icsPR, TRUE);\r
10162     }\r
10163 #if 0\r
10164     /* Save game if resource set and not already saved by GameEnds() */\r
10165     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
10166                              && forwardMostMove > 0) {\r
10167       if (*appData.saveGameFile != NULLCHAR) {\r
10168         SaveGameToFile(appData.saveGameFile, TRUE);\r
10169       } else if (appData.autoSaveGames) {\r
10170         AutoSaveGame();\r
10171       }\r
10172       if (*appData.savePositionFile != NULLCHAR) {\r
10173         SavePositionToFile(appData.savePositionFile);\r
10174       }\r
10175     }\r
10176     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10177 #else\r
10178     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
10179     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);\r
10180 #endif\r
10181     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
10182     /* make sure this other one finishes before killing it!                  */\r
10183     if(endingGame) { int count = 0;\r
10184         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
10185         while(endingGame && count++ < 10) DoSleep(1);\r
10186         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
10187     }\r
10188 \r
10189     /* Kill off chess programs */\r
10190     if (first.pr != NoProc) {\r
10191         ExitAnalyzeMode();\r
10192         \r
10193         DoSleep( appData.delayBeforeQuit );\r
10194         SendToProgram("quit\n", &first);\r
10195         DoSleep( appData.delayAfterQuit );\r
10196         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
10197     }\r
10198     if (second.pr != NoProc) {\r
10199         DoSleep( appData.delayBeforeQuit );\r
10200         SendToProgram("quit\n", &second);\r
10201         DoSleep( appData.delayAfterQuit );\r
10202         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
10203     }\r
10204     if (first.isr != NULL) {\r
10205         RemoveInputSource(first.isr);\r
10206     }\r
10207     if (second.isr != NULL) {\r
10208         RemoveInputSource(second.isr);\r
10209     }\r
10210 \r
10211     ShutDownFrontEnd();\r
10212     exit(status);\r
10213 }\r
10214 \r
10215 void\r
10216 PauseEvent()\r
10217 {\r
10218     if (appData.debugMode)\r
10219         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
10220     if (pausing) {\r
10221         pausing = FALSE;\r
10222         ModeHighlight();\r
10223         if (gameMode == MachinePlaysWhite ||\r
10224             gameMode == MachinePlaysBlack) {\r
10225             StartClocks();\r
10226         } else {\r
10227             DisplayBothClocks();\r
10228         }\r
10229         if (gameMode == PlayFromGameFile) {\r
10230             if (appData.timeDelay >= 0) \r
10231                 AutoPlayGameLoop();\r
10232         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
10233             Reset(FALSE, TRUE);\r
10234             SendToICS(ics_prefix);\r
10235             SendToICS("refresh\n");\r
10236         } else if (currentMove < forwardMostMove) {\r
10237             ForwardInner(forwardMostMove);\r
10238         }\r
10239         pauseExamInvalid = FALSE;\r
10240     } else {\r
10241         switch (gameMode) {\r
10242           default:\r
10243             return;\r
10244           case IcsExamining:\r
10245             pauseExamForwardMostMove = forwardMostMove;\r
10246             pauseExamInvalid = FALSE;\r
10247             /* fall through */\r
10248           case IcsObserving:\r
10249           case IcsPlayingWhite:\r
10250           case IcsPlayingBlack:\r
10251             pausing = TRUE;\r
10252             ModeHighlight();\r
10253             return;\r
10254           case PlayFromGameFile:\r
10255             (void) StopLoadGameTimer();\r
10256             pausing = TRUE;\r
10257             ModeHighlight();\r
10258             break;\r
10259           case BeginningOfGame:\r
10260             if (appData.icsActive) return;\r
10261             /* else fall through */\r
10262           case MachinePlaysWhite:\r
10263           case MachinePlaysBlack:\r
10264           case TwoMachinesPlay:\r
10265             if (forwardMostMove == 0)\r
10266               return;           /* don't pause if no one has moved */\r
10267             if ((gameMode == MachinePlaysWhite &&\r
10268                  !WhiteOnMove(forwardMostMove)) ||\r
10269                 (gameMode == MachinePlaysBlack &&\r
10270                  WhiteOnMove(forwardMostMove))) {\r
10271                 StopClocks();\r
10272             }\r
10273             pausing = TRUE;\r
10274             ModeHighlight();\r
10275             break;\r
10276         }\r
10277     }\r
10278 }\r
10279 \r
10280 void\r
10281 EditCommentEvent()\r
10282 {\r
10283     char title[MSG_SIZ];\r
10284 \r
10285     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
10286         strcpy(title, _("Edit comment"));\r
10287     } else {\r
10288         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,\r
10289                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
10290                 parseList[currentMove - 1]);\r
10291     }\r
10292 \r
10293     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
10294 }\r
10295 \r
10296 \r
10297 void\r
10298 EditTagsEvent()\r
10299 {\r
10300     char *tags = PGNTags(&gameInfo);\r
10301     EditTagsPopUp(tags);\r
10302     free(tags);\r
10303 }\r
10304 \r
10305 void\r
10306 AnalyzeModeEvent()\r
10307 {\r
10308     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
10309       return;\r
10310 \r
10311     if (gameMode != AnalyzeFile) {\r
10312         if (!appData.icsEngineAnalyze) {\r
10313                EditGameEvent();\r
10314                if (gameMode != EditGame) return;\r
10315         }\r
10316         ResurrectChessProgram();\r
10317         SendToProgram("analyze\n", &first);\r
10318         first.analyzing = TRUE;\r
10319         /*first.maybeThinking = TRUE;*/\r
10320         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10321         AnalysisPopUp(_("Analysis"),\r
10322                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10323     }\r
10324     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;\r
10325     pausing = FALSE;\r
10326     ModeHighlight();\r
10327     SetGameInfo();\r
10328 \r
10329     StartAnalysisClock();\r
10330     GetTimeMark(&lastNodeCountTime);\r
10331     lastNodeCount = 0;\r
10332 }\r
10333 \r
10334 void\r
10335 AnalyzeFileEvent()\r
10336 {\r
10337     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
10338       return;\r
10339 \r
10340     if (gameMode != AnalyzeMode) {\r
10341         EditGameEvent();\r
10342         if (gameMode != EditGame) return;\r
10343         ResurrectChessProgram();\r
10344         SendToProgram("analyze\n", &first);\r
10345         first.analyzing = TRUE;\r
10346         /*first.maybeThinking = TRUE;*/\r
10347         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10348         AnalysisPopUp(_("Analysis"),\r
10349                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10350     }\r
10351     gameMode = AnalyzeFile;\r
10352     pausing = FALSE;\r
10353     ModeHighlight();\r
10354     SetGameInfo();\r
10355 \r
10356     StartAnalysisClock();\r
10357     GetTimeMark(&lastNodeCountTime);\r
10358     lastNodeCount = 0;\r
10359 }\r
10360 \r
10361 void\r
10362 MachineWhiteEvent()\r
10363 {\r
10364     char buf[MSG_SIZ];\r
10365     char *bookHit = NULL;\r
10366 \r
10367     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
10368       return;\r
10369 \r
10370 \r
10371     if (gameMode == PlayFromGameFile || \r
10372         gameMode == TwoMachinesPlay  || \r
10373         gameMode == Training         || \r
10374         gameMode == AnalyzeMode      || \r
10375         gameMode == EndOfGame)\r
10376         EditGameEvent();\r
10377 \r
10378     if (gameMode == EditPosition) \r
10379         EditPositionDone();\r
10380 \r
10381     if (!WhiteOnMove(currentMove)) {\r
10382         DisplayError(_("It is not White's turn"), 0);\r
10383         return;\r
10384     }\r
10385   \r
10386     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10387       ExitAnalyzeMode();\r
10388 \r
10389     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10390         gameMode == AnalyzeFile)\r
10391         TruncateGame();\r
10392 \r
10393     ResurrectChessProgram();    /* in case it isn't running */\r
10394     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */\r
10395         gameMode = MachinePlaysWhite;\r
10396         ResetClocks();\r
10397     } else\r
10398     gameMode = MachinePlaysWhite;\r
10399     pausing = FALSE;\r
10400     ModeHighlight();\r
10401     SetGameInfo();\r
10402     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10403     DisplayTitle(buf);\r
10404     if (first.sendName) {\r
10405       sprintf(buf, "name %s\n", gameInfo.black);\r
10406       SendToProgram(buf, &first);\r
10407     }\r
10408     if (first.sendTime) {\r
10409       if (first.useColors) {\r
10410         SendToProgram("black\n", &first); /*gnu kludge*/\r
10411       }\r
10412       SendTimeRemaining(&first, TRUE);\r
10413     }\r
10414     if (first.useColors) {\r
10415       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately\r
10416     }\r
10417     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10418     SetMachineThinkingEnables();\r
10419     first.maybeThinking = TRUE;\r
10420     StartClocks();\r
10421 \r
10422     if (appData.autoFlipView && !flipView) {\r
10423       flipView = !flipView;\r
10424       DrawPosition(FALSE, NULL);\r
10425       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10426     }\r
10427 \r
10428     if(bookHit) { // [HGM] book: simulate book reply\r
10429         static char bookMove[MSG_SIZ]; // a bit generous?\r
10430 \r
10431         programStats.depth = programStats.nodes = programStats.time = \r
10432         programStats.score = programStats.got_only_move = 0;\r
10433         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10434 \r
10435         strcpy(bookMove, "move ");\r
10436         strcat(bookMove, bookHit);\r
10437         HandleMachineMove(bookMove, &first);\r
10438     }\r
10439 }\r
10440 \r
10441 void\r
10442 MachineBlackEvent()\r
10443 {\r
10444     char buf[MSG_SIZ];\r
10445    char *bookHit = NULL;\r
10446 \r
10447     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
10448         return;\r
10449 \r
10450 \r
10451     if (gameMode == PlayFromGameFile || \r
10452         gameMode == TwoMachinesPlay  || \r
10453         gameMode == Training         || \r
10454         gameMode == AnalyzeMode      || \r
10455         gameMode == EndOfGame)\r
10456         EditGameEvent();\r
10457 \r
10458     if (gameMode == EditPosition) \r
10459         EditPositionDone();\r
10460 \r
10461     if (WhiteOnMove(currentMove)) {\r
10462         DisplayError(_("It is not Black's turn"), 0);\r
10463         return;\r
10464     }\r
10465     \r
10466     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10467       ExitAnalyzeMode();\r
10468 \r
10469     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10470         gameMode == AnalyzeFile)\r
10471         TruncateGame();\r
10472 \r
10473     ResurrectChessProgram();    /* in case it isn't running */\r
10474     gameMode = MachinePlaysBlack;\r
10475     pausing = FALSE;\r
10476     ModeHighlight();\r
10477     SetGameInfo();\r
10478     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10479     DisplayTitle(buf);\r
10480     if (first.sendName) {\r
10481       sprintf(buf, "name %s\n", gameInfo.white);\r
10482       SendToProgram(buf, &first);\r
10483     }\r
10484     if (first.sendTime) {\r
10485       if (first.useColors) {\r
10486         SendToProgram("white\n", &first); /*gnu kludge*/\r
10487       }\r
10488       SendTimeRemaining(&first, FALSE);\r
10489     }\r
10490     if (first.useColors) {\r
10491       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately\r
10492     }\r
10493     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10494     SetMachineThinkingEnables();\r
10495     first.maybeThinking = TRUE;\r
10496     StartClocks();\r
10497 \r
10498     if (appData.autoFlipView && flipView) {\r
10499       flipView = !flipView;\r
10500       DrawPosition(FALSE, NULL);\r
10501       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10502     }\r
10503     if(bookHit) { // [HGM] book: simulate book reply\r
10504         static char bookMove[MSG_SIZ]; // a bit generous?\r
10505 \r
10506         programStats.depth = programStats.nodes = programStats.time = \r
10507         programStats.score = programStats.got_only_move = 0;\r
10508         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10509 \r
10510         strcpy(bookMove, "move ");\r
10511         strcat(bookMove, bookHit);\r
10512         HandleMachineMove(bookMove, &first);\r
10513     }\r
10514 }\r
10515 \r
10516 \r
10517 void\r
10518 DisplayTwoMachinesTitle()\r
10519 {\r
10520     char buf[MSG_SIZ];\r
10521     if (appData.matchGames > 0) {\r
10522         if (first.twoMachinesColor[0] == 'w') {\r
10523             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10524                     gameInfo.white, gameInfo.black,\r
10525                     first.matchWins, second.matchWins,\r
10526                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10527         } else {\r
10528             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10529                     gameInfo.white, gameInfo.black,\r
10530                     second.matchWins, first.matchWins,\r
10531                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10532         }\r
10533     } else {\r
10534         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10535     }\r
10536     DisplayTitle(buf);\r
10537 }\r
10538 \r
10539 void\r
10540 TwoMachinesEvent P((void))\r
10541 {\r
10542     int i;\r
10543     char buf[MSG_SIZ];\r
10544     ChessProgramState *onmove;\r
10545     char *bookHit = NULL;\r
10546     \r
10547     if (appData.noChessProgram) return;\r
10548 \r
10549     switch (gameMode) {\r
10550       case TwoMachinesPlay:\r
10551         return;\r
10552       case MachinePlaysWhite:\r
10553       case MachinePlaysBlack:\r
10554         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10555             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
10556             return;\r
10557         }\r
10558         /* fall through */\r
10559       case BeginningOfGame:\r
10560       case PlayFromGameFile:\r
10561       case EndOfGame:\r
10562         EditGameEvent();\r
10563         if (gameMode != EditGame) return;\r
10564         break;\r
10565       case EditPosition:\r
10566         EditPositionDone();\r
10567         break;\r
10568       case AnalyzeMode:\r
10569       case AnalyzeFile:\r
10570         ExitAnalyzeMode();\r
10571         break;\r
10572       case EditGame:\r
10573       default:\r
10574         break;\r
10575     }\r
10576 \r
10577     forwardMostMove = currentMove;\r
10578     ResurrectChessProgram();    /* in case first program isn't running */\r
10579 \r
10580     if (second.pr == NULL) {\r
10581         StartChessProgram(&second);\r
10582         if (second.protocolVersion == 1) {\r
10583           TwoMachinesEventIfReady();\r
10584         } else {\r
10585           /* kludge: allow timeout for initial "feature" command */\r
10586           FreezeUI();\r
10587           DisplayMessage("", _("Starting second chess program"));\r
10588           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
10589         }\r
10590         return;\r
10591     }\r
10592     DisplayMessage("", "");\r
10593     InitChessProgram(&second, FALSE);\r
10594     SendToProgram("force\n", &second);\r
10595     if (startedFromSetupPosition) {\r
10596         SendBoard(&second, backwardMostMove);\r
10597     if (appData.debugMode) {\r
10598         fprintf(debugFP, "Two Machines\n");\r
10599     }\r
10600     }\r
10601     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
10602         SendMoveToProgram(i, &second);\r
10603     }\r
10604 \r
10605     gameMode = TwoMachinesPlay;\r
10606     pausing = FALSE;\r
10607     ModeHighlight();\r
10608     SetGameInfo();\r
10609     DisplayTwoMachinesTitle();\r
10610     firstMove = TRUE;\r
10611     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
10612         onmove = &first;\r
10613     } else {\r
10614         onmove = &second;\r
10615     }\r
10616 \r
10617     SendToProgram(first.computerString, &first);\r
10618     if (first.sendName) {\r
10619       sprintf(buf, "name %s\n", second.tidy);\r
10620       SendToProgram(buf, &first);\r
10621     }\r
10622     SendToProgram(second.computerString, &second);\r
10623     if (second.sendName) {\r
10624       sprintf(buf, "name %s\n", first.tidy);\r
10625       SendToProgram(buf, &second);\r
10626     }\r
10627 \r
10628     ResetClocks();\r
10629     if (!first.sendTime || !second.sendTime) {\r
10630         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10631         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10632     }\r
10633     if (onmove->sendTime) {\r
10634       if (onmove->useColors) {\r
10635         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
10636       }\r
10637       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
10638     }\r
10639     if (onmove->useColors) {\r
10640       SendToProgram(onmove->twoMachinesColor, onmove);\r
10641     }\r
10642     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move\r
10643 //    SendToProgram("go\n", onmove);\r
10644     onmove->maybeThinking = TRUE;\r
10645     SetMachineThinkingEnables();\r
10646 \r
10647     StartClocks();\r
10648 \r
10649     if(bookHit) { // [HGM] book: simulate book reply\r
10650         static char bookMove[MSG_SIZ]; // a bit generous?\r
10651 \r
10652         programStats.depth = programStats.nodes = programStats.time = \r
10653         programStats.score = programStats.got_only_move = 0;\r
10654         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10655 \r
10656         strcpy(bookMove, "move ");\r
10657         strcat(bookMove, bookHit);\r
10658         HandleMachineMove(bookMove, &first);\r
10659     }\r
10660 }\r
10661 \r
10662 void\r
10663 TrainingEvent()\r
10664 {\r
10665     if (gameMode == Training) {\r
10666       SetTrainingModeOff();\r
10667       gameMode = PlayFromGameFile;\r
10668       DisplayMessage("", _("Training mode off"));\r
10669     } else {\r
10670       gameMode = Training;\r
10671       animateTraining = appData.animate;\r
10672 \r
10673       /* make sure we are not already at the end of the game */\r
10674       if (currentMove < forwardMostMove) {\r
10675         SetTrainingModeOn();\r
10676         DisplayMessage("", _("Training mode on"));\r
10677       } else {\r
10678         gameMode = PlayFromGameFile;\r
10679         DisplayError(_("Already at end of game"), 0);\r
10680       }\r
10681     }\r
10682     ModeHighlight();\r
10683 }\r
10684 \r
10685 void\r
10686 IcsClientEvent()\r
10687 {\r
10688     if (!appData.icsActive) return;\r
10689     switch (gameMode) {\r
10690       case IcsPlayingWhite:\r
10691       case IcsPlayingBlack:\r
10692       case IcsObserving:\r
10693       case IcsIdle:\r
10694       case BeginningOfGame:\r
10695       case IcsExamining:\r
10696         return;\r
10697 \r
10698       case EditGame:\r
10699         break;\r
10700 \r
10701       case EditPosition:\r
10702         EditPositionDone();\r
10703         break;\r
10704 \r
10705       case AnalyzeMode:\r
10706       case AnalyzeFile:\r
10707         ExitAnalyzeMode();\r
10708         break;\r
10709         \r
10710       default:\r
10711         EditGameEvent();\r
10712         break;\r
10713     }\r
10714 \r
10715     gameMode = IcsIdle;\r
10716     ModeHighlight();\r
10717     return;\r
10718 }\r
10719 \r
10720 \r
10721 void\r
10722 EditGameEvent()\r
10723 {\r
10724     int i;\r
10725 \r
10726     switch (gameMode) {\r
10727       case Training:\r
10728         SetTrainingModeOff();\r
10729         break;\r
10730       case MachinePlaysWhite:\r
10731       case MachinePlaysBlack:\r
10732       case BeginningOfGame:\r
10733         SendToProgram("force\n", &first);\r
10734         SetUserThinkingEnables();\r
10735         break;\r
10736       case PlayFromGameFile:\r
10737         (void) StopLoadGameTimer();\r
10738         if (gameFileFP != NULL) {\r
10739             gameFileFP = NULL;\r
10740         }\r
10741         break;\r
10742       case EditPosition:\r
10743         EditPositionDone();\r
10744         break;\r
10745       case AnalyzeMode:\r
10746       case AnalyzeFile:\r
10747         ExitAnalyzeMode();\r
10748         SendToProgram("force\n", &first);\r
10749         break;\r
10750       case TwoMachinesPlay:\r
10751         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10752         ResurrectChessProgram();\r
10753         SetUserThinkingEnables();\r
10754         break;\r
10755       case EndOfGame:\r
10756         ResurrectChessProgram();\r
10757         break;\r
10758       case IcsPlayingBlack:\r
10759       case IcsPlayingWhite:\r
10760         DisplayError(_("Warning: You are still playing a game"), 0);\r
10761         break;\r
10762       case IcsObserving:\r
10763         DisplayError(_("Warning: You are still observing a game"), 0);\r
10764         break;\r
10765       case IcsExamining:\r
10766         DisplayError(_("Warning: You are still examining a game"), 0);\r
10767         break;\r
10768       case IcsIdle:\r
10769         break;\r
10770       case EditGame:\r
10771       default:\r
10772         return;\r
10773     }\r
10774     \r
10775     pausing = FALSE;\r
10776     StopClocks();\r
10777     first.offeredDraw = second.offeredDraw = 0;\r
10778 \r
10779     if (gameMode == PlayFromGameFile) {\r
10780         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10781         blackTimeRemaining = timeRemaining[1][currentMove];\r
10782         DisplayTitle("");\r
10783     }\r
10784 \r
10785     if (gameMode == MachinePlaysWhite ||\r
10786         gameMode == MachinePlaysBlack ||\r
10787         gameMode == TwoMachinesPlay ||\r
10788         gameMode == EndOfGame) {\r
10789         i = forwardMostMove;\r
10790         while (i > currentMove) {\r
10791             SendToProgram("undo\n", &first);\r
10792             i--;\r
10793         }\r
10794         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10795         blackTimeRemaining = timeRemaining[1][currentMove];\r
10796         DisplayBothClocks();\r
10797         if (whiteFlag || blackFlag) {\r
10798             whiteFlag = blackFlag = 0;\r
10799         }\r
10800         DisplayTitle("");\r
10801     }           \r
10802     \r
10803     gameMode = EditGame;\r
10804     ModeHighlight();\r
10805     SetGameInfo();\r
10806 }\r
10807 \r
10808 \r
10809 void\r
10810 EditPositionEvent()\r
10811 {\r
10812     if (gameMode == EditPosition) {\r
10813         EditGameEvent();\r
10814         return;\r
10815     }\r
10816     \r
10817     EditGameEvent();\r
10818     if (gameMode != EditGame) return;\r
10819     \r
10820     gameMode = EditPosition;\r
10821     ModeHighlight();\r
10822     SetGameInfo();\r
10823     if (currentMove > 0)\r
10824       CopyBoard(boards[0], boards[currentMove]);\r
10825     \r
10826     blackPlaysFirst = !WhiteOnMove(currentMove);\r
10827     ResetClocks();\r
10828     currentMove = forwardMostMove = backwardMostMove = 0;\r
10829     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10830     DisplayMove(-1);\r
10831 }\r
10832 \r
10833 void\r
10834 ExitAnalyzeMode()\r
10835 {\r
10836     /* [DM] icsEngineAnalyze - possible call from other functions */\r
10837     if (appData.icsEngineAnalyze) {\r
10838         appData.icsEngineAnalyze = FALSE;\r
10839 \r
10840         DisplayMessage("",_("Close ICS engine analyze..."));\r
10841     }\r
10842     if (first.analysisSupport && first.analyzing) {\r
10843       SendToProgram("exit\n", &first);\r
10844       first.analyzing = FALSE;\r
10845     }\r
10846     AnalysisPopDown();\r
10847     thinkOutput[0] = NULLCHAR;\r
10848 }\r
10849 \r
10850 void\r
10851 EditPositionDone()\r
10852 {\r
10853     startedFromSetupPosition = TRUE;\r
10854     InitChessProgram(&first, FALSE);\r
10855     SendToProgram("force\n", &first);\r
10856     if (blackPlaysFirst) {\r
10857         strcpy(moveList[0], "");\r
10858         strcpy(parseList[0], "");\r
10859         currentMove = forwardMostMove = backwardMostMove = 1;\r
10860         CopyBoard(boards[1], boards[0]);\r
10861         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */\r
10862         { int i;\r
10863           epStatus[1] = epStatus[0];\r
10864           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];\r
10865         }\r
10866     } else {\r
10867         currentMove = forwardMostMove = backwardMostMove = 0;\r
10868     }\r
10869     SendBoard(&first, forwardMostMove);\r
10870     if (appData.debugMode) {\r
10871         fprintf(debugFP, "EditPosDone\n");\r
10872     }\r
10873     DisplayTitle("");\r
10874     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10875     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10876     gameMode = EditGame;\r
10877     ModeHighlight();\r
10878     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10879     ClearHighlights(); /* [AS] */\r
10880 }\r
10881 \r
10882 /* Pause for `ms' milliseconds */\r
10883 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10884 void\r
10885 TimeDelay(ms)\r
10886      long ms;\r
10887 {\r
10888     TimeMark m1, m2;\r
10889 \r
10890     GetTimeMark(&m1);\r
10891     do {\r
10892         GetTimeMark(&m2);\r
10893     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
10894 }\r
10895 \r
10896 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10897 void\r
10898 SendMultiLineToICS(buf)\r
10899      char *buf;\r
10900 {\r
10901     char temp[MSG_SIZ+1], *p;\r
10902     int len;\r
10903 \r
10904     len = strlen(buf);\r
10905     if (len > MSG_SIZ)\r
10906       len = MSG_SIZ;\r
10907   \r
10908     strncpy(temp, buf, len);\r
10909     temp[len] = 0;\r
10910 \r
10911     p = temp;\r
10912     while (*p) {\r
10913         if (*p == '\n' || *p == '\r')\r
10914           *p = ' ';\r
10915         ++p;\r
10916     }\r
10917 \r
10918     strcat(temp, "\n");\r
10919     SendToICS(temp);\r
10920     SendToPlayer(temp, strlen(temp));\r
10921 }\r
10922 \r
10923 void\r
10924 SetWhiteToPlayEvent()\r
10925 {\r
10926     if (gameMode == EditPosition) {\r
10927         blackPlaysFirst = FALSE;\r
10928         DisplayBothClocks();    /* works because currentMove is 0 */\r
10929     } else if (gameMode == IcsExamining) {\r
10930         SendToICS(ics_prefix);\r
10931         SendToICS("tomove white\n");\r
10932     }\r
10933 }\r
10934 \r
10935 void\r
10936 SetBlackToPlayEvent()\r
10937 {\r
10938     if (gameMode == EditPosition) {\r
10939         blackPlaysFirst = TRUE;\r
10940         currentMove = 1;        /* kludge */\r
10941         DisplayBothClocks();\r
10942         currentMove = 0;\r
10943     } else if (gameMode == IcsExamining) {\r
10944         SendToICS(ics_prefix);\r
10945         SendToICS("tomove black\n");\r
10946     }\r
10947 }\r
10948 \r
10949 void\r
10950 EditPositionMenuEvent(selection, x, y)\r
10951      ChessSquare selection;\r
10952      int x, y;\r
10953 {\r
10954     char buf[MSG_SIZ];\r
10955     ChessSquare piece = boards[0][y][x];\r
10956 \r
10957     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
10958 \r
10959     switch (selection) {\r
10960       case ClearBoard:\r
10961         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
10962             SendToICS(ics_prefix);\r
10963             SendToICS("bsetup clear\n");\r
10964         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
10965             SendToICS(ics_prefix);\r
10966             SendToICS("clearboard\n");\r
10967         } else {\r
10968             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;\r
10969                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */\r
10970                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
10971                     if (gameMode == IcsExamining) {\r
10972                         if (boards[currentMove][y][x] != EmptySquare) {\r
10973                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
10974                                     AAA + x, ONE + y);\r
10975                             SendToICS(buf);\r
10976                         }\r
10977                     } else {\r
10978                         boards[0][y][x] = p;\r
10979                     }\r
10980                 }\r
10981             }\r
10982         }\r
10983         if (gameMode == EditPosition) {\r
10984             DrawPosition(FALSE, boards[0]);\r
10985         }\r
10986         break;\r
10987 \r
10988       case WhitePlay:\r
10989         SetWhiteToPlayEvent();\r
10990         break;\r
10991 \r
10992       case BlackPlay:\r
10993         SetBlackToPlayEvent();\r
10994         break;\r
10995 \r
10996       case EmptySquare:\r
10997         if (gameMode == IcsExamining) {\r
10998             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
10999             SendToICS(buf);\r
11000         } else {\r
11001             boards[0][y][x] = EmptySquare;\r
11002             DrawPosition(FALSE, boards[0]);\r
11003         }\r
11004         break;\r
11005 \r
11006       case PromotePiece:\r
11007         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
11008            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
11009             selection = (ChessSquare) (PROMOTED piece);\r
11010         } else if(piece == EmptySquare) selection = WhiteSilver;\r
11011         else selection = (ChessSquare)((int)piece - 1);\r
11012         goto defaultlabel;\r
11013 \r
11014       case DemotePiece:\r
11015         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
11016            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
11017             selection = (ChessSquare) (DEMOTED piece);\r
11018         } else if(piece == EmptySquare) selection = BlackSilver;\r
11019         else selection = (ChessSquare)((int)piece + 1);       \r
11020         goto defaultlabel;\r
11021 \r
11022       case WhiteQueen:\r
11023       case BlackQueen:\r
11024         if(gameInfo.variant == VariantShatranj ||\r
11025            gameInfo.variant == VariantXiangqi  ||\r
11026            gameInfo.variant == VariantCourier    )\r
11027             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
11028         goto defaultlabel;\r
11029 \r
11030       case WhiteKing:\r
11031       case BlackKing:\r
11032         if(gameInfo.variant == VariantXiangqi)\r
11033             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
11034         if(gameInfo.variant == VariantKnightmate)\r
11035             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
11036       default:\r
11037         defaultlabel:\r
11038         if (gameMode == IcsExamining) {\r
11039             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
11040                     PieceToChar(selection), AAA + x, ONE + y);\r
11041             SendToICS(buf);\r
11042         } else {\r
11043             boards[0][y][x] = selection;\r
11044             DrawPosition(FALSE, boards[0]);\r
11045         }\r
11046         break;\r
11047     }\r
11048 }\r
11049 \r
11050 \r
11051 void\r
11052 DropMenuEvent(selection, x, y)\r
11053      ChessSquare selection;\r
11054      int x, y;\r
11055 {\r
11056     ChessMove moveType;\r
11057 \r
11058     switch (gameMode) {\r
11059       case IcsPlayingWhite:\r
11060       case MachinePlaysBlack:\r
11061         if (!WhiteOnMove(currentMove)) {\r
11062             DisplayMoveError(_("It is Black's turn"));\r
11063             return;\r
11064         }\r
11065         moveType = WhiteDrop;\r
11066         break;\r
11067       case IcsPlayingBlack:\r
11068       case MachinePlaysWhite:\r
11069         if (WhiteOnMove(currentMove)) {\r
11070             DisplayMoveError(_("It is White's turn"));\r
11071             return;\r
11072         }\r
11073         moveType = BlackDrop;\r
11074         break;\r
11075       case EditGame:\r
11076         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
11077         break;\r
11078       default:\r
11079         return;\r
11080     }\r
11081 \r
11082     if (moveType == BlackDrop && selection < BlackPawn) {\r
11083       selection = (ChessSquare) ((int) selection\r
11084                                  + (int) BlackPawn - (int) WhitePawn);\r
11085     }\r
11086     if (boards[currentMove][y][x] != EmptySquare) {\r
11087         DisplayMoveError(_("That square is occupied"));\r
11088         return;\r
11089     }\r
11090 \r
11091     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
11092 }\r
11093 \r
11094 void\r
11095 AcceptEvent()\r
11096 {\r
11097     /* Accept a pending offer of any kind from opponent */\r
11098     \r
11099     if (appData.icsActive) {\r
11100         SendToICS(ics_prefix);\r
11101         SendToICS("accept\n");\r
11102     } else if (cmailMsgLoaded) {\r
11103         if (currentMove == cmailOldMove &&\r
11104             commentList[cmailOldMove] != NULL &&\r
11105             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11106                    "Black offers a draw" : "White offers a draw")) {\r
11107             TruncateGame();\r
11108             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11109             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11110         } else {\r
11111             DisplayError(_("There is no pending offer on this move"), 0);\r
11112             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11113         }\r
11114     } else {\r
11115         /* Not used for offers from chess program */\r
11116     }\r
11117 }\r
11118 \r
11119 void\r
11120 DeclineEvent()\r
11121 {\r
11122     /* Decline a pending offer of any kind from opponent */\r
11123     \r
11124     if (appData.icsActive) {\r
11125         SendToICS(ics_prefix);\r
11126         SendToICS("decline\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 #ifdef NOTDEF\r
11133             AppendComment(cmailOldMove, "Draw declined");\r
11134             DisplayComment(cmailOldMove - 1, "Draw declined");\r
11135 #endif /*NOTDEF*/\r
11136         } else {\r
11137             DisplayError(_("There is no pending offer on this move"), 0);\r
11138         }\r
11139     } else {\r
11140         /* Not used for offers from chess program */\r
11141     }\r
11142 }\r
11143 \r
11144 void\r
11145 RematchEvent()\r
11146 {\r
11147     /* Issue ICS rematch command */\r
11148     if (appData.icsActive) {\r
11149         SendToICS(ics_prefix);\r
11150         SendToICS("rematch\n");\r
11151     }\r
11152 }\r
11153 \r
11154 void\r
11155 CallFlagEvent()\r
11156 {\r
11157     /* Call your opponent's flag (claim a win on time) */\r
11158     if (appData.icsActive) {\r
11159         SendToICS(ics_prefix);\r
11160         SendToICS("flag\n");\r
11161     } else {\r
11162         switch (gameMode) {\r
11163           default:\r
11164             return;\r
11165           case MachinePlaysWhite:\r
11166             if (whiteFlag) {\r
11167                 if (blackFlag)\r
11168                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11169                            GE_PLAYER);\r
11170                 else\r
11171                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
11172             } else {\r
11173                 DisplayError(_("Your opponent is not out of time"), 0);\r
11174             }\r
11175             break;\r
11176           case MachinePlaysBlack:\r
11177             if (blackFlag) {\r
11178                 if (whiteFlag)\r
11179                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11180                            GE_PLAYER);\r
11181                 else\r
11182                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
11183             } else {\r
11184                 DisplayError(_("Your opponent is not out of time"), 0);\r
11185             }\r
11186             break;\r
11187         }\r
11188     }\r
11189 }\r
11190 \r
11191 void\r
11192 DrawEvent()\r
11193 {\r
11194     /* Offer draw or accept pending draw offer from opponent */\r
11195     \r
11196     if (appData.icsActive) {\r
11197         /* Note: tournament rules require draw offers to be\r
11198            made after you make your move but before you punch\r
11199            your clock.  Currently ICS doesn't let you do that;\r
11200            instead, you immediately punch your clock after making\r
11201            a move, but you can offer a draw at any time. */\r
11202         \r
11203         SendToICS(ics_prefix);\r
11204         SendToICS("draw\n");\r
11205     } else if (cmailMsgLoaded) {\r
11206         if (currentMove == cmailOldMove &&\r
11207             commentList[cmailOldMove] != NULL &&\r
11208             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11209                    "Black offers a draw" : "White offers a draw")) {\r
11210             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11211             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11212         } else if (currentMove == cmailOldMove + 1) {\r
11213             char *offer = WhiteOnMove(cmailOldMove) ?\r
11214               "White offers a draw" : "Black offers a draw";\r
11215             AppendComment(currentMove, offer);\r
11216             DisplayComment(currentMove - 1, offer);\r
11217             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
11218         } else {\r
11219             DisplayError(_("You must make your move before offering a draw"), 0);\r
11220             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11221         }\r
11222     } else if (first.offeredDraw) {\r
11223         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
11224     } else {\r
11225         if (first.sendDrawOffers) {\r
11226             SendToProgram("draw\n", &first);\r
11227             userOfferedDraw = TRUE;\r
11228         }\r
11229     }\r
11230 }\r
11231 \r
11232 void\r
11233 AdjournEvent()\r
11234 {\r
11235     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
11236     \r
11237     if (appData.icsActive) {\r
11238         SendToICS(ics_prefix);\r
11239         SendToICS("adjourn\n");\r
11240     } else {\r
11241         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
11242     }\r
11243 }\r
11244 \r
11245 \r
11246 void\r
11247 AbortEvent()\r
11248 {\r
11249     /* Offer Abort or accept pending Abort offer from opponent */\r
11250     \r
11251     if (appData.icsActive) {\r
11252         SendToICS(ics_prefix);\r
11253         SendToICS("abort\n");\r
11254     } else {\r
11255         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
11256     }\r
11257 }\r
11258 \r
11259 void\r
11260 ResignEvent()\r
11261 {\r
11262     /* Resign.  You can do this even if it's not your turn. */\r
11263     \r
11264     if (appData.icsActive) {\r
11265         SendToICS(ics_prefix);\r
11266         SendToICS("resign\n");\r
11267     } else {\r
11268         switch (gameMode) {\r
11269           case MachinePlaysWhite:\r
11270             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11271             break;\r
11272           case MachinePlaysBlack:\r
11273             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11274             break;\r
11275           case EditGame:\r
11276             if (cmailMsgLoaded) {\r
11277                 TruncateGame();\r
11278                 if (WhiteOnMove(cmailOldMove)) {\r
11279                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11280                 } else {\r
11281                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11282                 }\r
11283                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
11284             }\r
11285             break;\r
11286           default:\r
11287             break;\r
11288         }\r
11289     }\r
11290 }\r
11291 \r
11292 \r
11293 void\r
11294 StopObservingEvent()\r
11295 {\r
11296     /* Stop observing current games */\r
11297     SendToICS(ics_prefix);\r
11298     SendToICS("unobserve\n");\r
11299 }\r
11300 \r
11301 void\r
11302 StopExaminingEvent()\r
11303 {\r
11304     /* Stop observing current game */\r
11305     SendToICS(ics_prefix);\r
11306     SendToICS("unexamine\n");\r
11307 }\r
11308 \r
11309 void\r
11310 ForwardInner(target)\r
11311      int target;\r
11312 {\r
11313     int limit;\r
11314 \r
11315     if (appData.debugMode)\r
11316         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
11317                 target, currentMove, forwardMostMove);\r
11318 \r
11319     if (gameMode == EditPosition)\r
11320       return;\r
11321 \r
11322     if (gameMode == PlayFromGameFile && !pausing)\r
11323       PauseEvent();\r
11324     \r
11325     if (gameMode == IcsExamining && pausing)\r
11326       limit = pauseExamForwardMostMove;\r
11327     else\r
11328       limit = forwardMostMove;\r
11329     \r
11330     if (target > limit) target = limit;\r
11331 \r
11332     if (target > 0 && moveList[target - 1][0]) {\r
11333         int fromX, fromY, toX, toY;\r
11334         toX = moveList[target - 1][2] - AAA;\r
11335         toY = moveList[target - 1][3] - ONE;\r
11336         if (moveList[target - 1][1] == '@') {\r
11337             if (appData.highlightLastMove) {\r
11338                 SetHighlights(-1, -1, toX, toY);\r
11339             }\r
11340         } else {\r
11341             fromX = moveList[target - 1][0] - AAA;\r
11342             fromY = moveList[target - 1][1] - ONE;\r
11343             if (target == currentMove + 1) {\r
11344                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
11345             }\r
11346             if (appData.highlightLastMove) {\r
11347                 SetHighlights(fromX, fromY, toX, toY);\r
11348             }\r
11349         }\r
11350     }\r
11351     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
11352         gameMode == Training || gameMode == PlayFromGameFile || \r
11353         gameMode == AnalyzeFile) {\r
11354         while (currentMove < target) {\r
11355             SendMoveToProgram(currentMove++, &first);\r
11356         }\r
11357     } else {\r
11358         currentMove = target;\r
11359     }\r
11360     \r
11361     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11362         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11363         blackTimeRemaining = timeRemaining[1][currentMove];\r
11364     }\r
11365     DisplayBothClocks();\r
11366     DisplayMove(currentMove - 1);\r
11367     DrawPosition(FALSE, boards[currentMove]);\r
11368     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11369     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
11370         DisplayComment(currentMove - 1, commentList[currentMove]);\r
11371     }\r
11372 }\r
11373 \r
11374 \r
11375 void\r
11376 ForwardEvent()\r
11377 {\r
11378     if (gameMode == IcsExamining && !pausing) {\r
11379         SendToICS(ics_prefix);\r
11380         SendToICS("forward\n");\r
11381     } else {\r
11382         ForwardInner(currentMove + 1);\r
11383     }\r
11384 }\r
11385 \r
11386 void\r
11387 ToEndEvent()\r
11388 {\r
11389     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11390         /* to optimze, we temporarily turn off analysis mode while we feed\r
11391          * the remaining moves to the engine. Otherwise we get analysis output\r
11392          * after each move.\r
11393          */ \r
11394         if (first.analysisSupport) {\r
11395           SendToProgram("exit\nforce\n", &first);\r
11396           first.analyzing = FALSE;\r
11397         }\r
11398     }\r
11399         \r
11400     if (gameMode == IcsExamining && !pausing) {\r
11401         SendToICS(ics_prefix);\r
11402         SendToICS("forward 999999\n");\r
11403     } else {\r
11404         ForwardInner(forwardMostMove);\r
11405     }\r
11406 \r
11407     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11408         /* we have fed all the moves, so reactivate analysis mode */\r
11409         SendToProgram("analyze\n", &first);\r
11410         first.analyzing = TRUE;\r
11411         /*first.maybeThinking = TRUE;*/\r
11412         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11413     }\r
11414 }\r
11415 \r
11416 void\r
11417 BackwardInner(target)\r
11418      int target;\r
11419 {\r
11420     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
11421 \r
11422     if (appData.debugMode)\r
11423         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
11424                 target, currentMove, forwardMostMove);\r
11425 \r
11426     if (gameMode == EditPosition) return;\r
11427     if (currentMove <= backwardMostMove) {\r
11428         ClearHighlights();\r
11429         DrawPosition(full_redraw, boards[currentMove]);\r
11430         return;\r
11431     }\r
11432     if (gameMode == PlayFromGameFile && !pausing)\r
11433       PauseEvent();\r
11434     \r
11435     if (moveList[target][0]) {\r
11436         int fromX, fromY, toX, toY;\r
11437         toX = moveList[target][2] - AAA;\r
11438         toY = moveList[target][3] - ONE;\r
11439         if (moveList[target][1] == '@') {\r
11440             if (appData.highlightLastMove) {\r
11441                 SetHighlights(-1, -1, toX, toY);\r
11442             }\r
11443         } else {\r
11444             fromX = moveList[target][0] - AAA;\r
11445             fromY = moveList[target][1] - ONE;\r
11446             if (target == currentMove - 1) {\r
11447                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
11448             }\r
11449             if (appData.highlightLastMove) {\r
11450                 SetHighlights(fromX, fromY, toX, toY);\r
11451             }\r
11452         }\r
11453     }\r
11454     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
11455         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
11456         while (currentMove > target) {\r
11457             SendToProgram("undo\n", &first);\r
11458             currentMove--;\r
11459         }\r
11460     } else {\r
11461         currentMove = target;\r
11462     }\r
11463     \r
11464     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11465         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11466         blackTimeRemaining = timeRemaining[1][currentMove];\r
11467     }\r
11468     DisplayBothClocks();\r
11469     DisplayMove(currentMove - 1);\r
11470     DrawPosition(full_redraw, boards[currentMove]);\r
11471     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11472     // [HGM] PV info: routine tests if comment empty\r
11473     DisplayComment(currentMove - 1, commentList[currentMove]);\r
11474 }\r
11475 \r
11476 void\r
11477 BackwardEvent()\r
11478 {\r
11479     if (gameMode == IcsExamining && !pausing) {\r
11480         SendToICS(ics_prefix);\r
11481         SendToICS("backward\n");\r
11482     } else {\r
11483         BackwardInner(currentMove - 1);\r
11484     }\r
11485 }\r
11486 \r
11487 void\r
11488 ToStartEvent()\r
11489 {\r
11490     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11491         /* to optimze, we temporarily turn off analysis mode while we undo\r
11492          * all the moves. Otherwise we get analysis output after each undo.\r
11493          */ \r
11494         if (first.analysisSupport) {\r
11495           SendToProgram("exit\nforce\n", &first);\r
11496           first.analyzing = FALSE;\r
11497         }\r
11498     }\r
11499 \r
11500     if (gameMode == IcsExamining && !pausing) {\r
11501         SendToICS(ics_prefix);\r
11502         SendToICS("backward 999999\n");\r
11503     } else {\r
11504         BackwardInner(backwardMostMove);\r
11505     }\r
11506 \r
11507     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11508         /* we have fed all the moves, so reactivate analysis mode */\r
11509         SendToProgram("analyze\n", &first);\r
11510         first.analyzing = TRUE;\r
11511         /*first.maybeThinking = TRUE;*/\r
11512         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11513     }\r
11514 }\r
11515 \r
11516 void\r
11517 ToNrEvent(int to)\r
11518 {\r
11519   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
11520   if (to >= forwardMostMove) to = forwardMostMove;\r
11521   if (to <= backwardMostMove) to = backwardMostMove;\r
11522   if (to < currentMove) {\r
11523     BackwardInner(to);\r
11524   } else {\r
11525     ForwardInner(to);\r
11526   }\r
11527 }\r
11528 \r
11529 void\r
11530 RevertEvent()\r
11531 {\r
11532     if (gameMode != IcsExamining) {\r
11533         DisplayError(_("You are not examining a game"), 0);\r
11534         return;\r
11535     }\r
11536     if (pausing) {\r
11537         DisplayError(_("You can't revert while pausing"), 0);\r
11538         return;\r
11539     }\r
11540     SendToICS(ics_prefix);\r
11541     SendToICS("revert\n");\r
11542 }\r
11543 \r
11544 void\r
11545 RetractMoveEvent()\r
11546 {\r
11547     switch (gameMode) {\r
11548       case MachinePlaysWhite:\r
11549       case MachinePlaysBlack:\r
11550         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
11551             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
11552             return;\r
11553         }\r
11554         if (forwardMostMove < 2) return;\r
11555         currentMove = forwardMostMove = forwardMostMove - 2;\r
11556         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11557         blackTimeRemaining = timeRemaining[1][currentMove];\r
11558         DisplayBothClocks();\r
11559         DisplayMove(currentMove - 1);\r
11560         ClearHighlights();/*!! could figure this out*/\r
11561         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
11562         SendToProgram("remove\n", &first);\r
11563         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
11564         break;\r
11565 \r
11566       case BeginningOfGame:\r
11567       default:\r
11568         break;\r
11569 \r
11570       case IcsPlayingWhite:\r
11571       case IcsPlayingBlack:\r
11572         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
11573             SendToICS(ics_prefix);\r
11574             SendToICS("takeback 2\n");\r
11575         } else {\r
11576             SendToICS(ics_prefix);\r
11577             SendToICS("takeback 1\n");\r
11578         }\r
11579         break;\r
11580     }\r
11581 }\r
11582 \r
11583 void\r
11584 MoveNowEvent()\r
11585 {\r
11586     ChessProgramState *cps;\r
11587 \r
11588     switch (gameMode) {\r
11589       case MachinePlaysWhite:\r
11590         if (!WhiteOnMove(forwardMostMove)) {\r
11591             DisplayError(_("It is your turn"), 0);\r
11592             return;\r
11593         }\r
11594         cps = &first;\r
11595         break;\r
11596       case MachinePlaysBlack:\r
11597         if (WhiteOnMove(forwardMostMove)) {\r
11598             DisplayError(_("It is your turn"), 0);\r
11599             return;\r
11600         }\r
11601         cps = &first;\r
11602         break;\r
11603       case TwoMachinesPlay:\r
11604         if (WhiteOnMove(forwardMostMove) ==\r
11605             (first.twoMachinesColor[0] == 'w')) {\r
11606             cps = &first;\r
11607         } else {\r
11608             cps = &second;\r
11609         }\r
11610         break;\r
11611       case BeginningOfGame:\r
11612       default:\r
11613         return;\r
11614     }\r
11615     SendToProgram("?\n", cps);\r
11616 }\r
11617 \r
11618 void\r
11619 TruncateGameEvent()\r
11620 {\r
11621     EditGameEvent();\r
11622     if (gameMode != EditGame) return;\r
11623     TruncateGame();\r
11624 }\r
11625 \r
11626 void\r
11627 TruncateGame()\r
11628 {\r
11629     if (forwardMostMove > currentMove) {\r
11630         if (gameInfo.resultDetails != NULL) {\r
11631             free(gameInfo.resultDetails);\r
11632             gameInfo.resultDetails = NULL;\r
11633             gameInfo.result = GameUnfinished;\r
11634         }\r
11635         forwardMostMove = currentMove;\r
11636         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
11637                    currentMove-1);\r
11638     }\r
11639 }\r
11640 \r
11641 void\r
11642 HintEvent()\r
11643 {\r
11644     if (appData.noChessProgram) return;\r
11645     switch (gameMode) {\r
11646       case MachinePlaysWhite:\r
11647         if (WhiteOnMove(forwardMostMove)) {\r
11648             DisplayError(_("Wait until your turn"), 0);\r
11649             return;\r
11650         }\r
11651         break;\r
11652       case BeginningOfGame:\r
11653       case MachinePlaysBlack:\r
11654         if (!WhiteOnMove(forwardMostMove)) {\r
11655             DisplayError(_("Wait until your turn"), 0);\r
11656             return;\r
11657         }\r
11658         break;\r
11659       default:\r
11660         DisplayError(_("No hint available"), 0);\r
11661         return;\r
11662     }\r
11663     SendToProgram("hint\n", &first);\r
11664     hintRequested = TRUE;\r
11665 }\r
11666 \r
11667 void\r
11668 BookEvent()\r
11669 {\r
11670     if (appData.noChessProgram) return;\r
11671     switch (gameMode) {\r
11672       case MachinePlaysWhite:\r
11673         if (WhiteOnMove(forwardMostMove)) {\r
11674             DisplayError(_("Wait until your turn"), 0);\r
11675             return;\r
11676         }\r
11677         break;\r
11678       case BeginningOfGame:\r
11679       case MachinePlaysBlack:\r
11680         if (!WhiteOnMove(forwardMostMove)) {\r
11681             DisplayError(_("Wait until your turn"), 0);\r
11682             return;\r
11683         }\r
11684         break;\r
11685       case EditPosition:\r
11686         EditPositionDone();\r
11687         break;\r
11688       case TwoMachinesPlay:\r
11689         return;\r
11690       default:\r
11691         break;\r
11692     }\r
11693     SendToProgram("bk\n", &first);\r
11694     bookOutput[0] = NULLCHAR;\r
11695     bookRequested = TRUE;\r
11696 }\r
11697 \r
11698 void\r
11699 AboutGameEvent()\r
11700 {\r
11701     char *tags = PGNTags(&gameInfo);\r
11702     TagsPopUp(tags, CmailMsg());\r
11703     free(tags);\r
11704 }\r
11705 \r
11706 /* end button procedures */\r
11707 \r
11708 void\r
11709 PrintPosition(fp, move)\r
11710      FILE *fp;\r
11711      int move;\r
11712 {\r
11713     int i, j;\r
11714     \r
11715     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11716         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11717             char c = PieceToChar(boards[move][i][j]);\r
11718             fputc(c == 'x' ? '.' : c, fp);\r
11719             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
11720         }\r
11721     }\r
11722     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
11723       fprintf(fp, "white to play\n");\r
11724     else\r
11725       fprintf(fp, "black to play\n");\r
11726 }\r
11727 \r
11728 void\r
11729 PrintOpponents(fp)\r
11730      FILE *fp;\r
11731 {\r
11732     if (gameInfo.white != NULL) {\r
11733         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
11734     } else {\r
11735         fprintf(fp, "\n");\r
11736     }\r
11737 }\r
11738 \r
11739 /* Find last component of program's own name, using some heuristics */\r
11740 void\r
11741 TidyProgramName(prog, host, buf)\r
11742      char *prog, *host, buf[MSG_SIZ];\r
11743 {\r
11744     char *p, *q;\r
11745     int local = (strcmp(host, "localhost") == 0);\r
11746     while (!local && (p = strchr(prog, ';')) != NULL) {\r
11747         p++;\r
11748         while (*p == ' ') p++;\r
11749         prog = p;\r
11750     }\r
11751     if (*prog == '"' || *prog == '\'') {\r
11752         q = strchr(prog + 1, *prog);\r
11753     } else {\r
11754         q = strchr(prog, ' ');\r
11755     }\r
11756     if (q == NULL) q = prog + strlen(prog);\r
11757     p = q;\r
11758     while (p >= prog && *p != '/' && *p != '\\') p--;\r
11759     p++;\r
11760     if(p == prog && *p == '"') p++;\r
11761     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
11762     memcpy(buf, p, q - p);\r
11763     buf[q - p] = NULLCHAR;\r
11764     if (!local) {\r
11765         strcat(buf, "@");\r
11766         strcat(buf, host);\r
11767     }\r
11768 }\r
11769 \r
11770 char *\r
11771 TimeControlTagValue()\r
11772 {\r
11773     char buf[MSG_SIZ];\r
11774     if (!appData.clockMode) {\r
11775         strcpy(buf, "-");\r
11776     } else if (movesPerSession > 0) {\r
11777         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
11778     } else if (timeIncrement == 0) {\r
11779         sprintf(buf, "%ld", timeControl/1000);\r
11780     } else {\r
11781         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
11782     }\r
11783     return StrSave(buf);\r
11784 }\r
11785 \r
11786 void\r
11787 SetGameInfo()\r
11788 {\r
11789     /* This routine is used only for certain modes */\r
11790     VariantClass v = gameInfo.variant;\r
11791     ClearGameInfo(&gameInfo);\r
11792     gameInfo.variant = v;\r
11793 \r
11794     switch (gameMode) {\r
11795       case MachinePlaysWhite:\r
11796         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11797         gameInfo.site = StrSave(HostName());\r
11798         gameInfo.date = PGNDate();\r
11799         gameInfo.round = StrSave("-");\r
11800         gameInfo.white = StrSave(first.tidy);\r
11801         gameInfo.black = StrSave(UserName());\r
11802         gameInfo.timeControl = TimeControlTagValue();\r
11803         break;\r
11804 \r
11805       case MachinePlaysBlack:\r
11806         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11807         gameInfo.site = StrSave(HostName());\r
11808         gameInfo.date = PGNDate();\r
11809         gameInfo.round = StrSave("-");\r
11810         gameInfo.white = StrSave(UserName());\r
11811         gameInfo.black = StrSave(first.tidy);\r
11812         gameInfo.timeControl = TimeControlTagValue();\r
11813         break;\r
11814 \r
11815       case TwoMachinesPlay:\r
11816         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11817         gameInfo.site = StrSave(HostName());\r
11818         gameInfo.date = PGNDate();\r
11819         if (matchGame > 0) {\r
11820             char buf[MSG_SIZ];\r
11821             sprintf(buf, "%d", matchGame);\r
11822             gameInfo.round = StrSave(buf);\r
11823         } else {\r
11824             gameInfo.round = StrSave("-");\r
11825         }\r
11826         if (first.twoMachinesColor[0] == 'w') {\r
11827             gameInfo.white = StrSave(first.tidy);\r
11828             gameInfo.black = StrSave(second.tidy);\r
11829         } else {\r
11830             gameInfo.white = StrSave(second.tidy);\r
11831             gameInfo.black = StrSave(first.tidy);\r
11832         }\r
11833         gameInfo.timeControl = TimeControlTagValue();\r
11834         break;\r
11835 \r
11836       case EditGame:\r
11837         gameInfo.event = StrSave("Edited game");\r
11838         gameInfo.site = StrSave(HostName());\r
11839         gameInfo.date = PGNDate();\r
11840         gameInfo.round = StrSave("-");\r
11841         gameInfo.white = StrSave("-");\r
11842         gameInfo.black = StrSave("-");\r
11843         break;\r
11844 \r
11845       case EditPosition:\r
11846         gameInfo.event = StrSave("Edited position");\r
11847         gameInfo.site = StrSave(HostName());\r
11848         gameInfo.date = PGNDate();\r
11849         gameInfo.round = StrSave("-");\r
11850         gameInfo.white = StrSave("-");\r
11851         gameInfo.black = StrSave("-");\r
11852         break;\r
11853 \r
11854       case IcsPlayingWhite:\r
11855       case IcsPlayingBlack:\r
11856       case IcsObserving:\r
11857       case IcsExamining:\r
11858         break;\r
11859 \r
11860       case PlayFromGameFile:\r
11861         gameInfo.event = StrSave("Game from non-PGN file");\r
11862         gameInfo.site = StrSave(HostName());\r
11863         gameInfo.date = PGNDate();\r
11864         gameInfo.round = StrSave("-");\r
11865         gameInfo.white = StrSave("?");\r
11866         gameInfo.black = StrSave("?");\r
11867         break;\r
11868 \r
11869       default:\r
11870         break;\r
11871     }\r
11872 }\r
11873 \r
11874 void\r
11875 ReplaceComment(index, text)\r
11876      int index;\r
11877      char *text;\r
11878 {\r
11879     int len;\r
11880 \r
11881     while (*text == '\n') text++;\r
11882     len = strlen(text);\r
11883     while (len > 0 && text[len - 1] == '\n') len--;\r
11884 \r
11885     if (commentList[index] != NULL)\r
11886       free(commentList[index]);\r
11887 \r
11888     if (len == 0) {\r
11889         commentList[index] = NULL;\r
11890         return;\r
11891     }\r
11892     commentList[index] = (char *) malloc(len + 2);\r
11893     strncpy(commentList[index], text, len);\r
11894     commentList[index][len] = '\n';\r
11895     commentList[index][len + 1] = NULLCHAR;\r
11896 }\r
11897 \r
11898 void\r
11899 CrushCRs(text)\r
11900      char *text;\r
11901 {\r
11902   char *p = text;\r
11903   char *q = text;\r
11904   char ch;\r
11905 \r
11906   do {\r
11907     ch = *p++;\r
11908     if (ch == '\r') continue;\r
11909     *q++ = ch;\r
11910   } while (ch != '\0');\r
11911 }\r
11912 \r
11913 void\r
11914 AppendComment(index, text)\r
11915      int index;\r
11916      char *text;\r
11917 {\r
11918     int oldlen, len;\r
11919     char *old;\r
11920 \r
11921     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11922 \r
11923     CrushCRs(text);\r
11924     while (*text == '\n') text++;\r
11925     len = strlen(text);\r
11926     while (len > 0 && text[len - 1] == '\n') len--;\r
11927 \r
11928     if (len == 0) return;\r
11929 \r
11930     if (commentList[index] != NULL) {\r
11931         old = commentList[index];\r
11932         oldlen = strlen(old);\r
11933         commentList[index] = (char *) malloc(oldlen + len + 2);\r
11934         strcpy(commentList[index], old);\r
11935         free(old);\r
11936         strncpy(&commentList[index][oldlen], text, len);\r
11937         commentList[index][oldlen + len] = '\n';\r
11938         commentList[index][oldlen + len + 1] = NULLCHAR;\r
11939     } else {\r
11940         commentList[index] = (char *) malloc(len + 2);\r
11941         strncpy(commentList[index], text, len);\r
11942         commentList[index][len] = '\n';\r
11943         commentList[index][len + 1] = NULLCHAR;\r
11944     }\r
11945 }\r
11946 \r
11947 static char * FindStr( char * text, char * sub_text )\r
11948 {\r
11949     char * result = strstr( text, sub_text );\r
11950 \r
11951     if( result != NULL ) {\r
11952         result += strlen( sub_text );\r
11953     }\r
11954 \r
11955     return result;\r
11956 }\r
11957 \r
11958 /* [AS] Try to extract PV info from PGN comment */\r
11959 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
11960 char *GetInfoFromComment( int index, char * text )\r
11961 {\r
11962     char * sep = text;\r
11963 \r
11964     if( text != NULL && index > 0 ) {\r
11965         int score = 0;\r
11966         int depth = 0;\r
11967         int time = -1, sec = 0, deci;\r
11968         char * s_eval = FindStr( text, "[%eval " );\r
11969         char * s_emt = FindStr( text, "[%emt " );\r
11970 \r
11971         if( s_eval != NULL || s_emt != NULL ) {\r
11972             /* New style */\r
11973             char delim;\r
11974 \r
11975             if( s_eval != NULL ) {\r
11976                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
11977                     return text;\r
11978                 }\r
11979 \r
11980                 if( delim != ']' ) {\r
11981                     return text;\r
11982                 }\r
11983             }\r
11984 \r
11985             if( s_emt != NULL ) {\r
11986             }\r
11987         }\r
11988         else {\r
11989             /* We expect something like: [+|-]nnn.nn/dd */\r
11990             int score_lo = 0;\r
11991 \r
11992             sep = strchr( text, '/' );\r
11993             if( sep == NULL || sep < (text+4) ) {\r
11994                 return text;\r
11995             }\r
11996 \r
11997             time = -1; sec = -1; deci = -1;\r
11998             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
11999                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&\r
12000                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
12001                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
12002                 return text;\r
12003             }\r
12004 \r
12005             if( score_lo < 0 || score_lo >= 100 ) {\r
12006                 return text;\r
12007             }\r
12008 \r
12009             if(sec >= 0) time = 600*time + 10*sec; else\r
12010             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec\r
12011 \r
12012             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
12013 \r
12014             /* [HGM] PV time: now locate end of PV info */\r
12015             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
12016             if(time >= 0)\r
12017             while( *++sep >= '0' && *sep <= '9'); // strip time\r
12018             if(sec >= 0)\r
12019             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
12020             if(deci >= 0)\r
12021             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds\r
12022             while(*sep == ' ') sep++;\r
12023         }\r
12024 \r
12025         if( depth <= 0 ) {\r
12026             return text;\r
12027         }\r
12028 \r
12029         if( time < 0 ) {\r
12030             time = -1;\r
12031         }\r
12032 \r
12033         pvInfoList[index-1].depth = depth;\r
12034         pvInfoList[index-1].score = score;\r
12035         pvInfoList[index-1].time  = 10*time; // centi-sec\r
12036     }\r
12037     return sep;\r
12038 }\r
12039 \r
12040 void\r
12041 SendToProgram(message, cps)\r
12042      char *message;\r
12043      ChessProgramState *cps;\r
12044 {\r
12045     int count, outCount, error;\r
12046     char buf[MSG_SIZ];\r
12047 \r
12048     if (cps->pr == NULL) return;\r
12049     Attention(cps);\r
12050     \r
12051     if (appData.debugMode) {\r
12052         TimeMark now;\r
12053         GetTimeMark(&now);\r
12054         fprintf(debugFP, "%ld >%-6s: %s", \r
12055                 SubtractTimeMarks(&now, &programStartTime),\r
12056                 cps->which, message);\r
12057     }\r
12058     \r
12059     count = strlen(message);\r
12060     outCount = OutputToProcess(cps->pr, message, count, &error);\r
12061     if (outCount < count && !exiting \r
12062                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
12063         sprintf(buf, _("Error writing to %s chess program"), cps->which);\r
12064         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12065             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12066                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12067                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
12068             } else {\r
12069                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12070             }\r
12071             gameInfo.resultDetails = buf;\r
12072         }\r
12073         DisplayFatalError(buf, error, 1);\r
12074     }\r
12075 }\r
12076 \r
12077 void\r
12078 ReceiveFromProgram(isr, closure, message, count, error)\r
12079      InputSourceRef isr;\r
12080      VOIDSTAR closure;\r
12081      char *message;\r
12082      int count;\r
12083      int error;\r
12084 {\r
12085     char *end_str;\r
12086     char buf[MSG_SIZ];\r
12087     ChessProgramState *cps = (ChessProgramState *)closure;\r
12088 \r
12089     if (isr != cps->isr) return; /* Killed intentionally */\r
12090     if (count <= 0) {\r
12091         if (count == 0) {\r
12092             sprintf(buf,\r
12093                     _("Error: %s chess program (%s) exited unexpectedly"),\r
12094                     cps->which, cps->program);\r
12095         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12096                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12097                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12098                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);\r
12099                 } else {\r
12100                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12101                 }\r
12102                 gameInfo.resultDetails = buf;\r
12103             }\r
12104             RemoveInputSource(cps->isr);\r
12105             DisplayFatalError(buf, 0, 1);\r
12106         } else {\r
12107             sprintf(buf,\r
12108                     _("Error reading from %s chess program (%s)"),\r
12109                     cps->which, cps->program);\r
12110             RemoveInputSource(cps->isr);\r
12111 \r
12112             /* [AS] Program is misbehaving badly... kill it */\r
12113             if( count == -2 ) {\r
12114                 DestroyChildProcess( cps->pr, 9 );\r
12115                 cps->pr = NoProc;\r
12116             }\r
12117 \r
12118             DisplayFatalError(buf, error, 1);\r
12119         }\r
12120         return;\r
12121     }\r
12122     \r
12123     if ((end_str = strchr(message, '\r')) != NULL)\r
12124       *end_str = NULLCHAR;\r
12125     if ((end_str = strchr(message, '\n')) != NULL)\r
12126       *end_str = NULLCHAR;\r
12127     \r
12128     if (appData.debugMode) {\r
12129         TimeMark now; int print = 1;\r
12130         char *quote = ""; char c; int i;\r
12131 \r
12132         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
12133                 char start = message[0];\r
12134                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
12135                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
12136                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&\r
12137                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
12138                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
12139                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&\r
12140                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
12141                         { quote = "# "; print = (appData.engineComments == 2); }\r
12142                 message[0] = start; // restore original message\r
12143         }\r
12144         if(print) {\r
12145                 GetTimeMark(&now);\r
12146                 fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
12147                         SubtractTimeMarks(&now, &programStartTime), cps->which, \r
12148                         quote,\r
12149                         message);\r
12150         }\r
12151     }\r
12152 \r
12153     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */\r
12154     if (appData.icsEngineAnalyze) {\r
12155         if (strstr(message, "whisper") != NULL ||\r
12156              strstr(message, "kibitz") != NULL || \r
12157             strstr(message, "tellics") != NULL) return;\r
12158     }\r
12159 \r
12160     HandleMachineMove(message, cps);\r
12161 }\r
12162 \r
12163 \r
12164 void\r
12165 SendTimeControl(cps, mps, tc, inc, sd, st)\r
12166      ChessProgramState *cps;\r
12167      int mps, inc, sd, st;\r
12168      long tc;\r
12169 {\r
12170     char buf[MSG_SIZ];\r
12171     int seconds;\r
12172 \r
12173     if( timeControl_2 > 0 ) {\r
12174         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
12175             tc = timeControl_2;\r
12176         }\r
12177     }\r
12178     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
12179     inc /= cps->timeOdds;\r
12180     st  /= cps->timeOdds;\r
12181 \r
12182     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
12183 \r
12184     if (st > 0) {\r
12185       /* Set exact time per move, normally using st command */\r
12186       if (cps->stKludge) {\r
12187         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
12188         seconds = st % 60;\r
12189         if (seconds == 0) {\r
12190           sprintf(buf, "level 1 %d\n", st/60);\r
12191         } else {\r
12192           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
12193         }\r
12194       } else {\r
12195         sprintf(buf, "st %d\n", st);\r
12196       }\r
12197     } else {\r
12198       /* Set conventional or incremental time control, using level command */\r
12199       if (seconds == 0) {\r
12200         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
12201            Fixed in later versions, but still avoid :seconds\r
12202            when seconds is 0. */\r
12203         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
12204       } else {\r
12205         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
12206                 seconds, inc/1000);\r
12207       }\r
12208     }\r
12209     SendToProgram(buf, cps);\r
12210 \r
12211     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
12212     /* Orthogonally, limit search to given depth */\r
12213     if (sd > 0) {\r
12214       if (cps->sdKludge) {\r
12215         sprintf(buf, "depth\n%d\n", sd);\r
12216       } else {\r
12217         sprintf(buf, "sd %d\n", sd);\r
12218       }\r
12219       SendToProgram(buf, cps);\r
12220     }\r
12221 \r
12222     if(cps->nps > 0) { /* [HGM] nps */\r
12223         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
12224         else {\r
12225                 sprintf(buf, "nps %d\n", cps->nps);\r
12226               SendToProgram(buf, cps);\r
12227         }\r
12228     }\r
12229 }\r
12230 \r
12231 ChessProgramState *WhitePlayer()\r
12232 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
12233 {\r
12234     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || \r
12235        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)\r
12236         return &second;\r
12237     return &first;\r
12238 }\r
12239 \r
12240 void\r
12241 SendTimeRemaining(cps, machineWhite)\r
12242      ChessProgramState *cps;\r
12243      int /*boolean*/ machineWhite;\r
12244 {\r
12245     char message[MSG_SIZ];\r
12246     long time, otime;\r
12247 \r
12248     /* Note: this routine must be called when the clocks are stopped\r
12249        or when they have *just* been set or switched; otherwise\r
12250        it will be off by the time since the current tick started.\r
12251     */\r
12252     if (machineWhite) {\r
12253         time = whiteTimeRemaining / 10;\r
12254         otime = blackTimeRemaining / 10;\r
12255     } else {\r
12256         time = blackTimeRemaining / 10;\r
12257         otime = whiteTimeRemaining / 10;\r
12258     }\r
12259     /* [HGM] translate opponent's time by time-odds factor */\r
12260     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
12261     if (appData.debugMode) {\r
12262         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
12263     }\r
12264 \r
12265     if (time <= 0) time = 1;\r
12266     if (otime <= 0) otime = 1;\r
12267     \r
12268     sprintf(message, "time %ld\n", time);\r
12269     SendToProgram(message, cps);\r
12270 \r
12271     sprintf(message, "otim %ld\n", otime);\r
12272     SendToProgram(message, cps);\r
12273 }\r
12274 \r
12275 int\r
12276 BoolFeature(p, name, loc, cps)\r
12277      char **p;\r
12278      char *name;\r
12279      int *loc;\r
12280      ChessProgramState *cps;\r
12281 {\r
12282   char buf[MSG_SIZ];\r
12283   int len = strlen(name);\r
12284   int val;\r
12285   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12286     (*p) += len + 1;\r
12287     sscanf(*p, "%d", &val);\r
12288     *loc = (val != 0);\r
12289     while (**p && **p != ' ') (*p)++;\r
12290     sprintf(buf, "accepted %s\n", name);\r
12291     SendToProgram(buf, cps);\r
12292     return TRUE;\r
12293   }\r
12294   return FALSE;\r
12295 }\r
12296 \r
12297 int\r
12298 IntFeature(p, name, loc, cps)\r
12299      char **p;\r
12300      char *name;\r
12301      int *loc;\r
12302      ChessProgramState *cps;\r
12303 {\r
12304   char buf[MSG_SIZ];\r
12305   int len = strlen(name);\r
12306   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12307     (*p) += len + 1;\r
12308     sscanf(*p, "%d", loc);\r
12309     while (**p && **p != ' ') (*p)++;\r
12310     sprintf(buf, "accepted %s\n", name);\r
12311     SendToProgram(buf, cps);\r
12312     return TRUE;\r
12313   }\r
12314   return FALSE;\r
12315 }\r
12316 \r
12317 int\r
12318 StringFeature(p, name, loc, cps)\r
12319      char **p;\r
12320      char *name;\r
12321      char loc[];\r
12322      ChessProgramState *cps;\r
12323 {\r
12324   char buf[MSG_SIZ];\r
12325   int len = strlen(name);\r
12326   if (strncmp((*p), name, len) == 0\r
12327       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
12328     (*p) += len + 2;\r
12329     sscanf(*p, "%[^\"]", loc);\r
12330     while (**p && **p != '\"') (*p)++;\r
12331     if (**p == '\"') (*p)++;\r
12332     sprintf(buf, "accepted %s\n", name);\r
12333     SendToProgram(buf, cps);\r
12334     return TRUE;\r
12335   }\r
12336   return FALSE;\r
12337 }\r
12338 \r
12339 int \r
12340 ParseOption(Option *opt, ChessProgramState *cps)\r
12341 // [HGM] options: process the string that defines an engine option, and determine\r
12342 // name, type, default value, and allowed value range\r
12343 {\r
12344         char *p, *q, buf[MSG_SIZ];\r
12345         int n, min = (-1)<<31, max = 1<<31, def;\r
12346 \r
12347         if(p = strstr(opt->name, " -spin ")) {\r
12348             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;\r
12349             if(max < min) max = min; // enforce consistency\r
12350             if(def < min) def = min;\r
12351             if(def > max) def = max;\r
12352             opt->value = def;\r
12353             opt->min = min;\r
12354             opt->max = max;\r
12355             opt->type = Spin;\r
12356         } else if(p = strstr(opt->name, " -string ")) {\r
12357             opt->textValue = p+9;\r
12358             opt->type = TextBox;\r
12359         } else if(p = strstr(opt->name, " -check ")) {\r
12360             if(sscanf(p, " -check %d", &def) < 1) return FALSE;\r
12361             opt->value = (def != 0);\r
12362             opt->type = CheckBox;\r
12363         } else if(p = strstr(opt->name, " -combo ")) {\r
12364             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type\r
12365             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices\r
12366             opt->value = n = 0;\r
12367             while(q = StrStr(q, " /// ")) {\r
12368                 n++; *q = 0;    // count choices, and null-terminate each of them\r
12369                 q += 5;\r
12370                 if(*q == '*') { // remember default, which is marked with * prefix\r
12371                     q++;\r
12372                     opt->value = n;\r
12373                 }\r
12374                 cps->comboList[cps->comboCnt++] = q;\r
12375             }\r
12376             cps->comboList[cps->comboCnt++] = NULL;\r
12377             opt->max = n + 1;\r
12378             opt->type = ComboBox;\r
12379         } else if(p = strstr(opt->name, " -button")) {\r
12380             opt->type = Button;\r
12381         } else if(p = strstr(opt->name, " -save")) {\r
12382             opt->type = SaveButton;\r
12383         } else return FALSE;\r
12384         *p = 0; // terminate option name\r
12385         // now look if the command-line options define a setting for this engine option.\r
12386         p = strstr(cps->optionSettings, opt->name);\r
12387         if(p == cps->optionSettings || p[-1] == ',') {\r
12388                 sprintf(buf, "option %s", p);\r
12389                 if(p = strstr(buf, ",")) *p = 0;\r
12390                 strcat(buf, "\n");\r
12391                 SendToProgram(buf, cps);\r
12392         }\r
12393         return TRUE;\r
12394 }\r
12395 \r
12396 void\r
12397 FeatureDone(cps, val)\r
12398      ChessProgramState* cps;\r
12399      int val;\r
12400 {\r
12401   DelayedEventCallback cb = GetDelayedEvent();\r
12402   if ((cb == InitBackEnd3 && cps == &first) ||\r
12403       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
12404     CancelDelayedEvent();\r
12405     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
12406   }\r
12407   cps->initDone = val;\r
12408 }\r
12409 \r
12410 /* Parse feature command from engine */\r
12411 void\r
12412 ParseFeatures(args, cps)\r
12413      char* args;\r
12414      ChessProgramState *cps;  \r
12415 {\r
12416   char *p = args;\r
12417   char *q;\r
12418   int val;\r
12419   char buf[MSG_SIZ];\r
12420 \r
12421   for (;;) {\r
12422     while (*p == ' ') p++;\r
12423     if (*p == NULLCHAR) return;\r
12424 \r
12425     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
12426     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
12427     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
12428     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
12429     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
12430     if (BoolFeature(&p, "reuse", &val, cps)) {\r
12431       /* Engine can disable reuse, but can't enable it if user said no */\r
12432       if (!val) cps->reuse = FALSE;\r
12433       continue;\r
12434     }\r
12435     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
12436     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
12437       if (gameMode == TwoMachinesPlay) {\r
12438         DisplayTwoMachinesTitle();\r
12439       } else {\r
12440         DisplayTitle("");\r
12441       }\r
12442       continue;\r
12443     }\r
12444     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
12445     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
12446     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
12447     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
12448     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
12449     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
12450     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
12451     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
12452     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
12453     if (IntFeature(&p, "done", &val, cps)) {\r
12454       FeatureDone(cps, val);\r
12455       continue;\r
12456     }\r
12457     /* Added by Tord: */\r
12458     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
12459     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
12460     /* End of additions by Tord */\r
12461 \r
12462     /* [HGM] added features: */\r
12463     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
12464     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
12465     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
12466     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
12467     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12468     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
12469     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {\r
12470         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature\r
12471         if(cps->nrOptions >= MAX_OPTIONS) {\r
12472             cps->nrOptions--;\r
12473             sprintf(buf, "%s engine has too many options\n", cps->which);\r
12474             DisplayError(buf, 0);\r
12475         }\r
12476         continue;\r
12477     }\r
12478     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12479     /* End of additions by HGM */\r
12480 \r
12481     /* unknown feature: complain and skip */\r
12482     q = p;\r
12483     while (*q && *q != '=') q++;\r
12484     sprintf(buf, "rejected %.*s\n", q-p, p);\r
12485     SendToProgram(buf, cps);\r
12486     p = q;\r
12487     if (*p == '=') {\r
12488       p++;\r
12489       if (*p == '\"') {\r
12490         p++;\r
12491         while (*p && *p != '\"') p++;\r
12492         if (*p == '\"') p++;\r
12493       } else {\r
12494         while (*p && *p != ' ') p++;\r
12495       }\r
12496     }\r
12497   }\r
12498 \r
12499 }\r
12500 \r
12501 void\r
12502 PeriodicUpdatesEvent(newState)\r
12503      int newState;\r
12504 {\r
12505     if (newState == appData.periodicUpdates)\r
12506       return;\r
12507 \r
12508     appData.periodicUpdates=newState;\r
12509 \r
12510     /* Display type changes, so update it now */\r
12511     DisplayAnalysis();\r
12512 \r
12513     /* Get the ball rolling again... */\r
12514     if (newState) {\r
12515         AnalysisPeriodicEvent(1);\r
12516         StartAnalysisClock();\r
12517     }\r
12518 }\r
12519 \r
12520 void\r
12521 PonderNextMoveEvent(newState)\r
12522      int newState;\r
12523 {\r
12524     if (newState == appData.ponderNextMove) return;\r
12525     if (gameMode == EditPosition) EditPositionDone();\r
12526     if (newState) {\r
12527         SendToProgram("hard\n", &first);\r
12528         if (gameMode == TwoMachinesPlay) {\r
12529             SendToProgram("hard\n", &second);\r
12530         }\r
12531     } else {\r
12532         SendToProgram("easy\n", &first);\r
12533         thinkOutput[0] = NULLCHAR;\r
12534         if (gameMode == TwoMachinesPlay) {\r
12535             SendToProgram("easy\n", &second);\r
12536         }\r
12537     }\r
12538     appData.ponderNextMove = newState;\r
12539 }\r
12540 \r
12541 void\r
12542 NewSettingEvent(option, command, value)\r
12543      char *command;\r
12544      int option, value;\r
12545 {\r
12546     char buf[MSG_SIZ];\r
12547 \r
12548     if (gameMode == EditPosition) EditPositionDone();\r
12549     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);\r
12550     SendToProgram(buf, &first);\r
12551     if (gameMode == TwoMachinesPlay) {\r
12552         SendToProgram(buf, &second);\r
12553     }\r
12554 }\r
12555 \r
12556 void\r
12557 ShowThinkingEvent()\r
12558 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup\r
12559 {\r
12560     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated\r
12561     int newState = appData.showThinking\r
12562         // [HGM] thinking: other features now need thinking output as well\r
12563         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();\r
12564     \r
12565     if (oldState == newState) return;\r
12566     oldState = newState;\r
12567     if (gameMode == EditPosition) EditPositionDone();\r
12568     if (oldState) {\r
12569         SendToProgram("post\n", &first);\r
12570         if (gameMode == TwoMachinesPlay) {\r
12571             SendToProgram("post\n", &second);\r
12572         }\r
12573     } else {\r
12574         SendToProgram("nopost\n", &first);\r
12575         thinkOutput[0] = NULLCHAR;\r
12576         if (gameMode == TwoMachinesPlay) {\r
12577             SendToProgram("nopost\n", &second);\r
12578         }\r
12579     }\r
12580 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!\r
12581 }\r
12582 \r
12583 void\r
12584 AskQuestionEvent(title, question, replyPrefix, which)\r
12585      char *title; char *question; char *replyPrefix; char *which;\r
12586 {\r
12587   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
12588   if (pr == NoProc) return;\r
12589   AskQuestion(title, question, replyPrefix, pr);\r
12590 }\r
12591 \r
12592 void\r
12593 DisplayMove(moveNumber)\r
12594      int moveNumber;\r
12595 {\r
12596     char message[MSG_SIZ];\r
12597     char res[MSG_SIZ];\r
12598     char cpThinkOutput[MSG_SIZ];\r
12599 \r
12600     if(appData.noGUI) return; // [HGM] fast: suppress display of moves\r
12601     \r
12602     if (moveNumber == forwardMostMove - 1 || \r
12603         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12604 \r
12605         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
12606 \r
12607         if (strchr(cpThinkOutput, '\n')) {\r
12608             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
12609         }\r
12610     } else {\r
12611         *cpThinkOutput = NULLCHAR;\r
12612     }\r
12613 \r
12614     /* [AS] Hide thinking from human user */\r
12615     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
12616         *cpThinkOutput = NULLCHAR;\r
12617         if( thinkOutput[0] != NULLCHAR ) {\r
12618             int i;\r
12619 \r
12620             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
12621                 cpThinkOutput[i] = '.';\r
12622             }\r
12623             cpThinkOutput[i] = NULLCHAR;\r
12624             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
12625         }\r
12626     }\r
12627 \r
12628     if (moveNumber == forwardMostMove - 1 &&\r
12629         gameInfo.resultDetails != NULL) {\r
12630         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
12631             sprintf(res, " %s", PGNResult(gameInfo.result));\r
12632         } else {\r
12633             sprintf(res, " {%s} %s",\r
12634                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
12635         }\r
12636     } else {\r
12637         res[0] = NULLCHAR;\r
12638     }\r
12639 \r
12640     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12641         DisplayMessage(res, cpThinkOutput);\r
12642     } else {\r
12643         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
12644                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
12645                 parseList[moveNumber], res);\r
12646         DisplayMessage(message, cpThinkOutput);\r
12647     }\r
12648 }\r
12649 \r
12650 void\r
12651 DisplayAnalysisText(text)\r
12652      char *text;\r
12653 {\r
12654     char buf[MSG_SIZ];\r
12655 \r
12656     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile \r
12657                || appData.icsEngineAnalyze) {\r
12658         sprintf(buf, "Analysis (%s)", first.tidy);\r
12659         AnalysisPopUp(buf, text);\r
12660     }\r
12661 }\r
12662 \r
12663 static int\r
12664 only_one_move(str)\r
12665      char *str;\r
12666 {\r
12667     while (*str && isspace(*str)) ++str;\r
12668     while (*str && !isspace(*str)) ++str;\r
12669     if (!*str) return 1;\r
12670     while (*str && isspace(*str)) ++str;\r
12671     if (!*str) return 1;\r
12672     return 0;\r
12673 }\r
12674 \r
12675 void\r
12676 DisplayAnalysis()\r
12677 {\r
12678     char buf[MSG_SIZ];\r
12679     char lst[MSG_SIZ / 2];\r
12680     double nps;\r
12681     static char *xtra[] = { "", " (--)", " (++)" };\r
12682     int h, m, s, cs;\r
12683   \r
12684     if (programStats.time == 0) {\r
12685         programStats.time = 1;\r
12686     }\r
12687   \r
12688     if (programStats.got_only_move) {\r
12689         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
12690     } else {\r
12691         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
12692 \r
12693         nps = (u64ToDouble(programStats.nodes) /\r
12694              ((double)programStats.time /100.0));\r
12695 \r
12696         cs = programStats.time % 100;\r
12697         s = programStats.time / 100;\r
12698         h = (s / (60*60));\r
12699         s = s - h*60*60;\r
12700         m = (s/60);\r
12701         s = s - m*60;\r
12702 \r
12703         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
12704           if (programStats.move_name[0] != NULLCHAR) {\r
12705             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12706                     programStats.depth,\r
12707                     programStats.nr_moves-programStats.moves_left,\r
12708                     programStats.nr_moves, programStats.move_name,\r
12709                     ((float)programStats.score)/100.0, lst,\r
12710                     only_one_move(lst)?\r
12711                     xtra[programStats.got_fail] : "",\r
12712                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12713           } else {\r
12714             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12715                     programStats.depth,\r
12716                     programStats.nr_moves-programStats.moves_left,\r
12717                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
12718                     lst,\r
12719                     only_one_move(lst)?\r
12720                     xtra[programStats.got_fail] : "",\r
12721                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12722           }\r
12723         } else {\r
12724             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12725                     programStats.depth,\r
12726                     ((float)programStats.score)/100.0,\r
12727                     lst,\r
12728                     only_one_move(lst)?\r
12729                     xtra[programStats.got_fail] : "",\r
12730                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12731         }\r
12732     }\r
12733     DisplayAnalysisText(buf);\r
12734 }\r
12735 \r
12736 void\r
12737 DisplayComment(moveNumber, text)\r
12738      int moveNumber;\r
12739      char *text;\r
12740 {\r
12741     char title[MSG_SIZ];\r
12742     char buf[8000]; // comment can be long!\r
12743     int score, depth;\r
12744 \r
12745     if( appData.autoDisplayComment ) {\r
12746         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12747             strcpy(title, "Comment");\r
12748         } else {\r
12749             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
12750                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
12751                     parseList[moveNumber]);\r
12752         }\r
12753     } else title[0] = 0;\r
12754 \r
12755     // [HGM] PV info: display PV info together with (or as) comment\r
12756     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
12757         if(text == NULL) text = "";                                           \r
12758         score = pvInfoList[moveNumber].score;\r
12759         sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
12760                               depth, (pvInfoList[moveNumber].time+50)/100, text);\r
12761         CommentPopUp(title, buf);\r
12762     } else\r
12763     if (text != NULL)\r
12764         CommentPopUp(title, text);\r
12765 }\r
12766 \r
12767 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
12768  * might be busy thinking or pondering.  It can be omitted if your\r
12769  * gnuchess is configured to stop thinking immediately on any user\r
12770  * input.  However, that gnuchess feature depends on the FIONREAD\r
12771  * ioctl, which does not work properly on some flavors of Unix.\r
12772  */\r
12773 void\r
12774 Attention(cps)\r
12775      ChessProgramState *cps;\r
12776 {\r
12777 #if ATTENTION\r
12778     if (!cps->useSigint) return;\r
12779     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
12780     switch (gameMode) {\r
12781       case MachinePlaysWhite:\r
12782       case MachinePlaysBlack:\r
12783       case TwoMachinesPlay:\r
12784       case IcsPlayingWhite:\r
12785       case IcsPlayingBlack:\r
12786       case AnalyzeMode:\r
12787       case AnalyzeFile:\r
12788         /* Skip if we know it isn't thinking */\r
12789         if (!cps->maybeThinking) return;\r
12790         if (appData.debugMode)\r
12791           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
12792         InterruptChildProcess(cps->pr);\r
12793         cps->maybeThinking = FALSE;\r
12794         break;\r
12795       default:\r
12796         break;\r
12797     }\r
12798 #endif /*ATTENTION*/\r
12799 }\r
12800 \r
12801 int\r
12802 CheckFlags()\r
12803 {\r
12804     if (whiteTimeRemaining <= 0) {\r
12805         if (!whiteFlag) {\r
12806             whiteFlag = TRUE;\r
12807             if (appData.icsActive) {\r
12808                 if (appData.autoCallFlag &&\r
12809                     gameMode == IcsPlayingBlack && !blackFlag) {\r
12810                   SendToICS(ics_prefix);\r
12811                   SendToICS("flag\n");\r
12812                 }\r
12813             } else {\r
12814                 if (blackFlag) {\r
12815                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12816                 } else {\r
12817                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));\r
12818                     if (appData.autoCallFlag) {\r
12819                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
12820                         return TRUE;\r
12821                     }\r
12822                 }\r
12823             }\r
12824         }\r
12825     }\r
12826     if (blackTimeRemaining <= 0) {\r
12827         if (!blackFlag) {\r
12828             blackFlag = TRUE;\r
12829             if (appData.icsActive) {\r
12830                 if (appData.autoCallFlag &&\r
12831                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
12832                   SendToICS(ics_prefix);\r
12833                   SendToICS("flag\n");\r
12834                 }\r
12835             } else {\r
12836                 if (whiteFlag) {\r
12837                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12838                 } else {\r
12839                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));\r
12840                     if (appData.autoCallFlag) {\r
12841                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
12842                         return TRUE;\r
12843                     }\r
12844                 }\r
12845             }\r
12846         }\r
12847     }\r
12848     return FALSE;\r
12849 }\r
12850 \r
12851 void\r
12852 CheckTimeControl()\r
12853 {\r
12854     if (!appData.clockMode || appData.icsActive ||\r
12855         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
12856 \r
12857     /*\r
12858      * add time to clocks when time control is achieved ([HGM] now also used for increment)\r
12859      */\r
12860     if ( !WhiteOnMove(forwardMostMove) )\r
12861         /* White made time control */\r
12862         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12863         /* [HGM] time odds: correct new time quota for time odds! */\r
12864                                             / WhitePlayer()->timeOdds;\r
12865       else\r
12866         /* Black made time control */\r
12867         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12868                                             / WhitePlayer()->other->timeOdds;\r
12869 }\r
12870 \r
12871 void\r
12872 DisplayBothClocks()\r
12873 {\r
12874     int wom = gameMode == EditPosition ?\r
12875       !blackPlaysFirst : WhiteOnMove(currentMove);\r
12876     DisplayWhiteClock(whiteTimeRemaining, wom);\r
12877     DisplayBlackClock(blackTimeRemaining, !wom);\r
12878 }\r
12879 \r
12880 \r
12881 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
12882    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
12883    to use other calls if you don't.  Clocks will be less accurate if\r
12884    you have neither ftime nor gettimeofday.\r
12885 */\r
12886 \r
12887 /* Get the current time as a TimeMark */\r
12888 void\r
12889 GetTimeMark(tm)\r
12890      TimeMark *tm;\r
12891 {\r
12892 #if HAVE_GETTIMEOFDAY\r
12893 \r
12894     struct timeval timeVal;\r
12895     struct timezone timeZone;\r
12896 \r
12897     gettimeofday(&timeVal, &timeZone);\r
12898     tm->sec = (long) timeVal.tv_sec; \r
12899     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
12900 \r
12901 #else /*!HAVE_GETTIMEOFDAY*/\r
12902 #if HAVE_FTIME\r
12903 \r
12904 #include <sys/timeb.h>\r
12905     struct timeb timeB;\r
12906 \r
12907     ftime(&timeB);\r
12908     tm->sec = (long) timeB.time;\r
12909     tm->ms = (int) timeB.millitm;\r
12910 \r
12911 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
12912     tm->sec = (long) time(NULL);\r
12913     tm->ms = 0;\r
12914 #endif\r
12915 #endif\r
12916 }\r
12917 \r
12918 /* Return the difference in milliseconds between two\r
12919    time marks.  We assume the difference will fit in a long!\r
12920 */\r
12921 long\r
12922 SubtractTimeMarks(tm2, tm1)\r
12923      TimeMark *tm2, *tm1;\r
12924 {\r
12925     return 1000L*(tm2->sec - tm1->sec) +\r
12926            (long) (tm2->ms - tm1->ms);\r
12927 }\r
12928 \r
12929 \r
12930 /*\r
12931  * Code to manage the game clocks.\r
12932  *\r
12933  * In tournament play, black starts the clock and then white makes a move.\r
12934  * We give the human user a slight advantage if he is playing white---the\r
12935  * clocks don't run until he makes his first move, so it takes zero time.\r
12936  * Also, we don't account for network lag, so we could get out of sync\r
12937  * with GNU Chess's clock -- but then, referees are always right.  \r
12938  */\r
12939 \r
12940 static TimeMark tickStartTM;\r
12941 static long intendedTickLength;\r
12942 \r
12943 long\r
12944 NextTickLength(timeRemaining)\r
12945      long timeRemaining;\r
12946 {\r
12947     long nominalTickLength, nextTickLength;\r
12948 \r
12949     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
12950       nominalTickLength = 100L;\r
12951     else\r
12952       nominalTickLength = 1000L;\r
12953     nextTickLength = timeRemaining % nominalTickLength;\r
12954     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
12955 \r
12956     return nextTickLength;\r
12957 }\r
12958 \r
12959 /* Adjust clock one minute up or down */\r
12960 void\r
12961 AdjustClock(Boolean which, int dir)\r
12962 {\r
12963     if(which) blackTimeRemaining += 60000*dir;\r
12964     else      whiteTimeRemaining += 60000*dir;\r
12965     DisplayBothClocks();\r
12966 }\r
12967 \r
12968 /* Stop clocks and reset to a fresh time control */\r
12969 void\r
12970 ResetClocks() \r
12971 {\r
12972     (void) StopClockTimer();\r
12973     if (appData.icsActive) {\r
12974         whiteTimeRemaining = blackTimeRemaining = 0;\r
12975     } else { /* [HGM] correct new time quote for time odds */\r
12976         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
12977         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
12978     }\r
12979     if (whiteFlag || blackFlag) {\r
12980         DisplayTitle("");\r
12981         whiteFlag = blackFlag = FALSE;\r
12982     }\r
12983     DisplayBothClocks();\r
12984 }\r
12985 \r
12986 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
12987 \r
12988 /* Decrement running clock by amount of time that has passed */\r
12989 void\r
12990 DecrementClocks()\r
12991 {\r
12992     long timeRemaining;\r
12993     long lastTickLength, fudge;\r
12994     TimeMark now;\r
12995 \r
12996     if (!appData.clockMode) return;\r
12997     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
12998         \r
12999     GetTimeMark(&now);\r
13000 \r
13001     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13002 \r
13003     /* Fudge if we woke up a little too soon */\r
13004     fudge = intendedTickLength - lastTickLength;\r
13005     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
13006 \r
13007     if (WhiteOnMove(forwardMostMove)) {\r
13008         if(whiteNPS >= 0) lastTickLength = 0;\r
13009         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
13010         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
13011                           WhiteOnMove(currentMove));\r
13012     } else {\r
13013         if(blackNPS >= 0) lastTickLength = 0;\r
13014         timeRemaining = blackTimeRemaining -= lastTickLength;\r
13015         DisplayBlackClock(blackTimeRemaining - fudge,\r
13016                           !WhiteOnMove(currentMove));\r
13017     }\r
13018 \r
13019     if (CheckFlags()) return;\r
13020         \r
13021     tickStartTM = now;\r
13022     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
13023     StartClockTimer(intendedTickLength);\r
13024 \r
13025     /* if the time remaining has fallen below the alarm threshold, sound the\r
13026      * alarm. if the alarm has sounded and (due to a takeback or time control\r
13027      * with increment) the time remaining has increased to a level above the\r
13028      * threshold, reset the alarm so it can sound again. \r
13029      */\r
13030     \r
13031     if (appData.icsActive && appData.icsAlarm) {\r
13032 \r
13033         /* make sure we are dealing with the user's clock */\r
13034         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
13035                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
13036            )) return;\r
13037 \r
13038         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
13039             alarmSounded = FALSE;\r
13040         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
13041             PlayAlarmSound();\r
13042             alarmSounded = TRUE;\r
13043         }\r
13044     }\r
13045 }\r
13046 \r
13047 \r
13048 /* A player has just moved, so stop the previously running\r
13049    clock and (if in clock mode) start the other one.\r
13050    We redisplay both clocks in case we're in ICS mode, because\r
13051    ICS gives us an update to both clocks after every move.\r
13052    Note that this routine is called *after* forwardMostMove\r
13053    is updated, so the last fractional tick must be subtracted\r
13054    from the color that is *not* on move now.\r
13055 */\r
13056 void\r
13057 SwitchClocks()\r
13058 {\r
13059     long lastTickLength;\r
13060     TimeMark now;\r
13061     int flagged = FALSE;\r
13062 \r
13063     GetTimeMark(&now);\r
13064 \r
13065     if (StopClockTimer() && appData.clockMode) {\r
13066         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13067         if (WhiteOnMove(forwardMostMove)) {\r
13068             if(blackNPS >= 0) lastTickLength = 0;\r
13069             blackTimeRemaining -= lastTickLength;\r
13070            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13071 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13072                  pvInfoList[forwardMostMove-1].time =               // use GUI time\r
13073                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
13074         } else {\r
13075            if(whiteNPS >= 0) lastTickLength = 0;\r
13076            whiteTimeRemaining -= lastTickLength;\r
13077            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13078 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13079                  pvInfoList[forwardMostMove-1].time = \r
13080                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
13081         }\r
13082         flagged = CheckFlags();\r
13083     }\r
13084     CheckTimeControl();\r
13085 \r
13086     if (flagged || !appData.clockMode) return;\r
13087 \r
13088     switch (gameMode) {\r
13089       case MachinePlaysBlack:\r
13090       case MachinePlaysWhite:\r
13091       case BeginningOfGame:\r
13092         if (pausing) return;\r
13093         break;\r
13094 \r
13095       case EditGame:\r
13096       case PlayFromGameFile:\r
13097       case IcsExamining:\r
13098         return;\r
13099 \r
13100       default:\r
13101         break;\r
13102     }\r
13103 \r
13104     tickStartTM = now;\r
13105     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13106       whiteTimeRemaining : blackTimeRemaining);\r
13107     StartClockTimer(intendedTickLength);\r
13108 }\r
13109         \r
13110 \r
13111 /* Stop both clocks */\r
13112 void\r
13113 StopClocks()\r
13114 {       \r
13115     long lastTickLength;\r
13116     TimeMark now;\r
13117 \r
13118     if (!StopClockTimer()) return;\r
13119     if (!appData.clockMode) return;\r
13120 \r
13121     GetTimeMark(&now);\r
13122 \r
13123     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13124     if (WhiteOnMove(forwardMostMove)) {\r
13125         if(whiteNPS >= 0) lastTickLength = 0;\r
13126         whiteTimeRemaining -= lastTickLength;\r
13127         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
13128     } else {\r
13129         if(blackNPS >= 0) lastTickLength = 0;\r
13130         blackTimeRemaining -= lastTickLength;\r
13131         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
13132     }\r
13133     CheckFlags();\r
13134 }\r
13135         \r
13136 /* Start clock of player on move.  Time may have been reset, so\r
13137    if clock is already running, stop and restart it. */\r
13138 void\r
13139 StartClocks()\r
13140 {\r
13141     (void) StopClockTimer(); /* in case it was running already */\r
13142     DisplayBothClocks();\r
13143     if (CheckFlags()) return;\r
13144 \r
13145     if (!appData.clockMode) return;\r
13146     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
13147 \r
13148     GetTimeMark(&tickStartTM);\r
13149     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13150       whiteTimeRemaining : blackTimeRemaining);\r
13151 \r
13152    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
13153     whiteNPS = blackNPS = -1; \r
13154     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
13155        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
13156         whiteNPS = first.nps;\r
13157     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
13158        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
13159         blackNPS = first.nps;\r
13160     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
13161         whiteNPS = second.nps;\r
13162     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
13163         blackNPS = second.nps;\r
13164     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
13165 \r
13166     StartClockTimer(intendedTickLength);\r
13167 }\r
13168 \r
13169 char *\r
13170 TimeString(ms)\r
13171      long ms;\r
13172 {\r
13173     long second, minute, hour, day;\r
13174     char *sign = "";\r
13175     static char buf[32];\r
13176     \r
13177     if (ms > 0 && ms <= 9900) {\r
13178       /* convert milliseconds to tenths, rounding up */\r
13179       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
13180 \r
13181       sprintf(buf, " %03.1f ", tenths/10.0);\r
13182       return buf;\r
13183     }\r
13184 \r
13185     /* convert milliseconds to seconds, rounding up */\r
13186     /* use floating point to avoid strangeness of integer division\r
13187        with negative dividends on many machines */\r
13188     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
13189 \r
13190     if (second < 0) {\r
13191         sign = "-";\r
13192         second = -second;\r
13193     }\r
13194     \r
13195     day = second / (60 * 60 * 24);\r
13196     second = second % (60 * 60 * 24);\r
13197     hour = second / (60 * 60);\r
13198     second = second % (60 * 60);\r
13199     minute = second / 60;\r
13200     second = second % 60;\r
13201     \r
13202     if (day > 0)\r
13203       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
13204               sign, day, hour, minute, second);\r
13205     else if (hour > 0)\r
13206       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
13207     else\r
13208       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
13209     \r
13210     return buf;\r
13211 }\r
13212 \r
13213 \r
13214 /*\r
13215  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
13216  */\r
13217 char *\r
13218 StrStr(string, match)\r
13219      char *string, *match;\r
13220 {\r
13221     int i, length;\r
13222     \r
13223     length = strlen(match);\r
13224     \r
13225     for (i = strlen(string) - length; i >= 0; i--, string++)\r
13226       if (!strncmp(match, string, length))\r
13227         return string;\r
13228     \r
13229     return NULL;\r
13230 }\r
13231 \r
13232 char *\r
13233 StrCaseStr(string, match)\r
13234      char *string, *match;\r
13235 {\r
13236     int i, j, length;\r
13237     \r
13238     length = strlen(match);\r
13239     \r
13240     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
13241         for (j = 0; j < length; j++) {\r
13242             if (ToLower(match[j]) != ToLower(string[j]))\r
13243               break;\r
13244         }\r
13245         if (j == length) return string;\r
13246     }\r
13247 \r
13248     return NULL;\r
13249 }\r
13250 \r
13251 #ifndef _amigados\r
13252 int\r
13253 StrCaseCmp(s1, s2)\r
13254      char *s1, *s2;\r
13255 {\r
13256     char c1, c2;\r
13257     \r
13258     for (;;) {\r
13259         c1 = ToLower(*s1++);\r
13260         c2 = ToLower(*s2++);\r
13261         if (c1 > c2) return 1;\r
13262         if (c1 < c2) return -1;\r
13263         if (c1 == NULLCHAR) return 0;\r
13264     }\r
13265 }\r
13266 \r
13267 \r
13268 int\r
13269 ToLower(c)\r
13270      int c;\r
13271 {\r
13272     return isupper(c) ? tolower(c) : c;\r
13273 }\r
13274 \r
13275 \r
13276 int\r
13277 ToUpper(c)\r
13278      int c;\r
13279 {\r
13280     return islower(c) ? toupper(c) : c;\r
13281 }\r
13282 #endif /* !_amigados    */\r
13283 \r
13284 char *\r
13285 StrSave(s)\r
13286      char *s;\r
13287 {\r
13288     char *ret;\r
13289 \r
13290     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
13291         strcpy(ret, s);\r
13292     }\r
13293     return ret;\r
13294 }\r
13295 \r
13296 char *\r
13297 StrSavePtr(s, savePtr)\r
13298      char *s, **savePtr;\r
13299 {\r
13300     if (*savePtr) {\r
13301         free(*savePtr);\r
13302     }\r
13303     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
13304         strcpy(*savePtr, s);\r
13305     }\r
13306     return(*savePtr);\r
13307 }\r
13308 \r
13309 char *\r
13310 PGNDate()\r
13311 {\r
13312     time_t clock;\r
13313     struct tm *tm;\r
13314     char buf[MSG_SIZ];\r
13315 \r
13316     clock = time((time_t *)NULL);\r
13317     tm = localtime(&clock);\r
13318     sprintf(buf, "%04d.%02d.%02d",\r
13319             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
13320     return StrSave(buf);\r
13321 }\r
13322 \r
13323 \r
13324 char *\r
13325 PositionToFEN(move, useFEN960)\r
13326      int move;\r
13327      int useFEN960;\r
13328 {\r
13329     int i, j, fromX, fromY, toX, toY;\r
13330     int whiteToPlay;\r
13331     char buf[128];\r
13332     char *p, *q;\r
13333     int emptycount;\r
13334     ChessSquare piece;\r
13335 \r
13336     whiteToPlay = (gameMode == EditPosition) ?\r
13337       !blackPlaysFirst : (move % 2 == 0);\r
13338     p = buf;\r
13339 \r
13340     /* Piece placement data */\r
13341     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13342         emptycount = 0;\r
13343         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
13344             if (boards[move][i][j] == EmptySquare) {\r
13345                 emptycount++;\r
13346             } else { ChessSquare piece = boards[move][i][j];\r
13347                 if (emptycount > 0) {\r
13348                     if(emptycount<10) /* [HGM] can be >= 10 */\r
13349                         *p++ = '0' + emptycount;\r
13350                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13351                     emptycount = 0;\r
13352                 }\r
13353                 if(PieceToChar(piece) == '+') {\r
13354                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
13355                     *p++ = '+';\r
13356                     piece = (ChessSquare)(DEMOTED piece);\r
13357                 } \r
13358                 *p++ = PieceToChar(piece);\r
13359                 if(p[-1] == '~') {\r
13360                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
13361                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
13362                     *p++ = '~';\r
13363                 }\r
13364             }\r
13365         }\r
13366         if (emptycount > 0) {\r
13367             if(emptycount<10) /* [HGM] can be >= 10 */\r
13368                 *p++ = '0' + emptycount;\r
13369             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13370             emptycount = 0;\r
13371         }\r
13372         *p++ = '/';\r
13373     }\r
13374     *(p - 1) = ' ';\r
13375 \r
13376     /* [HGM] print Crazyhouse or Shogi holdings */\r
13377     if( gameInfo.holdingsWidth ) {\r
13378         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
13379         q = p;\r
13380         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
13381             piece = boards[move][i][BOARD_WIDTH-1];\r
13382             if( piece != EmptySquare )\r
13383               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
13384                   *p++ = PieceToChar(piece);\r
13385         }\r
13386         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
13387             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
13388             if( piece != EmptySquare )\r
13389               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
13390                   *p++ = PieceToChar(piece);\r
13391         }\r
13392 \r
13393         if( q == p ) *p++ = '-';\r
13394         *p++ = ']';\r
13395         *p++ = ' ';\r
13396     }\r
13397 \r
13398     /* Active color */\r
13399     *p++ = whiteToPlay ? 'w' : 'b';\r
13400     *p++ = ' ';\r
13401 \r
13402   if(nrCastlingRights) {\r
13403      q = p;\r
13404      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
13405        /* [HGM] write directly from rights */\r
13406            if(castlingRights[move][2] >= 0 &&\r
13407               castlingRights[move][0] >= 0   )\r
13408                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
13409            if(castlingRights[move][2] >= 0 &&\r
13410               castlingRights[move][1] >= 0   )\r
13411                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
13412            if(castlingRights[move][5] >= 0 &&\r
13413               castlingRights[move][3] >= 0   )\r
13414                 *p++ = castlingRights[move][3] + AAA;\r
13415            if(castlingRights[move][5] >= 0 &&\r
13416               castlingRights[move][4] >= 0   )\r
13417                 *p++ = castlingRights[move][4] + AAA;\r
13418      } else {\r
13419 \r
13420         /* [HGM] write true castling rights */\r
13421         if( nrCastlingRights == 6 ) {\r
13422             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
13423                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
13424             if(castlingRights[move][1] == BOARD_LEFT &&\r
13425                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
13426             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
13427                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
13428             if(castlingRights[move][4] == BOARD_LEFT &&\r
13429                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
13430         }\r
13431      }\r
13432      if (q == p) *p++ = '-'; /* No castling rights */\r
13433      *p++ = ' ';\r
13434   }\r
13435 \r
13436   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13437      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13438     /* En passant target square */\r
13439     if (move > backwardMostMove) {\r
13440         fromX = moveList[move - 1][0] - AAA;\r
13441         fromY = moveList[move - 1][1] - ONE;\r
13442         toX = moveList[move - 1][2] - AAA;\r
13443         toY = moveList[move - 1][3] - ONE;\r
13444         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
13445             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
13446             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
13447             fromX == toX) {\r
13448             /* 2-square pawn move just happened */\r
13449             *p++ = toX + AAA;\r
13450             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
13451         } else {\r
13452             *p++ = '-';\r
13453         }\r
13454     } else {\r
13455         *p++ = '-';\r
13456     }\r
13457     *p++ = ' ';\r
13458   }\r
13459 \r
13460     /* [HGM] find reversible plies */\r
13461     {   int i = 0, j=move;\r
13462 \r
13463         if (appData.debugMode) { int k;\r
13464             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
13465             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
13466                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
13467 \r
13468         }\r
13469 \r
13470         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
13471         if( j == backwardMostMove ) i += initialRulePlies;\r
13472         sprintf(p, "%d ", i);\r
13473         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
13474     }\r
13475     /* Fullmove number */\r
13476     sprintf(p, "%d", (move / 2) + 1);\r
13477     \r
13478     return StrSave(buf);\r
13479 }\r
13480 \r
13481 Boolean\r
13482 ParseFEN(board, blackPlaysFirst, fen)\r
13483     Board board;\r
13484      int *blackPlaysFirst;\r
13485      char *fen;\r
13486 {\r
13487     int i, j;\r
13488     char *p;\r
13489     int emptycount;\r
13490     ChessSquare piece;\r
13491 \r
13492     p = fen;\r
13493 \r
13494     /* [HGM] by default clear Crazyhouse holdings, if present */\r
13495     if(gameInfo.holdingsWidth) {\r
13496        for(i=0; i<BOARD_HEIGHT; i++) {\r
13497            board[i][0]             = EmptySquare; /* black holdings */\r
13498            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
13499            board[i][1]             = (ChessSquare) 0; /* black counts */\r
13500            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
13501        }\r
13502     }\r
13503 \r
13504     /* Piece placement data */\r
13505     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13506         j = 0;\r
13507         for (;;) {\r
13508             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
13509                 if (*p == '/') p++;\r
13510                 emptycount = gameInfo.boardWidth - j;\r
13511                 while (emptycount--)\r
13512                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13513                 break;\r
13514 #if(BOARD_SIZE >= 10)\r
13515             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
13516                 p++; emptycount=10;\r
13517                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13518                 while (emptycount--)\r
13519                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13520 #endif\r
13521             } else if (isdigit(*p)) {\r
13522                 emptycount = *p++ - '0';\r
13523                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
13524                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13525                 while (emptycount--)\r
13526                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13527             } else if (*p == '+' || isalpha(*p)) {\r
13528                 if (j >= gameInfo.boardWidth) return FALSE;\r
13529                 if(*p=='+') {\r
13530                     piece = CharToPiece(*++p);\r
13531                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
13532                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
13533                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
13534                 } else piece = CharToPiece(*p++);\r
13535 \r
13536                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
13537                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
13538                     piece = (ChessSquare) (PROMOTED piece);\r
13539                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
13540                     p++;\r
13541                 }\r
13542                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
13543             } else {\r
13544                 return FALSE;\r
13545             }\r
13546         }\r
13547     }\r
13548     while (*p == '/' || *p == ' ') p++;\r
13549 \r
13550     /* [HGM] look for Crazyhouse holdings here */\r
13551     while(*p==' ') p++;\r
13552     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
13553         if(*p == '[') p++;\r
13554         if(*p == '-' ) *p++; /* empty holdings */ else {\r
13555             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
13556             /* if we would allow FEN reading to set board size, we would   */\r
13557             /* have to add holdings and shift the board read so far here   */\r
13558             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
13559                 *p++;\r
13560                 if((int) piece >= (int) BlackPawn ) {\r
13561                     i = (int)piece - (int)BlackPawn;\r
13562                     i = PieceToNumber((ChessSquare)i);\r
13563                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13564                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
13565                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
13566                 } else {\r
13567                     i = (int)piece - (int)WhitePawn;\r
13568                     i = PieceToNumber((ChessSquare)i);\r
13569                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13570                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
13571                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
13572                 }\r
13573             }\r
13574         }\r
13575         if(*p == ']') *p++;\r
13576     }\r
13577 \r
13578     while(*p == ' ') p++;\r
13579 \r
13580     /* Active color */\r
13581     switch (*p++) {\r
13582       case 'w':\r
13583         *blackPlaysFirst = FALSE;\r
13584         break;\r
13585       case 'b': \r
13586         *blackPlaysFirst = TRUE;\r
13587         break;\r
13588       default:\r
13589         return FALSE;\r
13590     }\r
13591 \r
13592     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
13593     /* return the extra info in global variiables             */\r
13594 \r
13595     /* set defaults in case FEN is incomplete */\r
13596     FENepStatus = EP_UNKNOWN;\r
13597     for(i=0; i<nrCastlingRights; i++ ) {\r
13598         FENcastlingRights[i] =\r
13599             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
13600     }   /* assume possible unless obviously impossible */\r
13601     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
13602     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
13603     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
13604     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
13605     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
13606     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
13607     FENrulePlies = 0;\r
13608 \r
13609     while(*p==' ') p++;\r
13610     if(nrCastlingRights) {\r
13611       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
13612           /* castling indicator present, so default becomes no castlings */\r
13613           for(i=0; i<nrCastlingRights; i++ ) {\r
13614                  FENcastlingRights[i] = -1;\r
13615           }\r
13616       }\r
13617       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
13618              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
13619              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
13620              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
13621         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
13622 \r
13623         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
13624             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
13625             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
13626         }\r
13627         switch(c) {\r
13628           case'K':\r
13629               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
13630               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
13631               FENcastlingRights[2] = whiteKingFile;\r
13632               break;\r
13633           case'Q':\r
13634               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
13635               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
13636               FENcastlingRights[2] = whiteKingFile;\r
13637               break;\r
13638           case'k':\r
13639               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
13640               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
13641               FENcastlingRights[5] = blackKingFile;\r
13642               break;\r
13643           case'q':\r
13644               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
13645               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
13646               FENcastlingRights[5] = blackKingFile;\r
13647           case '-':\r
13648               break;\r
13649           default: /* FRC castlings */\r
13650               if(c >= 'a') { /* black rights */\r
13651                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13652                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
13653                   if(i == BOARD_RGHT) break;\r
13654                   FENcastlingRights[5] = i;\r
13655                   c -= AAA;\r
13656                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
13657                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
13658                   if(c > i)\r
13659                       FENcastlingRights[3] = c;\r
13660                   else\r
13661                       FENcastlingRights[4] = c;\r
13662               } else { /* white rights */\r
13663                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13664                     if(board[0][i] == WhiteKing) break;\r
13665                   if(i == BOARD_RGHT) break;\r
13666                   FENcastlingRights[2] = i;\r
13667                   c -= AAA - 'a' + 'A';\r
13668                   if(board[0][c] >= WhiteKing) break;\r
13669                   if(c > i)\r
13670                       FENcastlingRights[0] = c;\r
13671                   else\r
13672                       FENcastlingRights[1] = c;\r
13673               }\r
13674         }\r
13675       }\r
13676     if (appData.debugMode) {\r
13677         fprintf(debugFP, "FEN castling rights:");\r
13678         for(i=0; i<nrCastlingRights; i++)\r
13679         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
13680         fprintf(debugFP, "\n");\r
13681     }\r
13682 \r
13683       while(*p==' ') p++;\r
13684     }\r
13685 \r
13686     /* read e.p. field in games that know e.p. capture */\r
13687     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13688        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13689       if(*p=='-') {\r
13690         p++; FENepStatus = EP_NONE;\r
13691       } else {\r
13692          char c = *p++ - AAA;\r
13693 \r
13694          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
13695          if(*p >= '0' && *p <='9') *p++;\r
13696          FENepStatus = c;\r
13697       }\r
13698     }\r
13699 \r
13700 \r
13701     if(sscanf(p, "%d", &i) == 1) {\r
13702         FENrulePlies = i; /* 50-move ply counter */\r
13703         /* (The move number is still ignored)    */\r
13704     }\r
13705 \r
13706     return TRUE;\r
13707 }\r
13708       \r
13709 void\r
13710 EditPositionPasteFEN(char *fen)\r
13711 {\r
13712   if (fen != NULL) {\r
13713     Board initial_position;\r
13714 \r
13715     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
13716       DisplayError(_("Bad FEN position in clipboard"), 0);\r
13717       return ;\r
13718     } else {\r
13719       int savedBlackPlaysFirst = blackPlaysFirst;\r
13720       EditPositionEvent();\r
13721       blackPlaysFirst = savedBlackPlaysFirst;\r
13722       CopyBoard(boards[0], initial_position);\r
13723           /* [HGM] copy FEN attributes as well */\r
13724           {   int i;\r
13725               initialRulePlies = FENrulePlies;\r
13726               epStatus[0] = FENepStatus;\r
13727               for( i=0; i<nrCastlingRights; i++ )\r
13728                   castlingRights[0][i] = FENcastlingRights[i];\r
13729           }\r
13730       EditPositionDone();\r
13731       DisplayBothClocks();\r
13732       DrawPosition(FALSE, boards[currentMove]);\r
13733     }\r
13734   }\r
13735 }\r