dd5106f4f31034d020baff908997a6db6d0fffd2
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "evalgraph.h"
133 #include "gettext.h"
134
135 #ifdef ENABLE_NLS
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
139 #else
140 # ifdef WIN32
141 #   define _(s) T_(s)
142 #   define N_(s) s
143 # else
144 #   define _(s) (s)
145 #   define N_(s) s
146 #   define T_(s) s
147 # endif
148 #endif
149
150
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153                          char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155                       char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244 Boolean abortMatch;
245
246 extern int tinyLayout, smallLayout;
247 ChessProgramStats programStats;
248 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 int endPV = -1;
250 static int exiting = 0; /* [HGM] moved to top */
251 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
252 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
253 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
254 int partnerHighlight[2];
255 Boolean partnerBoardValid = 0;
256 char partnerStatus[MSG_SIZ];
257 Boolean partnerUp;
258 Boolean originalFlip;
259 Boolean twoBoards = 0;
260 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
261 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
262 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
263 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
264 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
265 int opponentKibitzes;
266 int lastSavedGame; /* [HGM] save: ID of game */
267 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
268 extern int chatCount;
269 int chattingPartner;
270 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
271 char lastMsg[MSG_SIZ];
272 ChessSquare pieceSweep = EmptySquare;
273 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
274 int promoDefaultAltered;
275 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
276
277 /* States for ics_getting_history */
278 #define H_FALSE 0
279 #define H_REQUESTED 1
280 #define H_GOT_REQ_HEADER 2
281 #define H_GOT_UNREQ_HEADER 3
282 #define H_GETTING_MOVES 4
283 #define H_GOT_UNWANTED_HEADER 5
284
285 /* whosays values for GameEnds */
286 #define GE_ICS 0
287 #define GE_ENGINE 1
288 #define GE_PLAYER 2
289 #define GE_FILE 3
290 #define GE_XBOARD 4
291 #define GE_ENGINE1 5
292 #define GE_ENGINE2 6
293
294 /* Maximum number of games in a cmail message */
295 #define CMAIL_MAX_GAMES 20
296
297 /* Different types of move when calling RegisterMove */
298 #define CMAIL_MOVE   0
299 #define CMAIL_RESIGN 1
300 #define CMAIL_DRAW   2
301 #define CMAIL_ACCEPT 3
302
303 /* Different types of result to remember for each game */
304 #define CMAIL_NOT_RESULT 0
305 #define CMAIL_OLD_RESULT 1
306 #define CMAIL_NEW_RESULT 2
307
308 /* Telnet protocol constants */
309 #define TN_WILL 0373
310 #define TN_WONT 0374
311 #define TN_DO   0375
312 #define TN_DONT 0376
313 #define TN_IAC  0377
314 #define TN_ECHO 0001
315 #define TN_SGA  0003
316 #define TN_PORT 23
317
318 char*
319 safeStrCpy (char *dst, const char *src, size_t count)
320 { // [HGM] made safe
321   int i;
322   assert( dst != NULL );
323   assert( src != NULL );
324   assert( count > 0 );
325
326   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
327   if(  i == count && dst[count-1] != NULLCHAR)
328     {
329       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
330       if(appData.debugMode)
331       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
332     }
333
334   return dst;
335 }
336
337 /* Some compiler can't cast u64 to double
338  * This function do the job for us:
339
340  * We use the highest bit for cast, this only
341  * works if the highest bit is not
342  * in use (This should not happen)
343  *
344  * We used this for all compiler
345  */
346 double
347 u64ToDouble (u64 value)
348 {
349   double r;
350   u64 tmp = value & u64Const(0x7fffffffffffffff);
351   r = (double)(s64)tmp;
352   if (value & u64Const(0x8000000000000000))
353        r +=  9.2233720368547758080e18; /* 2^63 */
354  return r;
355 }
356
357 /* Fake up flags for now, as we aren't keeping track of castling
358    availability yet. [HGM] Change of logic: the flag now only
359    indicates the type of castlings allowed by the rule of the game.
360    The actual rights themselves are maintained in the array
361    castlingRights, as part of the game history, and are not probed
362    by this function.
363  */
364 int
365 PosFlags (index)
366 {
367   int flags = F_ALL_CASTLE_OK;
368   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
369   switch (gameInfo.variant) {
370   case VariantSuicide:
371     flags &= ~F_ALL_CASTLE_OK;
372   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
373     flags |= F_IGNORE_CHECK;
374   case VariantLosers:
375     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
376     break;
377   case VariantAtomic:
378     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
379     break;
380   case VariantKriegspiel:
381     flags |= F_KRIEGSPIEL_CAPTURE;
382     break;
383   case VariantCapaRandom:
384   case VariantFischeRandom:
385     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
386   case VariantNoCastle:
387   case VariantShatranj:
388   case VariantCourier:
389   case VariantMakruk:
390   case VariantGrand:
391     flags &= ~F_ALL_CASTLE_OK;
392     break;
393   default:
394     break;
395   }
396   return flags;
397 }
398
399 FILE *gameFileFP, *debugFP, *serverFP;
400 char *currentDebugFile; // [HGM] debug split: to remember name
401
402 /*
403     [AS] Note: sometimes, the sscanf() function is used to parse the input
404     into a fixed-size buffer. Because of this, we must be prepared to
405     receive strings as long as the size of the input buffer, which is currently
406     set to 4K for Windows and 8K for the rest.
407     So, we must either allocate sufficiently large buffers here, or
408     reduce the size of the input buffer in the input reading part.
409 */
410
411 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
412 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
413 char thinkOutput1[MSG_SIZ*10];
414
415 ChessProgramState first, second, pairing;
416
417 /* premove variables */
418 int premoveToX = 0;
419 int premoveToY = 0;
420 int premoveFromX = 0;
421 int premoveFromY = 0;
422 int premovePromoChar = 0;
423 int gotPremove = 0;
424 Boolean alarmSounded;
425 /* end premove variables */
426
427 char *ics_prefix = "$";
428 enum ICS_TYPE ics_type = ICS_GENERIC;
429
430 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
431 int pauseExamForwardMostMove = 0;
432 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
433 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
434 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
435 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
436 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
437 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
438 int whiteFlag = FALSE, blackFlag = FALSE;
439 int userOfferedDraw = FALSE;
440 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
441 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
442 int cmailMoveType[CMAIL_MAX_GAMES];
443 long ics_clock_paused = 0;
444 ProcRef icsPR = NoProc, cmailPR = NoProc;
445 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
446 GameMode gameMode = BeginningOfGame;
447 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
448 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
449 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
450 int hiddenThinkOutputState = 0; /* [AS] */
451 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
452 int adjudicateLossPlies = 6;
453 char white_holding[64], black_holding[64];
454 TimeMark lastNodeCountTime;
455 long lastNodeCount=0;
456 int shiftKey, controlKey; // [HGM] set by mouse handler
457
458 int have_sent_ICS_logon = 0;
459 int movesPerSession;
460 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
461 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
462 Boolean adjustedClock;
463 long timeControl_2; /* [AS] Allow separate time controls */
464 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
465 long timeRemaining[2][MAX_MOVES];
466 int matchGame = 0, nextGame = 0, roundNr = 0;
467 Boolean waitingForGame = FALSE;
468 TimeMark programStartTime, pauseStart;
469 char ics_handle[MSG_SIZ];
470 int have_set_title = 0;
471
472 /* animateTraining preserves the state of appData.animate
473  * when Training mode is activated. This allows the
474  * response to be animated when appData.animate == TRUE and
475  * appData.animateDragging == TRUE.
476  */
477 Boolean animateTraining;
478
479 GameInfo gameInfo;
480
481 AppData appData;
482
483 Board boards[MAX_MOVES];
484 /* [HGM] Following 7 needed for accurate legality tests: */
485 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
486 signed char  initialRights[BOARD_FILES];
487 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
488 int   initialRulePlies, FENrulePlies;
489 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
490 int loadFlag = 0;
491 Boolean shuffleOpenings;
492 int mute; // mute all sounds
493
494 // [HGM] vari: next 12 to save and restore variations
495 #define MAX_VARIATIONS 10
496 int framePtr = MAX_MOVES-1; // points to free stack entry
497 int storedGames = 0;
498 int savedFirst[MAX_VARIATIONS];
499 int savedLast[MAX_VARIATIONS];
500 int savedFramePtr[MAX_VARIATIONS];
501 char *savedDetails[MAX_VARIATIONS];
502 ChessMove savedResult[MAX_VARIATIONS];
503
504 void PushTail P((int firstMove, int lastMove));
505 Boolean PopTail P((Boolean annotate));
506 void PushInner P((int firstMove, int lastMove));
507 void PopInner P((Boolean annotate));
508 void CleanupTail P((void));
509
510 ChessSquare  FIDEArray[2][BOARD_FILES] = {
511     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
512         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
513     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
514         BlackKing, BlackBishop, BlackKnight, BlackRook }
515 };
516
517 ChessSquare twoKingsArray[2][BOARD_FILES] = {
518     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
519         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
521         BlackKing, BlackKing, BlackKnight, BlackRook }
522 };
523
524 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
525     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
526         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
527     { BlackRook, BlackMan, BlackBishop, BlackQueen,
528         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
529 };
530
531 ChessSquare SpartanArray[2][BOARD_FILES] = {
532     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
533         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
534     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
535         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
536 };
537
538 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
539     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
540         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
541     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
542         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
543 };
544
545 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
546     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
547         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
548     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
549         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
550 };
551
552 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
553     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
554         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
555     { BlackRook, BlackKnight, BlackMan, BlackFerz,
556         BlackKing, BlackMan, BlackKnight, BlackRook }
557 };
558
559
560 #if (BOARD_FILES>=10)
561 ChessSquare ShogiArray[2][BOARD_FILES] = {
562     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
563         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
564     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
565         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
566 };
567
568 ChessSquare XiangqiArray[2][BOARD_FILES] = {
569     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
570         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
571     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
572         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
573 };
574
575 ChessSquare CapablancaArray[2][BOARD_FILES] = {
576     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
577         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
579         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
580 };
581
582 ChessSquare GreatArray[2][BOARD_FILES] = {
583     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
584         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
585     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
586         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
587 };
588
589 ChessSquare JanusArray[2][BOARD_FILES] = {
590     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
591         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
592     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
593         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
594 };
595
596 ChessSquare GrandArray[2][BOARD_FILES] = {
597     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
598         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
599     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
600         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
601 };
602
603 #ifdef GOTHIC
604 ChessSquare GothicArray[2][BOARD_FILES] = {
605     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
606         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
607     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
608         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
609 };
610 #else // !GOTHIC
611 #define GothicArray CapablancaArray
612 #endif // !GOTHIC
613
614 #ifdef FALCON
615 ChessSquare FalconArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
617         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
619         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
620 };
621 #else // !FALCON
622 #define FalconArray CapablancaArray
623 #endif // !FALCON
624
625 #else // !(BOARD_FILES>=10)
626 #define XiangqiPosition FIDEArray
627 #define CapablancaArray FIDEArray
628 #define GothicArray FIDEArray
629 #define GreatArray FIDEArray
630 #endif // !(BOARD_FILES>=10)
631
632 #if (BOARD_FILES>=12)
633 ChessSquare CourierArray[2][BOARD_FILES] = {
634     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
635         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
636     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
637         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
638 };
639 #else // !(BOARD_FILES>=12)
640 #define CourierArray CapablancaArray
641 #endif // !(BOARD_FILES>=12)
642
643
644 Board initialPosition;
645
646
647 /* Convert str to a rating. Checks for special cases of "----",
648
649    "++++", etc. Also strips ()'s */
650 int
651 string_to_rating (char *str)
652 {
653   while(*str && !isdigit(*str)) ++str;
654   if (!*str)
655     return 0;   /* One of the special "no rating" cases */
656   else
657     return atoi(str);
658 }
659
660 void
661 ClearProgramStats ()
662 {
663     /* Init programStats */
664     programStats.movelist[0] = 0;
665     programStats.depth = 0;
666     programStats.nr_moves = 0;
667     programStats.moves_left = 0;
668     programStats.nodes = 0;
669     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
670     programStats.score = 0;
671     programStats.got_only_move = 0;
672     programStats.got_fail = 0;
673     programStats.line_is_book = 0;
674 }
675
676 void
677 CommonEngineInit ()
678 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
679     if (appData.firstPlaysBlack) {
680         first.twoMachinesColor = "black\n";
681         second.twoMachinesColor = "white\n";
682     } else {
683         first.twoMachinesColor = "white\n";
684         second.twoMachinesColor = "black\n";
685     }
686
687     first.other = &second;
688     second.other = &first;
689
690     { float norm = 1;
691         if(appData.timeOddsMode) {
692             norm = appData.timeOdds[0];
693             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
694         }
695         first.timeOdds  = appData.timeOdds[0]/norm;
696         second.timeOdds = appData.timeOdds[1]/norm;
697     }
698
699     if(programVersion) free(programVersion);
700     if (appData.noChessProgram) {
701         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
702         sprintf(programVersion, "%s", PACKAGE_STRING);
703     } else {
704       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
705       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
706       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
707     }
708 }
709
710 void
711 UnloadEngine (ChessProgramState *cps)
712 {
713         /* Kill off first chess program */
714         if (cps->isr != NULL)
715           RemoveInputSource(cps->isr);
716         cps->isr = NULL;
717
718         if (cps->pr != NoProc) {
719             ExitAnalyzeMode();
720             DoSleep( appData.delayBeforeQuit );
721             SendToProgram("quit\n", cps);
722             DoSleep( appData.delayAfterQuit );
723             DestroyChildProcess(cps->pr, cps->useSigterm);
724         }
725         cps->pr = NoProc;
726         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
727 }
728
729 void
730 ClearOptions (ChessProgramState *cps)
731 {
732     int i;
733     cps->nrOptions = cps->comboCnt = 0;
734     for(i=0; i<MAX_OPTIONS; i++) {
735         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
736         cps->option[i].textValue = 0;
737     }
738 }
739
740 char *engineNames[] = {
741   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
742      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
743 N_("first"),
744   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
745      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
746 N_("second")
747 };
748
749 void
750 InitEngine (ChessProgramState *cps, int n)
751 {   // [HGM] all engine initialiation put in a function that does one engine
752
753     ClearOptions(cps);
754
755     cps->which = engineNames[n];
756     cps->maybeThinking = FALSE;
757     cps->pr = NoProc;
758     cps->isr = NULL;
759     cps->sendTime = 2;
760     cps->sendDrawOffers = 1;
761
762     cps->program = appData.chessProgram[n];
763     cps->host = appData.host[n];
764     cps->dir = appData.directory[n];
765     cps->initString = appData.engInitString[n];
766     cps->computerString = appData.computerString[n];
767     cps->useSigint  = TRUE;
768     cps->useSigterm = TRUE;
769     cps->reuse = appData.reuse[n];
770     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
771     cps->useSetboard = FALSE;
772     cps->useSAN = FALSE;
773     cps->usePing = FALSE;
774     cps->lastPing = 0;
775     cps->lastPong = 0;
776     cps->usePlayother = FALSE;
777     cps->useColors = TRUE;
778     cps->useUsermove = FALSE;
779     cps->sendICS = FALSE;
780     cps->sendName = appData.icsActive;
781     cps->sdKludge = FALSE;
782     cps->stKludge = FALSE;
783     TidyProgramName(cps->program, cps->host, cps->tidy);
784     cps->matchWins = 0;
785     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
786     cps->analysisSupport = 2; /* detect */
787     cps->analyzing = FALSE;
788     cps->initDone = FALSE;
789     cps->reload = FALSE;
790
791     /* New features added by Tord: */
792     cps->useFEN960 = FALSE;
793     cps->useOOCastle = TRUE;
794     /* End of new features added by Tord. */
795     cps->fenOverride  = appData.fenOverride[n];
796
797     /* [HGM] time odds: set factor for each machine */
798     cps->timeOdds  = appData.timeOdds[n];
799
800     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
801     cps->accumulateTC = appData.accumulateTC[n];
802     cps->maxNrOfSessions = 1;
803
804     /* [HGM] debug */
805     cps->debug = FALSE;
806
807     cps->supportsNPS = UNKNOWN;
808     cps->memSize = FALSE;
809     cps->maxCores = FALSE;
810     cps->egtFormats[0] = NULLCHAR;
811
812     /* [HGM] options */
813     cps->optionSettings  = appData.engOptions[n];
814
815     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
816     cps->isUCI = appData.isUCI[n]; /* [AS] */
817     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
818
819     if (appData.protocolVersion[n] > PROTOVER
820         || appData.protocolVersion[n] < 1)
821       {
822         char buf[MSG_SIZ];
823         int len;
824
825         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
826                        appData.protocolVersion[n]);
827         if( (len >= MSG_SIZ) && appData.debugMode )
828           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
829
830         DisplayFatalError(buf, 0, 2);
831       }
832     else
833       {
834         cps->protocolVersion = appData.protocolVersion[n];
835       }
836
837     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
838     ParseFeatures(appData.featureDefaults, cps);
839 }
840
841 ChessProgramState *savCps;
842
843 void
844 LoadEngine ()
845 {
846     int i;
847     if(WaitForEngine(savCps, LoadEngine)) return;
848     CommonEngineInit(); // recalculate time odds
849     if(gameInfo.variant != StringToVariant(appData.variant)) {
850         // we changed variant when loading the engine; this forces us to reset
851         Reset(TRUE, savCps != &first);
852         EditGameEvent(); // for consistency with other path, as Reset changes mode
853     }
854     InitChessProgram(savCps, FALSE);
855     SendToProgram("force\n", savCps);
856     DisplayMessage("", "");
857     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
858     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
859     ThawUI();
860     SetGNUMode();
861 }
862
863 void
864 ReplaceEngine (ChessProgramState *cps, int n)
865 {
866     EditGameEvent();
867     UnloadEngine(cps);
868     appData.noChessProgram = FALSE;
869     appData.clockMode = TRUE;
870     InitEngine(cps, n);
871     UpdateLogos(TRUE);
872     if(n) return; // only startup first engine immediately; second can wait
873     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
874     LoadEngine();
875 }
876
877 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
878 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
879
880 static char resetOptions[] =
881         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
882         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
883         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
884         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
885
886 void
887 FloatToFront(char **list, char *engineLine)
888 {
889     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
890     int i=0;
891     if(appData.recentEngines <= 0) return;
892     TidyProgramName(engineLine, "localhost", tidy+1);
893     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
894     strncpy(buf+1, *list, MSG_SIZ-50);
895     if(p = strstr(buf, tidy)) { // tidy name appears in list
896         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
897         while(*p++ = *++q); // squeeze out
898     }
899     strcat(tidy, buf+1); // put list behind tidy name
900     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
901     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
902     ASSIGN(*list, tidy+1);
903 }
904
905 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
906
907 void
908 Load (ChessProgramState *cps, int i)
909 {
910     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
911     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
912         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
913         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
914         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
915         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
916         appData.firstProtocolVersion = PROTOVER;
917         ParseArgsFromString(buf);
918         SwapEngines(i);
919         ReplaceEngine(cps, i);
920         FloatToFront(&appData.recentEngineList, engineLine);
921         return;
922     }
923     p = engineName;
924     while(q = strchr(p, SLASH)) p = q+1;
925     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
926     if(engineDir[0] != NULLCHAR) {
927         ASSIGN(appData.directory[i], engineDir); p = engineName;
928     } else if(p != engineName) { // derive directory from engine path, when not given
929         p[-1] = 0;
930         ASSIGN(appData.directory[i], engineName);
931         p[-1] = SLASH;
932         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
933     } else { ASSIGN(appData.directory[i], "."); }
934     if(params[0]) {
935         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
936         snprintf(command, MSG_SIZ, "%s %s", p, params);
937         p = command;
938     }
939     ASSIGN(appData.chessProgram[i], p);
940     appData.isUCI[i] = isUCI;
941     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
942     appData.hasOwnBookUCI[i] = hasBook;
943     if(!nickName[0]) useNick = FALSE;
944     if(useNick) ASSIGN(appData.pgnName[i], nickName);
945     if(addToList) {
946         int len;
947         char quote;
948         q = firstChessProgramNames;
949         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
950         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
951         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
952                         quote, p, quote, appData.directory[i],
953                         useNick ? " -fn \"" : "",
954                         useNick ? nickName : "",
955                         useNick ? "\"" : "",
956                         v1 ? " -firstProtocolVersion 1" : "",
957                         hasBook ? "" : " -fNoOwnBookUCI",
958                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
959                         storeVariant ? " -variant " : "",
960                         storeVariant ? VariantName(gameInfo.variant) : "");
961         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
962         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
963         if(insert != q) insert[-1] = NULLCHAR;
964         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
965         if(q)   free(q);
966         FloatToFront(&appData.recentEngineList, buf);
967     }
968     ReplaceEngine(cps, i);
969 }
970
971 void
972 InitTimeControls ()
973 {
974     int matched, min, sec;
975     /*
976      * Parse timeControl resource
977      */
978     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
979                           appData.movesPerSession)) {
980         char buf[MSG_SIZ];
981         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
982         DisplayFatalError(buf, 0, 2);
983     }
984
985     /*
986      * Parse searchTime resource
987      */
988     if (*appData.searchTime != NULLCHAR) {
989         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
990         if (matched == 1) {
991             searchTime = min * 60;
992         } else if (matched == 2) {
993             searchTime = min * 60 + sec;
994         } else {
995             char buf[MSG_SIZ];
996             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
997             DisplayFatalError(buf, 0, 2);
998         }
999     }
1000 }
1001
1002 void
1003 InitBackEnd1 ()
1004 {
1005
1006     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1007     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1008
1009     GetTimeMark(&programStartTime);
1010     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1011     appData.seedBase = random() + (random()<<15);
1012     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1013
1014     ClearProgramStats();
1015     programStats.ok_to_send = 1;
1016     programStats.seen_stat = 0;
1017
1018     /*
1019      * Initialize game list
1020      */
1021     ListNew(&gameList);
1022
1023
1024     /*
1025      * Internet chess server status
1026      */
1027     if (appData.icsActive) {
1028         appData.matchMode = FALSE;
1029         appData.matchGames = 0;
1030 #if ZIPPY
1031         appData.noChessProgram = !appData.zippyPlay;
1032 #else
1033         appData.zippyPlay = FALSE;
1034         appData.zippyTalk = FALSE;
1035         appData.noChessProgram = TRUE;
1036 #endif
1037         if (*appData.icsHelper != NULLCHAR) {
1038             appData.useTelnet = TRUE;
1039             appData.telnetProgram = appData.icsHelper;
1040         }
1041     } else {
1042         appData.zippyTalk = appData.zippyPlay = FALSE;
1043     }
1044
1045     /* [AS] Initialize pv info list [HGM] and game state */
1046     {
1047         int i, j;
1048
1049         for( i=0; i<=framePtr; i++ ) {
1050             pvInfoList[i].depth = -1;
1051             boards[i][EP_STATUS] = EP_NONE;
1052             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1053         }
1054     }
1055
1056     InitTimeControls();
1057
1058     /* [AS] Adjudication threshold */
1059     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1060
1061     InitEngine(&first, 0);
1062     InitEngine(&second, 1);
1063     CommonEngineInit();
1064
1065     pairing.which = "pairing"; // pairing engine
1066     pairing.pr = NoProc;
1067     pairing.isr = NULL;
1068     pairing.program = appData.pairingEngine;
1069     pairing.host = "localhost";
1070     pairing.dir = ".";
1071
1072     if (appData.icsActive) {
1073         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1074     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1075         appData.clockMode = FALSE;
1076         first.sendTime = second.sendTime = 0;
1077     }
1078
1079 #if ZIPPY
1080     /* Override some settings from environment variables, for backward
1081        compatibility.  Unfortunately it's not feasible to have the env
1082        vars just set defaults, at least in xboard.  Ugh.
1083     */
1084     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1085       ZippyInit();
1086     }
1087 #endif
1088
1089     if (!appData.icsActive) {
1090       char buf[MSG_SIZ];
1091       int len;
1092
1093       /* Check for variants that are supported only in ICS mode,
1094          or not at all.  Some that are accepted here nevertheless
1095          have bugs; see comments below.
1096       */
1097       VariantClass variant = StringToVariant(appData.variant);
1098       switch (variant) {
1099       case VariantBughouse:     /* need four players and two boards */
1100       case VariantKriegspiel:   /* need to hide pieces and move details */
1101         /* case VariantFischeRandom: (Fabien: moved below) */
1102         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1103         if( (len >= MSG_SIZ) && appData.debugMode )
1104           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1105
1106         DisplayFatalError(buf, 0, 2);
1107         return;
1108
1109       case VariantUnknown:
1110       case VariantLoadable:
1111       case Variant29:
1112       case Variant30:
1113       case Variant31:
1114       case Variant32:
1115       case Variant33:
1116       case Variant34:
1117       case Variant35:
1118       case Variant36:
1119       default:
1120         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1121         if( (len >= MSG_SIZ) && appData.debugMode )
1122           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1123
1124         DisplayFatalError(buf, 0, 2);
1125         return;
1126
1127       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1128       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1129       case VariantGothic:     /* [HGM] should work */
1130       case VariantCapablanca: /* [HGM] should work */
1131       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1132       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1133       case VariantKnightmate: /* [HGM] should work */
1134       case VariantCylinder:   /* [HGM] untested */
1135       case VariantFalcon:     /* [HGM] untested */
1136       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1137                                  offboard interposition not understood */
1138       case VariantNormal:     /* definitely works! */
1139       case VariantWildCastle: /* pieces not automatically shuffled */
1140       case VariantNoCastle:   /* pieces not automatically shuffled */
1141       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1142       case VariantLosers:     /* should work except for win condition,
1143                                  and doesn't know captures are mandatory */
1144       case VariantSuicide:    /* should work except for win condition,
1145                                  and doesn't know captures are mandatory */
1146       case VariantGiveaway:   /* should work except for win condition,
1147                                  and doesn't know captures are mandatory */
1148       case VariantTwoKings:   /* should work */
1149       case VariantAtomic:     /* should work except for win condition */
1150       case Variant3Check:     /* should work except for win condition */
1151       case VariantShatranj:   /* should work except for all win conditions */
1152       case VariantMakruk:     /* should work except for draw countdown */
1153       case VariantBerolina:   /* might work if TestLegality is off */
1154       case VariantCapaRandom: /* should work */
1155       case VariantJanus:      /* should work */
1156       case VariantSuper:      /* experimental */
1157       case VariantGreat:      /* experimental, requires legality testing to be off */
1158       case VariantSChess:     /* S-Chess, should work */
1159       case VariantGrand:      /* should work */
1160       case VariantSpartan:    /* should work */
1161         break;
1162       }
1163     }
1164
1165 }
1166
1167 int
1168 NextIntegerFromString (char ** str, long * value)
1169 {
1170     int result = -1;
1171     char * s = *str;
1172
1173     while( *s == ' ' || *s == '\t' ) {
1174         s++;
1175     }
1176
1177     *value = 0;
1178
1179     if( *s >= '0' && *s <= '9' ) {
1180         while( *s >= '0' && *s <= '9' ) {
1181             *value = *value * 10 + (*s - '0');
1182             s++;
1183         }
1184
1185         result = 0;
1186     }
1187
1188     *str = s;
1189
1190     return result;
1191 }
1192
1193 int
1194 NextTimeControlFromString (char ** str, long * value)
1195 {
1196     long temp;
1197     int result = NextIntegerFromString( str, &temp );
1198
1199     if( result == 0 ) {
1200         *value = temp * 60; /* Minutes */
1201         if( **str == ':' ) {
1202             (*str)++;
1203             result = NextIntegerFromString( str, &temp );
1204             *value += temp; /* Seconds */
1205         }
1206     }
1207
1208     return result;
1209 }
1210
1211 int
1212 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1213 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1214     int result = -1, type = 0; long temp, temp2;
1215
1216     if(**str != ':') return -1; // old params remain in force!
1217     (*str)++;
1218     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1219     if( NextIntegerFromString( str, &temp ) ) return -1;
1220     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1221
1222     if(**str != '/') {
1223         /* time only: incremental or sudden-death time control */
1224         if(**str == '+') { /* increment follows; read it */
1225             (*str)++;
1226             if(**str == '!') type = *(*str)++; // Bronstein TC
1227             if(result = NextIntegerFromString( str, &temp2)) return -1;
1228             *inc = temp2 * 1000;
1229             if(**str == '.') { // read fraction of increment
1230                 char *start = ++(*str);
1231                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1232                 temp2 *= 1000;
1233                 while(start++ < *str) temp2 /= 10;
1234                 *inc += temp2;
1235             }
1236         } else *inc = 0;
1237         *moves = 0; *tc = temp * 1000; *incType = type;
1238         return 0;
1239     }
1240
1241     (*str)++; /* classical time control */
1242     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1243
1244     if(result == 0) {
1245         *moves = temp;
1246         *tc    = temp2 * 1000;
1247         *inc   = 0;
1248         *incType = type;
1249     }
1250     return result;
1251 }
1252
1253 int
1254 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1255 {   /* [HGM] get time to add from the multi-session time-control string */
1256     int incType, moves=1; /* kludge to force reading of first session */
1257     long time, increment;
1258     char *s = tcString;
1259
1260     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1261     do {
1262         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1263         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1264         if(movenr == -1) return time;    /* last move before new session     */
1265         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1266         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1267         if(!moves) return increment;     /* current session is incremental   */
1268         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1269     } while(movenr >= -1);               /* try again for next session       */
1270
1271     return 0; // no new time quota on this move
1272 }
1273
1274 int
1275 ParseTimeControl (char *tc, float ti, int mps)
1276 {
1277   long tc1;
1278   long tc2;
1279   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1280   int min, sec=0;
1281
1282   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1283   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1284       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1285   if(ti > 0) {
1286
1287     if(mps)
1288       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1289     else
1290       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1291   } else {
1292     if(mps)
1293       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1294     else
1295       snprintf(buf, MSG_SIZ, ":%s", mytc);
1296   }
1297   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1298
1299   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1300     return FALSE;
1301   }
1302
1303   if( *tc == '/' ) {
1304     /* Parse second time control */
1305     tc++;
1306
1307     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1308       return FALSE;
1309     }
1310
1311     if( tc2 == 0 ) {
1312       return FALSE;
1313     }
1314
1315     timeControl_2 = tc2 * 1000;
1316   }
1317   else {
1318     timeControl_2 = 0;
1319   }
1320
1321   if( tc1 == 0 ) {
1322     return FALSE;
1323   }
1324
1325   timeControl = tc1 * 1000;
1326
1327   if (ti >= 0) {
1328     timeIncrement = ti * 1000;  /* convert to ms */
1329     movesPerSession = 0;
1330   } else {
1331     timeIncrement = 0;
1332     movesPerSession = mps;
1333   }
1334   return TRUE;
1335 }
1336
1337 void
1338 InitBackEnd2 ()
1339 {
1340     if (appData.debugMode) {
1341         fprintf(debugFP, "%s\n", programVersion);
1342     }
1343     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1344
1345     set_cont_sequence(appData.wrapContSeq);
1346     if (appData.matchGames > 0) {
1347         appData.matchMode = TRUE;
1348     } else if (appData.matchMode) {
1349         appData.matchGames = 1;
1350     }
1351     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1352         appData.matchGames = appData.sameColorGames;
1353     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1354         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1355         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1356     }
1357     Reset(TRUE, FALSE);
1358     if (appData.noChessProgram || first.protocolVersion == 1) {
1359       InitBackEnd3();
1360     } else {
1361       /* kludge: allow timeout for initial "feature" commands */
1362       FreezeUI();
1363       DisplayMessage("", _("Starting chess program"));
1364       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1365     }
1366 }
1367
1368 int
1369 CalculateIndex (int index, int gameNr)
1370 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1371     int res;
1372     if(index > 0) return index; // fixed nmber
1373     if(index == 0) return 1;
1374     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1375     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1376     return res;
1377 }
1378
1379 int
1380 LoadGameOrPosition (int gameNr)
1381 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1382     if (*appData.loadGameFile != NULLCHAR) {
1383         if (!LoadGameFromFile(appData.loadGameFile,
1384                 CalculateIndex(appData.loadGameIndex, gameNr),
1385                               appData.loadGameFile, FALSE)) {
1386             DisplayFatalError(_("Bad game file"), 0, 1);
1387             return 0;
1388         }
1389     } else if (*appData.loadPositionFile != NULLCHAR) {
1390         if (!LoadPositionFromFile(appData.loadPositionFile,
1391                 CalculateIndex(appData.loadPositionIndex, gameNr),
1392                                   appData.loadPositionFile)) {
1393             DisplayFatalError(_("Bad position file"), 0, 1);
1394             return 0;
1395         }
1396     }
1397     return 1;
1398 }
1399
1400 void
1401 ReserveGame (int gameNr, char resChar)
1402 {
1403     FILE *tf = fopen(appData.tourneyFile, "r+");
1404     char *p, *q, c, buf[MSG_SIZ];
1405     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1406     safeStrCpy(buf, lastMsg, MSG_SIZ);
1407     DisplayMessage(_("Pick new game"), "");
1408     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1409     ParseArgsFromFile(tf);
1410     p = q = appData.results;
1411     if(appData.debugMode) {
1412       char *r = appData.participants;
1413       fprintf(debugFP, "results = '%s'\n", p);
1414       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1415       fprintf(debugFP, "\n");
1416     }
1417     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1418     nextGame = q - p;
1419     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1420     safeStrCpy(q, p, strlen(p) + 2);
1421     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1422     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1423     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1424         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1425         q[nextGame] = '*';
1426     }
1427     fseek(tf, -(strlen(p)+4), SEEK_END);
1428     c = fgetc(tf);
1429     if(c != '"') // depending on DOS or Unix line endings we can be one off
1430          fseek(tf, -(strlen(p)+2), SEEK_END);
1431     else fseek(tf, -(strlen(p)+3), SEEK_END);
1432     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1433     DisplayMessage(buf, "");
1434     free(p); appData.results = q;
1435     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1436        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1437       int round = appData.defaultMatchGames * appData.tourneyType;
1438       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1439          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1440         UnloadEngine(&first);  // next game belongs to other pairing;
1441         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1442     }
1443     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1444 }
1445
1446 void
1447 MatchEvent (int mode)
1448 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1449         int dummy;
1450         if(matchMode) { // already in match mode: switch it off
1451             abortMatch = TRUE;
1452             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1453             return;
1454         }
1455 //      if(gameMode != BeginningOfGame) {
1456 //          DisplayError(_("You can only start a match from the initial position."), 0);
1457 //          return;
1458 //      }
1459         abortMatch = FALSE;
1460         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1461         /* Set up machine vs. machine match */
1462         nextGame = 0;
1463         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1464         if(appData.tourneyFile[0]) {
1465             ReserveGame(-1, 0);
1466             if(nextGame > appData.matchGames) {
1467                 char buf[MSG_SIZ];
1468                 if(strchr(appData.results, '*') == NULL) {
1469                     FILE *f;
1470                     appData.tourneyCycles++;
1471                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1472                         fclose(f);
1473                         NextTourneyGame(-1, &dummy);
1474                         ReserveGame(-1, 0);
1475                         if(nextGame <= appData.matchGames) {
1476                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1477                             matchMode = mode;
1478                             ScheduleDelayedEvent(NextMatchGame, 10000);
1479                             return;
1480                         }
1481                     }
1482                 }
1483                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1484                 DisplayError(buf, 0);
1485                 appData.tourneyFile[0] = 0;
1486                 return;
1487             }
1488         } else
1489         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1490             DisplayFatalError(_("Can't have a match with no chess programs"),
1491                               0, 2);
1492             return;
1493         }
1494         matchMode = mode;
1495         matchGame = roundNr = 1;
1496         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1497         NextMatchGame();
1498 }
1499
1500 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1501
1502 void
1503 InitBackEnd3 P((void))
1504 {
1505     GameMode initialMode;
1506     char buf[MSG_SIZ];
1507     int err, len;
1508
1509     InitChessProgram(&first, startedFromSetupPosition);
1510
1511     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1512         free(programVersion);
1513         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1514         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1515         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1516     }
1517
1518     if (appData.icsActive) {
1519 #ifdef WIN32
1520         /* [DM] Make a console window if needed [HGM] merged ifs */
1521         ConsoleCreate();
1522 #endif
1523         err = establish();
1524         if (err != 0)
1525           {
1526             if (*appData.icsCommPort != NULLCHAR)
1527               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1528                              appData.icsCommPort);
1529             else
1530               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1531                         appData.icsHost, appData.icsPort);
1532
1533             if( (len >= MSG_SIZ) && appData.debugMode )
1534               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1535
1536             DisplayFatalError(buf, err, 1);
1537             return;
1538         }
1539         SetICSMode();
1540         telnetISR =
1541           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1542         fromUserISR =
1543           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1544         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1545             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1546     } else if (appData.noChessProgram) {
1547         SetNCPMode();
1548     } else {
1549         SetGNUMode();
1550     }
1551
1552     if (*appData.cmailGameName != NULLCHAR) {
1553         SetCmailMode();
1554         OpenLoopback(&cmailPR);
1555         cmailISR =
1556           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1557     }
1558
1559     ThawUI();
1560     DisplayMessage("", "");
1561     if (StrCaseCmp(appData.initialMode, "") == 0) {
1562       initialMode = BeginningOfGame;
1563       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1564         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1565         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1566         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1567         ModeHighlight();
1568       }
1569     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1570       initialMode = TwoMachinesPlay;
1571     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1572       initialMode = AnalyzeFile;
1573     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1574       initialMode = AnalyzeMode;
1575     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1576       initialMode = MachinePlaysWhite;
1577     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1578       initialMode = MachinePlaysBlack;
1579     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1580       initialMode = EditGame;
1581     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1582       initialMode = EditPosition;
1583     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1584       initialMode = Training;
1585     } else {
1586       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1587       if( (len >= MSG_SIZ) && appData.debugMode )
1588         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1589
1590       DisplayFatalError(buf, 0, 2);
1591       return;
1592     }
1593
1594     if (appData.matchMode) {
1595         if(appData.tourneyFile[0]) { // start tourney from command line
1596             FILE *f;
1597             if(f = fopen(appData.tourneyFile, "r")) {
1598                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1599                 fclose(f);
1600                 appData.clockMode = TRUE;
1601                 SetGNUMode();
1602             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1603         }
1604         MatchEvent(TRUE);
1605     } else if (*appData.cmailGameName != NULLCHAR) {
1606         /* Set up cmail mode */
1607         ReloadCmailMsgEvent(TRUE);
1608     } else {
1609         /* Set up other modes */
1610         if (initialMode == AnalyzeFile) {
1611           if (*appData.loadGameFile == NULLCHAR) {
1612             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1613             return;
1614           }
1615         }
1616         if (*appData.loadGameFile != NULLCHAR) {
1617             (void) LoadGameFromFile(appData.loadGameFile,
1618                                     appData.loadGameIndex,
1619                                     appData.loadGameFile, TRUE);
1620         } else if (*appData.loadPositionFile != NULLCHAR) {
1621             (void) LoadPositionFromFile(appData.loadPositionFile,
1622                                         appData.loadPositionIndex,
1623                                         appData.loadPositionFile);
1624             /* [HGM] try to make self-starting even after FEN load */
1625             /* to allow automatic setup of fairy variants with wtm */
1626             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1627                 gameMode = BeginningOfGame;
1628                 setboardSpoiledMachineBlack = 1;
1629             }
1630             /* [HGM] loadPos: make that every new game uses the setup */
1631             /* from file as long as we do not switch variant          */
1632             if(!blackPlaysFirst) {
1633                 startedFromPositionFile = TRUE;
1634                 CopyBoard(filePosition, boards[0]);
1635             }
1636         }
1637         if (initialMode == AnalyzeMode) {
1638           if (appData.noChessProgram) {
1639             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1640             return;
1641           }
1642           if (appData.icsActive) {
1643             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1644             return;
1645           }
1646           AnalyzeModeEvent();
1647         } else if (initialMode == AnalyzeFile) {
1648           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1649           ShowThinkingEvent();
1650           AnalyzeFileEvent();
1651           AnalysisPeriodicEvent(1);
1652         } else if (initialMode == MachinePlaysWhite) {
1653           if (appData.noChessProgram) {
1654             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1655                               0, 2);
1656             return;
1657           }
1658           if (appData.icsActive) {
1659             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1660                               0, 2);
1661             return;
1662           }
1663           MachineWhiteEvent();
1664         } else if (initialMode == MachinePlaysBlack) {
1665           if (appData.noChessProgram) {
1666             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1667                               0, 2);
1668             return;
1669           }
1670           if (appData.icsActive) {
1671             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1672                               0, 2);
1673             return;
1674           }
1675           MachineBlackEvent();
1676         } else if (initialMode == TwoMachinesPlay) {
1677           if (appData.noChessProgram) {
1678             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1679                               0, 2);
1680             return;
1681           }
1682           if (appData.icsActive) {
1683             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1684                               0, 2);
1685             return;
1686           }
1687           TwoMachinesEvent();
1688         } else if (initialMode == EditGame) {
1689           EditGameEvent();
1690         } else if (initialMode == EditPosition) {
1691           EditPositionEvent();
1692         } else if (initialMode == Training) {
1693           if (*appData.loadGameFile == NULLCHAR) {
1694             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1695             return;
1696           }
1697           TrainingEvent();
1698         }
1699     }
1700 }
1701
1702 void
1703 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1704 {
1705     DisplayBook(current+1);
1706
1707     MoveHistorySet( movelist, first, last, current, pvInfoList );
1708
1709     EvalGraphSet( first, last, current, pvInfoList );
1710
1711     MakeEngineOutputTitle();
1712 }
1713
1714 /*
1715  * Establish will establish a contact to a remote host.port.
1716  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1717  *  used to talk to the host.
1718  * Returns 0 if okay, error code if not.
1719  */
1720 int
1721 establish ()
1722 {
1723     char buf[MSG_SIZ];
1724
1725     if (*appData.icsCommPort != NULLCHAR) {
1726         /* Talk to the host through a serial comm port */
1727         return OpenCommPort(appData.icsCommPort, &icsPR);
1728
1729     } else if (*appData.gateway != NULLCHAR) {
1730         if (*appData.remoteShell == NULLCHAR) {
1731             /* Use the rcmd protocol to run telnet program on a gateway host */
1732             snprintf(buf, sizeof(buf), "%s %s %s",
1733                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1734             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1735
1736         } else {
1737             /* Use the rsh program to run telnet program on a gateway host */
1738             if (*appData.remoteUser == NULLCHAR) {
1739                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1740                         appData.gateway, appData.telnetProgram,
1741                         appData.icsHost, appData.icsPort);
1742             } else {
1743                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1744                         appData.remoteShell, appData.gateway,
1745                         appData.remoteUser, appData.telnetProgram,
1746                         appData.icsHost, appData.icsPort);
1747             }
1748             return StartChildProcess(buf, "", &icsPR);
1749
1750         }
1751     } else if (appData.useTelnet) {
1752         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1753
1754     } else {
1755         /* TCP socket interface differs somewhat between
1756            Unix and NT; handle details in the front end.
1757            */
1758         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1759     }
1760 }
1761
1762 void
1763 EscapeExpand (char *p, char *q)
1764 {       // [HGM] initstring: routine to shape up string arguments
1765         while(*p++ = *q++) if(p[-1] == '\\')
1766             switch(*q++) {
1767                 case 'n': p[-1] = '\n'; break;
1768                 case 'r': p[-1] = '\r'; break;
1769                 case 't': p[-1] = '\t'; break;
1770                 case '\\': p[-1] = '\\'; break;
1771                 case 0: *p = 0; return;
1772                 default: p[-1] = q[-1]; break;
1773             }
1774 }
1775
1776 void
1777 show_bytes (FILE *fp, char *buf, int count)
1778 {
1779     while (count--) {
1780         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1781             fprintf(fp, "\\%03o", *buf & 0xff);
1782         } else {
1783             putc(*buf, fp);
1784         }
1785         buf++;
1786     }
1787     fflush(fp);
1788 }
1789
1790 /* Returns an errno value */
1791 int
1792 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1793 {
1794     char buf[8192], *p, *q, *buflim;
1795     int left, newcount, outcount;
1796
1797     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1798         *appData.gateway != NULLCHAR) {
1799         if (appData.debugMode) {
1800             fprintf(debugFP, ">ICS: ");
1801             show_bytes(debugFP, message, count);
1802             fprintf(debugFP, "\n");
1803         }
1804         return OutputToProcess(pr, message, count, outError);
1805     }
1806
1807     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1808     p = message;
1809     q = buf;
1810     left = count;
1811     newcount = 0;
1812     while (left) {
1813         if (q >= buflim) {
1814             if (appData.debugMode) {
1815                 fprintf(debugFP, ">ICS: ");
1816                 show_bytes(debugFP, buf, newcount);
1817                 fprintf(debugFP, "\n");
1818             }
1819             outcount = OutputToProcess(pr, buf, newcount, outError);
1820             if (outcount < newcount) return -1; /* to be sure */
1821             q = buf;
1822             newcount = 0;
1823         }
1824         if (*p == '\n') {
1825             *q++ = '\r';
1826             newcount++;
1827         } else if (((unsigned char) *p) == TN_IAC) {
1828             *q++ = (char) TN_IAC;
1829             newcount ++;
1830         }
1831         *q++ = *p++;
1832         newcount++;
1833         left--;
1834     }
1835     if (appData.debugMode) {
1836         fprintf(debugFP, ">ICS: ");
1837         show_bytes(debugFP, buf, newcount);
1838         fprintf(debugFP, "\n");
1839     }
1840     outcount = OutputToProcess(pr, buf, newcount, outError);
1841     if (outcount < newcount) return -1; /* to be sure */
1842     return count;
1843 }
1844
1845 void
1846 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1847 {
1848     int outError, outCount;
1849     static int gotEof = 0;
1850     static FILE *ini;
1851
1852     /* Pass data read from player on to ICS */
1853     if (count > 0) {
1854         gotEof = 0;
1855         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1856         if (outCount < count) {
1857             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1858         }
1859         if(have_sent_ICS_logon == 2) {
1860           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1861             fprintf(ini, "%s", message);
1862             have_sent_ICS_logon = 3;
1863           } else
1864             have_sent_ICS_logon = 1;
1865         } else if(have_sent_ICS_logon == 3) {
1866             fprintf(ini, "%s", message);
1867             fclose(ini);
1868           have_sent_ICS_logon = 1;
1869         }
1870     } else if (count < 0) {
1871         RemoveInputSource(isr);
1872         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1873     } else if (gotEof++ > 0) {
1874         RemoveInputSource(isr);
1875         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1876     }
1877 }
1878
1879 void
1880 KeepAlive ()
1881 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1882     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1883     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1884     SendToICS("date\n");
1885     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1886 }
1887
1888 /* added routine for printf style output to ics */
1889 void
1890 ics_printf (char *format, ...)
1891 {
1892     char buffer[MSG_SIZ];
1893     va_list args;
1894
1895     va_start(args, format);
1896     vsnprintf(buffer, sizeof(buffer), format, args);
1897     buffer[sizeof(buffer)-1] = '\0';
1898     SendToICS(buffer);
1899     va_end(args);
1900 }
1901
1902 void
1903 SendToICS (char *s)
1904 {
1905     int count, outCount, outError;
1906
1907     if (icsPR == NoProc) return;
1908
1909     count = strlen(s);
1910     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1911     if (outCount < count) {
1912         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1913     }
1914 }
1915
1916 /* This is used for sending logon scripts to the ICS. Sending
1917    without a delay causes problems when using timestamp on ICC
1918    (at least on my machine). */
1919 void
1920 SendToICSDelayed (char *s, long msdelay)
1921 {
1922     int count, outCount, outError;
1923
1924     if (icsPR == NoProc) return;
1925
1926     count = strlen(s);
1927     if (appData.debugMode) {
1928         fprintf(debugFP, ">ICS: ");
1929         show_bytes(debugFP, s, count);
1930         fprintf(debugFP, "\n");
1931     }
1932     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1933                                       msdelay);
1934     if (outCount < count) {
1935         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1936     }
1937 }
1938
1939
1940 /* Remove all highlighting escape sequences in s
1941    Also deletes any suffix starting with '('
1942    */
1943 char *
1944 StripHighlightAndTitle (char *s)
1945 {
1946     static char retbuf[MSG_SIZ];
1947     char *p = retbuf;
1948
1949     while (*s != NULLCHAR) {
1950         while (*s == '\033') {
1951             while (*s != NULLCHAR && !isalpha(*s)) s++;
1952             if (*s != NULLCHAR) s++;
1953         }
1954         while (*s != NULLCHAR && *s != '\033') {
1955             if (*s == '(' || *s == '[') {
1956                 *p = NULLCHAR;
1957                 return retbuf;
1958             }
1959             *p++ = *s++;
1960         }
1961     }
1962     *p = NULLCHAR;
1963     return retbuf;
1964 }
1965
1966 /* Remove all highlighting escape sequences in s */
1967 char *
1968 StripHighlight (char *s)
1969 {
1970     static char retbuf[MSG_SIZ];
1971     char *p = retbuf;
1972
1973     while (*s != NULLCHAR) {
1974         while (*s == '\033') {
1975             while (*s != NULLCHAR && !isalpha(*s)) s++;
1976             if (*s != NULLCHAR) s++;
1977         }
1978         while (*s != NULLCHAR && *s != '\033') {
1979             *p++ = *s++;
1980         }
1981     }
1982     *p = NULLCHAR;
1983     return retbuf;
1984 }
1985
1986 char *variantNames[] = VARIANT_NAMES;
1987 char *
1988 VariantName (VariantClass v)
1989 {
1990     return variantNames[v];
1991 }
1992
1993
1994 /* Identify a variant from the strings the chess servers use or the
1995    PGN Variant tag names we use. */
1996 VariantClass
1997 StringToVariant (char *e)
1998 {
1999     char *p;
2000     int wnum = -1;
2001     VariantClass v = VariantNormal;
2002     int i, found = FALSE;
2003     char buf[MSG_SIZ];
2004     int len;
2005
2006     if (!e) return v;
2007
2008     /* [HGM] skip over optional board-size prefixes */
2009     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2010         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2011         while( *e++ != '_');
2012     }
2013
2014     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2015         v = VariantNormal;
2016         found = TRUE;
2017     } else
2018     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2019       if (StrCaseStr(e, variantNames[i])) {
2020         v = (VariantClass) i;
2021         found = TRUE;
2022         break;
2023       }
2024     }
2025
2026     if (!found) {
2027       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2028           || StrCaseStr(e, "wild/fr")
2029           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2030         v = VariantFischeRandom;
2031       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2032                  (i = 1, p = StrCaseStr(e, "w"))) {
2033         p += i;
2034         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2035         if (isdigit(*p)) {
2036           wnum = atoi(p);
2037         } else {
2038           wnum = -1;
2039         }
2040         switch (wnum) {
2041         case 0: /* FICS only, actually */
2042         case 1:
2043           /* Castling legal even if K starts on d-file */
2044           v = VariantWildCastle;
2045           break;
2046         case 2:
2047         case 3:
2048         case 4:
2049           /* Castling illegal even if K & R happen to start in
2050              normal positions. */
2051           v = VariantNoCastle;
2052           break;
2053         case 5:
2054         case 7:
2055         case 8:
2056         case 10:
2057         case 11:
2058         case 12:
2059         case 13:
2060         case 14:
2061         case 15:
2062         case 18:
2063         case 19:
2064           /* Castling legal iff K & R start in normal positions */
2065           v = VariantNormal;
2066           break;
2067         case 6:
2068         case 20:
2069         case 21:
2070           /* Special wilds for position setup; unclear what to do here */
2071           v = VariantLoadable;
2072           break;
2073         case 9:
2074           /* Bizarre ICC game */
2075           v = VariantTwoKings;
2076           break;
2077         case 16:
2078           v = VariantKriegspiel;
2079           break;
2080         case 17:
2081           v = VariantLosers;
2082           break;
2083         case 22:
2084           v = VariantFischeRandom;
2085           break;
2086         case 23:
2087           v = VariantCrazyhouse;
2088           break;
2089         case 24:
2090           v = VariantBughouse;
2091           break;
2092         case 25:
2093           v = Variant3Check;
2094           break;
2095         case 26:
2096           /* Not quite the same as FICS suicide! */
2097           v = VariantGiveaway;
2098           break;
2099         case 27:
2100           v = VariantAtomic;
2101           break;
2102         case 28:
2103           v = VariantShatranj;
2104           break;
2105
2106         /* Temporary names for future ICC types.  The name *will* change in
2107            the next xboard/WinBoard release after ICC defines it. */
2108         case 29:
2109           v = Variant29;
2110           break;
2111         case 30:
2112           v = Variant30;
2113           break;
2114         case 31:
2115           v = Variant31;
2116           break;
2117         case 32:
2118           v = Variant32;
2119           break;
2120         case 33:
2121           v = Variant33;
2122           break;
2123         case 34:
2124           v = Variant34;
2125           break;
2126         case 35:
2127           v = Variant35;
2128           break;
2129         case 36:
2130           v = Variant36;
2131           break;
2132         case 37:
2133           v = VariantShogi;
2134           break;
2135         case 38:
2136           v = VariantXiangqi;
2137           break;
2138         case 39:
2139           v = VariantCourier;
2140           break;
2141         case 40:
2142           v = VariantGothic;
2143           break;
2144         case 41:
2145           v = VariantCapablanca;
2146           break;
2147         case 42:
2148           v = VariantKnightmate;
2149           break;
2150         case 43:
2151           v = VariantFairy;
2152           break;
2153         case 44:
2154           v = VariantCylinder;
2155           break;
2156         case 45:
2157           v = VariantFalcon;
2158           break;
2159         case 46:
2160           v = VariantCapaRandom;
2161           break;
2162         case 47:
2163           v = VariantBerolina;
2164           break;
2165         case 48:
2166           v = VariantJanus;
2167           break;
2168         case 49:
2169           v = VariantSuper;
2170           break;
2171         case 50:
2172           v = VariantGreat;
2173           break;
2174         case -1:
2175           /* Found "wild" or "w" in the string but no number;
2176              must assume it's normal chess. */
2177           v = VariantNormal;
2178           break;
2179         default:
2180           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2181           if( (len >= MSG_SIZ) && appData.debugMode )
2182             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2183
2184           DisplayError(buf, 0);
2185           v = VariantUnknown;
2186           break;
2187         }
2188       }
2189     }
2190     if (appData.debugMode) {
2191       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2192               e, wnum, VariantName(v));
2193     }
2194     return v;
2195 }
2196
2197 static int leftover_start = 0, leftover_len = 0;
2198 char star_match[STAR_MATCH_N][MSG_SIZ];
2199
2200 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2201    advance *index beyond it, and set leftover_start to the new value of
2202    *index; else return FALSE.  If pattern contains the character '*', it
2203    matches any sequence of characters not containing '\r', '\n', or the
2204    character following the '*' (if any), and the matched sequence(s) are
2205    copied into star_match.
2206    */
2207 int
2208 looking_at ( char *buf, int *index, char *pattern)
2209 {
2210     char *bufp = &buf[*index], *patternp = pattern;
2211     int star_count = 0;
2212     char *matchp = star_match[0];
2213
2214     for (;;) {
2215         if (*patternp == NULLCHAR) {
2216             *index = leftover_start = bufp - buf;
2217             *matchp = NULLCHAR;
2218             return TRUE;
2219         }
2220         if (*bufp == NULLCHAR) return FALSE;
2221         if (*patternp == '*') {
2222             if (*bufp == *(patternp + 1)) {
2223                 *matchp = NULLCHAR;
2224                 matchp = star_match[++star_count];
2225                 patternp += 2;
2226                 bufp++;
2227                 continue;
2228             } else if (*bufp == '\n' || *bufp == '\r') {
2229                 patternp++;
2230                 if (*patternp == NULLCHAR)
2231                   continue;
2232                 else
2233                   return FALSE;
2234             } else {
2235                 *matchp++ = *bufp++;
2236                 continue;
2237             }
2238         }
2239         if (*patternp != *bufp) return FALSE;
2240         patternp++;
2241         bufp++;
2242     }
2243 }
2244
2245 void
2246 SendToPlayer (char *data, int length)
2247 {
2248     int error, outCount;
2249     outCount = OutputToProcess(NoProc, data, length, &error);
2250     if (outCount < length) {
2251         DisplayFatalError(_("Error writing to display"), error, 1);
2252     }
2253 }
2254
2255 void
2256 PackHolding (char packed[], char *holding)
2257 {
2258     char *p = holding;
2259     char *q = packed;
2260     int runlength = 0;
2261     int curr = 9999;
2262     do {
2263         if (*p == curr) {
2264             runlength++;
2265         } else {
2266             switch (runlength) {
2267               case 0:
2268                 break;
2269               case 1:
2270                 *q++ = curr;
2271                 break;
2272               case 2:
2273                 *q++ = curr;
2274                 *q++ = curr;
2275                 break;
2276               default:
2277                 sprintf(q, "%d", runlength);
2278                 while (*q) q++;
2279                 *q++ = curr;
2280                 break;
2281             }
2282             runlength = 1;
2283             curr = *p;
2284         }
2285     } while (*p++);
2286     *q = NULLCHAR;
2287 }
2288
2289 /* Telnet protocol requests from the front end */
2290 void
2291 TelnetRequest (unsigned char ddww, unsigned char option)
2292 {
2293     unsigned char msg[3];
2294     int outCount, outError;
2295
2296     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2297
2298     if (appData.debugMode) {
2299         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2300         switch (ddww) {
2301           case TN_DO:
2302             ddwwStr = "DO";
2303             break;
2304           case TN_DONT:
2305             ddwwStr = "DONT";
2306             break;
2307           case TN_WILL:
2308             ddwwStr = "WILL";
2309             break;
2310           case TN_WONT:
2311             ddwwStr = "WONT";
2312             break;
2313           default:
2314             ddwwStr = buf1;
2315             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2316             break;
2317         }
2318         switch (option) {
2319           case TN_ECHO:
2320             optionStr = "ECHO";
2321             break;
2322           default:
2323             optionStr = buf2;
2324             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2325             break;
2326         }
2327         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2328     }
2329     msg[0] = TN_IAC;
2330     msg[1] = ddww;
2331     msg[2] = option;
2332     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2333     if (outCount < 3) {
2334         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2335     }
2336 }
2337
2338 void
2339 DoEcho ()
2340 {
2341     if (!appData.icsActive) return;
2342     TelnetRequest(TN_DO, TN_ECHO);
2343 }
2344
2345 void
2346 DontEcho ()
2347 {
2348     if (!appData.icsActive) return;
2349     TelnetRequest(TN_DONT, TN_ECHO);
2350 }
2351
2352 void
2353 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2354 {
2355     /* put the holdings sent to us by the server on the board holdings area */
2356     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2357     char p;
2358     ChessSquare piece;
2359
2360     if(gameInfo.holdingsWidth < 2)  return;
2361     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2362         return; // prevent overwriting by pre-board holdings
2363
2364     if( (int)lowestPiece >= BlackPawn ) {
2365         holdingsColumn = 0;
2366         countsColumn = 1;
2367         holdingsStartRow = BOARD_HEIGHT-1;
2368         direction = -1;
2369     } else {
2370         holdingsColumn = BOARD_WIDTH-1;
2371         countsColumn = BOARD_WIDTH-2;
2372         holdingsStartRow = 0;
2373         direction = 1;
2374     }
2375
2376     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2377         board[i][holdingsColumn] = EmptySquare;
2378         board[i][countsColumn]   = (ChessSquare) 0;
2379     }
2380     while( (p=*holdings++) != NULLCHAR ) {
2381         piece = CharToPiece( ToUpper(p) );
2382         if(piece == EmptySquare) continue;
2383         /*j = (int) piece - (int) WhitePawn;*/
2384         j = PieceToNumber(piece);
2385         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2386         if(j < 0) continue;               /* should not happen */
2387         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2388         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2389         board[holdingsStartRow+j*direction][countsColumn]++;
2390     }
2391 }
2392
2393
2394 void
2395 VariantSwitch (Board board, VariantClass newVariant)
2396 {
2397    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2398    static Board oldBoard;
2399
2400    startedFromPositionFile = FALSE;
2401    if(gameInfo.variant == newVariant) return;
2402
2403    /* [HGM] This routine is called each time an assignment is made to
2404     * gameInfo.variant during a game, to make sure the board sizes
2405     * are set to match the new variant. If that means adding or deleting
2406     * holdings, we shift the playing board accordingly
2407     * This kludge is needed because in ICS observe mode, we get boards
2408     * of an ongoing game without knowing the variant, and learn about the
2409     * latter only later. This can be because of the move list we requested,
2410     * in which case the game history is refilled from the beginning anyway,
2411     * but also when receiving holdings of a crazyhouse game. In the latter
2412     * case we want to add those holdings to the already received position.
2413     */
2414
2415
2416    if (appData.debugMode) {
2417      fprintf(debugFP, "Switch board from %s to %s\n",
2418              VariantName(gameInfo.variant), VariantName(newVariant));
2419      setbuf(debugFP, NULL);
2420    }
2421    shuffleOpenings = 0;       /* [HGM] shuffle */
2422    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2423    switch(newVariant)
2424      {
2425      case VariantShogi:
2426        newWidth = 9;  newHeight = 9;
2427        gameInfo.holdingsSize = 7;
2428      case VariantBughouse:
2429      case VariantCrazyhouse:
2430        newHoldingsWidth = 2; break;
2431      case VariantGreat:
2432        newWidth = 10;
2433      case VariantSuper:
2434        newHoldingsWidth = 2;
2435        gameInfo.holdingsSize = 8;
2436        break;
2437      case VariantGothic:
2438      case VariantCapablanca:
2439      case VariantCapaRandom:
2440        newWidth = 10;
2441      default:
2442        newHoldingsWidth = gameInfo.holdingsSize = 0;
2443      };
2444
2445    if(newWidth  != gameInfo.boardWidth  ||
2446       newHeight != gameInfo.boardHeight ||
2447       newHoldingsWidth != gameInfo.holdingsWidth ) {
2448
2449      /* shift position to new playing area, if needed */
2450      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2451        for(i=0; i<BOARD_HEIGHT; i++)
2452          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2453            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2454              board[i][j];
2455        for(i=0; i<newHeight; i++) {
2456          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2457          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2458        }
2459      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2460        for(i=0; i<BOARD_HEIGHT; i++)
2461          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2462            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2463              board[i][j];
2464      }
2465      board[HOLDINGS_SET] = 0;
2466      gameInfo.boardWidth  = newWidth;
2467      gameInfo.boardHeight = newHeight;
2468      gameInfo.holdingsWidth = newHoldingsWidth;
2469      gameInfo.variant = newVariant;
2470      InitDrawingSizes(-2, 0);
2471    } else gameInfo.variant = newVariant;
2472    CopyBoard(oldBoard, board);   // remember correctly formatted board
2473      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2474    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2475 }
2476
2477 static int loggedOn = FALSE;
2478
2479 /*-- Game start info cache: --*/
2480 int gs_gamenum;
2481 char gs_kind[MSG_SIZ];
2482 static char player1Name[128] = "";
2483 static char player2Name[128] = "";
2484 static char cont_seq[] = "\n\\   ";
2485 static int player1Rating = -1;
2486 static int player2Rating = -1;
2487 /*----------------------------*/
2488
2489 ColorClass curColor = ColorNormal;
2490 int suppressKibitz = 0;
2491
2492 // [HGM] seekgraph
2493 Boolean soughtPending = FALSE;
2494 Boolean seekGraphUp;
2495 #define MAX_SEEK_ADS 200
2496 #define SQUARE 0x80
2497 char *seekAdList[MAX_SEEK_ADS];
2498 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2499 float tcList[MAX_SEEK_ADS];
2500 char colorList[MAX_SEEK_ADS];
2501 int nrOfSeekAds = 0;
2502 int minRating = 1010, maxRating = 2800;
2503 int hMargin = 10, vMargin = 20, h, w;
2504 extern int squareSize, lineGap;
2505
2506 void
2507 PlotSeekAd (int i)
2508 {
2509         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2510         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2511         if(r < minRating+100 && r >=0 ) r = minRating+100;
2512         if(r > maxRating) r = maxRating;
2513         if(tc < 1.f) tc = 1.f;
2514         if(tc > 95.f) tc = 95.f;
2515         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2516         y = ((double)r - minRating)/(maxRating - minRating)
2517             * (h-vMargin-squareSize/8-1) + vMargin;
2518         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2519         if(strstr(seekAdList[i], " u ")) color = 1;
2520         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2521            !strstr(seekAdList[i], "bullet") &&
2522            !strstr(seekAdList[i], "blitz") &&
2523            !strstr(seekAdList[i], "standard") ) color = 2;
2524         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2525         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2526 }
2527
2528 void
2529 PlotSingleSeekAd (int i)
2530 {
2531         PlotSeekAd(i);
2532 }
2533
2534 void
2535 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2536 {
2537         char buf[MSG_SIZ], *ext = "";
2538         VariantClass v = StringToVariant(type);
2539         if(strstr(type, "wild")) {
2540             ext = type + 4; // append wild number
2541             if(v == VariantFischeRandom) type = "chess960"; else
2542             if(v == VariantLoadable) type = "setup"; else
2543             type = VariantName(v);
2544         }
2545         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2546         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2547             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2548             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2549             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2550             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2551             seekNrList[nrOfSeekAds] = nr;
2552             zList[nrOfSeekAds] = 0;
2553             seekAdList[nrOfSeekAds++] = StrSave(buf);
2554             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2555         }
2556 }
2557
2558 void
2559 EraseSeekDot (int i)
2560 {
2561     int x = xList[i], y = yList[i], d=squareSize/4, k;
2562     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2563     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2564     // now replot every dot that overlapped
2565     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2566         int xx = xList[k], yy = yList[k];
2567         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2568             DrawSeekDot(xx, yy, colorList[k]);
2569     }
2570 }
2571
2572 void
2573 RemoveSeekAd (int nr)
2574 {
2575         int i;
2576         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2577             EraseSeekDot(i);
2578             if(seekAdList[i]) free(seekAdList[i]);
2579             seekAdList[i] = seekAdList[--nrOfSeekAds];
2580             seekNrList[i] = seekNrList[nrOfSeekAds];
2581             ratingList[i] = ratingList[nrOfSeekAds];
2582             colorList[i]  = colorList[nrOfSeekAds];
2583             tcList[i] = tcList[nrOfSeekAds];
2584             xList[i]  = xList[nrOfSeekAds];
2585             yList[i]  = yList[nrOfSeekAds];
2586             zList[i]  = zList[nrOfSeekAds];
2587             seekAdList[nrOfSeekAds] = NULL;
2588             break;
2589         }
2590 }
2591
2592 Boolean
2593 MatchSoughtLine (char *line)
2594 {
2595     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2596     int nr, base, inc, u=0; char dummy;
2597
2598     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2599        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2600        (u=1) &&
2601        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2602         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2603         // match: compact and save the line
2604         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2605         return TRUE;
2606     }
2607     return FALSE;
2608 }
2609
2610 int
2611 DrawSeekGraph ()
2612 {
2613     int i;
2614     if(!seekGraphUp) return FALSE;
2615     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2616     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2617
2618     DrawSeekBackground(0, 0, w, h);
2619     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2620     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2621     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2622         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2623         yy = h-1-yy;
2624         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2625         if(i%500 == 0) {
2626             char buf[MSG_SIZ];
2627             snprintf(buf, MSG_SIZ, "%d", i);
2628             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2629         }
2630     }
2631     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2632     for(i=1; i<100; i+=(i<10?1:5)) {
2633         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2634         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2635         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2636             char buf[MSG_SIZ];
2637             snprintf(buf, MSG_SIZ, "%d", i);
2638             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2639         }
2640     }
2641     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2642     return TRUE;
2643 }
2644
2645 int
2646 SeekGraphClick (ClickType click, int x, int y, int moving)
2647 {
2648     static int lastDown = 0, displayed = 0, lastSecond;
2649     if(y < 0) return FALSE;
2650     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2651         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2652         if(!seekGraphUp) return FALSE;
2653         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2654         DrawPosition(TRUE, NULL);
2655         return TRUE;
2656     }
2657     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2658         if(click == Release || moving) return FALSE;
2659         nrOfSeekAds = 0;
2660         soughtPending = TRUE;
2661         SendToICS(ics_prefix);
2662         SendToICS("sought\n"); // should this be "sought all"?
2663     } else { // issue challenge based on clicked ad
2664         int dist = 10000; int i, closest = 0, second = 0;
2665         for(i=0; i<nrOfSeekAds; i++) {
2666             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2667             if(d < dist) { dist = d; closest = i; }
2668             second += (d - zList[i] < 120); // count in-range ads
2669             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2670         }
2671         if(dist < 120) {
2672             char buf[MSG_SIZ];
2673             second = (second > 1);
2674             if(displayed != closest || second != lastSecond) {
2675                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2676                 lastSecond = second; displayed = closest;
2677             }
2678             if(click == Press) {
2679                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2680                 lastDown = closest;
2681                 return TRUE;
2682             } // on press 'hit', only show info
2683             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2684             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2685             SendToICS(ics_prefix);
2686             SendToICS(buf);
2687             return TRUE; // let incoming board of started game pop down the graph
2688         } else if(click == Release) { // release 'miss' is ignored
2689             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2690             if(moving == 2) { // right up-click
2691                 nrOfSeekAds = 0; // refresh graph
2692                 soughtPending = TRUE;
2693                 SendToICS(ics_prefix);
2694                 SendToICS("sought\n"); // should this be "sought all"?
2695             }
2696             return TRUE;
2697         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2698         // press miss or release hit 'pop down' seek graph
2699         seekGraphUp = FALSE;
2700         DrawPosition(TRUE, NULL);
2701     }
2702     return TRUE;
2703 }
2704
2705 void
2706 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2707 {
2708 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2709 #define STARTED_NONE 0
2710 #define STARTED_MOVES 1
2711 #define STARTED_BOARD 2
2712 #define STARTED_OBSERVE 3
2713 #define STARTED_HOLDINGS 4
2714 #define STARTED_CHATTER 5
2715 #define STARTED_COMMENT 6
2716 #define STARTED_MOVES_NOHIDE 7
2717
2718     static int started = STARTED_NONE;
2719     static char parse[20000];
2720     static int parse_pos = 0;
2721     static char buf[BUF_SIZE + 1];
2722     static int firstTime = TRUE, intfSet = FALSE;
2723     static ColorClass prevColor = ColorNormal;
2724     static int savingComment = FALSE;
2725     static int cmatch = 0; // continuation sequence match
2726     char *bp;
2727     char str[MSG_SIZ];
2728     int i, oldi;
2729     int buf_len;
2730     int next_out;
2731     int tkind;
2732     int backup;    /* [DM] For zippy color lines */
2733     char *p;
2734     char talker[MSG_SIZ]; // [HGM] chat
2735     int channel;
2736
2737     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2738
2739     if (appData.debugMode) {
2740       if (!error) {
2741         fprintf(debugFP, "<ICS: ");
2742         show_bytes(debugFP, data, count);
2743         fprintf(debugFP, "\n");
2744       }
2745     }
2746
2747     if (appData.debugMode) { int f = forwardMostMove;
2748         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2749                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2750                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2751     }
2752     if (count > 0) {
2753         /* If last read ended with a partial line that we couldn't parse,
2754            prepend it to the new read and try again. */
2755         if (leftover_len > 0) {
2756             for (i=0; i<leftover_len; i++)
2757               buf[i] = buf[leftover_start + i];
2758         }
2759
2760     /* copy new characters into the buffer */
2761     bp = buf + leftover_len;
2762     buf_len=leftover_len;
2763     for (i=0; i<count; i++)
2764     {
2765         // ignore these
2766         if (data[i] == '\r')
2767             continue;
2768
2769         // join lines split by ICS?
2770         if (!appData.noJoin)
2771         {
2772             /*
2773                 Joining just consists of finding matches against the
2774                 continuation sequence, and discarding that sequence
2775                 if found instead of copying it.  So, until a match
2776                 fails, there's nothing to do since it might be the
2777                 complete sequence, and thus, something we don't want
2778                 copied.
2779             */
2780             if (data[i] == cont_seq[cmatch])
2781             {
2782                 cmatch++;
2783                 if (cmatch == strlen(cont_seq))
2784                 {
2785                     cmatch = 0; // complete match.  just reset the counter
2786
2787                     /*
2788                         it's possible for the ICS to not include the space
2789                         at the end of the last word, making our [correct]
2790                         join operation fuse two separate words.  the server
2791                         does this when the space occurs at the width setting.
2792                     */
2793                     if (!buf_len || buf[buf_len-1] != ' ')
2794                     {
2795                         *bp++ = ' ';
2796                         buf_len++;
2797                     }
2798                 }
2799                 continue;
2800             }
2801             else if (cmatch)
2802             {
2803                 /*
2804                     match failed, so we have to copy what matched before
2805                     falling through and copying this character.  In reality,
2806                     this will only ever be just the newline character, but
2807                     it doesn't hurt to be precise.
2808                 */
2809                 strncpy(bp, cont_seq, cmatch);
2810                 bp += cmatch;
2811                 buf_len += cmatch;
2812                 cmatch = 0;
2813             }
2814         }
2815
2816         // copy this char
2817         *bp++ = data[i];
2818         buf_len++;
2819     }
2820
2821         buf[buf_len] = NULLCHAR;
2822 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2823         next_out = 0;
2824         leftover_start = 0;
2825
2826         i = 0;
2827         while (i < buf_len) {
2828             /* Deal with part of the TELNET option negotiation
2829                protocol.  We refuse to do anything beyond the
2830                defaults, except that we allow the WILL ECHO option,
2831                which ICS uses to turn off password echoing when we are
2832                directly connected to it.  We reject this option
2833                if localLineEditing mode is on (always on in xboard)
2834                and we are talking to port 23, which might be a real
2835                telnet server that will try to keep WILL ECHO on permanently.
2836              */
2837             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2838                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2839                 unsigned char option;
2840                 oldi = i;
2841                 switch ((unsigned char) buf[++i]) {
2842                   case TN_WILL:
2843                     if (appData.debugMode)
2844                       fprintf(debugFP, "\n<WILL ");
2845                     switch (option = (unsigned char) buf[++i]) {
2846                       case TN_ECHO:
2847                         if (appData.debugMode)
2848                           fprintf(debugFP, "ECHO ");
2849                         /* Reply only if this is a change, according
2850                            to the protocol rules. */
2851                         if (remoteEchoOption) break;
2852                         if (appData.localLineEditing &&
2853                             atoi(appData.icsPort) == TN_PORT) {
2854                             TelnetRequest(TN_DONT, TN_ECHO);
2855                         } else {
2856                             EchoOff();
2857                             TelnetRequest(TN_DO, TN_ECHO);
2858                             remoteEchoOption = TRUE;
2859                         }
2860                         break;
2861                       default:
2862                         if (appData.debugMode)
2863                           fprintf(debugFP, "%d ", option);
2864                         /* Whatever this is, we don't want it. */
2865                         TelnetRequest(TN_DONT, option);
2866                         break;
2867                     }
2868                     break;
2869                   case TN_WONT:
2870                     if (appData.debugMode)
2871                       fprintf(debugFP, "\n<WONT ");
2872                     switch (option = (unsigned char) buf[++i]) {
2873                       case TN_ECHO:
2874                         if (appData.debugMode)
2875                           fprintf(debugFP, "ECHO ");
2876                         /* Reply only if this is a change, according
2877                            to the protocol rules. */
2878                         if (!remoteEchoOption) break;
2879                         EchoOn();
2880                         TelnetRequest(TN_DONT, TN_ECHO);
2881                         remoteEchoOption = FALSE;
2882                         break;
2883                       default:
2884                         if (appData.debugMode)
2885                           fprintf(debugFP, "%d ", (unsigned char) option);
2886                         /* Whatever this is, it must already be turned
2887                            off, because we never agree to turn on
2888                            anything non-default, so according to the
2889                            protocol rules, we don't reply. */
2890                         break;
2891                     }
2892                     break;
2893                   case TN_DO:
2894                     if (appData.debugMode)
2895                       fprintf(debugFP, "\n<DO ");
2896                     switch (option = (unsigned char) buf[++i]) {
2897                       default:
2898                         /* Whatever this is, we refuse to do it. */
2899                         if (appData.debugMode)
2900                           fprintf(debugFP, "%d ", option);
2901                         TelnetRequest(TN_WONT, option);
2902                         break;
2903                     }
2904                     break;
2905                   case TN_DONT:
2906                     if (appData.debugMode)
2907                       fprintf(debugFP, "\n<DONT ");
2908                     switch (option = (unsigned char) buf[++i]) {
2909                       default:
2910                         if (appData.debugMode)
2911                           fprintf(debugFP, "%d ", option);
2912                         /* Whatever this is, we are already not doing
2913                            it, because we never agree to do anything
2914                            non-default, so according to the protocol
2915                            rules, we don't reply. */
2916                         break;
2917                     }
2918                     break;
2919                   case TN_IAC:
2920                     if (appData.debugMode)
2921                       fprintf(debugFP, "\n<IAC ");
2922                     /* Doubled IAC; pass it through */
2923                     i--;
2924                     break;
2925                   default:
2926                     if (appData.debugMode)
2927                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2928                     /* Drop all other telnet commands on the floor */
2929                     break;
2930                 }
2931                 if (oldi > next_out)
2932                   SendToPlayer(&buf[next_out], oldi - next_out);
2933                 if (++i > next_out)
2934                   next_out = i;
2935                 continue;
2936             }
2937
2938             /* OK, this at least will *usually* work */
2939             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2940                 loggedOn = TRUE;
2941             }
2942
2943             if (loggedOn && !intfSet) {
2944                 if (ics_type == ICS_ICC) {
2945                   snprintf(str, MSG_SIZ,
2946                           "/set-quietly interface %s\n/set-quietly style 12\n",
2947                           programVersion);
2948                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2949                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2950                 } else if (ics_type == ICS_CHESSNET) {
2951                   snprintf(str, MSG_SIZ, "/style 12\n");
2952                 } else {
2953                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2954                   strcat(str, programVersion);
2955                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2956                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2957                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2958 #ifdef WIN32
2959                   strcat(str, "$iset nohighlight 1\n");
2960 #endif
2961                   strcat(str, "$iset lock 1\n$style 12\n");
2962                 }
2963                 SendToICS(str);
2964                 NotifyFrontendLogin();
2965                 intfSet = TRUE;
2966             }
2967
2968             if (started == STARTED_COMMENT) {
2969                 /* Accumulate characters in comment */
2970                 parse[parse_pos++] = buf[i];
2971                 if (buf[i] == '\n') {
2972                     parse[parse_pos] = NULLCHAR;
2973                     if(chattingPartner>=0) {
2974                         char mess[MSG_SIZ];
2975                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2976                         OutputChatMessage(chattingPartner, mess);
2977                         chattingPartner = -1;
2978                         next_out = i+1; // [HGM] suppress printing in ICS window
2979                     } else
2980                     if(!suppressKibitz) // [HGM] kibitz
2981                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2982                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2983                         int nrDigit = 0, nrAlph = 0, j;
2984                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2985                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2986                         parse[parse_pos] = NULLCHAR;
2987                         // try to be smart: if it does not look like search info, it should go to
2988                         // ICS interaction window after all, not to engine-output window.
2989                         for(j=0; j<parse_pos; j++) { // count letters and digits
2990                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2991                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2992                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2993                         }
2994                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2995                             int depth=0; float score;
2996                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2997                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2998                                 pvInfoList[forwardMostMove-1].depth = depth;
2999                                 pvInfoList[forwardMostMove-1].score = 100*score;
3000                             }
3001                             OutputKibitz(suppressKibitz, parse);
3002                         } else {
3003                             char tmp[MSG_SIZ];
3004                             if(gameMode == IcsObserving) // restore original ICS messages
3005                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3006                             else
3007                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3008                             SendToPlayer(tmp, strlen(tmp));
3009                         }
3010                         next_out = i+1; // [HGM] suppress printing in ICS window
3011                     }
3012                     started = STARTED_NONE;
3013                 } else {
3014                     /* Don't match patterns against characters in comment */
3015                     i++;
3016                     continue;
3017                 }
3018             }
3019             if (started == STARTED_CHATTER) {
3020                 if (buf[i] != '\n') {
3021                     /* Don't match patterns against characters in chatter */
3022                     i++;
3023                     continue;
3024                 }
3025                 started = STARTED_NONE;
3026                 if(suppressKibitz) next_out = i+1;
3027             }
3028
3029             /* Kludge to deal with rcmd protocol */
3030             if (firstTime && looking_at(buf, &i, "\001*")) {
3031                 DisplayFatalError(&buf[1], 0, 1);
3032                 continue;
3033             } else {
3034                 firstTime = FALSE;
3035             }
3036
3037             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3038                 ics_type = ICS_ICC;
3039                 ics_prefix = "/";
3040                 if (appData.debugMode)
3041                   fprintf(debugFP, "ics_type %d\n", ics_type);
3042                 continue;
3043             }
3044             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3045                 ics_type = ICS_FICS;
3046                 ics_prefix = "$";
3047                 if (appData.debugMode)
3048                   fprintf(debugFP, "ics_type %d\n", ics_type);
3049                 continue;
3050             }
3051             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3052                 ics_type = ICS_CHESSNET;
3053                 ics_prefix = "/";
3054                 if (appData.debugMode)
3055                   fprintf(debugFP, "ics_type %d\n", ics_type);
3056                 continue;
3057             }
3058
3059             if (!loggedOn &&
3060                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3061                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3062                  looking_at(buf, &i, "will be \"*\""))) {
3063               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3064               continue;
3065             }
3066
3067             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3068               char buf[MSG_SIZ];
3069               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3070               DisplayIcsInteractionTitle(buf);
3071               have_set_title = TRUE;
3072             }
3073
3074             /* skip finger notes */
3075             if (started == STARTED_NONE &&
3076                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3077                  (buf[i] == '1' && buf[i+1] == '0')) &&
3078                 buf[i+2] == ':' && buf[i+3] == ' ') {
3079               started = STARTED_CHATTER;
3080               i += 3;
3081               continue;
3082             }
3083
3084             oldi = i;
3085             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3086             if(appData.seekGraph) {
3087                 if(soughtPending && MatchSoughtLine(buf+i)) {
3088                     i = strstr(buf+i, "rated") - buf;
3089                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3090                     next_out = leftover_start = i;
3091                     started = STARTED_CHATTER;
3092                     suppressKibitz = TRUE;
3093                     continue;
3094                 }
3095                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3096                         && looking_at(buf, &i, "* ads displayed")) {
3097                     soughtPending = FALSE;
3098                     seekGraphUp = TRUE;
3099                     DrawSeekGraph();
3100                     continue;
3101                 }
3102                 if(appData.autoRefresh) {
3103                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3104                         int s = (ics_type == ICS_ICC); // ICC format differs
3105                         if(seekGraphUp)
3106                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3107                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3108                         looking_at(buf, &i, "*% "); // eat prompt
3109                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3110                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3111                         next_out = i; // suppress
3112                         continue;
3113                     }
3114                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3115                         char *p = star_match[0];
3116                         while(*p) {
3117                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3118                             while(*p && *p++ != ' '); // next
3119                         }
3120                         looking_at(buf, &i, "*% "); // eat prompt
3121                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3122                         next_out = i;
3123                         continue;
3124                     }
3125                 }
3126             }
3127
3128             /* skip formula vars */
3129             if (started == STARTED_NONE &&
3130                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3131               started = STARTED_CHATTER;
3132               i += 3;
3133               continue;
3134             }
3135
3136             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3137             if (appData.autoKibitz && started == STARTED_NONE &&
3138                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3139                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3140                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3141                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3142                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3143                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3144                         suppressKibitz = TRUE;
3145                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3146                         next_out = i;
3147                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3148                                 && (gameMode == IcsPlayingWhite)) ||
3149                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3150                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3151                             started = STARTED_CHATTER; // own kibitz we simply discard
3152                         else {
3153                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3154                             parse_pos = 0; parse[0] = NULLCHAR;
3155                             savingComment = TRUE;
3156                             suppressKibitz = gameMode != IcsObserving ? 2 :
3157                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3158                         }
3159                         continue;
3160                 } else
3161                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3162                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3163                          && atoi(star_match[0])) {
3164                     // suppress the acknowledgements of our own autoKibitz
3165                     char *p;
3166                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3167                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3168                     SendToPlayer(star_match[0], strlen(star_match[0]));
3169                     if(looking_at(buf, &i, "*% ")) // eat prompt
3170                         suppressKibitz = FALSE;
3171                     next_out = i;
3172                     continue;
3173                 }
3174             } // [HGM] kibitz: end of patch
3175
3176             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3177
3178             // [HGM] chat: intercept tells by users for which we have an open chat window
3179             channel = -1;
3180             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3181                                            looking_at(buf, &i, "* whispers:") ||
3182                                            looking_at(buf, &i, "* kibitzes:") ||
3183                                            looking_at(buf, &i, "* shouts:") ||
3184                                            looking_at(buf, &i, "* c-shouts:") ||
3185                                            looking_at(buf, &i, "--> * ") ||
3186                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3187                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3188                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3189                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3190                 int p;
3191                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3192                 chattingPartner = -1;
3193
3194                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3195                 for(p=0; p<MAX_CHAT; p++) {
3196                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3197                     talker[0] = '['; strcat(talker, "] ");
3198                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3199                     chattingPartner = p; break;
3200                     }
3201                 } else
3202                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3203                 for(p=0; p<MAX_CHAT; p++) {
3204                     if(!strcmp("kibitzes", chatPartner[p])) {
3205                         talker[0] = '['; strcat(talker, "] ");
3206                         chattingPartner = p; break;
3207                     }
3208                 } else
3209                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3210                 for(p=0; p<MAX_CHAT; p++) {
3211                     if(!strcmp("whispers", chatPartner[p])) {
3212                         talker[0] = '['; strcat(talker, "] ");
3213                         chattingPartner = p; break;
3214                     }
3215                 } else
3216                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3217                   if(buf[i-8] == '-' && buf[i-3] == 't')
3218                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3219                     if(!strcmp("c-shouts", chatPartner[p])) {
3220                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3221                         chattingPartner = p; break;
3222                     }
3223                   }
3224                   if(chattingPartner < 0)
3225                   for(p=0; p<MAX_CHAT; p++) {
3226                     if(!strcmp("shouts", chatPartner[p])) {
3227                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3228                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3229                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3230                         chattingPartner = p; break;
3231                     }
3232                   }
3233                 }
3234                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3235                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3236                     talker[0] = 0; Colorize(ColorTell, FALSE);
3237                     chattingPartner = p; break;
3238                 }
3239                 if(chattingPartner<0) i = oldi; else {
3240                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3241                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3242                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243                     started = STARTED_COMMENT;
3244                     parse_pos = 0; parse[0] = NULLCHAR;
3245                     savingComment = 3 + chattingPartner; // counts as TRUE
3246                     suppressKibitz = TRUE;
3247                     continue;
3248                 }
3249             } // [HGM] chat: end of patch
3250
3251           backup = i;
3252             if (appData.zippyTalk || appData.zippyPlay) {
3253                 /* [DM] Backup address for color zippy lines */
3254 #if ZIPPY
3255                if (loggedOn == TRUE)
3256                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3257                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3258 #endif
3259             } // [DM] 'else { ' deleted
3260                 if (
3261                     /* Regular tells and says */
3262                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3263                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3264                     looking_at(buf, &i, "* says: ") ||
3265                     /* Don't color "message" or "messages" output */
3266                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3267                     looking_at(buf, &i, "*. * at *:*: ") ||
3268                     looking_at(buf, &i, "--* (*:*): ") ||
3269                     /* Message notifications (same color as tells) */
3270                     looking_at(buf, &i, "* has left a message ") ||
3271                     looking_at(buf, &i, "* just sent you a message:\n") ||
3272                     /* Whispers and kibitzes */
3273                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3274                     looking_at(buf, &i, "* kibitzes: ") ||
3275                     /* Channel tells */
3276                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3277
3278                   if (tkind == 1 && strchr(star_match[0], ':')) {
3279                       /* Avoid "tells you:" spoofs in channels */
3280                      tkind = 3;
3281                   }
3282                   if (star_match[0][0] == NULLCHAR ||
3283                       strchr(star_match[0], ' ') ||
3284                       (tkind == 3 && strchr(star_match[1], ' '))) {
3285                     /* Reject bogus matches */
3286                     i = oldi;
3287                   } else {
3288                     if (appData.colorize) {
3289                       if (oldi > next_out) {
3290                         SendToPlayer(&buf[next_out], oldi - next_out);
3291                         next_out = oldi;
3292                       }
3293                       switch (tkind) {
3294                       case 1:
3295                         Colorize(ColorTell, FALSE);
3296                         curColor = ColorTell;
3297                         break;
3298                       case 2:
3299                         Colorize(ColorKibitz, FALSE);
3300                         curColor = ColorKibitz;
3301                         break;
3302                       case 3:
3303                         p = strrchr(star_match[1], '(');
3304                         if (p == NULL) {
3305                           p = star_match[1];
3306                         } else {
3307                           p++;
3308                         }
3309                         if (atoi(p) == 1) {
3310                           Colorize(ColorChannel1, FALSE);
3311                           curColor = ColorChannel1;
3312                         } else {
3313                           Colorize(ColorChannel, FALSE);
3314                           curColor = ColorChannel;
3315                         }
3316                         break;
3317                       case 5:
3318                         curColor = ColorNormal;
3319                         break;
3320                       }
3321                     }
3322                     if (started == STARTED_NONE && appData.autoComment &&
3323                         (gameMode == IcsObserving ||
3324                          gameMode == IcsPlayingWhite ||
3325                          gameMode == IcsPlayingBlack)) {
3326                       parse_pos = i - oldi;
3327                       memcpy(parse, &buf[oldi], parse_pos);
3328                       parse[parse_pos] = NULLCHAR;
3329                       started = STARTED_COMMENT;
3330                       savingComment = TRUE;
3331                     } else {
3332                       started = STARTED_CHATTER;
3333                       savingComment = FALSE;
3334                     }
3335                     loggedOn = TRUE;
3336                     continue;
3337                   }
3338                 }
3339
3340                 if (looking_at(buf, &i, "* s-shouts: ") ||
3341                     looking_at(buf, &i, "* c-shouts: ")) {
3342                     if (appData.colorize) {
3343                         if (oldi > next_out) {
3344                             SendToPlayer(&buf[next_out], oldi - next_out);
3345                             next_out = oldi;
3346                         }
3347                         Colorize(ColorSShout, FALSE);
3348                         curColor = ColorSShout;
3349                     }
3350                     loggedOn = TRUE;
3351                     started = STARTED_CHATTER;
3352                     continue;
3353                 }
3354
3355                 if (looking_at(buf, &i, "--->")) {
3356                     loggedOn = TRUE;
3357                     continue;
3358                 }
3359
3360                 if (looking_at(buf, &i, "* shouts: ") ||
3361                     looking_at(buf, &i, "--> ")) {
3362                     if (appData.colorize) {
3363                         if (oldi > next_out) {
3364                             SendToPlayer(&buf[next_out], oldi - next_out);
3365                             next_out = oldi;
3366                         }
3367                         Colorize(ColorShout, FALSE);
3368                         curColor = ColorShout;
3369                     }
3370                     loggedOn = TRUE;
3371                     started = STARTED_CHATTER;
3372                     continue;
3373                 }
3374
3375                 if (looking_at( buf, &i, "Challenge:")) {
3376                     if (appData.colorize) {
3377                         if (oldi > next_out) {
3378                             SendToPlayer(&buf[next_out], oldi - next_out);
3379                             next_out = oldi;
3380                         }
3381                         Colorize(ColorChallenge, FALSE);
3382                         curColor = ColorChallenge;
3383                     }
3384                     loggedOn = TRUE;
3385                     continue;
3386                 }
3387
3388                 if (looking_at(buf, &i, "* offers you") ||
3389                     looking_at(buf, &i, "* offers to be") ||
3390                     looking_at(buf, &i, "* would like to") ||
3391                     looking_at(buf, &i, "* requests to") ||
3392                     looking_at(buf, &i, "Your opponent offers") ||
3393                     looking_at(buf, &i, "Your opponent requests")) {
3394
3395                     if (appData.colorize) {
3396                         if (oldi > next_out) {
3397                             SendToPlayer(&buf[next_out], oldi - next_out);
3398                             next_out = oldi;
3399                         }
3400                         Colorize(ColorRequest, FALSE);
3401                         curColor = ColorRequest;
3402                     }
3403                     continue;
3404                 }
3405
3406                 if (looking_at(buf, &i, "* (*) seeking")) {
3407                     if (appData.colorize) {
3408                         if (oldi > next_out) {
3409                             SendToPlayer(&buf[next_out], oldi - next_out);
3410                             next_out = oldi;
3411                         }
3412                         Colorize(ColorSeek, FALSE);
3413                         curColor = ColorSeek;
3414                     }
3415                     continue;
3416             }
3417
3418           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3419
3420             if (looking_at(buf, &i, "\\   ")) {
3421                 if (prevColor != ColorNormal) {
3422                     if (oldi > next_out) {
3423                         SendToPlayer(&buf[next_out], oldi - next_out);
3424                         next_out = oldi;
3425                     }
3426                     Colorize(prevColor, TRUE);
3427                     curColor = prevColor;
3428                 }
3429                 if (savingComment) {
3430                     parse_pos = i - oldi;
3431                     memcpy(parse, &buf[oldi], parse_pos);
3432                     parse[parse_pos] = NULLCHAR;
3433                     started = STARTED_COMMENT;
3434                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3435                         chattingPartner = savingComment - 3; // kludge to remember the box
3436                 } else {
3437                     started = STARTED_CHATTER;
3438                 }
3439                 continue;
3440             }
3441
3442             if (looking_at(buf, &i, "Black Strength :") ||
3443                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3444                 looking_at(buf, &i, "<10>") ||
3445                 looking_at(buf, &i, "#@#")) {
3446                 /* Wrong board style */
3447                 loggedOn = TRUE;
3448                 SendToICS(ics_prefix);
3449                 SendToICS("set style 12\n");
3450                 SendToICS(ics_prefix);
3451                 SendToICS("refresh\n");
3452                 continue;
3453             }
3454
3455             if (looking_at(buf, &i, "login:")) {
3456               if (!have_sent_ICS_logon) {
3457                 if(ICSInitScript())
3458                   have_sent_ICS_logon = 1;
3459                 else // no init script was found
3460                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3461               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3462                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3463               }
3464                 continue;
3465             }
3466
3467             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3468                 (looking_at(buf, &i, "\n<12> ") ||
3469                  looking_at(buf, &i, "<12> "))) {
3470                 loggedOn = TRUE;
3471                 if (oldi > next_out) {
3472                     SendToPlayer(&buf[next_out], oldi - next_out);
3473                 }
3474                 next_out = i;
3475                 started = STARTED_BOARD;
3476                 parse_pos = 0;
3477                 continue;
3478             }
3479
3480             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3481                 looking_at(buf, &i, "<b1> ")) {
3482                 if (oldi > next_out) {
3483                     SendToPlayer(&buf[next_out], oldi - next_out);
3484                 }
3485                 next_out = i;
3486                 started = STARTED_HOLDINGS;
3487                 parse_pos = 0;
3488                 continue;
3489             }
3490
3491             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3492                 loggedOn = TRUE;
3493                 /* Header for a move list -- first line */
3494
3495                 switch (ics_getting_history) {
3496                   case H_FALSE:
3497                     switch (gameMode) {
3498                       case IcsIdle:
3499                       case BeginningOfGame:
3500                         /* User typed "moves" or "oldmoves" while we
3501                            were idle.  Pretend we asked for these
3502                            moves and soak them up so user can step
3503                            through them and/or save them.
3504                            */
3505                         Reset(FALSE, TRUE);
3506                         gameMode = IcsObserving;
3507                         ModeHighlight();
3508                         ics_gamenum = -1;
3509                         ics_getting_history = H_GOT_UNREQ_HEADER;
3510                         break;
3511                       case EditGame: /*?*/
3512                       case EditPosition: /*?*/
3513                         /* Should above feature work in these modes too? */
3514                         /* For now it doesn't */
3515                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3516                         break;
3517                       default:
3518                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3519                         break;
3520                     }
3521                     break;
3522                   case H_REQUESTED:
3523                     /* Is this the right one? */
3524                     if (gameInfo.white && gameInfo.black &&
3525                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3526                         strcmp(gameInfo.black, star_match[2]) == 0) {
3527                         /* All is well */
3528                         ics_getting_history = H_GOT_REQ_HEADER;
3529                     }
3530                     break;
3531                   case H_GOT_REQ_HEADER:
3532                   case H_GOT_UNREQ_HEADER:
3533                   case H_GOT_UNWANTED_HEADER:
3534                   case H_GETTING_MOVES:
3535                     /* Should not happen */
3536                     DisplayError(_("Error gathering move list: two headers"), 0);
3537                     ics_getting_history = H_FALSE;
3538                     break;
3539                 }
3540
3541                 /* Save player ratings into gameInfo if needed */
3542                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3543                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3544                     (gameInfo.whiteRating == -1 ||
3545                      gameInfo.blackRating == -1)) {
3546
3547                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3548                     gameInfo.blackRating = string_to_rating(star_match[3]);
3549                     if (appData.debugMode)
3550                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3551                               gameInfo.whiteRating, gameInfo.blackRating);
3552                 }
3553                 continue;
3554             }
3555
3556             if (looking_at(buf, &i,
3557               "* * match, initial time: * minute*, increment: * second")) {
3558                 /* Header for a move list -- second line */
3559                 /* Initial board will follow if this is a wild game */
3560                 if (gameInfo.event != NULL) free(gameInfo.event);
3561                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3562                 gameInfo.event = StrSave(str);
3563                 /* [HGM] we switched variant. Translate boards if needed. */
3564                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3565                 continue;
3566             }
3567
3568             if (looking_at(buf, &i, "Move  ")) {
3569                 /* Beginning of a move list */
3570                 switch (ics_getting_history) {
3571                   case H_FALSE:
3572                     /* Normally should not happen */
3573                     /* Maybe user hit reset while we were parsing */
3574                     break;
3575                   case H_REQUESTED:
3576                     /* Happens if we are ignoring a move list that is not
3577                      * the one we just requested.  Common if the user
3578                      * tries to observe two games without turning off
3579                      * getMoveList */
3580                     break;
3581                   case H_GETTING_MOVES:
3582                     /* Should not happen */
3583                     DisplayError(_("Error gathering move list: nested"), 0);
3584                     ics_getting_history = H_FALSE;
3585                     break;
3586                   case H_GOT_REQ_HEADER:
3587                     ics_getting_history = H_GETTING_MOVES;
3588                     started = STARTED_MOVES;
3589                     parse_pos = 0;
3590                     if (oldi > next_out) {
3591                         SendToPlayer(&buf[next_out], oldi - next_out);
3592                     }
3593                     break;
3594                   case H_GOT_UNREQ_HEADER:
3595                     ics_getting_history = H_GETTING_MOVES;
3596                     started = STARTED_MOVES_NOHIDE;
3597                     parse_pos = 0;
3598                     break;
3599                   case H_GOT_UNWANTED_HEADER:
3600                     ics_getting_history = H_FALSE;
3601                     break;
3602                 }
3603                 continue;
3604             }
3605
3606             if (looking_at(buf, &i, "% ") ||
3607                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3608                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3609                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3610                     soughtPending = FALSE;
3611                     seekGraphUp = TRUE;
3612                     DrawSeekGraph();
3613                 }
3614                 if(suppressKibitz) next_out = i;
3615                 savingComment = FALSE;
3616                 suppressKibitz = 0;
3617                 switch (started) {
3618                   case STARTED_MOVES:
3619                   case STARTED_MOVES_NOHIDE:
3620                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3621                     parse[parse_pos + i - oldi] = NULLCHAR;
3622                     ParseGameHistory(parse);
3623 #if ZIPPY
3624                     if (appData.zippyPlay && first.initDone) {
3625                         FeedMovesToProgram(&first, forwardMostMove);
3626                         if (gameMode == IcsPlayingWhite) {
3627                             if (WhiteOnMove(forwardMostMove)) {
3628                                 if (first.sendTime) {
3629                                   if (first.useColors) {
3630                                     SendToProgram("black\n", &first);
3631                                   }
3632                                   SendTimeRemaining(&first, TRUE);
3633                                 }
3634                                 if (first.useColors) {
3635                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3636                                 }
3637                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3638                                 first.maybeThinking = TRUE;
3639                             } else {
3640                                 if (first.usePlayother) {
3641                                   if (first.sendTime) {
3642                                     SendTimeRemaining(&first, TRUE);
3643                                   }
3644                                   SendToProgram("playother\n", &first);
3645                                   firstMove = FALSE;
3646                                 } else {
3647                                   firstMove = TRUE;
3648                                 }
3649                             }
3650                         } else if (gameMode == IcsPlayingBlack) {
3651                             if (!WhiteOnMove(forwardMostMove)) {
3652                                 if (first.sendTime) {
3653                                   if (first.useColors) {
3654                                     SendToProgram("white\n", &first);
3655                                   }
3656                                   SendTimeRemaining(&first, FALSE);
3657                                 }
3658                                 if (first.useColors) {
3659                                   SendToProgram("black\n", &first);
3660                                 }
3661                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3662                                 first.maybeThinking = TRUE;
3663                             } else {
3664                                 if (first.usePlayother) {
3665                                   if (first.sendTime) {
3666                                     SendTimeRemaining(&first, FALSE);
3667                                   }
3668                                   SendToProgram("playother\n", &first);
3669                                   firstMove = FALSE;
3670                                 } else {
3671                                   firstMove = TRUE;
3672                                 }
3673                             }
3674                         }
3675                     }
3676 #endif
3677                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3678                         /* Moves came from oldmoves or moves command
3679                            while we weren't doing anything else.
3680                            */
3681                         currentMove = forwardMostMove;
3682                         ClearHighlights();/*!!could figure this out*/
3683                         flipView = appData.flipView;
3684                         DrawPosition(TRUE, boards[currentMove]);
3685                         DisplayBothClocks();
3686                         snprintf(str, MSG_SIZ, "%s %s %s",
3687                                 gameInfo.white, _("vs."),  gameInfo.black);
3688                         DisplayTitle(str);
3689                         gameMode = IcsIdle;
3690                     } else {
3691                         /* Moves were history of an active game */
3692                         if (gameInfo.resultDetails != NULL) {
3693                             free(gameInfo.resultDetails);
3694                             gameInfo.resultDetails = NULL;
3695                         }
3696                     }
3697                     HistorySet(parseList, backwardMostMove,
3698                                forwardMostMove, currentMove-1);
3699                     DisplayMove(currentMove - 1);
3700                     if (started == STARTED_MOVES) next_out = i;
3701                     started = STARTED_NONE;
3702                     ics_getting_history = H_FALSE;
3703                     break;
3704
3705                   case STARTED_OBSERVE:
3706                     started = STARTED_NONE;
3707                     SendToICS(ics_prefix);
3708                     SendToICS("refresh\n");
3709                     break;
3710
3711                   default:
3712                     break;
3713                 }
3714                 if(bookHit) { // [HGM] book: simulate book reply
3715                     static char bookMove[MSG_SIZ]; // a bit generous?
3716
3717                     programStats.nodes = programStats.depth = programStats.time =
3718                     programStats.score = programStats.got_only_move = 0;
3719                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3720
3721                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3722                     strcat(bookMove, bookHit);
3723                     HandleMachineMove(bookMove, &first);
3724                 }
3725                 continue;
3726             }
3727
3728             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3729                  started == STARTED_HOLDINGS ||
3730                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3731                 /* Accumulate characters in move list or board */
3732                 parse[parse_pos++] = buf[i];
3733             }
3734
3735             /* Start of game messages.  Mostly we detect start of game
3736                when the first board image arrives.  On some versions
3737                of the ICS, though, we need to do a "refresh" after starting
3738                to observe in order to get the current board right away. */
3739             if (looking_at(buf, &i, "Adding game * to observation list")) {
3740                 started = STARTED_OBSERVE;
3741                 continue;
3742             }
3743
3744             /* Handle auto-observe */
3745             if (appData.autoObserve &&
3746                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3747                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3748                 char *player;
3749                 /* Choose the player that was highlighted, if any. */
3750                 if (star_match[0][0] == '\033' ||
3751                     star_match[1][0] != '\033') {
3752                     player = star_match[0];
3753                 } else {
3754                     player = star_match[2];
3755                 }
3756                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3757                         ics_prefix, StripHighlightAndTitle(player));
3758                 SendToICS(str);
3759
3760                 /* Save ratings from notify string */
3761                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3762                 player1Rating = string_to_rating(star_match[1]);
3763                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3764                 player2Rating = string_to_rating(star_match[3]);
3765
3766                 if (appData.debugMode)
3767                   fprintf(debugFP,
3768                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3769                           player1Name, player1Rating,
3770                           player2Name, player2Rating);
3771
3772                 continue;
3773             }
3774
3775             /* Deal with automatic examine mode after a game,
3776                and with IcsObserving -> IcsExamining transition */
3777             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3778                 looking_at(buf, &i, "has made you an examiner of game *")) {
3779
3780                 int gamenum = atoi(star_match[0]);
3781                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3782                     gamenum == ics_gamenum) {
3783                     /* We were already playing or observing this game;
3784                        no need to refetch history */
3785                     gameMode = IcsExamining;
3786                     if (pausing) {
3787                         pauseExamForwardMostMove = forwardMostMove;
3788                     } else if (currentMove < forwardMostMove) {
3789                         ForwardInner(forwardMostMove);
3790                     }
3791                 } else {
3792                     /* I don't think this case really can happen */
3793                     SendToICS(ics_prefix);
3794                     SendToICS("refresh\n");
3795                 }
3796                 continue;
3797             }
3798
3799             /* Error messages */
3800 //          if (ics_user_moved) {
3801             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3802                 if (looking_at(buf, &i, "Illegal move") ||
3803                     looking_at(buf, &i, "Not a legal move") ||
3804                     looking_at(buf, &i, "Your king is in check") ||
3805                     looking_at(buf, &i, "It isn't your turn") ||
3806                     looking_at(buf, &i, "It is not your move")) {
3807                     /* Illegal move */
3808                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3809                         currentMove = forwardMostMove-1;
3810                         DisplayMove(currentMove - 1); /* before DMError */
3811                         DrawPosition(FALSE, boards[currentMove]);
3812                         SwitchClocks(forwardMostMove-1); // [HGM] race
3813                         DisplayBothClocks();
3814                     }
3815                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3816                     ics_user_moved = 0;
3817                     continue;
3818                 }
3819             }
3820
3821             if (looking_at(buf, &i, "still have time") ||
3822                 looking_at(buf, &i, "not out of time") ||
3823                 looking_at(buf, &i, "either player is out of time") ||
3824                 looking_at(buf, &i, "has timeseal; checking")) {
3825                 /* We must have called his flag a little too soon */
3826                 whiteFlag = blackFlag = FALSE;
3827                 continue;
3828             }
3829
3830             if (looking_at(buf, &i, "added * seconds to") ||
3831                 looking_at(buf, &i, "seconds were added to")) {
3832                 /* Update the clocks */
3833                 SendToICS(ics_prefix);
3834                 SendToICS("refresh\n");
3835                 continue;
3836             }
3837
3838             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3839                 ics_clock_paused = TRUE;
3840                 StopClocks();
3841                 continue;
3842             }
3843
3844             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3845                 ics_clock_paused = FALSE;
3846                 StartClocks();
3847                 continue;
3848             }
3849
3850             /* Grab player ratings from the Creating: message.
3851                Note we have to check for the special case when
3852                the ICS inserts things like [white] or [black]. */
3853             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3854                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3855                 /* star_matches:
3856                    0    player 1 name (not necessarily white)
3857                    1    player 1 rating
3858                    2    empty, white, or black (IGNORED)
3859                    3    player 2 name (not necessarily black)
3860                    4    player 2 rating
3861
3862                    The names/ratings are sorted out when the game
3863                    actually starts (below).
3864                 */
3865                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3866                 player1Rating = string_to_rating(star_match[1]);
3867                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3868                 player2Rating = string_to_rating(star_match[4]);
3869
3870                 if (appData.debugMode)
3871                   fprintf(debugFP,
3872                           "Ratings from 'Creating:' %s %d, %s %d\n",
3873                           player1Name, player1Rating,
3874                           player2Name, player2Rating);
3875
3876                 continue;
3877             }
3878
3879             /* Improved generic start/end-of-game messages */
3880             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3881                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3882                 /* If tkind == 0: */
3883                 /* star_match[0] is the game number */
3884                 /*           [1] is the white player's name */
3885                 /*           [2] is the black player's name */
3886                 /* For end-of-game: */
3887                 /*           [3] is the reason for the game end */
3888                 /*           [4] is a PGN end game-token, preceded by " " */
3889                 /* For start-of-game: */
3890                 /*           [3] begins with "Creating" or "Continuing" */
3891                 /*           [4] is " *" or empty (don't care). */
3892                 int gamenum = atoi(star_match[0]);
3893                 char *whitename, *blackname, *why, *endtoken;
3894                 ChessMove endtype = EndOfFile;
3895
3896                 if (tkind == 0) {
3897                   whitename = star_match[1];
3898                   blackname = star_match[2];
3899                   why = star_match[3];
3900                   endtoken = star_match[4];
3901                 } else {
3902                   whitename = star_match[1];
3903                   blackname = star_match[3];
3904                   why = star_match[5];
3905                   endtoken = star_match[6];
3906                 }
3907
3908                 /* Game start messages */
3909                 if (strncmp(why, "Creating ", 9) == 0 ||
3910                     strncmp(why, "Continuing ", 11) == 0) {
3911                     gs_gamenum = gamenum;
3912                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3913                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3914                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3915 #if ZIPPY
3916                     if (appData.zippyPlay) {
3917                         ZippyGameStart(whitename, blackname);
3918                     }
3919 #endif /*ZIPPY*/
3920                     partnerBoardValid = FALSE; // [HGM] bughouse
3921                     continue;
3922                 }
3923
3924                 /* Game end messages */
3925                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3926                     ics_gamenum != gamenum) {
3927                     continue;
3928                 }
3929                 while (endtoken[0] == ' ') endtoken++;
3930                 switch (endtoken[0]) {
3931                   case '*':
3932                   default:
3933                     endtype = GameUnfinished;
3934                     break;
3935                   case '0':
3936                     endtype = BlackWins;
3937                     break;
3938                   case '1':
3939                     if (endtoken[1] == '/')
3940                       endtype = GameIsDrawn;
3941                     else
3942                       endtype = WhiteWins;
3943                     break;
3944                 }
3945                 GameEnds(endtype, why, GE_ICS);
3946 #if ZIPPY
3947                 if (appData.zippyPlay && first.initDone) {
3948                     ZippyGameEnd(endtype, why);
3949                     if (first.pr == NoProc) {
3950                       /* Start the next process early so that we'll
3951                          be ready for the next challenge */
3952                       StartChessProgram(&first);
3953                     }
3954                     /* Send "new" early, in case this command takes
3955                        a long time to finish, so that we'll be ready
3956                        for the next challenge. */
3957                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3958                     Reset(TRUE, TRUE);
3959                 }
3960 #endif /*ZIPPY*/
3961                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3962                 continue;
3963             }
3964
3965             if (looking_at(buf, &i, "Removing game * from observation") ||
3966                 looking_at(buf, &i, "no longer observing game *") ||
3967                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3968                 if (gameMode == IcsObserving &&
3969                     atoi(star_match[0]) == ics_gamenum)
3970                   {
3971                       /* icsEngineAnalyze */
3972                       if (appData.icsEngineAnalyze) {
3973                             ExitAnalyzeMode();
3974                             ModeHighlight();
3975                       }
3976                       StopClocks();
3977                       gameMode = IcsIdle;
3978                       ics_gamenum = -1;
3979                       ics_user_moved = FALSE;
3980                   }
3981                 continue;
3982             }
3983
3984             if (looking_at(buf, &i, "no longer examining game *")) {
3985                 if (gameMode == IcsExamining &&
3986                     atoi(star_match[0]) == ics_gamenum)
3987                   {
3988                       gameMode = IcsIdle;
3989                       ics_gamenum = -1;
3990                       ics_user_moved = FALSE;
3991                   }
3992                 continue;
3993             }
3994
3995             /* Advance leftover_start past any newlines we find,
3996                so only partial lines can get reparsed */
3997             if (looking_at(buf, &i, "\n")) {
3998                 prevColor = curColor;
3999                 if (curColor != ColorNormal) {
4000                     if (oldi > next_out) {
4001                         SendToPlayer(&buf[next_out], oldi - next_out);
4002                         next_out = oldi;
4003                     }
4004                     Colorize(ColorNormal, FALSE);
4005                     curColor = ColorNormal;
4006                 }
4007                 if (started == STARTED_BOARD) {
4008                     started = STARTED_NONE;
4009                     parse[parse_pos] = NULLCHAR;
4010                     ParseBoard12(parse);
4011                     ics_user_moved = 0;
4012
4013                     /* Send premove here */
4014                     if (appData.premove) {
4015                       char str[MSG_SIZ];
4016                       if (currentMove == 0 &&
4017                           gameMode == IcsPlayingWhite &&
4018                           appData.premoveWhite) {
4019                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4020                         if (appData.debugMode)
4021                           fprintf(debugFP, "Sending premove:\n");
4022                         SendToICS(str);
4023                       } else if (currentMove == 1 &&
4024                                  gameMode == IcsPlayingBlack &&
4025                                  appData.premoveBlack) {
4026                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4027                         if (appData.debugMode)
4028                           fprintf(debugFP, "Sending premove:\n");
4029                         SendToICS(str);
4030                       } else if (gotPremove) {
4031                         gotPremove = 0;
4032                         ClearPremoveHighlights();
4033                         if (appData.debugMode)
4034                           fprintf(debugFP, "Sending premove:\n");
4035                           UserMoveEvent(premoveFromX, premoveFromY,
4036                                         premoveToX, premoveToY,
4037                                         premovePromoChar);
4038                       }
4039                     }
4040
4041                     /* Usually suppress following prompt */
4042                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4043                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4044                         if (looking_at(buf, &i, "*% ")) {
4045                             savingComment = FALSE;
4046                             suppressKibitz = 0;
4047                         }
4048                     }
4049                     next_out = i;
4050                 } else if (started == STARTED_HOLDINGS) {
4051                     int gamenum;
4052                     char new_piece[MSG_SIZ];
4053                     started = STARTED_NONE;
4054                     parse[parse_pos] = NULLCHAR;
4055                     if (appData.debugMode)
4056                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4057                                                         parse, currentMove);
4058                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4059                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4060                         if (gameInfo.variant == VariantNormal) {
4061                           /* [HGM] We seem to switch variant during a game!
4062                            * Presumably no holdings were displayed, so we have
4063                            * to move the position two files to the right to
4064                            * create room for them!
4065                            */
4066                           VariantClass newVariant;
4067                           switch(gameInfo.boardWidth) { // base guess on board width
4068                                 case 9:  newVariant = VariantShogi; break;
4069                                 case 10: newVariant = VariantGreat; break;
4070                                 default: newVariant = VariantCrazyhouse; break;
4071                           }
4072                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4073                           /* Get a move list just to see the header, which
4074                              will tell us whether this is really bug or zh */
4075                           if (ics_getting_history == H_FALSE) {
4076                             ics_getting_history = H_REQUESTED;
4077                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4078                             SendToICS(str);
4079                           }
4080                         }
4081                         new_piece[0] = NULLCHAR;
4082                         sscanf(parse, "game %d white [%s black [%s <- %s",
4083                                &gamenum, white_holding, black_holding,
4084                                new_piece);
4085                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4086                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4087                         /* [HGM] copy holdings to board holdings area */
4088                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4089                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4090                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4091 #if ZIPPY
4092                         if (appData.zippyPlay && first.initDone) {
4093                             ZippyHoldings(white_holding, black_holding,
4094                                           new_piece);
4095                         }
4096 #endif /*ZIPPY*/
4097                         if (tinyLayout || smallLayout) {
4098                             char wh[16], bh[16];
4099                             PackHolding(wh, white_holding);
4100                             PackHolding(bh, black_holding);
4101                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4102                                     gameInfo.white, gameInfo.black);
4103                         } else {
4104                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4105                                     gameInfo.white, white_holding, _("vs."),
4106                                     gameInfo.black, black_holding);
4107                         }
4108                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4109                         DrawPosition(FALSE, boards[currentMove]);
4110                         DisplayTitle(str);
4111                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4112                         sscanf(parse, "game %d white [%s black [%s <- %s",
4113                                &gamenum, white_holding, black_holding,
4114                                new_piece);
4115                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4116                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4117                         /* [HGM] copy holdings to partner-board holdings area */
4118                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4119                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4120                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4121                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4122                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4123                       }
4124                     }
4125                     /* Suppress following prompt */
4126                     if (looking_at(buf, &i, "*% ")) {
4127                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4128                         savingComment = FALSE;
4129                         suppressKibitz = 0;
4130                     }
4131                     next_out = i;
4132                 }
4133                 continue;
4134             }
4135
4136             i++;                /* skip unparsed character and loop back */
4137         }
4138
4139         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4140 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4141 //          SendToPlayer(&buf[next_out], i - next_out);
4142             started != STARTED_HOLDINGS && leftover_start > next_out) {
4143             SendToPlayer(&buf[next_out], leftover_start - next_out);
4144             next_out = i;
4145         }
4146
4147         leftover_len = buf_len - leftover_start;
4148         /* if buffer ends with something we couldn't parse,
4149            reparse it after appending the next read */
4150
4151     } else if (count == 0) {
4152         RemoveInputSource(isr);
4153         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4154     } else {
4155         DisplayFatalError(_("Error reading from ICS"), error, 1);
4156     }
4157 }
4158
4159
4160 /* Board style 12 looks like this:
4161
4162    <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
4163
4164  * The "<12> " is stripped before it gets to this routine.  The two
4165  * trailing 0's (flip state and clock ticking) are later addition, and
4166  * some chess servers may not have them, or may have only the first.
4167  * Additional trailing fields may be added in the future.
4168  */
4169
4170 #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"
4171
4172 #define RELATION_OBSERVING_PLAYED    0
4173 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4174 #define RELATION_PLAYING_MYMOVE      1
4175 #define RELATION_PLAYING_NOTMYMOVE  -1
4176 #define RELATION_EXAMINING           2
4177 #define RELATION_ISOLATED_BOARD     -3
4178 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4179
4180 void
4181 ParseBoard12 (char *string)
4182 {
4183 #if ZIPPY
4184     int i, takeback;
4185     char *bookHit = NULL; // [HGM] book
4186 #endif
4187     GameMode newGameMode;
4188     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4189     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4190     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4191     char to_play, board_chars[200];
4192     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4193     char black[32], white[32];
4194     Board board;
4195     int prevMove = currentMove;
4196     int ticking = 2;
4197     ChessMove moveType;
4198     int fromX, fromY, toX, toY;
4199     char promoChar;
4200     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4201     Boolean weird = FALSE, reqFlag = FALSE;
4202
4203     fromX = fromY = toX = toY = -1;
4204
4205     newGame = FALSE;
4206
4207     if (appData.debugMode)
4208       fprintf(debugFP, _("Parsing board: %s\n"), string);
4209
4210     move_str[0] = NULLCHAR;
4211     elapsed_time[0] = NULLCHAR;
4212     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4213         int  i = 0, j;
4214         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4215             if(string[i] == ' ') { ranks++; files = 0; }
4216             else files++;
4217             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4218             i++;
4219         }
4220         for(j = 0; j <i; j++) board_chars[j] = string[j];
4221         board_chars[i] = '\0';
4222         string += i + 1;
4223     }
4224     n = sscanf(string, PATTERN, &to_play, &double_push,
4225                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4226                &gamenum, white, black, &relation, &basetime, &increment,
4227                &white_stren, &black_stren, &white_time, &black_time,
4228                &moveNum, str, elapsed_time, move_str, &ics_flip,
4229                &ticking);
4230
4231     if (n < 21) {
4232         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4233         DisplayError(str, 0);
4234         return;
4235     }
4236
4237     /* Convert the move number to internal form */
4238     moveNum = (moveNum - 1) * 2;
4239     if (to_play == 'B') moveNum++;
4240     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4241       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4242                         0, 1);
4243       return;
4244     }
4245
4246     switch (relation) {
4247       case RELATION_OBSERVING_PLAYED:
4248       case RELATION_OBSERVING_STATIC:
4249         if (gamenum == -1) {
4250             /* Old ICC buglet */
4251             relation = RELATION_OBSERVING_STATIC;
4252         }
4253         newGameMode = IcsObserving;
4254         break;
4255       case RELATION_PLAYING_MYMOVE:
4256       case RELATION_PLAYING_NOTMYMOVE:
4257         newGameMode =
4258           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4259             IcsPlayingWhite : IcsPlayingBlack;
4260         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4261         break;
4262       case RELATION_EXAMINING:
4263         newGameMode = IcsExamining;
4264         break;
4265       case RELATION_ISOLATED_BOARD:
4266       default:
4267         /* Just display this board.  If user was doing something else,
4268            we will forget about it until the next board comes. */
4269         newGameMode = IcsIdle;
4270         break;
4271       case RELATION_STARTING_POSITION:
4272         newGameMode = gameMode;
4273         break;
4274     }
4275
4276     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4277         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4278          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4279       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4280       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4281       static int lastBgGame = -1;
4282       char *toSqr;
4283       for (k = 0; k < ranks; k++) {
4284         for (j = 0; j < files; j++)
4285           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4286         if(gameInfo.holdingsWidth > 1) {
4287              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4288              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4289         }
4290       }
4291       CopyBoard(partnerBoard, board);
4292       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4293         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4294         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4295       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4296       if(toSqr = strchr(str, '-')) {
4297         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4298         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4299       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4300       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4301       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4302       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4303       if(twoBoards) {
4304           DisplayWhiteClock(white_time*fac, to_play == 'W');
4305           DisplayBlackClock(black_time*fac, to_play != 'W');
4306           activePartner = to_play;
4307           if(gamenum != lastBgGame) {
4308               char buf[MSG_SIZ];
4309               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4310               DisplayTitle(buf);
4311           }
4312           lastBgGame = gamenum;
4313           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4314                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4315       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4316                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4317       DisplayMessage(partnerStatus, "");
4318         partnerBoardValid = TRUE;
4319       return;
4320     }
4321
4322     if(appData.dualBoard && appData.bgObserve) {
4323         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4324             SendToICS(ics_prefix), SendToICS("pobserve\n");
4325         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4326             char buf[MSG_SIZ];
4327             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4328             SendToICS(buf);
4329         }
4330     }
4331
4332     /* Modify behavior for initial board display on move listing
4333        of wild games.
4334        */
4335     switch (ics_getting_history) {
4336       case H_FALSE:
4337       case H_REQUESTED:
4338         break;
4339       case H_GOT_REQ_HEADER:
4340       case H_GOT_UNREQ_HEADER:
4341         /* This is the initial position of the current game */
4342         gamenum = ics_gamenum;
4343         moveNum = 0;            /* old ICS bug workaround */
4344         if (to_play == 'B') {
4345           startedFromSetupPosition = TRUE;
4346           blackPlaysFirst = TRUE;
4347           moveNum = 1;
4348           if (forwardMostMove == 0) forwardMostMove = 1;
4349           if (backwardMostMove == 0) backwardMostMove = 1;
4350           if (currentMove == 0) currentMove = 1;
4351         }
4352         newGameMode = gameMode;
4353         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4354         break;
4355       case H_GOT_UNWANTED_HEADER:
4356         /* This is an initial board that we don't want */
4357         return;
4358       case H_GETTING_MOVES:
4359         /* Should not happen */
4360         DisplayError(_("Error gathering move list: extra board"), 0);
4361         ics_getting_history = H_FALSE;
4362         return;
4363     }
4364
4365    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4366                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4367                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4368      /* [HGM] We seem to have switched variant unexpectedly
4369       * Try to guess new variant from board size
4370       */
4371           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4372           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4373           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4374           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4375           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4376           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4377           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4378           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4379           /* Get a move list just to see the header, which
4380              will tell us whether this is really bug or zh */
4381           if (ics_getting_history == H_FALSE) {
4382             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4383             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4384             SendToICS(str);
4385           }
4386     }
4387
4388     /* Take action if this is the first board of a new game, or of a
4389        different game than is currently being displayed.  */
4390     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4391         relation == RELATION_ISOLATED_BOARD) {
4392
4393         /* Forget the old game and get the history (if any) of the new one */
4394         if (gameMode != BeginningOfGame) {
4395           Reset(TRUE, TRUE);
4396         }
4397         newGame = TRUE;
4398         if (appData.autoRaiseBoard) BoardToTop();
4399         prevMove = -3;
4400         if (gamenum == -1) {
4401             newGameMode = IcsIdle;
4402         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4403                    appData.getMoveList && !reqFlag) {
4404             /* Need to get game history */
4405             ics_getting_history = H_REQUESTED;
4406             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4407             SendToICS(str);
4408         }
4409
4410         /* Initially flip the board to have black on the bottom if playing
4411            black or if the ICS flip flag is set, but let the user change
4412            it with the Flip View button. */
4413         flipView = appData.autoFlipView ?
4414           (newGameMode == IcsPlayingBlack) || ics_flip :
4415           appData.flipView;
4416
4417         /* Done with values from previous mode; copy in new ones */
4418         gameMode = newGameMode;
4419         ModeHighlight();
4420         ics_gamenum = gamenum;
4421         if (gamenum == gs_gamenum) {
4422             int klen = strlen(gs_kind);
4423             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4424             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4425             gameInfo.event = StrSave(str);
4426         } else {
4427             gameInfo.event = StrSave("ICS game");
4428         }
4429         gameInfo.site = StrSave(appData.icsHost);
4430         gameInfo.date = PGNDate();
4431         gameInfo.round = StrSave("-");
4432         gameInfo.white = StrSave(white);
4433         gameInfo.black = StrSave(black);
4434         timeControl = basetime * 60 * 1000;
4435         timeControl_2 = 0;
4436         timeIncrement = increment * 1000;
4437         movesPerSession = 0;
4438         gameInfo.timeControl = TimeControlTagValue();
4439         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4440   if (appData.debugMode) {
4441     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4442     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4443     setbuf(debugFP, NULL);
4444   }
4445
4446         gameInfo.outOfBook = NULL;
4447
4448         /* Do we have the ratings? */
4449         if (strcmp(player1Name, white) == 0 &&
4450             strcmp(player2Name, black) == 0) {
4451             if (appData.debugMode)
4452               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4453                       player1Rating, player2Rating);
4454             gameInfo.whiteRating = player1Rating;
4455             gameInfo.blackRating = player2Rating;
4456         } else if (strcmp(player2Name, white) == 0 &&
4457                    strcmp(player1Name, black) == 0) {
4458             if (appData.debugMode)
4459               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4460                       player2Rating, player1Rating);
4461             gameInfo.whiteRating = player2Rating;
4462             gameInfo.blackRating = player1Rating;
4463         }
4464         player1Name[0] = player2Name[0] = NULLCHAR;
4465
4466         /* Silence shouts if requested */
4467         if (appData.quietPlay &&
4468             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4469             SendToICS(ics_prefix);
4470             SendToICS("set shout 0\n");
4471         }
4472     }
4473
4474     /* Deal with midgame name changes */
4475     if (!newGame) {
4476         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4477             if (gameInfo.white) free(gameInfo.white);
4478             gameInfo.white = StrSave(white);
4479         }
4480         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4481             if (gameInfo.black) free(gameInfo.black);
4482             gameInfo.black = StrSave(black);
4483         }
4484     }
4485
4486     /* Throw away game result if anything actually changes in examine mode */
4487     if (gameMode == IcsExamining && !newGame) {
4488         gameInfo.result = GameUnfinished;
4489         if (gameInfo.resultDetails != NULL) {
4490             free(gameInfo.resultDetails);
4491             gameInfo.resultDetails = NULL;
4492         }
4493     }
4494
4495     /* In pausing && IcsExamining mode, we ignore boards coming
4496        in if they are in a different variation than we are. */
4497     if (pauseExamInvalid) return;
4498     if (pausing && gameMode == IcsExamining) {
4499         if (moveNum <= pauseExamForwardMostMove) {
4500             pauseExamInvalid = TRUE;
4501             forwardMostMove = pauseExamForwardMostMove;
4502             return;
4503         }
4504     }
4505
4506   if (appData.debugMode) {
4507     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4508   }
4509     /* Parse the board */
4510     for (k = 0; k < ranks; k++) {
4511       for (j = 0; j < files; j++)
4512         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4513       if(gameInfo.holdingsWidth > 1) {
4514            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4515            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4516       }
4517     }
4518     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4519       board[5][BOARD_RGHT+1] = WhiteAngel;
4520       board[6][BOARD_RGHT+1] = WhiteMarshall;
4521       board[1][0] = BlackMarshall;
4522       board[2][0] = BlackAngel;
4523       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4524     }
4525     CopyBoard(boards[moveNum], board);
4526     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4527     if (moveNum == 0) {
4528         startedFromSetupPosition =
4529           !CompareBoards(board, initialPosition);
4530         if(startedFromSetupPosition)
4531             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4532     }
4533
4534     /* [HGM] Set castling rights. Take the outermost Rooks,
4535        to make it also work for FRC opening positions. Note that board12
4536        is really defective for later FRC positions, as it has no way to
4537        indicate which Rook can castle if they are on the same side of King.
4538        For the initial position we grant rights to the outermost Rooks,
4539        and remember thos rights, and we then copy them on positions
4540        later in an FRC game. This means WB might not recognize castlings with
4541        Rooks that have moved back to their original position as illegal,
4542        but in ICS mode that is not its job anyway.
4543     */
4544     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4545     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4546
4547         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4548             if(board[0][i] == WhiteRook) j = i;
4549         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4550         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4551             if(board[0][i] == WhiteRook) j = i;
4552         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4553         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4554             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4555         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4556         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4557             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4558         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4559
4560         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4561         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4562         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4563             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4564         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4565             if(board[BOARD_HEIGHT-1][k] == bKing)
4566                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4567         if(gameInfo.variant == VariantTwoKings) {
4568             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4569             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4570             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4571         }
4572     } else { int r;
4573         r = boards[moveNum][CASTLING][0] = initialRights[0];
4574         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4575         r = boards[moveNum][CASTLING][1] = initialRights[1];
4576         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4577         r = boards[moveNum][CASTLING][3] = initialRights[3];
4578         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4579         r = boards[moveNum][CASTLING][4] = initialRights[4];
4580         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4581         /* wildcastle kludge: always assume King has rights */
4582         r = boards[moveNum][CASTLING][2] = initialRights[2];
4583         r = boards[moveNum][CASTLING][5] = initialRights[5];
4584     }
4585     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4586     boards[moveNum][EP_STATUS] = EP_NONE;
4587     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4588     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4589     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4590
4591
4592     if (ics_getting_history == H_GOT_REQ_HEADER ||
4593         ics_getting_history == H_GOT_UNREQ_HEADER) {
4594         /* This was an initial position from a move list, not
4595            the current position */
4596         return;
4597     }
4598
4599     /* Update currentMove and known move number limits */
4600     newMove = newGame || moveNum > forwardMostMove;
4601
4602     if (newGame) {
4603         forwardMostMove = backwardMostMove = currentMove = moveNum;
4604         if (gameMode == IcsExamining && moveNum == 0) {
4605           /* Workaround for ICS limitation: we are not told the wild
4606              type when starting to examine a game.  But if we ask for
4607              the move list, the move list header will tell us */
4608             ics_getting_history = H_REQUESTED;
4609             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4610             SendToICS(str);
4611         }
4612     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4613                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4614 #if ZIPPY
4615         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4616         /* [HGM] applied this also to an engine that is silently watching        */
4617         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4618             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4619             gameInfo.variant == currentlyInitializedVariant) {
4620           takeback = forwardMostMove - moveNum;
4621           for (i = 0; i < takeback; i++) {
4622             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4623             SendToProgram("undo\n", &first);
4624           }
4625         }
4626 #endif
4627
4628         forwardMostMove = moveNum;
4629         if (!pausing || currentMove > forwardMostMove)
4630           currentMove = forwardMostMove;
4631     } else {
4632         /* New part of history that is not contiguous with old part */
4633         if (pausing && gameMode == IcsExamining) {
4634             pauseExamInvalid = TRUE;
4635             forwardMostMove = pauseExamForwardMostMove;
4636             return;
4637         }
4638         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4639 #if ZIPPY
4640             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4641                 // [HGM] when we will receive the move list we now request, it will be
4642                 // fed to the engine from the first move on. So if the engine is not
4643                 // in the initial position now, bring it there.
4644                 InitChessProgram(&first, 0);
4645             }
4646 #endif
4647             ics_getting_history = H_REQUESTED;
4648             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4649             SendToICS(str);
4650         }
4651         forwardMostMove = backwardMostMove = currentMove = moveNum;
4652     }
4653
4654     /* Update the clocks */
4655     if (strchr(elapsed_time, '.')) {
4656       /* Time is in ms */
4657       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4658       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4659     } else {
4660       /* Time is in seconds */
4661       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4662       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4663     }
4664
4665
4666 #if ZIPPY
4667     if (appData.zippyPlay && newGame &&
4668         gameMode != IcsObserving && gameMode != IcsIdle &&
4669         gameMode != IcsExamining)
4670       ZippyFirstBoard(moveNum, basetime, increment);
4671 #endif
4672
4673     /* Put the move on the move list, first converting
4674        to canonical algebraic form. */
4675     if (moveNum > 0) {
4676   if (appData.debugMode) {
4677     if (appData.debugMode) { int f = forwardMostMove;
4678         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4679                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4680                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4681     }
4682     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4683     fprintf(debugFP, "moveNum = %d\n", moveNum);
4684     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4685     setbuf(debugFP, NULL);
4686   }
4687         if (moveNum <= backwardMostMove) {
4688             /* We don't know what the board looked like before
4689                this move.  Punt. */
4690           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4691             strcat(parseList[moveNum - 1], " ");
4692             strcat(parseList[moveNum - 1], elapsed_time);
4693             moveList[moveNum - 1][0] = NULLCHAR;
4694         } else if (strcmp(move_str, "none") == 0) {
4695             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4696             /* Again, we don't know what the board looked like;
4697                this is really the start of the game. */
4698             parseList[moveNum - 1][0] = NULLCHAR;
4699             moveList[moveNum - 1][0] = NULLCHAR;
4700             backwardMostMove = moveNum;
4701             startedFromSetupPosition = TRUE;
4702             fromX = fromY = toX = toY = -1;
4703         } else {
4704           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4705           //                 So we parse the long-algebraic move string in stead of the SAN move
4706           int valid; char buf[MSG_SIZ], *prom;
4707
4708           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4709                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4710           // str looks something like "Q/a1-a2"; kill the slash
4711           if(str[1] == '/')
4712             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4713           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4714           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4715                 strcat(buf, prom); // long move lacks promo specification!
4716           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4717                 if(appData.debugMode)
4718                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4719                 safeStrCpy(move_str, buf, MSG_SIZ);
4720           }
4721           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4722                                 &fromX, &fromY, &toX, &toY, &promoChar)
4723                || ParseOneMove(buf, moveNum - 1, &moveType,
4724                                 &fromX, &fromY, &toX, &toY, &promoChar);
4725           // end of long SAN patch
4726           if (valid) {
4727             (void) CoordsToAlgebraic(boards[moveNum - 1],
4728                                      PosFlags(moveNum - 1),
4729                                      fromY, fromX, toY, toX, promoChar,
4730                                      parseList[moveNum-1]);
4731             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4732               case MT_NONE:
4733               case MT_STALEMATE:
4734               default:
4735                 break;
4736               case MT_CHECK:
4737                 if(gameInfo.variant != VariantShogi)
4738                     strcat(parseList[moveNum - 1], "+");
4739                 break;
4740               case MT_CHECKMATE:
4741               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4742                 strcat(parseList[moveNum - 1], "#");
4743                 break;
4744             }
4745             strcat(parseList[moveNum - 1], " ");
4746             strcat(parseList[moveNum - 1], elapsed_time);
4747             /* currentMoveString is set as a side-effect of ParseOneMove */
4748             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4749             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4750             strcat(moveList[moveNum - 1], "\n");
4751
4752             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4753                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4754               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4755                 ChessSquare old, new = boards[moveNum][k][j];
4756                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4757                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4758                   if(old == new) continue;
4759                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4760                   else if(new == WhiteWazir || new == BlackWazir) {
4761                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4762                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4763                       else boards[moveNum][k][j] = old; // preserve type of Gold
4764                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4765                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4766               }
4767           } else {
4768             /* Move from ICS was illegal!?  Punt. */
4769             if (appData.debugMode) {
4770               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4771               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4772             }
4773             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4774             strcat(parseList[moveNum - 1], " ");
4775             strcat(parseList[moveNum - 1], elapsed_time);
4776             moveList[moveNum - 1][0] = NULLCHAR;
4777             fromX = fromY = toX = toY = -1;
4778           }
4779         }
4780   if (appData.debugMode) {
4781     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4782     setbuf(debugFP, NULL);
4783   }
4784
4785 #if ZIPPY
4786         /* Send move to chess program (BEFORE animating it). */
4787         if (appData.zippyPlay && !newGame && newMove &&
4788            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4789
4790             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4791                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4792                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4793                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4794                             move_str);
4795                     DisplayError(str, 0);
4796                 } else {
4797                     if (first.sendTime) {
4798                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4799                     }
4800                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4801                     if (firstMove && !bookHit) {
4802                         firstMove = FALSE;
4803                         if (first.useColors) {
4804                           SendToProgram(gameMode == IcsPlayingWhite ?
4805                                         "white\ngo\n" :
4806                                         "black\ngo\n", &first);
4807                         } else {
4808                           SendToProgram("go\n", &first);
4809                         }
4810                         first.maybeThinking = TRUE;
4811                     }
4812                 }
4813             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4814               if (moveList[moveNum - 1][0] == NULLCHAR) {
4815                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4816                 DisplayError(str, 0);
4817               } else {
4818                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4819                 SendMoveToProgram(moveNum - 1, &first);
4820               }
4821             }
4822         }
4823 #endif
4824     }
4825
4826     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4827         /* If move comes from a remote source, animate it.  If it
4828            isn't remote, it will have already been animated. */
4829         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4830             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4831         }
4832         if (!pausing && appData.highlightLastMove) {
4833             SetHighlights(fromX, fromY, toX, toY);
4834         }
4835     }
4836
4837     /* Start the clocks */
4838     whiteFlag = blackFlag = FALSE;
4839     appData.clockMode = !(basetime == 0 && increment == 0);
4840     if (ticking == 0) {
4841       ics_clock_paused = TRUE;
4842       StopClocks();
4843     } else if (ticking == 1) {
4844       ics_clock_paused = FALSE;
4845     }
4846     if (gameMode == IcsIdle ||
4847         relation == RELATION_OBSERVING_STATIC ||
4848         relation == RELATION_EXAMINING ||
4849         ics_clock_paused)
4850       DisplayBothClocks();
4851     else
4852       StartClocks();
4853
4854     /* Display opponents and material strengths */
4855     if (gameInfo.variant != VariantBughouse &&
4856         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4857         if (tinyLayout || smallLayout) {
4858             if(gameInfo.variant == VariantNormal)
4859               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4860                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4861                     basetime, increment);
4862             else
4863               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4864                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4865                     basetime, increment, (int) gameInfo.variant);
4866         } else {
4867             if(gameInfo.variant == VariantNormal)
4868               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4869                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4870                     basetime, increment);
4871             else
4872               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4873                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4874                     basetime, increment, VariantName(gameInfo.variant));
4875         }
4876         DisplayTitle(str);
4877   if (appData.debugMode) {
4878     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4879   }
4880     }
4881
4882
4883     /* Display the board */
4884     if (!pausing && !appData.noGUI) {
4885
4886       if (appData.premove)
4887           if (!gotPremove ||
4888              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4889              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4890               ClearPremoveHighlights();
4891
4892       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4893         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4894       DrawPosition(j, boards[currentMove]);
4895
4896       DisplayMove(moveNum - 1);
4897       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4898             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4899               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4900         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4901       }
4902     }
4903
4904     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4905 #if ZIPPY
4906     if(bookHit) { // [HGM] book: simulate book reply
4907         static char bookMove[MSG_SIZ]; // a bit generous?
4908
4909         programStats.nodes = programStats.depth = programStats.time =
4910         programStats.score = programStats.got_only_move = 0;
4911         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4912
4913         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4914         strcat(bookMove, bookHit);
4915         HandleMachineMove(bookMove, &first);
4916     }
4917 #endif
4918 }
4919
4920 void
4921 GetMoveListEvent ()
4922 {
4923     char buf[MSG_SIZ];
4924     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4925         ics_getting_history = H_REQUESTED;
4926         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4927         SendToICS(buf);
4928     }
4929 }
4930
4931 void
4932 SendToBoth (char *msg)
4933 {   // to make it easy to keep two engines in step in dual analysis
4934     SendToProgram(msg, &first);
4935     if(second.analyzing) SendToProgram(msg, &second);
4936 }
4937
4938 void
4939 AnalysisPeriodicEvent (int force)
4940 {
4941     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4942          && !force) || !appData.periodicUpdates)
4943       return;
4944
4945     /* Send . command to Crafty to collect stats */
4946     SendToBoth(".\n");
4947
4948     /* Don't send another until we get a response (this makes
4949        us stop sending to old Crafty's which don't understand
4950        the "." command (sending illegal cmds resets node count & time,
4951        which looks bad)) */
4952     programStats.ok_to_send = 0;
4953 }
4954
4955 void
4956 ics_update_width (int new_width)
4957 {
4958         ics_printf("set width %d\n", new_width);
4959 }
4960
4961 void
4962 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4963 {
4964     char buf[MSG_SIZ];
4965
4966     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4967         // null move in variant where engine does not understand it (for analysis purposes)
4968         SendBoard(cps, moveNum + 1); // send position after move in stead.
4969         return;
4970     }
4971     if (cps->useUsermove) {
4972       SendToProgram("usermove ", cps);
4973     }
4974     if (cps->useSAN) {
4975       char *space;
4976       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4977         int len = space - parseList[moveNum];
4978         memcpy(buf, parseList[moveNum], len);
4979         buf[len++] = '\n';
4980         buf[len] = NULLCHAR;
4981       } else {
4982         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4983       }
4984       SendToProgram(buf, cps);
4985     } else {
4986       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4987         AlphaRank(moveList[moveNum], 4);
4988         SendToProgram(moveList[moveNum], cps);
4989         AlphaRank(moveList[moveNum], 4); // and back
4990       } else
4991       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4992        * the engine. It would be nice to have a better way to identify castle
4993        * moves here. */
4994       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4995                                                                          && cps->useOOCastle) {
4996         int fromX = moveList[moveNum][0] - AAA;
4997         int fromY = moveList[moveNum][1] - ONE;
4998         int toX = moveList[moveNum][2] - AAA;
4999         int toY = moveList[moveNum][3] - ONE;
5000         if((boards[moveNum][fromY][fromX] == WhiteKing
5001             && boards[moveNum][toY][toX] == WhiteRook)
5002            || (boards[moveNum][fromY][fromX] == BlackKing
5003                && boards[moveNum][toY][toX] == BlackRook)) {
5004           if(toX > fromX) SendToProgram("O-O\n", cps);
5005           else SendToProgram("O-O-O\n", cps);
5006         }
5007         else SendToProgram(moveList[moveNum], cps);
5008       } else
5009       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5010         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5011           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5012           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5013                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5014         } else
5015           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5016                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5017         SendToProgram(buf, cps);
5018       }
5019       else SendToProgram(moveList[moveNum], cps);
5020       /* End of additions by Tord */
5021     }
5022
5023     /* [HGM] setting up the opening has brought engine in force mode! */
5024     /*       Send 'go' if we are in a mode where machine should play. */
5025     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5026         (gameMode == TwoMachinesPlay   ||
5027 #if ZIPPY
5028          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5029 #endif
5030          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5031         SendToProgram("go\n", cps);
5032   if (appData.debugMode) {
5033     fprintf(debugFP, "(extra)\n");
5034   }
5035     }
5036     setboardSpoiledMachineBlack = 0;
5037 }
5038
5039 void
5040 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5041 {
5042     char user_move[MSG_SIZ];
5043     char suffix[4];
5044
5045     if(gameInfo.variant == VariantSChess && promoChar) {
5046         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5047         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5048     } else suffix[0] = NULLCHAR;
5049
5050     switch (moveType) {
5051       default:
5052         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5053                 (int)moveType, fromX, fromY, toX, toY);
5054         DisplayError(user_move + strlen("say "), 0);
5055         break;
5056       case WhiteKingSideCastle:
5057       case BlackKingSideCastle:
5058       case WhiteQueenSideCastleWild:
5059       case BlackQueenSideCastleWild:
5060       /* PUSH Fabien */
5061       case WhiteHSideCastleFR:
5062       case BlackHSideCastleFR:
5063       /* POP Fabien */
5064         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5065         break;
5066       case WhiteQueenSideCastle:
5067       case BlackQueenSideCastle:
5068       case WhiteKingSideCastleWild:
5069       case BlackKingSideCastleWild:
5070       /* PUSH Fabien */
5071       case WhiteASideCastleFR:
5072       case BlackASideCastleFR:
5073       /* POP Fabien */
5074         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5075         break;
5076       case WhiteNonPromotion:
5077       case BlackNonPromotion:
5078         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5079         break;
5080       case WhitePromotion:
5081       case BlackPromotion:
5082         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5083           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5084                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5085                 PieceToChar(WhiteFerz));
5086         else if(gameInfo.variant == VariantGreat)
5087           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5088                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5089                 PieceToChar(WhiteMan));
5090         else
5091           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5092                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5093                 promoChar);
5094         break;
5095       case WhiteDrop:
5096       case BlackDrop:
5097       drop:
5098         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5099                  ToUpper(PieceToChar((ChessSquare) fromX)),
5100                  AAA + toX, ONE + toY);
5101         break;
5102       case IllegalMove:  /* could be a variant we don't quite understand */
5103         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5104       case NormalMove:
5105       case WhiteCapturesEnPassant:
5106       case BlackCapturesEnPassant:
5107         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5108                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5109         break;
5110     }
5111     SendToICS(user_move);
5112     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5113         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5114 }
5115
5116 void
5117 UploadGameEvent ()
5118 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5119     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5120     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5121     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5122       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5123       return;
5124     }
5125     if(gameMode != IcsExamining) { // is this ever not the case?
5126         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5127
5128         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5129           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5130         } else { // on FICS we must first go to general examine mode
5131           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5132         }
5133         if(gameInfo.variant != VariantNormal) {
5134             // try figure out wild number, as xboard names are not always valid on ICS
5135             for(i=1; i<=36; i++) {
5136               snprintf(buf, MSG_SIZ, "wild/%d", i);
5137                 if(StringToVariant(buf) == gameInfo.variant) break;
5138             }
5139             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5140             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5141             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5142         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5143         SendToICS(ics_prefix);
5144         SendToICS(buf);
5145         if(startedFromSetupPosition || backwardMostMove != 0) {
5146           fen = PositionToFEN(backwardMostMove, NULL);
5147           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5148             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5149             SendToICS(buf);
5150           } else { // FICS: everything has to set by separate bsetup commands
5151             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5152             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5153             SendToICS(buf);
5154             if(!WhiteOnMove(backwardMostMove)) {
5155                 SendToICS("bsetup tomove black\n");
5156             }
5157             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5158             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5159             SendToICS(buf);
5160             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5161             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5162             SendToICS(buf);
5163             i = boards[backwardMostMove][EP_STATUS];
5164             if(i >= 0) { // set e.p.
5165               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5166                 SendToICS(buf);
5167             }
5168             bsetup++;
5169           }
5170         }
5171       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5172             SendToICS("bsetup done\n"); // switch to normal examining.
5173     }
5174     for(i = backwardMostMove; i<last; i++) {
5175         char buf[20];
5176         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5177         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5178             int len = strlen(moveList[i]);
5179             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5180             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5181         }
5182         SendToICS(buf);
5183     }
5184     SendToICS(ics_prefix);
5185     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5186 }
5187
5188 void
5189 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5190 {
5191     if (rf == DROP_RANK) {
5192       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5193       sprintf(move, "%c@%c%c\n",
5194                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5195     } else {
5196         if (promoChar == 'x' || promoChar == NULLCHAR) {
5197           sprintf(move, "%c%c%c%c\n",
5198                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5199         } else {
5200             sprintf(move, "%c%c%c%c%c\n",
5201                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5202         }
5203     }
5204 }
5205
5206 void
5207 ProcessICSInitScript (FILE *f)
5208 {
5209     char buf[MSG_SIZ];
5210
5211     while (fgets(buf, MSG_SIZ, f)) {
5212         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5213     }
5214
5215     fclose(f);
5216 }
5217
5218
5219 static int lastX, lastY, selectFlag, dragging;
5220
5221 void
5222 Sweep (int step)
5223 {
5224     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5225     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5226     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5227     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5228     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5229     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5230     do {
5231         promoSweep -= step;
5232         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5233         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5234         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5235         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5236         if(!step) step = -1;
5237     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5238             appData.testLegality && (promoSweep == king ||
5239             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5240     if(toX >= 0) {
5241         int victim = boards[currentMove][toY][toX];
5242         boards[currentMove][toY][toX] = promoSweep;
5243         DrawPosition(FALSE, boards[currentMove]);
5244         boards[currentMove][toY][toX] = victim;
5245     } else
5246     ChangeDragPiece(promoSweep);
5247 }
5248
5249 int
5250 PromoScroll (int x, int y)
5251 {
5252   int step = 0;
5253
5254   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5255   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5256   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5257   if(!step) return FALSE;
5258   lastX = x; lastY = y;
5259   if((promoSweep < BlackPawn) == flipView) step = -step;
5260   if(step > 0) selectFlag = 1;
5261   if(!selectFlag) Sweep(step);
5262   return FALSE;
5263 }
5264
5265 void
5266 NextPiece (int step)
5267 {
5268     ChessSquare piece = boards[currentMove][toY][toX];
5269     do {
5270         pieceSweep -= step;
5271         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5272         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5273         if(!step) step = -1;
5274     } while(PieceToChar(pieceSweep) == '.');
5275     boards[currentMove][toY][toX] = pieceSweep;
5276     DrawPosition(FALSE, boards[currentMove]);
5277     boards[currentMove][toY][toX] = piece;
5278 }
5279 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5280 void
5281 AlphaRank (char *move, int n)
5282 {
5283 //    char *p = move, c; int x, y;
5284
5285     if (appData.debugMode) {
5286         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5287     }
5288
5289     if(move[1]=='*' &&
5290        move[2]>='0' && move[2]<='9' &&
5291        move[3]>='a' && move[3]<='x'    ) {
5292         move[1] = '@';
5293         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5294         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5295     } else
5296     if(move[0]>='0' && move[0]<='9' &&
5297        move[1]>='a' && move[1]<='x' &&
5298        move[2]>='0' && move[2]<='9' &&
5299        move[3]>='a' && move[3]<='x'    ) {
5300         /* input move, Shogi -> normal */
5301         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5302         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5303         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5304         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5305     } else
5306     if(move[1]=='@' &&
5307        move[3]>='0' && move[3]<='9' &&
5308        move[2]>='a' && move[2]<='x'    ) {
5309         move[1] = '*';
5310         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5311         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5312     } else
5313     if(
5314        move[0]>='a' && move[0]<='x' &&
5315        move[3]>='0' && move[3]<='9' &&
5316        move[2]>='a' && move[2]<='x'    ) {
5317          /* output move, normal -> Shogi */
5318         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5319         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5320         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5321         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5322         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5323     }
5324     if (appData.debugMode) {
5325         fprintf(debugFP, "   out = '%s'\n", move);
5326     }
5327 }
5328
5329 char yy_textstr[8000];
5330
5331 /* Parser for moves from gnuchess, ICS, or user typein box */
5332 Boolean
5333 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5334 {
5335     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5336
5337     switch (*moveType) {
5338       case WhitePromotion:
5339       case BlackPromotion:
5340       case WhiteNonPromotion:
5341       case BlackNonPromotion:
5342       case NormalMove:
5343       case WhiteCapturesEnPassant:
5344       case BlackCapturesEnPassant:
5345       case WhiteKingSideCastle:
5346       case WhiteQueenSideCastle:
5347       case BlackKingSideCastle:
5348       case BlackQueenSideCastle:
5349       case WhiteKingSideCastleWild:
5350       case WhiteQueenSideCastleWild:
5351       case BlackKingSideCastleWild:
5352       case BlackQueenSideCastleWild:
5353       /* Code added by Tord: */
5354       case WhiteHSideCastleFR:
5355       case WhiteASideCastleFR:
5356       case BlackHSideCastleFR:
5357       case BlackASideCastleFR:
5358       /* End of code added by Tord */
5359       case IllegalMove:         /* bug or odd chess variant */
5360         *fromX = currentMoveString[0] - AAA;
5361         *fromY = currentMoveString[1] - ONE;
5362         *toX = currentMoveString[2] - AAA;
5363         *toY = currentMoveString[3] - ONE;
5364         *promoChar = currentMoveString[4];
5365         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5366             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5367     if (appData.debugMode) {
5368         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5369     }
5370             *fromX = *fromY = *toX = *toY = 0;
5371             return FALSE;
5372         }
5373         if (appData.testLegality) {
5374           return (*moveType != IllegalMove);
5375         } else {
5376           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5377                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5378         }
5379
5380       case WhiteDrop:
5381       case BlackDrop:
5382         *fromX = *moveType == WhiteDrop ?
5383           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5384           (int) CharToPiece(ToLower(currentMoveString[0]));
5385         *fromY = DROP_RANK;
5386         *toX = currentMoveString[2] - AAA;
5387         *toY = currentMoveString[3] - ONE;
5388         *promoChar = NULLCHAR;
5389         return TRUE;
5390
5391       case AmbiguousMove:
5392       case ImpossibleMove:
5393       case EndOfFile:
5394       case ElapsedTime:
5395       case Comment:
5396       case PGNTag:
5397       case NAG:
5398       case WhiteWins:
5399       case BlackWins:
5400       case GameIsDrawn:
5401       default:
5402     if (appData.debugMode) {
5403         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5404     }
5405         /* bug? */
5406         *fromX = *fromY = *toX = *toY = 0;
5407         *promoChar = NULLCHAR;
5408         return FALSE;
5409     }
5410 }
5411
5412 Boolean pushed = FALSE;
5413 char *lastParseAttempt;
5414
5415 void
5416 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5417 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5418   int fromX, fromY, toX, toY; char promoChar;
5419   ChessMove moveType;
5420   Boolean valid;
5421   int nr = 0;
5422
5423   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5424   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5425     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5426     pushed = TRUE;
5427   }
5428   endPV = forwardMostMove;
5429   do {
5430     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5431     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5432     lastParseAttempt = pv;
5433     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5434     if(!valid && nr == 0 &&
5435        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5436         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5437         // Hande case where played move is different from leading PV move
5438         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5439         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5440         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5441         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5442           endPV += 2; // if position different, keep this
5443           moveList[endPV-1][0] = fromX + AAA;
5444           moveList[endPV-1][1] = fromY + ONE;
5445           moveList[endPV-1][2] = toX + AAA;
5446           moveList[endPV-1][3] = toY + ONE;
5447           parseList[endPV-1][0] = NULLCHAR;
5448           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5449         }
5450       }
5451     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5452     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5453     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5454     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5455         valid++; // allow comments in PV
5456         continue;
5457     }
5458     nr++;
5459     if(endPV+1 > framePtr) break; // no space, truncate
5460     if(!valid) break;
5461     endPV++;
5462     CopyBoard(boards[endPV], boards[endPV-1]);
5463     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5464     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5465     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5466     CoordsToAlgebraic(boards[endPV - 1],
5467                              PosFlags(endPV - 1),
5468                              fromY, fromX, toY, toX, promoChar,
5469                              parseList[endPV - 1]);
5470   } while(valid);
5471   if(atEnd == 2) return; // used hidden, for PV conversion
5472   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5473   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5474   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5475                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5476   DrawPosition(TRUE, boards[currentMove]);
5477 }
5478
5479 int
5480 MultiPV (ChessProgramState *cps)
5481 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5482         int i;
5483         for(i=0; i<cps->nrOptions; i++)
5484             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5485                 return i;
5486         return -1;
5487 }
5488
5489 Boolean
5490 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5491 {
5492         int startPV, multi, lineStart, origIndex = index;
5493         char *p, buf2[MSG_SIZ];
5494         ChessProgramState *cps = (pane ? &second : &first);
5495
5496         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5497         lastX = x; lastY = y;
5498         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5499         lineStart = startPV = index;
5500         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5501         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5502         index = startPV;
5503         do{ while(buf[index] && buf[index] != '\n') index++;
5504         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5505         buf[index] = 0;
5506         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5507                 int n = cps->option[multi].value;
5508                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5509                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5510                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5511                 cps->option[multi].value = n;
5512                 *start = *end = 0;
5513                 return FALSE;
5514         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5515                 ExcludeClick(origIndex - lineStart);
5516                 return FALSE;
5517         }
5518         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5519         *start = startPV; *end = index-1;
5520         return TRUE;
5521 }
5522
5523 char *
5524 PvToSAN (char *pv)
5525 {
5526         static char buf[10*MSG_SIZ];
5527         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5528         *buf = NULLCHAR;
5529         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5530         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5531         for(i = forwardMostMove; i<endPV; i++){
5532             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5533             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5534             k += strlen(buf+k);
5535         }
5536         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5537         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5538         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5539         endPV = savedEnd;
5540         return buf;
5541 }
5542
5543 Boolean
5544 LoadPV (int x, int y)
5545 { // called on right mouse click to load PV
5546   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5547   lastX = x; lastY = y;
5548   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5549   return TRUE;
5550 }
5551
5552 void
5553 UnLoadPV ()
5554 {
5555   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5556   if(endPV < 0) return;
5557   if(appData.autoCopyPV) CopyFENToClipboard();
5558   endPV = -1;
5559   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5560         Boolean saveAnimate = appData.animate;
5561         if(pushed) {
5562             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5563                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5564             } else storedGames--; // abandon shelved tail of original game
5565         }
5566         pushed = FALSE;
5567         forwardMostMove = currentMove;
5568         currentMove = oldFMM;
5569         appData.animate = FALSE;
5570         ToNrEvent(forwardMostMove);
5571         appData.animate = saveAnimate;
5572   }
5573   currentMove = forwardMostMove;
5574   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5575   ClearPremoveHighlights();
5576   DrawPosition(TRUE, boards[currentMove]);
5577 }
5578
5579 void
5580 MovePV (int x, int y, int h)
5581 { // step through PV based on mouse coordinates (called on mouse move)
5582   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5583
5584   // we must somehow check if right button is still down (might be released off board!)
5585   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5586   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5587   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5588   if(!step) return;
5589   lastX = x; lastY = y;
5590
5591   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5592   if(endPV < 0) return;
5593   if(y < margin) step = 1; else
5594   if(y > h - margin) step = -1;
5595   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5596   currentMove += step;
5597   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5598   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5599                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5600   DrawPosition(FALSE, boards[currentMove]);
5601 }
5602
5603
5604 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5605 // All positions will have equal probability, but the current method will not provide a unique
5606 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5607 #define DARK 1
5608 #define LITE 2
5609 #define ANY 3
5610
5611 int squaresLeft[4];
5612 int piecesLeft[(int)BlackPawn];
5613 int seed, nrOfShuffles;
5614
5615 void
5616 GetPositionNumber ()
5617 {       // sets global variable seed
5618         int i;
5619
5620         seed = appData.defaultFrcPosition;
5621         if(seed < 0) { // randomize based on time for negative FRC position numbers
5622                 for(i=0; i<50; i++) seed += random();
5623                 seed = random() ^ random() >> 8 ^ random() << 8;
5624                 if(seed<0) seed = -seed;
5625         }
5626 }
5627
5628 int
5629 put (Board board, int pieceType, int rank, int n, int shade)
5630 // put the piece on the (n-1)-th empty squares of the given shade
5631 {
5632         int i;
5633
5634         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5635                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5636                         board[rank][i] = (ChessSquare) pieceType;
5637                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5638                         squaresLeft[ANY]--;
5639                         piecesLeft[pieceType]--;
5640                         return i;
5641                 }
5642         }
5643         return -1;
5644 }
5645
5646
5647 void
5648 AddOnePiece (Board board, int pieceType, int rank, int shade)
5649 // calculate where the next piece goes, (any empty square), and put it there
5650 {
5651         int i;
5652
5653         i = seed % squaresLeft[shade];
5654         nrOfShuffles *= squaresLeft[shade];
5655         seed /= squaresLeft[shade];
5656         put(board, pieceType, rank, i, shade);
5657 }
5658
5659 void
5660 AddTwoPieces (Board board, int pieceType, int rank)
5661 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5662 {
5663         int i, n=squaresLeft[ANY], j=n-1, k;
5664
5665         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5666         i = seed % k;  // pick one
5667         nrOfShuffles *= k;
5668         seed /= k;
5669         while(i >= j) i -= j--;
5670         j = n - 1 - j; i += j;
5671         put(board, pieceType, rank, j, ANY);
5672         put(board, pieceType, rank, i, ANY);
5673 }
5674
5675 void
5676 SetUpShuffle (Board board, int number)
5677 {
5678         int i, p, first=1;
5679
5680         GetPositionNumber(); nrOfShuffles = 1;
5681
5682         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5683         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5684         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5685
5686         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5687
5688         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5689             p = (int) board[0][i];
5690             if(p < (int) BlackPawn) piecesLeft[p] ++;
5691             board[0][i] = EmptySquare;
5692         }
5693
5694         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5695             // shuffles restricted to allow normal castling put KRR first
5696             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5697                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5698             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5699                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5700             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5701                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5702             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5703                 put(board, WhiteRook, 0, 0, ANY);
5704             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5705         }
5706
5707         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5708             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5709             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5710                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5711                 while(piecesLeft[p] >= 2) {
5712                     AddOnePiece(board, p, 0, LITE);
5713                     AddOnePiece(board, p, 0, DARK);
5714                 }
5715                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5716             }
5717
5718         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5719             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5720             // but we leave King and Rooks for last, to possibly obey FRC restriction
5721             if(p == (int)WhiteRook) continue;
5722             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5723             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5724         }
5725
5726         // now everything is placed, except perhaps King (Unicorn) and Rooks
5727
5728         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5729             // Last King gets castling rights
5730             while(piecesLeft[(int)WhiteUnicorn]) {
5731                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5732                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5733             }
5734
5735             while(piecesLeft[(int)WhiteKing]) {
5736                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5737                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5738             }
5739
5740
5741         } else {
5742             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5743             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5744         }
5745
5746         // Only Rooks can be left; simply place them all
5747         while(piecesLeft[(int)WhiteRook]) {
5748                 i = put(board, WhiteRook, 0, 0, ANY);
5749                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5750                         if(first) {
5751                                 first=0;
5752                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5753                         }
5754                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5755                 }
5756         }
5757         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5758             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5759         }
5760
5761         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5762 }
5763
5764 int
5765 SetCharTable (char *table, const char * map)
5766 /* [HGM] moved here from winboard.c because of its general usefulness */
5767 /*       Basically a safe strcpy that uses the last character as King */
5768 {
5769     int result = FALSE; int NrPieces;
5770
5771     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5772                     && NrPieces >= 12 && !(NrPieces&1)) {
5773         int i; /* [HGM] Accept even length from 12 to 34 */
5774
5775         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5776         for( i=0; i<NrPieces/2-1; i++ ) {
5777             table[i] = map[i];
5778             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5779         }
5780         table[(int) WhiteKing]  = map[NrPieces/2-1];
5781         table[(int) BlackKing]  = map[NrPieces-1];
5782
5783         result = TRUE;
5784     }
5785
5786     return result;
5787 }
5788
5789 void
5790 Prelude (Board board)
5791 {       // [HGM] superchess: random selection of exo-pieces
5792         int i, j, k; ChessSquare p;
5793         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5794
5795         GetPositionNumber(); // use FRC position number
5796
5797         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5798             SetCharTable(pieceToChar, appData.pieceToCharTable);
5799             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5800                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5801         }
5802
5803         j = seed%4;                 seed /= 4;
5804         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5805         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5806         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5807         j = seed%3 + (seed%3 >= j); seed /= 3;
5808         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5809         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5810         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5811         j = seed%3;                 seed /= 3;
5812         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5813         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5814         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5815         j = seed%2 + (seed%2 >= j); seed /= 2;
5816         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5817         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5818         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5819         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5820         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5821         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5822         put(board, exoPieces[0],    0, 0, ANY);
5823         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5824 }
5825
5826 void
5827 InitPosition (int redraw)
5828 {
5829     ChessSquare (* pieces)[BOARD_FILES];
5830     int i, j, pawnRow, overrule,
5831     oldx = gameInfo.boardWidth,
5832     oldy = gameInfo.boardHeight,
5833     oldh = gameInfo.holdingsWidth;
5834     static int oldv;
5835
5836     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5837
5838     /* [AS] Initialize pv info list [HGM] and game status */
5839     {
5840         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5841             pvInfoList[i].depth = 0;
5842             boards[i][EP_STATUS] = EP_NONE;
5843             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5844         }
5845
5846         initialRulePlies = 0; /* 50-move counter start */
5847
5848         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5849         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5850     }
5851
5852
5853     /* [HGM] logic here is completely changed. In stead of full positions */
5854     /* the initialized data only consist of the two backranks. The switch */
5855     /* selects which one we will use, which is than copied to the Board   */
5856     /* initialPosition, which for the rest is initialized by Pawns and    */
5857     /* empty squares. This initial position is then copied to boards[0],  */
5858     /* possibly after shuffling, so that it remains available.            */
5859
5860     gameInfo.holdingsWidth = 0; /* default board sizes */
5861     gameInfo.boardWidth    = 8;
5862     gameInfo.boardHeight   = 8;
5863     gameInfo.holdingsSize  = 0;
5864     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5865     for(i=0; i<BOARD_FILES-2; i++)
5866       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5867     initialPosition[EP_STATUS] = EP_NONE;
5868     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5869     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5870          SetCharTable(pieceNickName, appData.pieceNickNames);
5871     else SetCharTable(pieceNickName, "............");
5872     pieces = FIDEArray;
5873
5874     switch (gameInfo.variant) {
5875     case VariantFischeRandom:
5876       shuffleOpenings = TRUE;
5877     default:
5878       break;
5879     case VariantShatranj:
5880       pieces = ShatranjArray;
5881       nrCastlingRights = 0;
5882       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5883       break;
5884     case VariantMakruk:
5885       pieces = makrukArray;
5886       nrCastlingRights = 0;
5887       startedFromSetupPosition = TRUE;
5888       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5889       break;
5890     case VariantTwoKings:
5891       pieces = twoKingsArray;
5892       break;
5893     case VariantGrand:
5894       pieces = GrandArray;
5895       nrCastlingRights = 0;
5896       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5897       gameInfo.boardWidth = 10;
5898       gameInfo.boardHeight = 10;
5899       gameInfo.holdingsSize = 7;
5900       break;
5901     case VariantCapaRandom:
5902       shuffleOpenings = TRUE;
5903     case VariantCapablanca:
5904       pieces = CapablancaArray;
5905       gameInfo.boardWidth = 10;
5906       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5907       break;
5908     case VariantGothic:
5909       pieces = GothicArray;
5910       gameInfo.boardWidth = 10;
5911       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5912       break;
5913     case VariantSChess:
5914       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5915       gameInfo.holdingsSize = 7;
5916       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5917       break;
5918     case VariantJanus:
5919       pieces = JanusArray;
5920       gameInfo.boardWidth = 10;
5921       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5922       nrCastlingRights = 6;
5923         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5924         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5925         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5926         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5927         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5928         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5929       break;
5930     case VariantFalcon:
5931       pieces = FalconArray;
5932       gameInfo.boardWidth = 10;
5933       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5934       break;
5935     case VariantXiangqi:
5936       pieces = XiangqiArray;
5937       gameInfo.boardWidth  = 9;
5938       gameInfo.boardHeight = 10;
5939       nrCastlingRights = 0;
5940       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5941       break;
5942     case VariantShogi:
5943       pieces = ShogiArray;
5944       gameInfo.boardWidth  = 9;
5945       gameInfo.boardHeight = 9;
5946       gameInfo.holdingsSize = 7;
5947       nrCastlingRights = 0;
5948       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5949       break;
5950     case VariantCourier:
5951       pieces = CourierArray;
5952       gameInfo.boardWidth  = 12;
5953       nrCastlingRights = 0;
5954       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5955       break;
5956     case VariantKnightmate:
5957       pieces = KnightmateArray;
5958       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5959       break;
5960     case VariantSpartan:
5961       pieces = SpartanArray;
5962       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5963       break;
5964     case VariantFairy:
5965       pieces = fairyArray;
5966       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5967       break;
5968     case VariantGreat:
5969       pieces = GreatArray;
5970       gameInfo.boardWidth = 10;
5971       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5972       gameInfo.holdingsSize = 8;
5973       break;
5974     case VariantSuper:
5975       pieces = FIDEArray;
5976       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5977       gameInfo.holdingsSize = 8;
5978       startedFromSetupPosition = TRUE;
5979       break;
5980     case VariantCrazyhouse:
5981     case VariantBughouse:
5982       pieces = FIDEArray;
5983       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5984       gameInfo.holdingsSize = 5;
5985       break;
5986     case VariantWildCastle:
5987       pieces = FIDEArray;
5988       /* !!?shuffle with kings guaranteed to be on d or e file */
5989       shuffleOpenings = 1;
5990       break;
5991     case VariantNoCastle:
5992       pieces = FIDEArray;
5993       nrCastlingRights = 0;
5994       /* !!?unconstrained back-rank shuffle */
5995       shuffleOpenings = 1;
5996       break;
5997     }
5998
5999     overrule = 0;
6000     if(appData.NrFiles >= 0) {
6001         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6002         gameInfo.boardWidth = appData.NrFiles;
6003     }
6004     if(appData.NrRanks >= 0) {
6005         gameInfo.boardHeight = appData.NrRanks;
6006     }
6007     if(appData.holdingsSize >= 0) {
6008         i = appData.holdingsSize;
6009         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6010         gameInfo.holdingsSize = i;
6011     }
6012     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6013     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6014         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6015
6016     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6017     if(pawnRow < 1) pawnRow = 1;
6018     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6019
6020     /* User pieceToChar list overrules defaults */
6021     if(appData.pieceToCharTable != NULL)
6022         SetCharTable(pieceToChar, appData.pieceToCharTable);
6023
6024     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6025
6026         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6027             s = (ChessSquare) 0; /* account holding counts in guard band */
6028         for( i=0; i<BOARD_HEIGHT; i++ )
6029             initialPosition[i][j] = s;
6030
6031         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6032         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6033         initialPosition[pawnRow][j] = WhitePawn;
6034         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6035         if(gameInfo.variant == VariantXiangqi) {
6036             if(j&1) {
6037                 initialPosition[pawnRow][j] =
6038                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6039                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6040                    initialPosition[2][j] = WhiteCannon;
6041                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6042                 }
6043             }
6044         }
6045         if(gameInfo.variant == VariantGrand) {
6046             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6047                initialPosition[0][j] = WhiteRook;
6048                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6049             }
6050         }
6051         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6052     }
6053     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6054
6055             j=BOARD_LEFT+1;
6056             initialPosition[1][j] = WhiteBishop;
6057             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6058             j=BOARD_RGHT-2;
6059             initialPosition[1][j] = WhiteRook;
6060             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6061     }
6062
6063     if( nrCastlingRights == -1) {
6064         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6065         /*       This sets default castling rights from none to normal corners   */
6066         /* Variants with other castling rights must set them themselves above    */
6067         nrCastlingRights = 6;
6068
6069         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6070         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6071         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6072         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6073         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6074         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6075      }
6076
6077      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6078      if(gameInfo.variant == VariantGreat) { // promotion commoners
6079         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6080         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6081         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6082         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6083      }
6084      if( gameInfo.variant == VariantSChess ) {
6085       initialPosition[1][0] = BlackMarshall;
6086       initialPosition[2][0] = BlackAngel;
6087       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6088       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6089       initialPosition[1][1] = initialPosition[2][1] =
6090       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6091      }
6092   if (appData.debugMode) {
6093     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6094   }
6095     if(shuffleOpenings) {
6096         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6097         startedFromSetupPosition = TRUE;
6098     }
6099     if(startedFromPositionFile) {
6100       /* [HGM] loadPos: use PositionFile for every new game */
6101       CopyBoard(initialPosition, filePosition);
6102       for(i=0; i<nrCastlingRights; i++)
6103           initialRights[i] = filePosition[CASTLING][i];
6104       startedFromSetupPosition = TRUE;
6105     }
6106
6107     CopyBoard(boards[0], initialPosition);
6108
6109     if(oldx != gameInfo.boardWidth ||
6110        oldy != gameInfo.boardHeight ||
6111        oldv != gameInfo.variant ||
6112        oldh != gameInfo.holdingsWidth
6113                                          )
6114             InitDrawingSizes(-2 ,0);
6115
6116     oldv = gameInfo.variant;
6117     if (redraw)
6118       DrawPosition(TRUE, boards[currentMove]);
6119 }
6120
6121 void
6122 SendBoard (ChessProgramState *cps, int moveNum)
6123 {
6124     char message[MSG_SIZ];
6125
6126     if (cps->useSetboard) {
6127       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6128       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6129       SendToProgram(message, cps);
6130       free(fen);
6131
6132     } else {
6133       ChessSquare *bp;
6134       int i, j, left=0, right=BOARD_WIDTH;
6135       /* Kludge to set black to move, avoiding the troublesome and now
6136        * deprecated "black" command.
6137        */
6138       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6139         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6140
6141       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6142
6143       SendToProgram("edit\n", cps);
6144       SendToProgram("#\n", cps);
6145       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6146         bp = &boards[moveNum][i][left];
6147         for (j = left; j < right; j++, bp++) {
6148           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6149           if ((int) *bp < (int) BlackPawn) {
6150             if(j == BOARD_RGHT+1)
6151                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6152             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6153             if(message[0] == '+' || message[0] == '~') {
6154               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6155                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6156                         AAA + j, ONE + i);
6157             }
6158             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6159                 message[1] = BOARD_RGHT   - 1 - j + '1';
6160                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6161             }
6162             SendToProgram(message, cps);
6163           }
6164         }
6165       }
6166
6167       SendToProgram("c\n", cps);
6168       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6169         bp = &boards[moveNum][i][left];
6170         for (j = left; j < right; j++, bp++) {
6171           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6172           if (((int) *bp != (int) EmptySquare)
6173               && ((int) *bp >= (int) BlackPawn)) {
6174             if(j == BOARD_LEFT-2)
6175                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6176             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6177                     AAA + j, ONE + i);
6178             if(message[0] == '+' || message[0] == '~') {
6179               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6180                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6181                         AAA + j, ONE + i);
6182             }
6183             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6184                 message[1] = BOARD_RGHT   - 1 - j + '1';
6185                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6186             }
6187             SendToProgram(message, cps);
6188           }
6189         }
6190       }
6191
6192       SendToProgram(".\n", cps);
6193     }
6194     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6195 }
6196
6197 char exclusionHeader[MSG_SIZ];
6198 int exCnt, excludePtr;
6199 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6200 static Exclusion excluTab[200];
6201 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6202
6203 static void
6204 WriteMap (int s)
6205 {
6206     int j;
6207     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6208     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6209 }
6210
6211 static void
6212 ClearMap ()
6213 {
6214     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6215     excludePtr = 24; exCnt = 0;
6216     WriteMap(0);
6217 }
6218
6219 static void
6220 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6221 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6222     char buf[2*MOVE_LEN], *p;
6223     Exclusion *e = excluTab;
6224     int i;
6225     for(i=0; i<exCnt; i++)
6226         if(e[i].ff == fromX && e[i].fr == fromY &&
6227            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6228     if(i == exCnt) { // was not in exclude list; add it
6229         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6230         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6231             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6232             return; // abort
6233         }
6234         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6235         excludePtr++; e[i].mark = excludePtr++;
6236         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6237         exCnt++;
6238     }
6239     exclusionHeader[e[i].mark] = state;
6240 }
6241
6242 static int
6243 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6244 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6245     char buf[MSG_SIZ];
6246     int j, k;
6247     ChessMove moveType;
6248     if((signed char)promoChar == -1) { // kludge to indicate best move
6249         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6250             return 1; // if unparsable, abort
6251     }
6252     // update exclusion map (resolving toggle by consulting existing state)
6253     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6254     j = k%8; k >>= 3;
6255     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6256     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6257          excludeMap[k] |=   1<<j;
6258     else excludeMap[k] &= ~(1<<j);
6259     // update header
6260     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6261     // inform engine
6262     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6263     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6264     SendToBoth(buf);
6265     return (state == '+');
6266 }
6267
6268 static void
6269 ExcludeClick (int index)
6270 {
6271     int i, j;
6272     Exclusion *e = excluTab;
6273     if(index < 25) { // none, best or tail clicked
6274         if(index < 13) { // none: include all
6275             WriteMap(0); // clear map
6276             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6277             SendToBoth("include all\n"); // and inform engine
6278         } else if(index > 18) { // tail
6279             if(exclusionHeader[19] == '-') { // tail was excluded
6280                 SendToBoth("include all\n");
6281                 WriteMap(0); // clear map completely
6282                 // now re-exclude selected moves
6283                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6284                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6285             } else { // tail was included or in mixed state
6286                 SendToBoth("exclude all\n");
6287                 WriteMap(0xFF); // fill map completely
6288                 // now re-include selected moves
6289                 j = 0; // count them
6290                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6291                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6292                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6293             }
6294         } else { // best
6295             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6296         }
6297     } else {
6298         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6299             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6300             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6301             break;
6302         }
6303     }
6304 }
6305
6306 ChessSquare
6307 DefaultPromoChoice (int white)
6308 {
6309     ChessSquare result;
6310     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6311         result = WhiteFerz; // no choice
6312     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6313         result= WhiteKing; // in Suicide Q is the last thing we want
6314     else if(gameInfo.variant == VariantSpartan)
6315         result = white ? WhiteQueen : WhiteAngel;
6316     else result = WhiteQueen;
6317     if(!white) result = WHITE_TO_BLACK result;
6318     return result;
6319 }
6320
6321 static int autoQueen; // [HGM] oneclick
6322
6323 int
6324 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6325 {
6326     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6327     /* [HGM] add Shogi promotions */
6328     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6329     ChessSquare piece;
6330     ChessMove moveType;
6331     Boolean premove;
6332
6333     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6334     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6335
6336     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6337       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6338         return FALSE;
6339
6340     piece = boards[currentMove][fromY][fromX];
6341     if(gameInfo.variant == VariantShogi) {
6342         promotionZoneSize = BOARD_HEIGHT/3;
6343         highestPromotingPiece = (int)WhiteFerz;
6344     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6345         promotionZoneSize = 3;
6346     }
6347
6348     // Treat Lance as Pawn when it is not representing Amazon
6349     if(gameInfo.variant != VariantSuper) {
6350         if(piece == WhiteLance) piece = WhitePawn; else
6351         if(piece == BlackLance) piece = BlackPawn;
6352     }
6353
6354     // next weed out all moves that do not touch the promotion zone at all
6355     if((int)piece >= BlackPawn) {
6356         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6357              return FALSE;
6358         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6359     } else {
6360         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6361            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6362     }
6363
6364     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6365
6366     // weed out mandatory Shogi promotions
6367     if(gameInfo.variant == VariantShogi) {
6368         if(piece >= BlackPawn) {
6369             if(toY == 0 && piece == BlackPawn ||
6370                toY == 0 && piece == BlackQueen ||
6371                toY <= 1 && piece == BlackKnight) {
6372                 *promoChoice = '+';
6373                 return FALSE;
6374             }
6375         } else {
6376             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6377                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6378                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6379                 *promoChoice = '+';
6380                 return FALSE;
6381             }
6382         }
6383     }
6384
6385     // weed out obviously illegal Pawn moves
6386     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6387         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6388         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6389         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6390         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6391         // note we are not allowed to test for valid (non-)capture, due to premove
6392     }
6393
6394     // we either have a choice what to promote to, or (in Shogi) whether to promote
6395     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6396         *promoChoice = PieceToChar(BlackFerz);  // no choice
6397         return FALSE;
6398     }
6399     // no sense asking what we must promote to if it is going to explode...
6400     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6401         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6402         return FALSE;
6403     }
6404     // give caller the default choice even if we will not make it
6405     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6406     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6407     if(        sweepSelect && gameInfo.variant != VariantGreat
6408                            && gameInfo.variant != VariantGrand
6409                            && gameInfo.variant != VariantSuper) return FALSE;
6410     if(autoQueen) return FALSE; // predetermined
6411
6412     // suppress promotion popup on illegal moves that are not premoves
6413     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6414               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6415     if(appData.testLegality && !premove) {
6416         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6417                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6418         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6419             return FALSE;
6420     }
6421
6422     return TRUE;
6423 }
6424
6425 int
6426 InPalace (int row, int column)
6427 {   /* [HGM] for Xiangqi */
6428     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6429          column < (BOARD_WIDTH + 4)/2 &&
6430          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6431     return FALSE;
6432 }
6433
6434 int
6435 PieceForSquare (int x, int y)
6436 {
6437   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6438      return -1;
6439   else
6440      return boards[currentMove][y][x];
6441 }
6442
6443 int
6444 OKToStartUserMove (int x, int y)
6445 {
6446     ChessSquare from_piece;
6447     int white_piece;
6448
6449     if (matchMode) return FALSE;
6450     if (gameMode == EditPosition) return TRUE;
6451
6452     if (x >= 0 && y >= 0)
6453       from_piece = boards[currentMove][y][x];
6454     else
6455       from_piece = EmptySquare;
6456
6457     if (from_piece == EmptySquare) return FALSE;
6458
6459     white_piece = (int)from_piece >= (int)WhitePawn &&
6460       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6461
6462     switch (gameMode) {
6463       case AnalyzeFile:
6464       case TwoMachinesPlay:
6465       case EndOfGame:
6466         return FALSE;
6467
6468       case IcsObserving:
6469       case IcsIdle:
6470         return FALSE;
6471
6472       case MachinePlaysWhite:
6473       case IcsPlayingBlack:
6474         if (appData.zippyPlay) return FALSE;
6475         if (white_piece) {
6476             DisplayMoveError(_("You are playing Black"));
6477             return FALSE;
6478         }
6479         break;
6480
6481       case MachinePlaysBlack:
6482       case IcsPlayingWhite:
6483         if (appData.zippyPlay) return FALSE;
6484         if (!white_piece) {
6485             DisplayMoveError(_("You are playing White"));
6486             return FALSE;
6487         }
6488         break;
6489
6490       case PlayFromGameFile:
6491             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6492       case EditGame:
6493         if (!white_piece && WhiteOnMove(currentMove)) {
6494             DisplayMoveError(_("It is White's turn"));
6495             return FALSE;
6496         }
6497         if (white_piece && !WhiteOnMove(currentMove)) {
6498             DisplayMoveError(_("It is Black's turn"));
6499             return FALSE;
6500         }
6501         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6502             /* Editing correspondence game history */
6503             /* Could disallow this or prompt for confirmation */
6504             cmailOldMove = -1;
6505         }
6506         break;
6507
6508       case BeginningOfGame:
6509         if (appData.icsActive) return FALSE;
6510         if (!appData.noChessProgram) {
6511             if (!white_piece) {
6512                 DisplayMoveError(_("You are playing White"));
6513                 return FALSE;
6514             }
6515         }
6516         break;
6517
6518       case Training:
6519         if (!white_piece && WhiteOnMove(currentMove)) {
6520             DisplayMoveError(_("It is White's turn"));
6521             return FALSE;
6522         }
6523         if (white_piece && !WhiteOnMove(currentMove)) {
6524             DisplayMoveError(_("It is Black's turn"));
6525             return FALSE;
6526         }
6527         break;
6528
6529       default:
6530       case IcsExamining:
6531         break;
6532     }
6533     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6534         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6535         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6536         && gameMode != AnalyzeFile && gameMode != Training) {
6537         DisplayMoveError(_("Displayed position is not current"));
6538         return FALSE;
6539     }
6540     return TRUE;
6541 }
6542
6543 Boolean
6544 OnlyMove (int *x, int *y, Boolean captures)
6545 {
6546     DisambiguateClosure cl;
6547     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6548     switch(gameMode) {
6549       case MachinePlaysBlack:
6550       case IcsPlayingWhite:
6551       case BeginningOfGame:
6552         if(!WhiteOnMove(currentMove)) return FALSE;
6553         break;
6554       case MachinePlaysWhite:
6555       case IcsPlayingBlack:
6556         if(WhiteOnMove(currentMove)) return FALSE;
6557         break;
6558       case EditGame:
6559         break;
6560       default:
6561         return FALSE;
6562     }
6563     cl.pieceIn = EmptySquare;
6564     cl.rfIn = *y;
6565     cl.ffIn = *x;
6566     cl.rtIn = -1;
6567     cl.ftIn = -1;
6568     cl.promoCharIn = NULLCHAR;
6569     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6570     if( cl.kind == NormalMove ||
6571         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6572         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6573         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6574       fromX = cl.ff;
6575       fromY = cl.rf;
6576       *x = cl.ft;
6577       *y = cl.rt;
6578       return TRUE;
6579     }
6580     if(cl.kind != ImpossibleMove) return FALSE;
6581     cl.pieceIn = EmptySquare;
6582     cl.rfIn = -1;
6583     cl.ffIn = -1;
6584     cl.rtIn = *y;
6585     cl.ftIn = *x;
6586     cl.promoCharIn = NULLCHAR;
6587     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6588     if( cl.kind == NormalMove ||
6589         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6590         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6591         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6592       fromX = cl.ff;
6593       fromY = cl.rf;
6594       *x = cl.ft;
6595       *y = cl.rt;
6596       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6597       return TRUE;
6598     }
6599     return FALSE;
6600 }
6601
6602 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6603 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6604 int lastLoadGameUseList = FALSE;
6605 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6606 ChessMove lastLoadGameStart = EndOfFile;
6607 int doubleClick;
6608
6609 void
6610 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6611 {
6612     ChessMove moveType;
6613     ChessSquare pup;
6614     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6615
6616     /* Check if the user is playing in turn.  This is complicated because we
6617        let the user "pick up" a piece before it is his turn.  So the piece he
6618        tried to pick up may have been captured by the time he puts it down!
6619        Therefore we use the color the user is supposed to be playing in this
6620        test, not the color of the piece that is currently on the starting
6621        square---except in EditGame mode, where the user is playing both
6622        sides; fortunately there the capture race can't happen.  (It can
6623        now happen in IcsExamining mode, but that's just too bad.  The user
6624        will get a somewhat confusing message in that case.)
6625        */
6626
6627     switch (gameMode) {
6628       case AnalyzeFile:
6629       case TwoMachinesPlay:
6630       case EndOfGame:
6631       case IcsObserving:
6632       case IcsIdle:
6633         /* We switched into a game mode where moves are not accepted,
6634            perhaps while the mouse button was down. */
6635         return;
6636
6637       case MachinePlaysWhite:
6638         /* User is moving for Black */
6639         if (WhiteOnMove(currentMove)) {
6640             DisplayMoveError(_("It is White's turn"));
6641             return;
6642         }
6643         break;
6644
6645       case MachinePlaysBlack:
6646         /* User is moving for White */
6647         if (!WhiteOnMove(currentMove)) {
6648             DisplayMoveError(_("It is Black's turn"));
6649             return;
6650         }
6651         break;
6652
6653       case PlayFromGameFile:
6654             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6655       case EditGame:
6656       case IcsExamining:
6657       case BeginningOfGame:
6658       case AnalyzeMode:
6659       case Training:
6660         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6661         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6662             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6663             /* User is moving for Black */
6664             if (WhiteOnMove(currentMove)) {
6665                 DisplayMoveError(_("It is White's turn"));
6666                 return;
6667             }
6668         } else {
6669             /* User is moving for White */
6670             if (!WhiteOnMove(currentMove)) {
6671                 DisplayMoveError(_("It is Black's turn"));
6672                 return;
6673             }
6674         }
6675         break;
6676
6677       case IcsPlayingBlack:
6678         /* User is moving for Black */
6679         if (WhiteOnMove(currentMove)) {
6680             if (!appData.premove) {
6681                 DisplayMoveError(_("It is White's turn"));
6682             } else if (toX >= 0 && toY >= 0) {
6683                 premoveToX = toX;
6684                 premoveToY = toY;
6685                 premoveFromX = fromX;
6686                 premoveFromY = fromY;
6687                 premovePromoChar = promoChar;
6688                 gotPremove = 1;
6689                 if (appData.debugMode)
6690                     fprintf(debugFP, "Got premove: fromX %d,"
6691                             "fromY %d, toX %d, toY %d\n",
6692                             fromX, fromY, toX, toY);
6693             }
6694             return;
6695         }
6696         break;
6697
6698       case IcsPlayingWhite:
6699         /* User is moving for White */
6700         if (!WhiteOnMove(currentMove)) {
6701             if (!appData.premove) {
6702                 DisplayMoveError(_("It is Black's turn"));
6703             } else if (toX >= 0 && toY >= 0) {
6704                 premoveToX = toX;
6705                 premoveToY = toY;
6706                 premoveFromX = fromX;
6707                 premoveFromY = fromY;
6708                 premovePromoChar = promoChar;
6709                 gotPremove = 1;
6710                 if (appData.debugMode)
6711                     fprintf(debugFP, "Got premove: fromX %d,"
6712                             "fromY %d, toX %d, toY %d\n",
6713                             fromX, fromY, toX, toY);
6714             }
6715             return;
6716         }
6717         break;
6718
6719       default:
6720         break;
6721
6722       case EditPosition:
6723         /* EditPosition, empty square, or different color piece;
6724            click-click move is possible */
6725         if (toX == -2 || toY == -2) {
6726             boards[0][fromY][fromX] = EmptySquare;
6727             DrawPosition(FALSE, boards[currentMove]);
6728             return;
6729         } else if (toX >= 0 && toY >= 0) {
6730             boards[0][toY][toX] = boards[0][fromY][fromX];
6731             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6732                 if(boards[0][fromY][0] != EmptySquare) {
6733                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6734                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6735                 }
6736             } else
6737             if(fromX == BOARD_RGHT+1) {
6738                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6739                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6740                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6741                 }
6742             } else
6743             boards[0][fromY][fromX] = gatingPiece;
6744             DrawPosition(FALSE, boards[currentMove]);
6745             return;
6746         }
6747         return;
6748     }
6749
6750     if(toX < 0 || toY < 0) return;
6751     pup = boards[currentMove][toY][toX];
6752
6753     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6754     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6755          if( pup != EmptySquare ) return;
6756          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6757            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6758                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6759            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6760            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6761            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6762            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6763          fromY = DROP_RANK;
6764     }
6765
6766     /* [HGM] always test for legality, to get promotion info */
6767     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6768                                          fromY, fromX, toY, toX, promoChar);
6769
6770     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6771
6772     /* [HGM] but possibly ignore an IllegalMove result */
6773     if (appData.testLegality) {
6774         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6775             DisplayMoveError(_("Illegal move"));
6776             return;
6777         }
6778     }
6779
6780     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6781         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6782              ClearPremoveHighlights(); // was included
6783         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6784         return;
6785     }
6786
6787     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6788 }
6789
6790 /* Common tail of UserMoveEvent and DropMenuEvent */
6791 int
6792 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6793 {
6794     char *bookHit = 0;
6795
6796     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6797         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6798         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6799         if(WhiteOnMove(currentMove)) {
6800             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6801         } else {
6802             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6803         }
6804     }
6805
6806     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6807        move type in caller when we know the move is a legal promotion */
6808     if(moveType == NormalMove && promoChar)
6809         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6810
6811     /* [HGM] <popupFix> The following if has been moved here from
6812        UserMoveEvent(). Because it seemed to belong here (why not allow
6813        piece drops in training games?), and because it can only be
6814        performed after it is known to what we promote. */
6815     if (gameMode == Training) {
6816       /* compare the move played on the board to the next move in the
6817        * game. If they match, display the move and the opponent's response.
6818        * If they don't match, display an error message.
6819        */
6820       int saveAnimate;
6821       Board testBoard;
6822       CopyBoard(testBoard, boards[currentMove]);
6823       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6824
6825       if (CompareBoards(testBoard, boards[currentMove+1])) {
6826         ForwardInner(currentMove+1);
6827
6828         /* Autoplay the opponent's response.
6829          * if appData.animate was TRUE when Training mode was entered,
6830          * the response will be animated.
6831          */
6832         saveAnimate = appData.animate;
6833         appData.animate = animateTraining;
6834         ForwardInner(currentMove+1);
6835         appData.animate = saveAnimate;
6836
6837         /* check for the end of the game */
6838         if (currentMove >= forwardMostMove) {
6839           gameMode = PlayFromGameFile;
6840           ModeHighlight();
6841           SetTrainingModeOff();
6842           DisplayInformation(_("End of game"));
6843         }
6844       } else {
6845         DisplayError(_("Incorrect move"), 0);
6846       }
6847       return 1;
6848     }
6849
6850   /* Ok, now we know that the move is good, so we can kill
6851      the previous line in Analysis Mode */
6852   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6853                                 && currentMove < forwardMostMove) {
6854     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6855     else forwardMostMove = currentMove;
6856   }
6857
6858   ClearMap();
6859
6860   /* If we need the chess program but it's dead, restart it */
6861   ResurrectChessProgram();
6862
6863   /* A user move restarts a paused game*/
6864   if (pausing)
6865     PauseEvent();
6866
6867   thinkOutput[0] = NULLCHAR;
6868
6869   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6870
6871   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6872     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6873     return 1;
6874   }
6875
6876   if (gameMode == BeginningOfGame) {
6877     if (appData.noChessProgram) {
6878       gameMode = EditGame;
6879       SetGameInfo();
6880     } else {
6881       char buf[MSG_SIZ];
6882       gameMode = MachinePlaysBlack;
6883       StartClocks();
6884       SetGameInfo();
6885       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6886       DisplayTitle(buf);
6887       if (first.sendName) {
6888         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6889         SendToProgram(buf, &first);
6890       }
6891       StartClocks();
6892     }
6893     ModeHighlight();
6894   }
6895
6896   /* Relay move to ICS or chess engine */
6897   if (appData.icsActive) {
6898     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6899         gameMode == IcsExamining) {
6900       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6901         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6902         SendToICS("draw ");
6903         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6904       }
6905       // also send plain move, in case ICS does not understand atomic claims
6906       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6907       ics_user_moved = 1;
6908     }
6909   } else {
6910     if (first.sendTime && (gameMode == BeginningOfGame ||
6911                            gameMode == MachinePlaysWhite ||
6912                            gameMode == MachinePlaysBlack)) {
6913       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6914     }
6915     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6916          // [HGM] book: if program might be playing, let it use book
6917         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6918         first.maybeThinking = TRUE;
6919     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6920         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6921         SendBoard(&first, currentMove+1);
6922         if(second.analyzing) {
6923             if(!second.useSetboard) SendToProgram("undo\n", &second);
6924             SendBoard(&second, currentMove+1);
6925         }
6926     } else {
6927         SendMoveToProgram(forwardMostMove-1, &first);
6928         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6929     }
6930     if (currentMove == cmailOldMove + 1) {
6931       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6932     }
6933   }
6934
6935   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6936
6937   switch (gameMode) {
6938   case EditGame:
6939     if(appData.testLegality)
6940     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6941     case MT_NONE:
6942     case MT_CHECK:
6943       break;
6944     case MT_CHECKMATE:
6945     case MT_STAINMATE:
6946       if (WhiteOnMove(currentMove)) {
6947         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6948       } else {
6949         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6950       }
6951       break;
6952     case MT_STALEMATE:
6953       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6954       break;
6955     }
6956     break;
6957
6958   case MachinePlaysBlack:
6959   case MachinePlaysWhite:
6960     /* disable certain menu options while machine is thinking */
6961     SetMachineThinkingEnables();
6962     break;
6963
6964   default:
6965     break;
6966   }
6967
6968   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6969   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6970
6971   if(bookHit) { // [HGM] book: simulate book reply
6972         static char bookMove[MSG_SIZ]; // a bit generous?
6973
6974         programStats.nodes = programStats.depth = programStats.time =
6975         programStats.score = programStats.got_only_move = 0;
6976         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6977
6978         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6979         strcat(bookMove, bookHit);
6980         HandleMachineMove(bookMove, &first);
6981   }
6982   return 1;
6983 }
6984
6985 void
6986 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6987 {
6988     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6989     Markers *m = (Markers *) closure;
6990     if(rf == fromY && ff == fromX)
6991         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6992                          || kind == WhiteCapturesEnPassant
6993                          || kind == BlackCapturesEnPassant);
6994     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6995 }
6996
6997 void
6998 MarkTargetSquares (int clear)
6999 {
7000   int x, y;
7001   if(clear) // no reason to ever suppress clearing
7002     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7003   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7004      !appData.testLegality || gameMode == EditPosition) return;
7005   if(!clear) {
7006     int capt = 0;
7007     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7008     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7009       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7010       if(capt)
7011       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7012     }
7013   }
7014   DrawPosition(FALSE, NULL);
7015 }
7016
7017 int
7018 Explode (Board board, int fromX, int fromY, int toX, int toY)
7019 {
7020     if(gameInfo.variant == VariantAtomic &&
7021        (board[toY][toX] != EmptySquare ||                     // capture?
7022         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7023                          board[fromY][fromX] == BlackPawn   )
7024       )) {
7025         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7026         return TRUE;
7027     }
7028     return FALSE;
7029 }
7030
7031 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7032
7033 int
7034 CanPromote (ChessSquare piece, int y)
7035 {
7036         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7037         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7038         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7039            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7040            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7041                                                   gameInfo.variant == VariantMakruk) return FALSE;
7042         return (piece == BlackPawn && y == 1 ||
7043                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7044                 piece == BlackLance && y == 1 ||
7045                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7046 }
7047
7048 void
7049 LeftClick (ClickType clickType, int xPix, int yPix)
7050 {
7051     int x, y;
7052     Boolean saveAnimate;
7053     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7054     char promoChoice = NULLCHAR;
7055     ChessSquare piece;
7056     static TimeMark lastClickTime, prevClickTime;
7057
7058     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7059
7060     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7061
7062     if (clickType == Press) ErrorPopDown();
7063
7064     x = EventToSquare(xPix, BOARD_WIDTH);
7065     y = EventToSquare(yPix, BOARD_HEIGHT);
7066     if (!flipView && y >= 0) {
7067         y = BOARD_HEIGHT - 1 - y;
7068     }
7069     if (flipView && x >= 0) {
7070         x = BOARD_WIDTH - 1 - x;
7071     }
7072
7073     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7074         defaultPromoChoice = promoSweep;
7075         promoSweep = EmptySquare;   // terminate sweep
7076         promoDefaultAltered = TRUE;
7077         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7078     }
7079
7080     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7081         if(clickType == Release) return; // ignore upclick of click-click destination
7082         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7083         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7084         if(gameInfo.holdingsWidth &&
7085                 (WhiteOnMove(currentMove)
7086                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7087                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7088             // click in right holdings, for determining promotion piece
7089             ChessSquare p = boards[currentMove][y][x];
7090             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7091             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7092             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7093                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7094                 fromX = fromY = -1;
7095                 return;
7096             }
7097         }
7098         DrawPosition(FALSE, boards[currentMove]);
7099         return;
7100     }
7101
7102     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7103     if(clickType == Press
7104             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7105               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7106               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7107         return;
7108
7109     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7110         // could be static click on premove from-square: abort premove
7111         gotPremove = 0;
7112         ClearPremoveHighlights();
7113     }
7114
7115     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7116         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7117
7118     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7119         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7120                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7121         defaultPromoChoice = DefaultPromoChoice(side);
7122     }
7123
7124     autoQueen = appData.alwaysPromoteToQueen;
7125
7126     if (fromX == -1) {
7127       int originalY = y;
7128       gatingPiece = EmptySquare;
7129       if (clickType != Press) {
7130         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7131             DragPieceEnd(xPix, yPix); dragging = 0;
7132             DrawPosition(FALSE, NULL);
7133         }
7134         return;
7135       }
7136       doubleClick = FALSE;
7137       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7138         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7139       }
7140       fromX = x; fromY = y; toX = toY = -1;
7141       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7142          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7143          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7144             /* First square */
7145             if (OKToStartUserMove(fromX, fromY)) {
7146                 second = 0;
7147                 MarkTargetSquares(0);
7148                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7149                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7150                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7151                     promoSweep = defaultPromoChoice;
7152                     selectFlag = 0; lastX = xPix; lastY = yPix;
7153                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7154                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7155                 }
7156                 if (appData.highlightDragging) {
7157                     SetHighlights(fromX, fromY, -1, -1);
7158                 } else {
7159                     ClearHighlights();
7160                 }
7161             } else fromX = fromY = -1;
7162             return;
7163         }
7164     }
7165
7166     /* fromX != -1 */
7167     if (clickType == Press && gameMode != EditPosition) {
7168         ChessSquare fromP;
7169         ChessSquare toP;
7170         int frc;
7171
7172         // ignore off-board to clicks
7173         if(y < 0 || x < 0) return;
7174
7175         /* Check if clicking again on the same color piece */
7176         fromP = boards[currentMove][fromY][fromX];
7177         toP = boards[currentMove][y][x];
7178         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7179         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7180              WhitePawn <= toP && toP <= WhiteKing &&
7181              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7182              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7183             (BlackPawn <= fromP && fromP <= BlackKing &&
7184              BlackPawn <= toP && toP <= BlackKing &&
7185              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7186              !(fromP == BlackKing && toP == BlackRook && frc))) {
7187             /* Clicked again on same color piece -- changed his mind */
7188             second = (x == fromX && y == fromY);
7189             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7190                 second = FALSE; // first double-click rather than scond click
7191                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7192             }
7193             promoDefaultAltered = FALSE;
7194             MarkTargetSquares(1);
7195            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7196             if (appData.highlightDragging) {
7197                 SetHighlights(x, y, -1, -1);
7198             } else {
7199                 ClearHighlights();
7200             }
7201             if (OKToStartUserMove(x, y)) {
7202                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7203                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7204                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7205                  gatingPiece = boards[currentMove][fromY][fromX];
7206                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7207                 fromX = x;
7208                 fromY = y; dragging = 1;
7209                 MarkTargetSquares(0);
7210                 DragPieceBegin(xPix, yPix, FALSE);
7211                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7212                     promoSweep = defaultPromoChoice;
7213                     selectFlag = 0; lastX = xPix; lastY = yPix;
7214                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7215                 }
7216             }
7217            }
7218            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7219            second = FALSE;
7220         }
7221         // ignore clicks on holdings
7222         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7223     }
7224
7225     if (clickType == Release && x == fromX && y == fromY) {
7226         DragPieceEnd(xPix, yPix); dragging = 0;
7227         if(clearFlag) {
7228             // a deferred attempt to click-click move an empty square on top of a piece
7229             boards[currentMove][y][x] = EmptySquare;
7230             ClearHighlights();
7231             DrawPosition(FALSE, boards[currentMove]);
7232             fromX = fromY = -1; clearFlag = 0;
7233             return;
7234         }
7235         if (appData.animateDragging) {
7236             /* Undo animation damage if any */
7237             DrawPosition(FALSE, NULL);
7238         }
7239         if (second || sweepSelecting) {
7240             /* Second up/down in same square; just abort move */
7241             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7242             second = sweepSelecting = 0;
7243             fromX = fromY = -1;
7244             gatingPiece = EmptySquare;
7245             ClearHighlights();
7246             gotPremove = 0;
7247             ClearPremoveHighlights();
7248         } else {
7249             /* First upclick in same square; start click-click mode */
7250             SetHighlights(x, y, -1, -1);
7251         }
7252         return;
7253     }
7254
7255     clearFlag = 0;
7256
7257     /* we now have a different from- and (possibly off-board) to-square */
7258     /* Completed move */
7259     if(!sweepSelecting) {
7260         toX = x;
7261         toY = y;
7262     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7263
7264     saveAnimate = appData.animate;
7265     if (clickType == Press) {
7266         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7267             // must be Edit Position mode with empty-square selected
7268             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7269             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7270             return;
7271         }
7272         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7273           if(appData.sweepSelect) {
7274             ChessSquare piece = boards[currentMove][fromY][fromX];
7275             promoSweep = defaultPromoChoice;
7276             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7277             selectFlag = 0; lastX = xPix; lastY = yPix;
7278             Sweep(0); // Pawn that is going to promote: preview promotion piece
7279             sweepSelecting = 1;
7280             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7281             MarkTargetSquares(1);
7282           }
7283           return; // promo popup appears on up-click
7284         }
7285         /* Finish clickclick move */
7286         if (appData.animate || appData.highlightLastMove) {
7287             SetHighlights(fromX, fromY, toX, toY);
7288         } else {
7289             ClearHighlights();
7290         }
7291     } else {
7292 #if 0
7293 // [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
7294         /* Finish drag move */
7295         if (appData.highlightLastMove) {
7296             SetHighlights(fromX, fromY, toX, toY);
7297         } else {
7298             ClearHighlights();
7299         }
7300 #endif
7301         DragPieceEnd(xPix, yPix); dragging = 0;
7302         /* Don't animate move and drag both */
7303         appData.animate = FALSE;
7304     }
7305
7306     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7307     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7308         ChessSquare piece = boards[currentMove][fromY][fromX];
7309         if(gameMode == EditPosition && piece != EmptySquare &&
7310            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7311             int n;
7312
7313             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7314                 n = PieceToNumber(piece - (int)BlackPawn);
7315                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7316                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7317                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7318             } else
7319             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7320                 n = PieceToNumber(piece);
7321                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7322                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7323                 boards[currentMove][n][BOARD_WIDTH-2]++;
7324             }
7325             boards[currentMove][fromY][fromX] = EmptySquare;
7326         }
7327         ClearHighlights();
7328         fromX = fromY = -1;
7329         MarkTargetSquares(1);
7330         DrawPosition(TRUE, boards[currentMove]);
7331         return;
7332     }
7333
7334     // off-board moves should not be highlighted
7335     if(x < 0 || y < 0) ClearHighlights();
7336
7337     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7338
7339     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7340         SetHighlights(fromX, fromY, toX, toY);
7341         MarkTargetSquares(1);
7342         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7343             // [HGM] super: promotion to captured piece selected from holdings
7344             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7345             promotionChoice = TRUE;
7346             // kludge follows to temporarily execute move on display, without promoting yet
7347             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7348             boards[currentMove][toY][toX] = p;
7349             DrawPosition(FALSE, boards[currentMove]);
7350             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7351             boards[currentMove][toY][toX] = q;
7352             DisplayMessage("Click in holdings to choose piece", "");
7353             return;
7354         }
7355         PromotionPopUp();
7356     } else {
7357         int oldMove = currentMove;
7358         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7359         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7360         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7361         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7362            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7363             DrawPosition(TRUE, boards[currentMove]);
7364         MarkTargetSquares(1);
7365         fromX = fromY = -1;
7366     }
7367     appData.animate = saveAnimate;
7368     if (appData.animate || appData.animateDragging) {
7369         /* Undo animation damage if needed */
7370         DrawPosition(FALSE, NULL);
7371     }
7372 }
7373
7374 int
7375 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7376 {   // front-end-free part taken out of PieceMenuPopup
7377     int whichMenu; int xSqr, ySqr;
7378
7379     if(seekGraphUp) { // [HGM] seekgraph
7380         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7381         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7382         return -2;
7383     }
7384
7385     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7386          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7387         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7388         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7389         if(action == Press)   {
7390             originalFlip = flipView;
7391             flipView = !flipView; // temporarily flip board to see game from partners perspective
7392             DrawPosition(TRUE, partnerBoard);
7393             DisplayMessage(partnerStatus, "");
7394             partnerUp = TRUE;
7395         } else if(action == Release) {
7396             flipView = originalFlip;
7397             DrawPosition(TRUE, boards[currentMove]);
7398             partnerUp = FALSE;
7399         }
7400         return -2;
7401     }
7402
7403     xSqr = EventToSquare(x, BOARD_WIDTH);
7404     ySqr = EventToSquare(y, BOARD_HEIGHT);
7405     if (action == Release) {
7406         if(pieceSweep != EmptySquare) {
7407             EditPositionMenuEvent(pieceSweep, toX, toY);
7408             pieceSweep = EmptySquare;
7409         } else UnLoadPV(); // [HGM] pv
7410     }
7411     if (action != Press) return -2; // return code to be ignored
7412     switch (gameMode) {
7413       case IcsExamining:
7414         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7415       case EditPosition:
7416         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7417         if (xSqr < 0 || ySqr < 0) return -1;
7418         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7419         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7420         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7421         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7422         NextPiece(0);
7423         return 2; // grab
7424       case IcsObserving:
7425         if(!appData.icsEngineAnalyze) return -1;
7426       case IcsPlayingWhite:
7427       case IcsPlayingBlack:
7428         if(!appData.zippyPlay) goto noZip;
7429       case AnalyzeMode:
7430       case AnalyzeFile:
7431       case MachinePlaysWhite:
7432       case MachinePlaysBlack:
7433       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7434         if (!appData.dropMenu) {
7435           LoadPV(x, y);
7436           return 2; // flag front-end to grab mouse events
7437         }
7438         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7439            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7440       case EditGame:
7441       noZip:
7442         if (xSqr < 0 || ySqr < 0) return -1;
7443         if (!appData.dropMenu || appData.testLegality &&
7444             gameInfo.variant != VariantBughouse &&
7445             gameInfo.variant != VariantCrazyhouse) return -1;
7446         whichMenu = 1; // drop menu
7447         break;
7448       default:
7449         return -1;
7450     }
7451
7452     if (((*fromX = xSqr) < 0) ||
7453         ((*fromY = ySqr) < 0)) {
7454         *fromX = *fromY = -1;
7455         return -1;
7456     }
7457     if (flipView)
7458       *fromX = BOARD_WIDTH - 1 - *fromX;
7459     else
7460       *fromY = BOARD_HEIGHT - 1 - *fromY;
7461
7462     return whichMenu;
7463 }
7464
7465 void
7466 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7467 {
7468 //    char * hint = lastHint;
7469     FrontEndProgramStats stats;
7470
7471     stats.which = cps == &first ? 0 : 1;
7472     stats.depth = cpstats->depth;
7473     stats.nodes = cpstats->nodes;
7474     stats.score = cpstats->score;
7475     stats.time = cpstats->time;
7476     stats.pv = cpstats->movelist;
7477     stats.hint = lastHint;
7478     stats.an_move_index = 0;
7479     stats.an_move_count = 0;
7480
7481     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7482         stats.hint = cpstats->move_name;
7483         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7484         stats.an_move_count = cpstats->nr_moves;
7485     }
7486
7487     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7488
7489     SetProgramStats( &stats );
7490 }
7491
7492 void
7493 ClearEngineOutputPane (int which)
7494 {
7495     static FrontEndProgramStats dummyStats;
7496     dummyStats.which = which;
7497     dummyStats.pv = "#";
7498     SetProgramStats( &dummyStats );
7499 }
7500
7501 #define MAXPLAYERS 500
7502
7503 char *
7504 TourneyStandings (int display)
7505 {
7506     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7507     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7508     char result, *p, *names[MAXPLAYERS];
7509
7510     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7511         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7512     names[0] = p = strdup(appData.participants);
7513     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7514
7515     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7516
7517     while(result = appData.results[nr]) {
7518         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7519         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7520         wScore = bScore = 0;
7521         switch(result) {
7522           case '+': wScore = 2; break;
7523           case '-': bScore = 2; break;
7524           case '=': wScore = bScore = 1; break;
7525           case ' ':
7526           case '*': return strdup("busy"); // tourney not finished
7527         }
7528         score[w] += wScore;
7529         score[b] += bScore;
7530         games[w]++;
7531         games[b]++;
7532         nr++;
7533     }
7534     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7535     for(w=0; w<nPlayers; w++) {
7536         bScore = -1;
7537         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7538         ranking[w] = b; points[w] = bScore; score[b] = -2;
7539     }
7540     p = malloc(nPlayers*34+1);
7541     for(w=0; w<nPlayers && w<display; w++)
7542         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7543     free(names[0]);
7544     return p;
7545 }
7546
7547 void
7548 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7549 {       // count all piece types
7550         int p, f, r;
7551         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7552         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7553         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7554                 p = board[r][f];
7555                 pCnt[p]++;
7556                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7557                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7558                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7559                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7560                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7561                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7562         }
7563 }
7564
7565 int
7566 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7567 {
7568         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7569         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7570
7571         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7572         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7573         if(myPawns == 2 && nMine == 3) // KPP
7574             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7575         if(myPawns == 1 && nMine == 2) // KP
7576             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7577         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7578             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7579         if(myPawns) return FALSE;
7580         if(pCnt[WhiteRook+side])
7581             return pCnt[BlackRook-side] ||
7582                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7583                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7584                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7585         if(pCnt[WhiteCannon+side]) {
7586             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7587             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7588         }
7589         if(pCnt[WhiteKnight+side])
7590             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7591         return FALSE;
7592 }
7593
7594 int
7595 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7596 {
7597         VariantClass v = gameInfo.variant;
7598
7599         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7600         if(v == VariantShatranj) return TRUE; // always winnable through baring
7601         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7602         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7603
7604         if(v == VariantXiangqi) {
7605                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7606
7607                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7608                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7609                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7610                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7611                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7612                 if(stale) // we have at least one last-rank P plus perhaps C
7613                     return majors // KPKX
7614                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7615                 else // KCA*E*
7616                     return pCnt[WhiteFerz+side] // KCAK
7617                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7618                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7619                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7620
7621         } else if(v == VariantKnightmate) {
7622                 if(nMine == 1) return FALSE;
7623                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7624         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7625                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7626
7627                 if(nMine == 1) return FALSE; // bare King
7628                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
7629                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7630                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7631                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7632                 if(pCnt[WhiteKnight+side])
7633                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7634                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7635                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7636                 if(nBishops)
7637                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7638                 if(pCnt[WhiteAlfil+side])
7639                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7640                 if(pCnt[WhiteWazir+side])
7641                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7642         }
7643
7644         return TRUE;
7645 }
7646
7647 int
7648 CompareWithRights (Board b1, Board b2)
7649 {
7650     int rights = 0;
7651     if(!CompareBoards(b1, b2)) return FALSE;
7652     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7653     /* compare castling rights */
7654     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7655            rights++; /* King lost rights, while rook still had them */
7656     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7657         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7658            rights++; /* but at least one rook lost them */
7659     }
7660     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7661            rights++;
7662     if( b1[CASTLING][5] != NoRights ) {
7663         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7664            rights++;
7665     }
7666     return rights == 0;
7667 }
7668
7669 int
7670 Adjudicate (ChessProgramState *cps)
7671 {       // [HGM] some adjudications useful with buggy engines
7672         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7673         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7674         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7675         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7676         int k, drop, count = 0; static int bare = 1;
7677         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7678         Boolean canAdjudicate = !appData.icsActive;
7679
7680         // most tests only when we understand the game, i.e. legality-checking on
7681             if( appData.testLegality )
7682             {   /* [HGM] Some more adjudications for obstinate engines */
7683                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7684                 static int moveCount = 6;
7685                 ChessMove result;
7686                 char *reason = NULL;
7687
7688                 /* Count what is on board. */
7689                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7690
7691                 /* Some material-based adjudications that have to be made before stalemate test */
7692                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7693                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7694                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7695                      if(canAdjudicate && appData.checkMates) {
7696                          if(engineOpponent)
7697                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7698                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7699                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7700                          return 1;
7701                      }
7702                 }
7703
7704                 /* Bare King in Shatranj (loses) or Losers (wins) */
7705                 if( nrW == 1 || nrB == 1) {
7706                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7707                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7708                      if(canAdjudicate && appData.checkMates) {
7709                          if(engineOpponent)
7710                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7711                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7712                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7713                          return 1;
7714                      }
7715                   } else
7716                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7717                   {    /* bare King */
7718                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7719                         if(canAdjudicate && appData.checkMates) {
7720                             /* but only adjudicate if adjudication enabled */
7721                             if(engineOpponent)
7722                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7723                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7724                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7725                             return 1;
7726                         }
7727                   }
7728                 } else bare = 1;
7729
7730
7731             // don't wait for engine to announce game end if we can judge ourselves
7732             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7733               case MT_CHECK:
7734                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7735                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7736                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7737                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7738                             checkCnt++;
7739                         if(checkCnt >= 2) {
7740                             reason = "Xboard adjudication: 3rd check";
7741                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7742                             break;
7743                         }
7744                     }
7745                 }
7746               case MT_NONE:
7747               default:
7748                 break;
7749               case MT_STALEMATE:
7750               case MT_STAINMATE:
7751                 reason = "Xboard adjudication: Stalemate";
7752                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7753                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7754                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7755                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7756                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7757                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7758                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7759                                                                         EP_CHECKMATE : EP_WINS);
7760                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7761                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7762                 }
7763                 break;
7764               case MT_CHECKMATE:
7765                 reason = "Xboard adjudication: Checkmate";
7766                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7767                 if(gameInfo.variant == VariantShogi) {
7768                     if(forwardMostMove > backwardMostMove
7769                        && moveList[forwardMostMove-1][1] == '@'
7770                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7771                         reason = "XBoard adjudication: pawn-drop mate";
7772                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7773                     }
7774                 }
7775                 break;
7776             }
7777
7778                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7779                     case EP_STALEMATE:
7780                         result = GameIsDrawn; break;
7781                     case EP_CHECKMATE:
7782                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7783                     case EP_WINS:
7784                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7785                     default:
7786                         result = EndOfFile;
7787                 }
7788                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7789                     if(engineOpponent)
7790                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7791                     GameEnds( result, reason, GE_XBOARD );
7792                     return 1;
7793                 }
7794
7795                 /* Next absolutely insufficient mating material. */
7796                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7797                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7798                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7799
7800                      /* always flag draws, for judging claims */
7801                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7802
7803                      if(canAdjudicate && appData.materialDraws) {
7804                          /* but only adjudicate them if adjudication enabled */
7805                          if(engineOpponent) {
7806                            SendToProgram("force\n", engineOpponent); // suppress reply
7807                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7808                          }
7809                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7810                          return 1;
7811                      }
7812                 }
7813
7814                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7815                 if(gameInfo.variant == VariantXiangqi ?
7816                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7817                  : nrW + nrB == 4 &&
7818                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7819                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7820                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7821                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7822                    ) ) {
7823                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7824                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7825                           if(engineOpponent) {
7826                             SendToProgram("force\n", engineOpponent); // suppress reply
7827                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7828                           }
7829                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7830                           return 1;
7831                      }
7832                 } else moveCount = 6;
7833             }
7834
7835         // Repetition draws and 50-move rule can be applied independently of legality testing
7836
7837                 /* Check for rep-draws */
7838                 count = 0;
7839                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7840                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7841                 for(k = forwardMostMove-2;
7842                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7843                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7844                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7845                     k-=2)
7846                 {   int rights=0;
7847                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7848                         /* compare castling rights */
7849                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7850                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7851                                 rights++; /* King lost rights, while rook still had them */
7852                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7853                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7854                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7855                                    rights++; /* but at least one rook lost them */
7856                         }
7857                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7858                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7859                                 rights++;
7860                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7861                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7862                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7863                                    rights++;
7864                         }
7865                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7866                             && appData.drawRepeats > 1) {
7867                              /* adjudicate after user-specified nr of repeats */
7868                              int result = GameIsDrawn;
7869                              char *details = "XBoard adjudication: repetition draw";
7870                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7871                                 // [HGM] xiangqi: check for forbidden perpetuals
7872                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7873                                 for(m=forwardMostMove; m>k; m-=2) {
7874                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7875                                         ourPerpetual = 0; // the current mover did not always check
7876                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7877                                         hisPerpetual = 0; // the opponent did not always check
7878                                 }
7879                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7880                                                                         ourPerpetual, hisPerpetual);
7881                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7882                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7883                                     details = "Xboard adjudication: perpetual checking";
7884                                 } else
7885                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7886                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7887                                 } else
7888                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7889                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7890                                         result = BlackWins;
7891                                         details = "Xboard adjudication: repetition";
7892                                     }
7893                                 } else // it must be XQ
7894                                 // Now check for perpetual chases
7895                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7896                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7897                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7898                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7899                                         static char resdet[MSG_SIZ];
7900                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7901                                         details = resdet;
7902                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7903                                     } else
7904                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7905                                         break; // Abort repetition-checking loop.
7906                                 }
7907                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7908                              }
7909                              if(engineOpponent) {
7910                                SendToProgram("force\n", engineOpponent); // suppress reply
7911                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7912                              }
7913                              GameEnds( result, details, GE_XBOARD );
7914                              return 1;
7915                         }
7916                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7917                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7918                     }
7919                 }
7920
7921                 /* Now we test for 50-move draws. Determine ply count */
7922                 count = forwardMostMove;
7923                 /* look for last irreversble move */
7924                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7925                     count--;
7926                 /* if we hit starting position, add initial plies */
7927                 if( count == backwardMostMove )
7928                     count -= initialRulePlies;
7929                 count = forwardMostMove - count;
7930                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7931                         // adjust reversible move counter for checks in Xiangqi
7932                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7933                         if(i < backwardMostMove) i = backwardMostMove;
7934                         while(i <= forwardMostMove) {
7935                                 lastCheck = inCheck; // check evasion does not count
7936                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7937                                 if(inCheck || lastCheck) count--; // check does not count
7938                                 i++;
7939                         }
7940                 }
7941                 if( count >= 100)
7942                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7943                          /* this is used to judge if draw claims are legal */
7944                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7945                          if(engineOpponent) {
7946                            SendToProgram("force\n", engineOpponent); // suppress reply
7947                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7948                          }
7949                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7950                          return 1;
7951                 }
7952
7953                 /* if draw offer is pending, treat it as a draw claim
7954                  * when draw condition present, to allow engines a way to
7955                  * claim draws before making their move to avoid a race
7956                  * condition occurring after their move
7957                  */
7958                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7959                          char *p = NULL;
7960                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7961                              p = "Draw claim: 50-move rule";
7962                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7963                              p = "Draw claim: 3-fold repetition";
7964                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7965                              p = "Draw claim: insufficient mating material";
7966                          if( p != NULL && canAdjudicate) {
7967                              if(engineOpponent) {
7968                                SendToProgram("force\n", engineOpponent); // suppress reply
7969                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7970                              }
7971                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7972                              return 1;
7973                          }
7974                 }
7975
7976                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7977                     if(engineOpponent) {
7978                       SendToProgram("force\n", engineOpponent); // suppress reply
7979                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7980                     }
7981                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7982                     return 1;
7983                 }
7984         return 0;
7985 }
7986
7987 char *
7988 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7989 {   // [HGM] book: this routine intercepts moves to simulate book replies
7990     char *bookHit = NULL;
7991
7992     //first determine if the incoming move brings opponent into his book
7993     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7994         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7995     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7996     if(bookHit != NULL && !cps->bookSuspend) {
7997         // make sure opponent is not going to reply after receiving move to book position
7998         SendToProgram("force\n", cps);
7999         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8000     }
8001     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8002     // now arrange restart after book miss
8003     if(bookHit) {
8004         // after a book hit we never send 'go', and the code after the call to this routine
8005         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8006         char buf[MSG_SIZ], *move = bookHit;
8007         if(cps->useSAN) {
8008             int fromX, fromY, toX, toY;
8009             char promoChar;
8010             ChessMove moveType;
8011             move = buf + 30;
8012             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8013                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8014                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8015                                     PosFlags(forwardMostMove),
8016                                     fromY, fromX, toY, toX, promoChar, move);
8017             } else {
8018                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8019                 bookHit = NULL;
8020             }
8021         }
8022         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8023         SendToProgram(buf, cps);
8024         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8025     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8026         SendToProgram("go\n", cps);
8027         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8028     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8029         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8030             SendToProgram("go\n", cps);
8031         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8032     }
8033     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8034 }
8035
8036 int
8037 LoadError (char *errmess, ChessProgramState *cps)
8038 {   // unloads engine and switches back to -ncp mode if it was first
8039     if(cps->initDone) return FALSE;
8040     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8041     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8042     cps->pr = NoProc;
8043     if(cps == &first) {
8044         appData.noChessProgram = TRUE;
8045         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8046         gameMode = BeginningOfGame; ModeHighlight();
8047         SetNCPMode();
8048     }
8049     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8050     DisplayMessage("", ""); // erase waiting message
8051     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8052     return TRUE;
8053 }
8054
8055 char *savedMessage;
8056 ChessProgramState *savedState;
8057 void
8058 DeferredBookMove (void)
8059 {
8060         if(savedState->lastPing != savedState->lastPong)
8061                     ScheduleDelayedEvent(DeferredBookMove, 10);
8062         else
8063         HandleMachineMove(savedMessage, savedState);
8064 }
8065
8066 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8067 static ChessProgramState *stalledEngine;
8068 static char stashedInputMove[MSG_SIZ];
8069
8070 void
8071 HandleMachineMove (char *message, ChessProgramState *cps)
8072 {
8073     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8074     char realname[MSG_SIZ];
8075     int fromX, fromY, toX, toY;
8076     ChessMove moveType;
8077     char promoChar;
8078     char *p, *pv=buf1;
8079     int machineWhite, oldError;
8080     char *bookHit;
8081
8082     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8083         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8084         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8085             DisplayError(_("Invalid pairing from pairing engine"), 0);
8086             return;
8087         }
8088         pairingReceived = 1;
8089         NextMatchGame();
8090         return; // Skim the pairing messages here.
8091     }
8092
8093     oldError = cps->userError; cps->userError = 0;
8094
8095 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8096     /*
8097      * Kludge to ignore BEL characters
8098      */
8099     while (*message == '\007') message++;
8100
8101     /*
8102      * [HGM] engine debug message: ignore lines starting with '#' character
8103      */
8104     if(cps->debug && *message == '#') return;
8105
8106     /*
8107      * Look for book output
8108      */
8109     if (cps == &first && bookRequested) {
8110         if (message[0] == '\t' || message[0] == ' ') {
8111             /* Part of the book output is here; append it */
8112             strcat(bookOutput, message);
8113             strcat(bookOutput, "  \n");
8114             return;
8115         } else if (bookOutput[0] != NULLCHAR) {
8116             /* All of book output has arrived; display it */
8117             char *p = bookOutput;
8118             while (*p != NULLCHAR) {
8119                 if (*p == '\t') *p = ' ';
8120                 p++;
8121             }
8122             DisplayInformation(bookOutput);
8123             bookRequested = FALSE;
8124             /* Fall through to parse the current output */
8125         }
8126     }
8127
8128     /*
8129      * Look for machine move.
8130      */
8131     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8132         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8133     {
8134         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8135             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8136             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8137             stalledEngine = cps;
8138             if(appData.ponderNextMove) { // bring opponent out of ponder
8139                 if(gameMode == TwoMachinesPlay) {
8140                     if(cps->other->pause)
8141                         PauseEngine(cps->other);
8142                     else
8143                         SendToProgram("easy\n", cps->other);
8144                 }
8145             }
8146             StopClocks();
8147             return;
8148         }
8149
8150         /* This method is only useful on engines that support ping */
8151         if (cps->lastPing != cps->lastPong) {
8152           if (gameMode == BeginningOfGame) {
8153             /* Extra move from before last new; ignore */
8154             if (appData.debugMode) {
8155                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8156             }
8157           } else {
8158             if (appData.debugMode) {
8159                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8160                         cps->which, gameMode);
8161             }
8162
8163             SendToProgram("undo\n", cps);
8164           }
8165           return;
8166         }
8167
8168         switch (gameMode) {
8169           case BeginningOfGame:
8170             /* Extra move from before last reset; ignore */
8171             if (appData.debugMode) {
8172                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8173             }
8174             return;
8175
8176           case EndOfGame:
8177           case IcsIdle:
8178           default:
8179             /* Extra move after we tried to stop.  The mode test is
8180                not a reliable way of detecting this problem, but it's
8181                the best we can do on engines that don't support ping.
8182             */
8183             if (appData.debugMode) {
8184                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8185                         cps->which, gameMode);
8186             }
8187             SendToProgram("undo\n", cps);
8188             return;
8189
8190           case MachinePlaysWhite:
8191           case IcsPlayingWhite:
8192             machineWhite = TRUE;
8193             break;
8194
8195           case MachinePlaysBlack:
8196           case IcsPlayingBlack:
8197             machineWhite = FALSE;
8198             break;
8199
8200           case TwoMachinesPlay:
8201             machineWhite = (cps->twoMachinesColor[0] == 'w');
8202             break;
8203         }
8204         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8205             if (appData.debugMode) {
8206                 fprintf(debugFP,
8207                         "Ignoring move out of turn by %s, gameMode %d"
8208                         ", forwardMost %d\n",
8209                         cps->which, gameMode, forwardMostMove);
8210             }
8211             return;
8212         }
8213
8214         if(cps->alphaRank) AlphaRank(machineMove, 4);
8215         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8216                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8217             /* Machine move could not be parsed; ignore it. */
8218           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8219                     machineMove, _(cps->which));
8220             DisplayMoveError(buf1);
8221             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8222                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8223             if (gameMode == TwoMachinesPlay) {
8224               GameEnds(machineWhite ? BlackWins : WhiteWins,
8225                        buf1, GE_XBOARD);
8226             }
8227             return;
8228         }
8229
8230         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8231         /* So we have to redo legality test with true e.p. status here,  */
8232         /* to make sure an illegal e.p. capture does not slip through,   */
8233         /* to cause a forfeit on a justified illegal-move complaint      */
8234         /* of the opponent.                                              */
8235         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8236            ChessMove moveType;
8237            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8238                              fromY, fromX, toY, toX, promoChar);
8239             if(moveType == IllegalMove) {
8240               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8241                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8242                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8243                            buf1, GE_XBOARD);
8244                 return;
8245            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8246            /* [HGM] Kludge to handle engines that send FRC-style castling
8247               when they shouldn't (like TSCP-Gothic) */
8248            switch(moveType) {
8249              case WhiteASideCastleFR:
8250              case BlackASideCastleFR:
8251                toX+=2;
8252                currentMoveString[2]++;
8253                break;
8254              case WhiteHSideCastleFR:
8255              case BlackHSideCastleFR:
8256                toX--;
8257                currentMoveString[2]--;
8258                break;
8259              default: ; // nothing to do, but suppresses warning of pedantic compilers
8260            }
8261         }
8262         hintRequested = FALSE;
8263         lastHint[0] = NULLCHAR;
8264         bookRequested = FALSE;
8265         /* Program may be pondering now */
8266         cps->maybeThinking = TRUE;
8267         if (cps->sendTime == 2) cps->sendTime = 1;
8268         if (cps->offeredDraw) cps->offeredDraw--;
8269
8270         /* [AS] Save move info*/
8271         pvInfoList[ forwardMostMove ].score = programStats.score;
8272         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8273         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8274
8275         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8276
8277         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8278         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8279             int count = 0;
8280
8281             while( count < adjudicateLossPlies ) {
8282                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8283
8284                 if( count & 1 ) {
8285                     score = -score; /* Flip score for winning side */
8286                 }
8287
8288                 if( score > adjudicateLossThreshold ) {
8289                     break;
8290                 }
8291
8292                 count++;
8293             }
8294
8295             if( count >= adjudicateLossPlies ) {
8296                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8297
8298                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8299                     "Xboard adjudication",
8300                     GE_XBOARD );
8301
8302                 return;
8303             }
8304         }
8305
8306         if(Adjudicate(cps)) {
8307             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8308             return; // [HGM] adjudicate: for all automatic game ends
8309         }
8310
8311 #if ZIPPY
8312         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8313             first.initDone) {
8314           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8315                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8316                 SendToICS("draw ");
8317                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8318           }
8319           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8320           ics_user_moved = 1;
8321           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8322                 char buf[3*MSG_SIZ];
8323
8324                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8325                         programStats.score / 100.,
8326                         programStats.depth,
8327                         programStats.time / 100.,
8328                         (unsigned int)programStats.nodes,
8329                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8330                         programStats.movelist);
8331                 SendToICS(buf);
8332 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8333           }
8334         }
8335 #endif
8336
8337         /* [AS] Clear stats for next move */
8338         ClearProgramStats();
8339         thinkOutput[0] = NULLCHAR;
8340         hiddenThinkOutputState = 0;
8341
8342         bookHit = NULL;
8343         if (gameMode == TwoMachinesPlay) {
8344             /* [HGM] relaying draw offers moved to after reception of move */
8345             /* and interpreting offer as claim if it brings draw condition */
8346             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8347                 SendToProgram("draw\n", cps->other);
8348             }
8349             if (cps->other->sendTime) {
8350                 SendTimeRemaining(cps->other,
8351                                   cps->other->twoMachinesColor[0] == 'w');
8352             }
8353             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8354             if (firstMove && !bookHit) {
8355                 firstMove = FALSE;
8356                 if (cps->other->useColors) {
8357                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8358                 }
8359                 SendToProgram("go\n", cps->other);
8360             }
8361             cps->other->maybeThinking = TRUE;
8362         }
8363
8364         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8365
8366         if (!pausing && appData.ringBellAfterMoves) {
8367             RingBell();
8368         }
8369
8370         /*
8371          * Reenable menu items that were disabled while
8372          * machine was thinking
8373          */
8374         if (gameMode != TwoMachinesPlay)
8375             SetUserThinkingEnables();
8376
8377         // [HGM] book: after book hit opponent has received move and is now in force mode
8378         // force the book reply into it, and then fake that it outputted this move by jumping
8379         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8380         if(bookHit) {
8381                 static char bookMove[MSG_SIZ]; // a bit generous?
8382
8383                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8384                 strcat(bookMove, bookHit);
8385                 message = bookMove;
8386                 cps = cps->other;
8387                 programStats.nodes = programStats.depth = programStats.time =
8388                 programStats.score = programStats.got_only_move = 0;
8389                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8390
8391                 if(cps->lastPing != cps->lastPong) {
8392                     savedMessage = message; // args for deferred call
8393                     savedState = cps;
8394                     ScheduleDelayedEvent(DeferredBookMove, 10);
8395                     return;
8396                 }
8397                 goto FakeBookMove;
8398         }
8399
8400         return;
8401     }
8402
8403     /* Set special modes for chess engines.  Later something general
8404      *  could be added here; for now there is just one kludge feature,
8405      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8406      *  when "xboard" is given as an interactive command.
8407      */
8408     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8409         cps->useSigint = FALSE;
8410         cps->useSigterm = FALSE;
8411     }
8412     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8413       ParseFeatures(message+8, cps);
8414       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8415     }
8416
8417     if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8418                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8419       int dummy, s=6; char buf[MSG_SIZ];
8420       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8421       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8422       if(startedFromSetupPosition) return;
8423       ParseFEN(boards[0], &dummy, message+s);
8424       DrawPosition(TRUE, boards[0]);
8425       startedFromSetupPosition = TRUE;
8426       return;
8427     }
8428     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8429      * want this, I was asked to put it in, and obliged.
8430      */
8431     if (!strncmp(message, "setboard ", 9)) {
8432         Board initial_position;
8433
8434         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8435
8436         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8437             DisplayError(_("Bad FEN received from engine"), 0);
8438             return ;
8439         } else {
8440            Reset(TRUE, FALSE);
8441            CopyBoard(boards[0], initial_position);
8442            initialRulePlies = FENrulePlies;
8443            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8444            else gameMode = MachinePlaysBlack;
8445            DrawPosition(FALSE, boards[currentMove]);
8446         }
8447         return;
8448     }
8449
8450     /*
8451      * Look for communication commands
8452      */
8453     if (!strncmp(message, "telluser ", 9)) {
8454         if(message[9] == '\\' && message[10] == '\\')
8455             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8456         PlayTellSound();
8457         DisplayNote(message + 9);
8458         return;
8459     }
8460     if (!strncmp(message, "tellusererror ", 14)) {
8461         cps->userError = 1;
8462         if(message[14] == '\\' && message[15] == '\\')
8463             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8464         PlayTellSound();
8465         DisplayError(message + 14, 0);
8466         return;
8467     }
8468     if (!strncmp(message, "tellopponent ", 13)) {
8469       if (appData.icsActive) {
8470         if (loggedOn) {
8471           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8472           SendToICS(buf1);
8473         }
8474       } else {
8475         DisplayNote(message + 13);
8476       }
8477       return;
8478     }
8479     if (!strncmp(message, "tellothers ", 11)) {
8480       if (appData.icsActive) {
8481         if (loggedOn) {
8482           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8483           SendToICS(buf1);
8484         }
8485       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8486       return;
8487     }
8488     if (!strncmp(message, "tellall ", 8)) {
8489       if (appData.icsActive) {
8490         if (loggedOn) {
8491           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8492           SendToICS(buf1);
8493         }
8494       } else {
8495         DisplayNote(message + 8);
8496       }
8497       return;
8498     }
8499     if (strncmp(message, "warning", 7) == 0) {
8500         /* Undocumented feature, use tellusererror in new code */
8501         DisplayError(message, 0);
8502         return;
8503     }
8504     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8505         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8506         strcat(realname, " query");
8507         AskQuestion(realname, buf2, buf1, cps->pr);
8508         return;
8509     }
8510     /* Commands from the engine directly to ICS.  We don't allow these to be
8511      *  sent until we are logged on. Crafty kibitzes have been known to
8512      *  interfere with the login process.
8513      */
8514     if (loggedOn) {
8515         if (!strncmp(message, "tellics ", 8)) {
8516             SendToICS(message + 8);
8517             SendToICS("\n");
8518             return;
8519         }
8520         if (!strncmp(message, "tellicsnoalias ", 15)) {
8521             SendToICS(ics_prefix);
8522             SendToICS(message + 15);
8523             SendToICS("\n");
8524             return;
8525         }
8526         /* The following are for backward compatibility only */
8527         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8528             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8529             SendToICS(ics_prefix);
8530             SendToICS(message);
8531             SendToICS("\n");
8532             return;
8533         }
8534     }
8535     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8536         return;
8537     }
8538     /*
8539      * If the move is illegal, cancel it and redraw the board.
8540      * Also deal with other error cases.  Matching is rather loose
8541      * here to accommodate engines written before the spec.
8542      */
8543     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8544         strncmp(message, "Error", 5) == 0) {
8545         if (StrStr(message, "name") ||
8546             StrStr(message, "rating") || StrStr(message, "?") ||
8547             StrStr(message, "result") || StrStr(message, "board") ||
8548             StrStr(message, "bk") || StrStr(message, "computer") ||
8549             StrStr(message, "variant") || StrStr(message, "hint") ||
8550             StrStr(message, "random") || StrStr(message, "depth") ||
8551             StrStr(message, "accepted")) {
8552             return;
8553         }
8554         if (StrStr(message, "protover")) {
8555           /* Program is responding to input, so it's apparently done
8556              initializing, and this error message indicates it is
8557              protocol version 1.  So we don't need to wait any longer
8558              for it to initialize and send feature commands. */
8559           FeatureDone(cps, 1);
8560           cps->protocolVersion = 1;
8561           return;
8562         }
8563         cps->maybeThinking = FALSE;
8564
8565         if (StrStr(message, "draw")) {
8566             /* Program doesn't have "draw" command */
8567             cps->sendDrawOffers = 0;
8568             return;
8569         }
8570         if (cps->sendTime != 1 &&
8571             (StrStr(message, "time") || StrStr(message, "otim"))) {
8572           /* Program apparently doesn't have "time" or "otim" command */
8573           cps->sendTime = 0;
8574           return;
8575         }
8576         if (StrStr(message, "analyze")) {
8577             cps->analysisSupport = FALSE;
8578             cps->analyzing = FALSE;
8579 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8580             EditGameEvent(); // [HGM] try to preserve loaded game
8581             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8582             DisplayError(buf2, 0);
8583             return;
8584         }
8585         if (StrStr(message, "(no matching move)st")) {
8586           /* Special kludge for GNU Chess 4 only */
8587           cps->stKludge = TRUE;
8588           SendTimeControl(cps, movesPerSession, timeControl,
8589                           timeIncrement, appData.searchDepth,
8590                           searchTime);
8591           return;
8592         }
8593         if (StrStr(message, "(no matching move)sd")) {
8594           /* Special kludge for GNU Chess 4 only */
8595           cps->sdKludge = TRUE;
8596           SendTimeControl(cps, movesPerSession, timeControl,
8597                           timeIncrement, appData.searchDepth,
8598                           searchTime);
8599           return;
8600         }
8601         if (!StrStr(message, "llegal")) {
8602             return;
8603         }
8604         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8605             gameMode == IcsIdle) return;
8606         if (forwardMostMove <= backwardMostMove) return;
8607         if (pausing) PauseEvent();
8608       if(appData.forceIllegal) {
8609             // [HGM] illegal: machine refused move; force position after move into it
8610           SendToProgram("force\n", cps);
8611           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8612                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8613                 // when black is to move, while there might be nothing on a2 or black
8614                 // might already have the move. So send the board as if white has the move.
8615                 // But first we must change the stm of the engine, as it refused the last move
8616                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8617                 if(WhiteOnMove(forwardMostMove)) {
8618                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8619                     SendBoard(cps, forwardMostMove); // kludgeless board
8620                 } else {
8621                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8622                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8623                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8624                 }
8625           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8626             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8627                  gameMode == TwoMachinesPlay)
8628               SendToProgram("go\n", cps);
8629             return;
8630       } else
8631         if (gameMode == PlayFromGameFile) {
8632             /* Stop reading this game file */
8633             gameMode = EditGame;
8634             ModeHighlight();
8635         }
8636         /* [HGM] illegal-move claim should forfeit game when Xboard */
8637         /* only passes fully legal moves                            */
8638         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8639             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8640                                 "False illegal-move claim", GE_XBOARD );
8641             return; // do not take back move we tested as valid
8642         }
8643         currentMove = forwardMostMove-1;
8644         DisplayMove(currentMove-1); /* before DisplayMoveError */
8645         SwitchClocks(forwardMostMove-1); // [HGM] race
8646         DisplayBothClocks();
8647         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8648                 parseList[currentMove], _(cps->which));
8649         DisplayMoveError(buf1);
8650         DrawPosition(FALSE, boards[currentMove]);
8651
8652         SetUserThinkingEnables();
8653         return;
8654     }
8655     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8656         /* Program has a broken "time" command that
8657            outputs a string not ending in newline.
8658            Don't use it. */
8659         cps->sendTime = 0;
8660     }
8661
8662     /*
8663      * If chess program startup fails, exit with an error message.
8664      * Attempts to recover here are futile. [HGM] Well, we try anyway
8665      */
8666     if ((StrStr(message, "unknown host") != NULL)
8667         || (StrStr(message, "No remote directory") != NULL)
8668         || (StrStr(message, "not found") != NULL)
8669         || (StrStr(message, "No such file") != NULL)
8670         || (StrStr(message, "can't alloc") != NULL)
8671         || (StrStr(message, "Permission denied") != NULL)) {
8672
8673         cps->maybeThinking = FALSE;
8674         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8675                 _(cps->which), cps->program, cps->host, message);
8676         RemoveInputSource(cps->isr);
8677         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8678             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8679             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8680         }
8681         return;
8682     }
8683
8684     /*
8685      * Look for hint output
8686      */
8687     if (sscanf(message, "Hint: %s", buf1) == 1) {
8688         if (cps == &first && hintRequested) {
8689             hintRequested = FALSE;
8690             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8691                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8692                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8693                                     PosFlags(forwardMostMove),
8694                                     fromY, fromX, toY, toX, promoChar, buf1);
8695                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8696                 DisplayInformation(buf2);
8697             } else {
8698                 /* Hint move could not be parsed!? */
8699               snprintf(buf2, sizeof(buf2),
8700                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8701                         buf1, _(cps->which));
8702                 DisplayError(buf2, 0);
8703             }
8704         } else {
8705           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8706         }
8707         return;
8708     }
8709
8710     /*
8711      * Ignore other messages if game is not in progress
8712      */
8713     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8714         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8715
8716     /*
8717      * look for win, lose, draw, or draw offer
8718      */
8719     if (strncmp(message, "1-0", 3) == 0) {
8720         char *p, *q, *r = "";
8721         p = strchr(message, '{');
8722         if (p) {
8723             q = strchr(p, '}');
8724             if (q) {
8725                 *q = NULLCHAR;
8726                 r = p + 1;
8727             }
8728         }
8729         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8730         return;
8731     } else if (strncmp(message, "0-1", 3) == 0) {
8732         char *p, *q, *r = "";
8733         p = strchr(message, '{');
8734         if (p) {
8735             q = strchr(p, '}');
8736             if (q) {
8737                 *q = NULLCHAR;
8738                 r = p + 1;
8739             }
8740         }
8741         /* Kludge for Arasan 4.1 bug */
8742         if (strcmp(r, "Black resigns") == 0) {
8743             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8744             return;
8745         }
8746         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8747         return;
8748     } else if (strncmp(message, "1/2", 3) == 0) {
8749         char *p, *q, *r = "";
8750         p = strchr(message, '{');
8751         if (p) {
8752             q = strchr(p, '}');
8753             if (q) {
8754                 *q = NULLCHAR;
8755                 r = p + 1;
8756             }
8757         }
8758
8759         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8760         return;
8761
8762     } else if (strncmp(message, "White resign", 12) == 0) {
8763         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8764         return;
8765     } else if (strncmp(message, "Black resign", 12) == 0) {
8766         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8767         return;
8768     } else if (strncmp(message, "White matches", 13) == 0 ||
8769                strncmp(message, "Black matches", 13) == 0   ) {
8770         /* [HGM] ignore GNUShogi noises */
8771         return;
8772     } else if (strncmp(message, "White", 5) == 0 &&
8773                message[5] != '(' &&
8774                StrStr(message, "Black") == NULL) {
8775         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8776         return;
8777     } else if (strncmp(message, "Black", 5) == 0 &&
8778                message[5] != '(') {
8779         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8780         return;
8781     } else if (strcmp(message, "resign") == 0 ||
8782                strcmp(message, "computer resigns") == 0) {
8783         switch (gameMode) {
8784           case MachinePlaysBlack:
8785           case IcsPlayingBlack:
8786             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8787             break;
8788           case MachinePlaysWhite:
8789           case IcsPlayingWhite:
8790             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8791             break;
8792           case TwoMachinesPlay:
8793             if (cps->twoMachinesColor[0] == 'w')
8794               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8795             else
8796               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8797             break;
8798           default:
8799             /* can't happen */
8800             break;
8801         }
8802         return;
8803     } else if (strncmp(message, "opponent mates", 14) == 0) {
8804         switch (gameMode) {
8805           case MachinePlaysBlack:
8806           case IcsPlayingBlack:
8807             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8808             break;
8809           case MachinePlaysWhite:
8810           case IcsPlayingWhite:
8811             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8812             break;
8813           case TwoMachinesPlay:
8814             if (cps->twoMachinesColor[0] == 'w')
8815               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8816             else
8817               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8818             break;
8819           default:
8820             /* can't happen */
8821             break;
8822         }
8823         return;
8824     } else if (strncmp(message, "computer mates", 14) == 0) {
8825         switch (gameMode) {
8826           case MachinePlaysBlack:
8827           case IcsPlayingBlack:
8828             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8829             break;
8830           case MachinePlaysWhite:
8831           case IcsPlayingWhite:
8832             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8833             break;
8834           case TwoMachinesPlay:
8835             if (cps->twoMachinesColor[0] == 'w')
8836               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8837             else
8838               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8839             break;
8840           default:
8841             /* can't happen */
8842             break;
8843         }
8844         return;
8845     } else if (strncmp(message, "checkmate", 9) == 0) {
8846         if (WhiteOnMove(forwardMostMove)) {
8847             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8848         } else {
8849             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8850         }
8851         return;
8852     } else if (strstr(message, "Draw") != NULL ||
8853                strstr(message, "game is a draw") != NULL) {
8854         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8855         return;
8856     } else if (strstr(message, "offer") != NULL &&
8857                strstr(message, "draw") != NULL) {
8858 #if ZIPPY
8859         if (appData.zippyPlay && first.initDone) {
8860             /* Relay offer to ICS */
8861             SendToICS(ics_prefix);
8862             SendToICS("draw\n");
8863         }
8864 #endif
8865         cps->offeredDraw = 2; /* valid until this engine moves twice */
8866         if (gameMode == TwoMachinesPlay) {
8867             if (cps->other->offeredDraw) {
8868                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8869             /* [HGM] in two-machine mode we delay relaying draw offer      */
8870             /* until after we also have move, to see if it is really claim */
8871             }
8872         } else if (gameMode == MachinePlaysWhite ||
8873                    gameMode == MachinePlaysBlack) {
8874           if (userOfferedDraw) {
8875             DisplayInformation(_("Machine accepts your draw offer"));
8876             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8877           } else {
8878             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8879           }
8880         }
8881     }
8882
8883
8884     /*
8885      * Look for thinking output
8886      */
8887     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8888           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8889                                 ) {
8890         int plylev, mvleft, mvtot, curscore, time;
8891         char mvname[MOVE_LEN];
8892         u64 nodes; // [DM]
8893         char plyext;
8894         int ignore = FALSE;
8895         int prefixHint = FALSE;
8896         mvname[0] = NULLCHAR;
8897
8898         switch (gameMode) {
8899           case MachinePlaysBlack:
8900           case IcsPlayingBlack:
8901             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8902             break;
8903           case MachinePlaysWhite:
8904           case IcsPlayingWhite:
8905             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8906             break;
8907           case AnalyzeMode:
8908           case AnalyzeFile:
8909             break;
8910           case IcsObserving: /* [DM] icsEngineAnalyze */
8911             if (!appData.icsEngineAnalyze) ignore = TRUE;
8912             break;
8913           case TwoMachinesPlay:
8914             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8915                 ignore = TRUE;
8916             }
8917             break;
8918           default:
8919             ignore = TRUE;
8920             break;
8921         }
8922
8923         if (!ignore) {
8924             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8925             buf1[0] = NULLCHAR;
8926             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8927                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8928
8929                 if (plyext != ' ' && plyext != '\t') {
8930                     time *= 100;
8931                 }
8932
8933                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8934                 if( cps->scoreIsAbsolute &&
8935                     ( gameMode == MachinePlaysBlack ||
8936                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8937                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8938                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8939                      !WhiteOnMove(currentMove)
8940                     ) )
8941                 {
8942                     curscore = -curscore;
8943                 }
8944
8945                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8946
8947                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8948                         char buf[MSG_SIZ];
8949                         FILE *f;
8950                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8951                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8952                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8953                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8954                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8955                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8956                                 fclose(f);
8957                         } else DisplayError(_("failed writing PV"), 0);
8958                 }
8959
8960                 tempStats.depth = plylev;
8961                 tempStats.nodes = nodes;
8962                 tempStats.time = time;
8963                 tempStats.score = curscore;
8964                 tempStats.got_only_move = 0;
8965
8966                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8967                         int ticklen;
8968
8969                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8970                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8971                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8972                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8973                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8974                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8975                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8976                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8977                 }
8978
8979                 /* Buffer overflow protection */
8980                 if (pv[0] != NULLCHAR) {
8981                     if (strlen(pv) >= sizeof(tempStats.movelist)
8982                         && appData.debugMode) {
8983                         fprintf(debugFP,
8984                                 "PV is too long; using the first %u bytes.\n",
8985                                 (unsigned) sizeof(tempStats.movelist) - 1);
8986                     }
8987
8988                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8989                 } else {
8990                     sprintf(tempStats.movelist, " no PV\n");
8991                 }
8992
8993                 if (tempStats.seen_stat) {
8994                     tempStats.ok_to_send = 1;
8995                 }
8996
8997                 if (strchr(tempStats.movelist, '(') != NULL) {
8998                     tempStats.line_is_book = 1;
8999                     tempStats.nr_moves = 0;
9000                     tempStats.moves_left = 0;
9001                 } else {
9002                     tempStats.line_is_book = 0;
9003                 }
9004
9005                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9006                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9007
9008                 SendProgramStatsToFrontend( cps, &tempStats );
9009
9010                 /*
9011                     [AS] Protect the thinkOutput buffer from overflow... this
9012                     is only useful if buf1 hasn't overflowed first!
9013                 */
9014                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9015                          plylev,
9016                          (gameMode == TwoMachinesPlay ?
9017                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9018                          ((double) curscore) / 100.0,
9019                          prefixHint ? lastHint : "",
9020                          prefixHint ? " " : "" );
9021
9022                 if( buf1[0] != NULLCHAR ) {
9023                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9024
9025                     if( strlen(pv) > max_len ) {
9026                         if( appData.debugMode) {
9027                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9028                         }
9029                         pv[max_len+1] = '\0';
9030                     }
9031
9032                     strcat( thinkOutput, pv);
9033                 }
9034
9035                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9036                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9037                     DisplayMove(currentMove - 1);
9038                 }
9039                 return;
9040
9041             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9042                 /* crafty (9.25+) says "(only move) <move>"
9043                  * if there is only 1 legal move
9044                  */
9045                 sscanf(p, "(only move) %s", buf1);
9046                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9047                 sprintf(programStats.movelist, "%s (only move)", buf1);
9048                 programStats.depth = 1;
9049                 programStats.nr_moves = 1;
9050                 programStats.moves_left = 1;
9051                 programStats.nodes = 1;
9052                 programStats.time = 1;
9053                 programStats.got_only_move = 1;
9054
9055                 /* Not really, but we also use this member to
9056                    mean "line isn't going to change" (Crafty
9057                    isn't searching, so stats won't change) */
9058                 programStats.line_is_book = 1;
9059
9060                 SendProgramStatsToFrontend( cps, &programStats );
9061
9062                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9063                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9064                     DisplayMove(currentMove - 1);
9065                 }
9066                 return;
9067             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9068                               &time, &nodes, &plylev, &mvleft,
9069                               &mvtot, mvname) >= 5) {
9070                 /* The stat01: line is from Crafty (9.29+) in response
9071                    to the "." command */
9072                 programStats.seen_stat = 1;
9073                 cps->maybeThinking = TRUE;
9074
9075                 if (programStats.got_only_move || !appData.periodicUpdates)
9076                   return;
9077
9078                 programStats.depth = plylev;
9079                 programStats.time = time;
9080                 programStats.nodes = nodes;
9081                 programStats.moves_left = mvleft;
9082                 programStats.nr_moves = mvtot;
9083                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9084                 programStats.ok_to_send = 1;
9085                 programStats.movelist[0] = '\0';
9086
9087                 SendProgramStatsToFrontend( cps, &programStats );
9088
9089                 return;
9090
9091             } else if (strncmp(message,"++",2) == 0) {
9092                 /* Crafty 9.29+ outputs this */
9093                 programStats.got_fail = 2;
9094                 return;
9095
9096             } else if (strncmp(message,"--",2) == 0) {
9097                 /* Crafty 9.29+ outputs this */
9098                 programStats.got_fail = 1;
9099                 return;
9100
9101             } else if (thinkOutput[0] != NULLCHAR &&
9102                        strncmp(message, "    ", 4) == 0) {
9103                 unsigned message_len;
9104
9105                 p = message;
9106                 while (*p && *p == ' ') p++;
9107
9108                 message_len = strlen( p );
9109
9110                 /* [AS] Avoid buffer overflow */
9111                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9112                     strcat(thinkOutput, " ");
9113                     strcat(thinkOutput, p);
9114                 }
9115
9116                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9117                     strcat(programStats.movelist, " ");
9118                     strcat(programStats.movelist, p);
9119                 }
9120
9121                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9122                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9123                     DisplayMove(currentMove - 1);
9124                 }
9125                 return;
9126             }
9127         }
9128         else {
9129             buf1[0] = NULLCHAR;
9130
9131             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9132                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9133             {
9134                 ChessProgramStats cpstats;
9135
9136                 if (plyext != ' ' && plyext != '\t') {
9137                     time *= 100;
9138                 }
9139
9140                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9141                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9142                     curscore = -curscore;
9143                 }
9144
9145                 cpstats.depth = plylev;
9146                 cpstats.nodes = nodes;
9147                 cpstats.time = time;
9148                 cpstats.score = curscore;
9149                 cpstats.got_only_move = 0;
9150                 cpstats.movelist[0] = '\0';
9151
9152                 if (buf1[0] != NULLCHAR) {
9153                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9154                 }
9155
9156                 cpstats.ok_to_send = 0;
9157                 cpstats.line_is_book = 0;
9158                 cpstats.nr_moves = 0;
9159                 cpstats.moves_left = 0;
9160
9161                 SendProgramStatsToFrontend( cps, &cpstats );
9162             }
9163         }
9164     }
9165 }
9166
9167
9168 /* Parse a game score from the character string "game", and
9169    record it as the history of the current game.  The game
9170    score is NOT assumed to start from the standard position.
9171    The display is not updated in any way.
9172    */
9173 void
9174 ParseGameHistory (char *game)
9175 {
9176     ChessMove moveType;
9177     int fromX, fromY, toX, toY, boardIndex;
9178     char promoChar;
9179     char *p, *q;
9180     char buf[MSG_SIZ];
9181
9182     if (appData.debugMode)
9183       fprintf(debugFP, "Parsing game history: %s\n", game);
9184
9185     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9186     gameInfo.site = StrSave(appData.icsHost);
9187     gameInfo.date = PGNDate();
9188     gameInfo.round = StrSave("-");
9189
9190     /* Parse out names of players */
9191     while (*game == ' ') game++;
9192     p = buf;
9193     while (*game != ' ') *p++ = *game++;
9194     *p = NULLCHAR;
9195     gameInfo.white = StrSave(buf);
9196     while (*game == ' ') game++;
9197     p = buf;
9198     while (*game != ' ' && *game != '\n') *p++ = *game++;
9199     *p = NULLCHAR;
9200     gameInfo.black = StrSave(buf);
9201
9202     /* Parse moves */
9203     boardIndex = blackPlaysFirst ? 1 : 0;
9204     yynewstr(game);
9205     for (;;) {
9206         yyboardindex = boardIndex;
9207         moveType = (ChessMove) Myylex();
9208         switch (moveType) {
9209           case IllegalMove:             /* maybe suicide chess, etc. */
9210   if (appData.debugMode) {
9211     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9212     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9213     setbuf(debugFP, NULL);
9214   }
9215           case WhitePromotion:
9216           case BlackPromotion:
9217           case WhiteNonPromotion:
9218           case BlackNonPromotion:
9219           case NormalMove:
9220           case WhiteCapturesEnPassant:
9221           case BlackCapturesEnPassant:
9222           case WhiteKingSideCastle:
9223           case WhiteQueenSideCastle:
9224           case BlackKingSideCastle:
9225           case BlackQueenSideCastle:
9226           case WhiteKingSideCastleWild:
9227           case WhiteQueenSideCastleWild:
9228           case BlackKingSideCastleWild:
9229           case BlackQueenSideCastleWild:
9230           /* PUSH Fabien */
9231           case WhiteHSideCastleFR:
9232           case WhiteASideCastleFR:
9233           case BlackHSideCastleFR:
9234           case BlackASideCastleFR:
9235           /* POP Fabien */
9236             fromX = currentMoveString[0] - AAA;
9237             fromY = currentMoveString[1] - ONE;
9238             toX = currentMoveString[2] - AAA;
9239             toY = currentMoveString[3] - ONE;
9240             promoChar = currentMoveString[4];
9241             break;
9242           case WhiteDrop:
9243           case BlackDrop:
9244             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9245             fromX = moveType == WhiteDrop ?
9246               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9247             (int) CharToPiece(ToLower(currentMoveString[0]));
9248             fromY = DROP_RANK;
9249             toX = currentMoveString[2] - AAA;
9250             toY = currentMoveString[3] - ONE;
9251             promoChar = NULLCHAR;
9252             break;
9253           case AmbiguousMove:
9254             /* bug? */
9255             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9256   if (appData.debugMode) {
9257     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9258     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9259     setbuf(debugFP, NULL);
9260   }
9261             DisplayError(buf, 0);
9262             return;
9263           case ImpossibleMove:
9264             /* bug? */
9265             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9266   if (appData.debugMode) {
9267     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9268     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9269     setbuf(debugFP, NULL);
9270   }
9271             DisplayError(buf, 0);
9272             return;
9273           case EndOfFile:
9274             if (boardIndex < backwardMostMove) {
9275                 /* Oops, gap.  How did that happen? */
9276                 DisplayError(_("Gap in move list"), 0);
9277                 return;
9278             }
9279             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9280             if (boardIndex > forwardMostMove) {
9281                 forwardMostMove = boardIndex;
9282             }
9283             return;
9284           case ElapsedTime:
9285             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9286                 strcat(parseList[boardIndex-1], " ");
9287                 strcat(parseList[boardIndex-1], yy_text);
9288             }
9289             continue;
9290           case Comment:
9291           case PGNTag:
9292           case NAG:
9293           default:
9294             /* ignore */
9295             continue;
9296           case WhiteWins:
9297           case BlackWins:
9298           case GameIsDrawn:
9299           case GameUnfinished:
9300             if (gameMode == IcsExamining) {
9301                 if (boardIndex < backwardMostMove) {
9302                     /* Oops, gap.  How did that happen? */
9303                     return;
9304                 }
9305                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9306                 return;
9307             }
9308             gameInfo.result = moveType;
9309             p = strchr(yy_text, '{');
9310             if (p == NULL) p = strchr(yy_text, '(');
9311             if (p == NULL) {
9312                 p = yy_text;
9313                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9314             } else {
9315                 q = strchr(p, *p == '{' ? '}' : ')');
9316                 if (q != NULL) *q = NULLCHAR;
9317                 p++;
9318             }
9319             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9320             gameInfo.resultDetails = StrSave(p);
9321             continue;
9322         }
9323         if (boardIndex >= forwardMostMove &&
9324             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9325             backwardMostMove = blackPlaysFirst ? 1 : 0;
9326             return;
9327         }
9328         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9329                                  fromY, fromX, toY, toX, promoChar,
9330                                  parseList[boardIndex]);
9331         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9332         /* currentMoveString is set as a side-effect of yylex */
9333         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9334         strcat(moveList[boardIndex], "\n");
9335         boardIndex++;
9336         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9337         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9338           case MT_NONE:
9339           case MT_STALEMATE:
9340           default:
9341             break;
9342           case MT_CHECK:
9343             if(gameInfo.variant != VariantShogi)
9344                 strcat(parseList[boardIndex - 1], "+");
9345             break;
9346           case MT_CHECKMATE:
9347           case MT_STAINMATE:
9348             strcat(parseList[boardIndex - 1], "#");
9349             break;
9350         }
9351     }
9352 }
9353
9354
9355 /* Apply a move to the given board  */
9356 void
9357 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9358 {
9359   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9360   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9361
9362     /* [HGM] compute & store e.p. status and castling rights for new position */
9363     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9364
9365       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9366       oldEP = (signed char)board[EP_STATUS];
9367       board[EP_STATUS] = EP_NONE;
9368
9369   if (fromY == DROP_RANK) {
9370         /* must be first */
9371         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9372             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9373             return;
9374         }
9375         piece = board[toY][toX] = (ChessSquare) fromX;
9376   } else {
9377       int i;
9378
9379       if( board[toY][toX] != EmptySquare )
9380            board[EP_STATUS] = EP_CAPTURE;
9381
9382       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9383            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9384                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9385       } else
9386       if( board[fromY][fromX] == WhitePawn ) {
9387            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9388                board[EP_STATUS] = EP_PAWN_MOVE;
9389            if( toY-fromY==2) {
9390                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9391                         gameInfo.variant != VariantBerolina || toX < fromX)
9392                       board[EP_STATUS] = toX | berolina;
9393                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9394                         gameInfo.variant != VariantBerolina || toX > fromX)
9395                       board[EP_STATUS] = toX;
9396            }
9397       } else
9398       if( board[fromY][fromX] == BlackPawn ) {
9399            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9400                board[EP_STATUS] = EP_PAWN_MOVE;
9401            if( toY-fromY== -2) {
9402                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9403                         gameInfo.variant != VariantBerolina || toX < fromX)
9404                       board[EP_STATUS] = toX | berolina;
9405                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9406                         gameInfo.variant != VariantBerolina || toX > fromX)
9407                       board[EP_STATUS] = toX;
9408            }
9409        }
9410
9411        for(i=0; i<nrCastlingRights; i++) {
9412            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9413               board[CASTLING][i] == toX   && castlingRank[i] == toY
9414              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9415        }
9416
9417        if(gameInfo.variant == VariantSChess) { // update virginity
9418            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9419            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9420            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9421            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9422        }
9423
9424      if (fromX == toX && fromY == toY) return;
9425
9426      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9427      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9428      if(gameInfo.variant == VariantKnightmate)
9429          king += (int) WhiteUnicorn - (int) WhiteKing;
9430
9431     /* Code added by Tord: */
9432     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9433     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9434         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9435       board[fromY][fromX] = EmptySquare;
9436       board[toY][toX] = EmptySquare;
9437       if((toX > fromX) != (piece == WhiteRook)) {
9438         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9439       } else {
9440         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9441       }
9442     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9443                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9444       board[fromY][fromX] = EmptySquare;
9445       board[toY][toX] = EmptySquare;
9446       if((toX > fromX) != (piece == BlackRook)) {
9447         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9448       } else {
9449         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9450       }
9451     /* End of code added by Tord */
9452
9453     } else if (board[fromY][fromX] == king
9454         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9455         && toY == fromY && toX > fromX+1) {
9456         board[fromY][fromX] = EmptySquare;
9457         board[toY][toX] = king;
9458         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9459         board[fromY][BOARD_RGHT-1] = EmptySquare;
9460     } else if (board[fromY][fromX] == king
9461         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9462                && toY == fromY && toX < fromX-1) {
9463         board[fromY][fromX] = EmptySquare;
9464         board[toY][toX] = king;
9465         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9466         board[fromY][BOARD_LEFT] = EmptySquare;
9467     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9468                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9469                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9470                ) {
9471         /* white pawn promotion */
9472         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9473         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9474             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9475         board[fromY][fromX] = EmptySquare;
9476     } else if ((fromY >= BOARD_HEIGHT>>1)
9477                && (toX != fromX)
9478                && gameInfo.variant != VariantXiangqi
9479                && gameInfo.variant != VariantBerolina
9480                && (board[fromY][fromX] == WhitePawn)
9481                && (board[toY][toX] == EmptySquare)) {
9482         board[fromY][fromX] = EmptySquare;
9483         board[toY][toX] = WhitePawn;
9484         captured = board[toY - 1][toX];
9485         board[toY - 1][toX] = EmptySquare;
9486     } else if ((fromY == BOARD_HEIGHT-4)
9487                && (toX == fromX)
9488                && gameInfo.variant == VariantBerolina
9489                && (board[fromY][fromX] == WhitePawn)
9490                && (board[toY][toX] == EmptySquare)) {
9491         board[fromY][fromX] = EmptySquare;
9492         board[toY][toX] = WhitePawn;
9493         if(oldEP & EP_BEROLIN_A) {
9494                 captured = board[fromY][fromX-1];
9495                 board[fromY][fromX-1] = EmptySquare;
9496         }else{  captured = board[fromY][fromX+1];
9497                 board[fromY][fromX+1] = EmptySquare;
9498         }
9499     } else if (board[fromY][fromX] == king
9500         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9501                && toY == fromY && toX > fromX+1) {
9502         board[fromY][fromX] = EmptySquare;
9503         board[toY][toX] = king;
9504         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9505         board[fromY][BOARD_RGHT-1] = EmptySquare;
9506     } else if (board[fromY][fromX] == king
9507         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9508                && toY == fromY && toX < fromX-1) {
9509         board[fromY][fromX] = EmptySquare;
9510         board[toY][toX] = king;
9511         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9512         board[fromY][BOARD_LEFT] = EmptySquare;
9513     } else if (fromY == 7 && fromX == 3
9514                && board[fromY][fromX] == BlackKing
9515                && toY == 7 && toX == 5) {
9516         board[fromY][fromX] = EmptySquare;
9517         board[toY][toX] = BlackKing;
9518         board[fromY][7] = EmptySquare;
9519         board[toY][4] = BlackRook;
9520     } else if (fromY == 7 && fromX == 3
9521                && board[fromY][fromX] == BlackKing
9522                && toY == 7 && toX == 1) {
9523         board[fromY][fromX] = EmptySquare;
9524         board[toY][toX] = BlackKing;
9525         board[fromY][0] = EmptySquare;
9526         board[toY][2] = BlackRook;
9527     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9528                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9529                && toY < promoRank && promoChar
9530                ) {
9531         /* black pawn promotion */
9532         board[toY][toX] = CharToPiece(ToLower(promoChar));
9533         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9534             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9535         board[fromY][fromX] = EmptySquare;
9536     } else if ((fromY < BOARD_HEIGHT>>1)
9537                && (toX != fromX)
9538                && gameInfo.variant != VariantXiangqi
9539                && gameInfo.variant != VariantBerolina
9540                && (board[fromY][fromX] == BlackPawn)
9541                && (board[toY][toX] == EmptySquare)) {
9542         board[fromY][fromX] = EmptySquare;
9543         board[toY][toX] = BlackPawn;
9544         captured = board[toY + 1][toX];
9545         board[toY + 1][toX] = EmptySquare;
9546     } else if ((fromY == 3)
9547                && (toX == fromX)
9548                && gameInfo.variant == VariantBerolina
9549                && (board[fromY][fromX] == BlackPawn)
9550                && (board[toY][toX] == EmptySquare)) {
9551         board[fromY][fromX] = EmptySquare;
9552         board[toY][toX] = BlackPawn;
9553         if(oldEP & EP_BEROLIN_A) {
9554                 captured = board[fromY][fromX-1];
9555                 board[fromY][fromX-1] = EmptySquare;
9556         }else{  captured = board[fromY][fromX+1];
9557                 board[fromY][fromX+1] = EmptySquare;
9558         }
9559     } else {
9560         board[toY][toX] = board[fromY][fromX];
9561         board[fromY][fromX] = EmptySquare;
9562     }
9563   }
9564
9565     if (gameInfo.holdingsWidth != 0) {
9566
9567       /* !!A lot more code needs to be written to support holdings  */
9568       /* [HGM] OK, so I have written it. Holdings are stored in the */
9569       /* penultimate board files, so they are automaticlly stored   */
9570       /* in the game history.                                       */
9571       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9572                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9573         /* Delete from holdings, by decreasing count */
9574         /* and erasing image if necessary            */
9575         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9576         if(p < (int) BlackPawn) { /* white drop */
9577              p -= (int)WhitePawn;
9578                  p = PieceToNumber((ChessSquare)p);
9579              if(p >= gameInfo.holdingsSize) p = 0;
9580              if(--board[p][BOARD_WIDTH-2] <= 0)
9581                   board[p][BOARD_WIDTH-1] = EmptySquare;
9582              if((int)board[p][BOARD_WIDTH-2] < 0)
9583                         board[p][BOARD_WIDTH-2] = 0;
9584         } else {                  /* black drop */
9585              p -= (int)BlackPawn;
9586                  p = PieceToNumber((ChessSquare)p);
9587              if(p >= gameInfo.holdingsSize) p = 0;
9588              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9589                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9590              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9591                         board[BOARD_HEIGHT-1-p][1] = 0;
9592         }
9593       }
9594       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9595           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9596         /* [HGM] holdings: Add to holdings, if holdings exist */
9597         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9598                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9599                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9600         }
9601         p = (int) captured;
9602         if (p >= (int) BlackPawn) {
9603           p -= (int)BlackPawn;
9604           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9605                   /* in Shogi restore piece to its original  first */
9606                   captured = (ChessSquare) (DEMOTED captured);
9607                   p = DEMOTED p;
9608           }
9609           p = PieceToNumber((ChessSquare)p);
9610           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9611           board[p][BOARD_WIDTH-2]++;
9612           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9613         } else {
9614           p -= (int)WhitePawn;
9615           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9616                   captured = (ChessSquare) (DEMOTED captured);
9617                   p = DEMOTED p;
9618           }
9619           p = PieceToNumber((ChessSquare)p);
9620           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9621           board[BOARD_HEIGHT-1-p][1]++;
9622           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9623         }
9624       }
9625     } else if (gameInfo.variant == VariantAtomic) {
9626       if (captured != EmptySquare) {
9627         int y, x;
9628         for (y = toY-1; y <= toY+1; y++) {
9629           for (x = toX-1; x <= toX+1; x++) {
9630             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9631                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9632               board[y][x] = EmptySquare;
9633             }
9634           }
9635         }
9636         board[toY][toX] = EmptySquare;
9637       }
9638     }
9639     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9640         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9641     } else
9642     if(promoChar == '+') {
9643         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9644         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9645     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9646         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9647         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9648            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9649         board[toY][toX] = newPiece;
9650     }
9651     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9652                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9653         // [HGM] superchess: take promotion piece out of holdings
9654         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9655         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9656             if(!--board[k][BOARD_WIDTH-2])
9657                 board[k][BOARD_WIDTH-1] = EmptySquare;
9658         } else {
9659             if(!--board[BOARD_HEIGHT-1-k][1])
9660                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9661         }
9662     }
9663
9664 }
9665
9666 /* Updates forwardMostMove */
9667 void
9668 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9669 {
9670 //    forwardMostMove++; // [HGM] bare: moved downstream
9671
9672     (void) CoordsToAlgebraic(boards[forwardMostMove],
9673                              PosFlags(forwardMostMove),
9674                              fromY, fromX, toY, toX, promoChar,
9675                              parseList[forwardMostMove]);
9676
9677     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9678         int timeLeft; static int lastLoadFlag=0; int king, piece;
9679         piece = boards[forwardMostMove][fromY][fromX];
9680         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9681         if(gameInfo.variant == VariantKnightmate)
9682             king += (int) WhiteUnicorn - (int) WhiteKing;
9683         if(forwardMostMove == 0) {
9684             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9685                 fprintf(serverMoves, "%s;", UserName());
9686             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9687                 fprintf(serverMoves, "%s;", second.tidy);
9688             fprintf(serverMoves, "%s;", first.tidy);
9689             if(gameMode == MachinePlaysWhite)
9690                 fprintf(serverMoves, "%s;", UserName());
9691             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9692                 fprintf(serverMoves, "%s;", second.tidy);
9693         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9694         lastLoadFlag = loadFlag;
9695         // print base move
9696         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9697         // print castling suffix
9698         if( toY == fromY && piece == king ) {
9699             if(toX-fromX > 1)
9700                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9701             if(fromX-toX >1)
9702                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9703         }
9704         // e.p. suffix
9705         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9706              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9707              boards[forwardMostMove][toY][toX] == EmptySquare
9708              && fromX != toX && fromY != toY)
9709                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9710         // promotion suffix
9711         if(promoChar != NULLCHAR) {
9712             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9713                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9714                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9715             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9716         }
9717         if(!loadFlag) {
9718                 char buf[MOVE_LEN*2], *p; int len;
9719             fprintf(serverMoves, "/%d/%d",
9720                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9721             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9722             else                      timeLeft = blackTimeRemaining/1000;
9723             fprintf(serverMoves, "/%d", timeLeft);
9724                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9725                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9726                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9727                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9728             fprintf(serverMoves, "/%s", buf);
9729         }
9730         fflush(serverMoves);
9731     }
9732
9733     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9734         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9735       return;
9736     }
9737     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9738     if (commentList[forwardMostMove+1] != NULL) {
9739         free(commentList[forwardMostMove+1]);
9740         commentList[forwardMostMove+1] = NULL;
9741     }
9742     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9743     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9744     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9745     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9746     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9747     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9748     adjustedClock = FALSE;
9749     gameInfo.result = GameUnfinished;
9750     if (gameInfo.resultDetails != NULL) {
9751         free(gameInfo.resultDetails);
9752         gameInfo.resultDetails = NULL;
9753     }
9754     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9755                               moveList[forwardMostMove - 1]);
9756     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9757       case MT_NONE:
9758       case MT_STALEMATE:
9759       default:
9760         break;
9761       case MT_CHECK:
9762         if(gameInfo.variant != VariantShogi)
9763             strcat(parseList[forwardMostMove - 1], "+");
9764         break;
9765       case MT_CHECKMATE:
9766       case MT_STAINMATE:
9767         strcat(parseList[forwardMostMove - 1], "#");
9768         break;
9769     }
9770
9771 }
9772
9773 /* Updates currentMove if not pausing */
9774 void
9775 ShowMove (int fromX, int fromY, int toX, int toY)
9776 {
9777     int instant = (gameMode == PlayFromGameFile) ?
9778         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9779     if(appData.noGUI) return;
9780     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9781         if (!instant) {
9782             if (forwardMostMove == currentMove + 1) {
9783                 AnimateMove(boards[forwardMostMove - 1],
9784                             fromX, fromY, toX, toY);
9785             }
9786         }
9787         currentMove = forwardMostMove;
9788     }
9789
9790     if (instant) return;
9791
9792     DisplayMove(currentMove - 1);
9793     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9794             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9795                 SetHighlights(fromX, fromY, toX, toY);
9796             }
9797     }
9798     DrawPosition(FALSE, boards[currentMove]);
9799     DisplayBothClocks();
9800     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9801 }
9802
9803 void
9804 SendEgtPath (ChessProgramState *cps)
9805 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9806         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9807
9808         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9809
9810         while(*p) {
9811             char c, *q = name+1, *r, *s;
9812
9813             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9814             while(*p && *p != ',') *q++ = *p++;
9815             *q++ = ':'; *q = 0;
9816             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9817                 strcmp(name, ",nalimov:") == 0 ) {
9818                 // take nalimov path from the menu-changeable option first, if it is defined
9819               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9820                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9821             } else
9822             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9823                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9824                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9825                 s = r = StrStr(s, ":") + 1; // beginning of path info
9826                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9827                 c = *r; *r = 0;             // temporarily null-terminate path info
9828                     *--q = 0;               // strip of trailig ':' from name
9829                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9830                 *r = c;
9831                 SendToProgram(buf,cps);     // send egtbpath command for this format
9832             }
9833             if(*p == ',') p++; // read away comma to position for next format name
9834         }
9835 }
9836
9837 void
9838 InitChessProgram (ChessProgramState *cps, int setup)
9839 /* setup needed to setup FRC opening position */
9840 {
9841     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9842     if (appData.noChessProgram) return;
9843     hintRequested = FALSE;
9844     bookRequested = FALSE;
9845
9846     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9847     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9848     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9849     if(cps->memSize) { /* [HGM] memory */
9850       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9851         SendToProgram(buf, cps);
9852     }
9853     SendEgtPath(cps); /* [HGM] EGT */
9854     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9855       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9856         SendToProgram(buf, cps);
9857     }
9858
9859     SendToProgram(cps->initString, cps);
9860     if (gameInfo.variant != VariantNormal &&
9861         gameInfo.variant != VariantLoadable
9862         /* [HGM] also send variant if board size non-standard */
9863         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9864                                             ) {
9865       char *v = VariantName(gameInfo.variant);
9866       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9867         /* [HGM] in protocol 1 we have to assume all variants valid */
9868         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9869         DisplayFatalError(buf, 0, 1);
9870         return;
9871       }
9872
9873       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9874       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9875       if( gameInfo.variant == VariantXiangqi )
9876            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9877       if( gameInfo.variant == VariantShogi )
9878            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9879       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9880            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9881       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9882           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9883            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9884       if( gameInfo.variant == VariantCourier )
9885            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9886       if( gameInfo.variant == VariantSuper )
9887            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9888       if( gameInfo.variant == VariantGreat )
9889            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9890       if( gameInfo.variant == VariantSChess )
9891            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9892       if( gameInfo.variant == VariantGrand )
9893            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9894
9895       if(overruled) {
9896         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9897                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9898            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9899            if(StrStr(cps->variants, b) == NULL) {
9900                // specific sized variant not known, check if general sizing allowed
9901                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9902                    if(StrStr(cps->variants, "boardsize") == NULL) {
9903                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9904                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9905                        DisplayFatalError(buf, 0, 1);
9906                        return;
9907                    }
9908                    /* [HGM] here we really should compare with the maximum supported board size */
9909                }
9910            }
9911       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9912       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9913       SendToProgram(buf, cps);
9914     }
9915     currentlyInitializedVariant = gameInfo.variant;
9916
9917     /* [HGM] send opening position in FRC to first engine */
9918     if(setup) {
9919           SendToProgram("force\n", cps);
9920           SendBoard(cps, 0);
9921           /* engine is now in force mode! Set flag to wake it up after first move. */
9922           setboardSpoiledMachineBlack = 1;
9923     }
9924
9925     if (cps->sendICS) {
9926       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9927       SendToProgram(buf, cps);
9928     }
9929     cps->maybeThinking = FALSE;
9930     cps->offeredDraw = 0;
9931     if (!appData.icsActive) {
9932         SendTimeControl(cps, movesPerSession, timeControl,
9933                         timeIncrement, appData.searchDepth,
9934                         searchTime);
9935     }
9936     if (appData.showThinking
9937         // [HGM] thinking: four options require thinking output to be sent
9938         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9939                                 ) {
9940         SendToProgram("post\n", cps);
9941     }
9942     SendToProgram("hard\n", cps);
9943     if (!appData.ponderNextMove) {
9944         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9945            it without being sure what state we are in first.  "hard"
9946            is not a toggle, so that one is OK.
9947          */
9948         SendToProgram("easy\n", cps);
9949     }
9950     if (cps->usePing) {
9951       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9952       SendToProgram(buf, cps);
9953     }
9954     cps->initDone = TRUE;
9955     ClearEngineOutputPane(cps == &second);
9956 }
9957
9958
9959 void
9960 ResendOptions (ChessProgramState *cps)
9961 { // send the stored value of the options
9962   int i;
9963   char buf[MSG_SIZ];
9964   Option *opt = cps->option;
9965   for(i=0; i<cps->nrOptions; i++, opt++) {
9966       switch(opt->type) {
9967         case Spin:
9968         case Slider:
9969         case CheckBox:
9970             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9971           break;
9972         case ComboBox:
9973           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
9974           break;
9975         default:
9976             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
9977           break;
9978         case Button:
9979         case SaveButton:
9980           continue;
9981       }
9982       SendToProgram(buf, cps);
9983   }
9984 }
9985
9986 void
9987 StartChessProgram (ChessProgramState *cps)
9988 {
9989     char buf[MSG_SIZ];
9990     int err;
9991
9992     if (appData.noChessProgram) return;
9993     cps->initDone = FALSE;
9994
9995     if (strcmp(cps->host, "localhost") == 0) {
9996         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9997     } else if (*appData.remoteShell == NULLCHAR) {
9998         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9999     } else {
10000         if (*appData.remoteUser == NULLCHAR) {
10001           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10002                     cps->program);
10003         } else {
10004           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10005                     cps->host, appData.remoteUser, cps->program);
10006         }
10007         err = StartChildProcess(buf, "", &cps->pr);
10008     }
10009
10010     if (err != 0) {
10011       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10012         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10013         if(cps != &first) return;
10014         appData.noChessProgram = TRUE;
10015         ThawUI();
10016         SetNCPMode();
10017 //      DisplayFatalError(buf, err, 1);
10018 //      cps->pr = NoProc;
10019 //      cps->isr = NULL;
10020         return;
10021     }
10022
10023     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10024     if (cps->protocolVersion > 1) {
10025       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10026       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10027         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10028         cps->comboCnt = 0;  //                and values of combo boxes
10029       }
10030       SendToProgram(buf, cps);
10031       if(cps->reload) ResendOptions(cps);
10032     } else {
10033       SendToProgram("xboard\n", cps);
10034     }
10035 }
10036
10037 void
10038 TwoMachinesEventIfReady P((void))
10039 {
10040   static int curMess = 0;
10041   if (first.lastPing != first.lastPong || !first.initDone) {
10042     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10043     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10044     return;
10045   }
10046   if (second.lastPing != second.lastPong) {
10047     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10048     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10049     return;
10050   }
10051   DisplayMessage("", ""); curMess = 0;
10052   ThawUI();
10053   TwoMachinesEvent();
10054 }
10055
10056 char *
10057 MakeName (char *template)
10058 {
10059     time_t clock;
10060     struct tm *tm;
10061     static char buf[MSG_SIZ];
10062     char *p = buf;
10063     int i;
10064
10065     clock = time((time_t *)NULL);
10066     tm = localtime(&clock);
10067
10068     while(*p++ = *template++) if(p[-1] == '%') {
10069         switch(*template++) {
10070           case 0:   *p = 0; return buf;
10071           case 'Y': i = tm->tm_year+1900; break;
10072           case 'y': i = tm->tm_year-100; break;
10073           case 'M': i = tm->tm_mon+1; break;
10074           case 'd': i = tm->tm_mday; break;
10075           case 'h': i = tm->tm_hour; break;
10076           case 'm': i = tm->tm_min; break;
10077           case 's': i = tm->tm_sec; break;
10078           default:  i = 0;
10079         }
10080         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10081     }
10082     return buf;
10083 }
10084
10085 int
10086 CountPlayers (char *p)
10087 {
10088     int n = 0;
10089     while(p = strchr(p, '\n')) p++, n++; // count participants
10090     return n;
10091 }
10092
10093 FILE *
10094 WriteTourneyFile (char *results, FILE *f)
10095 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10096     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10097     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10098         // create a file with tournament description
10099         fprintf(f, "-participants {%s}\n", appData.participants);
10100         fprintf(f, "-seedBase %d\n", appData.seedBase);
10101         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10102         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10103         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10104         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10105         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10106         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10107         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10108         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10109         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10110         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10111         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10112         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10113         fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
10114         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10115         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10116         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10117         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10118         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10119         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10120         fprintf(f, "-smpCores %d\n", appData.smpCores);
10121         if(searchTime > 0)
10122                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10123         else {
10124                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10125                 fprintf(f, "-tc %s\n", appData.timeControl);
10126                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10127         }
10128         fprintf(f, "-results \"%s\"\n", results);
10129     }
10130     return f;
10131 }
10132
10133 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10134
10135 void
10136 Substitute (char *participants, int expunge)
10137 {
10138     int i, changed, changes=0, nPlayers=0;
10139     char *p, *q, *r, buf[MSG_SIZ];
10140     if(participants == NULL) return;
10141     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10142     r = p = participants; q = appData.participants;
10143     while(*p && *p == *q) {
10144         if(*p == '\n') r = p+1, nPlayers++;
10145         p++; q++;
10146     }
10147     if(*p) { // difference
10148         while(*p && *p++ != '\n');
10149         while(*q && *q++ != '\n');
10150       changed = nPlayers;
10151         changes = 1 + (strcmp(p, q) != 0);
10152     }
10153     if(changes == 1) { // a single engine mnemonic was changed
10154         q = r; while(*q) nPlayers += (*q++ == '\n');
10155         p = buf; while(*r && (*p = *r++) != '\n') p++;
10156         *p = NULLCHAR;
10157         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10158         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10159         if(mnemonic[i]) { // The substitute is valid
10160             FILE *f;
10161             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10162                 flock(fileno(f), LOCK_EX);
10163                 ParseArgsFromFile(f);
10164                 fseek(f, 0, SEEK_SET);
10165                 FREE(appData.participants); appData.participants = participants;
10166                 if(expunge) { // erase results of replaced engine
10167                     int len = strlen(appData.results), w, b, dummy;
10168                     for(i=0; i<len; i++) {
10169                         Pairing(i, nPlayers, &w, &b, &dummy);
10170                         if((w == changed || b == changed) && appData.results[i] == '*') {
10171                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10172                             fclose(f);
10173                             return;
10174                         }
10175                     }
10176                     for(i=0; i<len; i++) {
10177                         Pairing(i, nPlayers, &w, &b, &dummy);
10178                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10179                     }
10180                 }
10181                 WriteTourneyFile(appData.results, f);
10182                 fclose(f); // release lock
10183                 return;
10184             }
10185         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10186     }
10187     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10188     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10189     free(participants);
10190     return;
10191 }
10192
10193 int
10194 CheckPlayers (char *participants)
10195 {
10196         int i;
10197         char buf[MSG_SIZ], *p;
10198         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10199         while(p = strchr(participants, '\n')) {
10200             *p = NULLCHAR;
10201             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10202             if(!mnemonic[i]) {
10203                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10204                 *p = '\n';
10205                 DisplayError(buf, 0);
10206                 return 1;
10207             }
10208             *p = '\n';
10209             participants = p + 1;
10210         }
10211         return 0;
10212 }
10213
10214 int
10215 CreateTourney (char *name)
10216 {
10217         FILE *f;
10218         if(matchMode && strcmp(name, appData.tourneyFile)) {
10219              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10220         }
10221         if(name[0] == NULLCHAR) {
10222             if(appData.participants[0])
10223                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10224             return 0;
10225         }
10226         f = fopen(name, "r");
10227         if(f) { // file exists
10228             ASSIGN(appData.tourneyFile, name);
10229             ParseArgsFromFile(f); // parse it
10230         } else {
10231             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10232             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10233                 DisplayError(_("Not enough participants"), 0);
10234                 return 0;
10235             }
10236             if(CheckPlayers(appData.participants)) return 0;
10237             ASSIGN(appData.tourneyFile, name);
10238             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10239             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10240         }
10241         fclose(f);
10242         appData.noChessProgram = FALSE;
10243         appData.clockMode = TRUE;
10244         SetGNUMode();
10245         return 1;
10246 }
10247
10248 int
10249 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10250 {
10251     char buf[MSG_SIZ], *p, *q;
10252     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10253     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10254     skip = !all && group[0]; // if group requested, we start in skip mode
10255     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10256         p = names; q = buf; header = 0;
10257         while(*p && *p != '\n') *q++ = *p++;
10258         *q = 0;
10259         if(*p == '\n') p++;
10260         if(buf[0] == '#') {
10261             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10262             depth++; // we must be entering a new group
10263             if(all) continue; // suppress printing group headers when complete list requested
10264             header = 1;
10265             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10266         }
10267         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10268         if(engineList[i]) free(engineList[i]);
10269         engineList[i] = strdup(buf);
10270         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10271         if(engineMnemonic[i]) free(engineMnemonic[i]);
10272         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10273             strcat(buf, " (");
10274             sscanf(q + 8, "%s", buf + strlen(buf));
10275             strcat(buf, ")");
10276         }
10277         engineMnemonic[i] = strdup(buf);
10278         i++;
10279     }
10280     engineList[i] = engineMnemonic[i] = NULL;
10281     return i;
10282 }
10283
10284 // following implemented as macro to avoid type limitations
10285 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10286
10287 void
10288 SwapEngines (int n)
10289 {   // swap settings for first engine and other engine (so far only some selected options)
10290     int h;
10291     char *p;
10292     if(n == 0) return;
10293     SWAP(directory, p)
10294     SWAP(chessProgram, p)
10295     SWAP(isUCI, h)
10296     SWAP(hasOwnBookUCI, h)
10297     SWAP(protocolVersion, h)
10298     SWAP(reuse, h)
10299     SWAP(scoreIsAbsolute, h)
10300     SWAP(timeOdds, h)
10301     SWAP(logo, p)
10302     SWAP(pgnName, p)
10303     SWAP(pvSAN, h)
10304     SWAP(engOptions, p)
10305     SWAP(engInitString, p)
10306     SWAP(computerString, p)
10307     SWAP(features, p)
10308     SWAP(fenOverride, p)
10309     SWAP(NPS, h)
10310     SWAP(accumulateTC, h)
10311     SWAP(host, p)
10312 }
10313
10314 int
10315 GetEngineLine (char *s, int n)
10316 {
10317     int i;
10318     char buf[MSG_SIZ];
10319     extern char *icsNames;
10320     if(!s || !*s) return 0;
10321     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10322     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10323     if(!mnemonic[i]) return 0;
10324     if(n == 11) return 1; // just testing if there was a match
10325     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10326     if(n == 1) SwapEngines(n);
10327     ParseArgsFromString(buf);
10328     if(n == 1) SwapEngines(n);
10329     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10330         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10331         ParseArgsFromString(buf);
10332     }
10333     return 1;
10334 }
10335
10336 int
10337 SetPlayer (int player, char *p)
10338 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10339     int i;
10340     char buf[MSG_SIZ], *engineName;
10341     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10342     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10343     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10344     if(mnemonic[i]) {
10345         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10346         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10347         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10348         ParseArgsFromString(buf);
10349     } else { // no engine with this nickname is installed!
10350         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10351         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10352         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10353         ModeHighlight();
10354         DisplayError(buf, 0);
10355         return 0;
10356     }
10357     free(engineName);
10358     return i;
10359 }
10360
10361 char *recentEngines;
10362
10363 void
10364 RecentEngineEvent (int nr)
10365 {
10366     int n;
10367 //    SwapEngines(1); // bump first to second
10368 //    ReplaceEngine(&second, 1); // and load it there
10369     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10370     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10371     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10372         ReplaceEngine(&first, 0);
10373         FloatToFront(&appData.recentEngineList, command[n]);
10374     }
10375 }
10376
10377 int
10378 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10379 {   // determine players from game number
10380     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10381
10382     if(appData.tourneyType == 0) {
10383         roundsPerCycle = (nPlayers - 1) | 1;
10384         pairingsPerRound = nPlayers / 2;
10385     } else if(appData.tourneyType > 0) {
10386         roundsPerCycle = nPlayers - appData.tourneyType;
10387         pairingsPerRound = appData.tourneyType;
10388     }
10389     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10390     gamesPerCycle = gamesPerRound * roundsPerCycle;
10391     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10392     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10393     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10394     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10395     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10396     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10397
10398     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10399     if(appData.roundSync) *syncInterval = gamesPerRound;
10400
10401     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10402
10403     if(appData.tourneyType == 0) {
10404         if(curPairing == (nPlayers-1)/2 ) {
10405             *whitePlayer = curRound;
10406             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10407         } else {
10408             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10409             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10410             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10411             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10412         }
10413     } else if(appData.tourneyType > 1) {
10414         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10415         *whitePlayer = curRound + appData.tourneyType;
10416     } else if(appData.tourneyType > 0) {
10417         *whitePlayer = curPairing;
10418         *blackPlayer = curRound + appData.tourneyType;
10419     }
10420
10421     // take care of white/black alternation per round.
10422     // For cycles and games this is already taken care of by default, derived from matchGame!
10423     return curRound & 1;
10424 }
10425
10426 int
10427 NextTourneyGame (int nr, int *swapColors)
10428 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10429     char *p, *q;
10430     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10431     FILE *tf;
10432     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10433     tf = fopen(appData.tourneyFile, "r");
10434     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10435     ParseArgsFromFile(tf); fclose(tf);
10436     InitTimeControls(); // TC might be altered from tourney file
10437
10438     nPlayers = CountPlayers(appData.participants); // count participants
10439     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10440     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10441
10442     if(syncInterval) {
10443         p = q = appData.results;
10444         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10445         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10446             DisplayMessage(_("Waiting for other game(s)"),"");
10447             waitingForGame = TRUE;
10448             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10449             return 0;
10450         }
10451         waitingForGame = FALSE;
10452     }
10453
10454     if(appData.tourneyType < 0) {
10455         if(nr>=0 && !pairingReceived) {
10456             char buf[1<<16];
10457             if(pairing.pr == NoProc) {
10458                 if(!appData.pairingEngine[0]) {
10459                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10460                     return 0;
10461                 }
10462                 StartChessProgram(&pairing); // starts the pairing engine
10463             }
10464             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10465             SendToProgram(buf, &pairing);
10466             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10467             SendToProgram(buf, &pairing);
10468             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10469         }
10470         pairingReceived = 0;                              // ... so we continue here
10471         *swapColors = 0;
10472         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10473         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10474         matchGame = 1; roundNr = nr / syncInterval + 1;
10475     }
10476
10477     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10478
10479     // redefine engines, engine dir, etc.
10480     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10481     if(first.pr == NoProc) {
10482       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10483       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10484     }
10485     if(second.pr == NoProc) {
10486       SwapEngines(1);
10487       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10488       SwapEngines(1);         // and make that valid for second engine by swapping
10489       InitEngine(&second, 1);
10490     }
10491     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10492     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10493     return OK;
10494 }
10495
10496 void
10497 NextMatchGame ()
10498 {   // performs game initialization that does not invoke engines, and then tries to start the game
10499     int res, firstWhite, swapColors = 0;
10500     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10501     if(matchMode && appData.debugMode) { // [HGM] debug split: game is part of a match; we might have to create a debug file just for this game
10502         char buf[MSG_SIZ];
10503         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10504         if(strcmp(buf, currentDebugFile)) { // name has changed
10505             FILE *f = fopen(buf, "w");
10506             if(f) { // if opening the new file failed, just keep using the old one
10507                 ASSIGN(currentDebugFile, buf);
10508                 fclose(debugFP);
10509                 debugFP = f;
10510             }
10511             if(appData.serverFileName) {
10512                 if(serverFP) fclose(serverFP);
10513                 serverFP = fopen(appData.serverFileName, "w");
10514                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10515                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10516             }
10517         }
10518     }
10519     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10520     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10521     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10522     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10523     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10524     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10525     Reset(FALSE, first.pr != NoProc);
10526     res = LoadGameOrPosition(matchGame); // setup game
10527     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10528     if(!res) return; // abort when bad game/pos file
10529     TwoMachinesEvent();
10530 }
10531
10532 void
10533 UserAdjudicationEvent (int result)
10534 {
10535     ChessMove gameResult = GameIsDrawn;
10536
10537     if( result > 0 ) {
10538         gameResult = WhiteWins;
10539     }
10540     else if( result < 0 ) {
10541         gameResult = BlackWins;
10542     }
10543
10544     if( gameMode == TwoMachinesPlay ) {
10545         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10546     }
10547 }
10548
10549
10550 // [HGM] save: calculate checksum of game to make games easily identifiable
10551 int
10552 StringCheckSum (char *s)
10553 {
10554         int i = 0;
10555         if(s==NULL) return 0;
10556         while(*s) i = i*259 + *s++;
10557         return i;
10558 }
10559
10560 int
10561 GameCheckSum ()
10562 {
10563         int i, sum=0;
10564         for(i=backwardMostMove; i<forwardMostMove; i++) {
10565                 sum += pvInfoList[i].depth;
10566                 sum += StringCheckSum(parseList[i]);
10567                 sum += StringCheckSum(commentList[i]);
10568                 sum *= 261;
10569         }
10570         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10571         return sum + StringCheckSum(commentList[i]);
10572 } // end of save patch
10573
10574 void
10575 GameEnds (ChessMove result, char *resultDetails, int whosays)
10576 {
10577     GameMode nextGameMode;
10578     int isIcsGame;
10579     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10580
10581     if(endingGame) return; /* [HGM] crash: forbid recursion */
10582     endingGame = 1;
10583     if(twoBoards) { // [HGM] dual: switch back to one board
10584         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10585         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10586     }
10587     if (appData.debugMode) {
10588       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10589               result, resultDetails ? resultDetails : "(null)", whosays);
10590     }
10591
10592     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10593
10594     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10595
10596     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10597         /* If we are playing on ICS, the server decides when the
10598            game is over, but the engine can offer to draw, claim
10599            a draw, or resign.
10600          */
10601 #if ZIPPY
10602         if (appData.zippyPlay && first.initDone) {
10603             if (result == GameIsDrawn) {
10604                 /* In case draw still needs to be claimed */
10605                 SendToICS(ics_prefix);
10606                 SendToICS("draw\n");
10607             } else if (StrCaseStr(resultDetails, "resign")) {
10608                 SendToICS(ics_prefix);
10609                 SendToICS("resign\n");
10610             }
10611         }
10612 #endif
10613         endingGame = 0; /* [HGM] crash */
10614         return;
10615     }
10616
10617     /* If we're loading the game from a file, stop */
10618     if (whosays == GE_FILE) {
10619       (void) StopLoadGameTimer();
10620       gameFileFP = NULL;
10621     }
10622
10623     /* Cancel draw offers */
10624     first.offeredDraw = second.offeredDraw = 0;
10625
10626     /* If this is an ICS game, only ICS can really say it's done;
10627        if not, anyone can. */
10628     isIcsGame = (gameMode == IcsPlayingWhite ||
10629                  gameMode == IcsPlayingBlack ||
10630                  gameMode == IcsObserving    ||
10631                  gameMode == IcsExamining);
10632
10633     if (!isIcsGame || whosays == GE_ICS) {
10634         /* OK -- not an ICS game, or ICS said it was done */
10635         StopClocks();
10636         if (!isIcsGame && !appData.noChessProgram)
10637           SetUserThinkingEnables();
10638
10639         /* [HGM] if a machine claims the game end we verify this claim */
10640         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10641             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10642                 char claimer;
10643                 ChessMove trueResult = (ChessMove) -1;
10644
10645                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10646                                             first.twoMachinesColor[0] :
10647                                             second.twoMachinesColor[0] ;
10648
10649                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10650                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10651                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10652                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10653                 } else
10654                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10655                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10656                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10657                 } else
10658                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10659                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10660                 }
10661
10662                 // now verify win claims, but not in drop games, as we don't understand those yet
10663                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10664                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10665                     (result == WhiteWins && claimer == 'w' ||
10666                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10667                       if (appData.debugMode) {
10668                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10669                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10670                       }
10671                       if(result != trueResult) {
10672                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10673                               result = claimer == 'w' ? BlackWins : WhiteWins;
10674                               resultDetails = buf;
10675                       }
10676                 } else
10677                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10678                     && (forwardMostMove <= backwardMostMove ||
10679                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10680                         (claimer=='b')==(forwardMostMove&1))
10681                                                                                   ) {
10682                       /* [HGM] verify: draws that were not flagged are false claims */
10683                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10684                       result = claimer == 'w' ? BlackWins : WhiteWins;
10685                       resultDetails = buf;
10686                 }
10687                 /* (Claiming a loss is accepted no questions asked!) */
10688             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10689                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10690                 result = GameUnfinished;
10691                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10692             }
10693             /* [HGM] bare: don't allow bare King to win */
10694             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10695                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10696                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10697                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10698                && result != GameIsDrawn)
10699             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10700                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10701                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10702                         if(p >= 0 && p <= (int)WhiteKing) k++;
10703                 }
10704                 if (appData.debugMode) {
10705                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10706                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10707                 }
10708                 if(k <= 1) {
10709                         result = GameIsDrawn;
10710                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10711                         resultDetails = buf;
10712                 }
10713             }
10714         }
10715
10716
10717         if(serverMoves != NULL && !loadFlag) { char c = '=';
10718             if(result==WhiteWins) c = '+';
10719             if(result==BlackWins) c = '-';
10720             if(resultDetails != NULL)
10721                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10722         }
10723         if (resultDetails != NULL) {
10724             gameInfo.result = result;
10725             gameInfo.resultDetails = StrSave(resultDetails);
10726
10727             /* display last move only if game was not loaded from file */
10728             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10729                 DisplayMove(currentMove - 1);
10730
10731             if (forwardMostMove != 0) {
10732                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10733                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10734                                                                 ) {
10735                     if (*appData.saveGameFile != NULLCHAR) {
10736                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10737                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10738                         else
10739                         SaveGameToFile(appData.saveGameFile, TRUE);
10740                     } else if (appData.autoSaveGames) {
10741                         AutoSaveGame();
10742                     }
10743                     if (*appData.savePositionFile != NULLCHAR) {
10744                         SavePositionToFile(appData.savePositionFile);
10745                     }
10746                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10747                 }
10748             }
10749
10750             /* Tell program how game ended in case it is learning */
10751             /* [HGM] Moved this to after saving the PGN, just in case */
10752             /* engine died and we got here through time loss. In that */
10753             /* case we will get a fatal error writing the pipe, which */
10754             /* would otherwise lose us the PGN.                       */
10755             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10756             /* output during GameEnds should never be fatal anymore   */
10757             if (gameMode == MachinePlaysWhite ||
10758                 gameMode == MachinePlaysBlack ||
10759                 gameMode == TwoMachinesPlay ||
10760                 gameMode == IcsPlayingWhite ||
10761                 gameMode == IcsPlayingBlack ||
10762                 gameMode == BeginningOfGame) {
10763                 char buf[MSG_SIZ];
10764                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10765                         resultDetails);
10766                 if (first.pr != NoProc) {
10767                     SendToProgram(buf, &first);
10768                 }
10769                 if (second.pr != NoProc &&
10770                     gameMode == TwoMachinesPlay) {
10771                     SendToProgram(buf, &second);
10772                 }
10773             }
10774         }
10775
10776         if (appData.icsActive) {
10777             if (appData.quietPlay &&
10778                 (gameMode == IcsPlayingWhite ||
10779                  gameMode == IcsPlayingBlack)) {
10780                 SendToICS(ics_prefix);
10781                 SendToICS("set shout 1\n");
10782             }
10783             nextGameMode = IcsIdle;
10784             ics_user_moved = FALSE;
10785             /* clean up premove.  It's ugly when the game has ended and the
10786              * premove highlights are still on the board.
10787              */
10788             if (gotPremove) {
10789               gotPremove = FALSE;
10790               ClearPremoveHighlights();
10791               DrawPosition(FALSE, boards[currentMove]);
10792             }
10793             if (whosays == GE_ICS) {
10794                 switch (result) {
10795                 case WhiteWins:
10796                     if (gameMode == IcsPlayingWhite)
10797                         PlayIcsWinSound();
10798                     else if(gameMode == IcsPlayingBlack)
10799                         PlayIcsLossSound();
10800                     break;
10801                 case BlackWins:
10802                     if (gameMode == IcsPlayingBlack)
10803                         PlayIcsWinSound();
10804                     else if(gameMode == IcsPlayingWhite)
10805                         PlayIcsLossSound();
10806                     break;
10807                 case GameIsDrawn:
10808                     PlayIcsDrawSound();
10809                     break;
10810                 default:
10811                     PlayIcsUnfinishedSound();
10812                 }
10813             }
10814         } else if (gameMode == EditGame ||
10815                    gameMode == PlayFromGameFile ||
10816                    gameMode == AnalyzeMode ||
10817                    gameMode == AnalyzeFile) {
10818             nextGameMode = gameMode;
10819         } else {
10820             nextGameMode = EndOfGame;
10821         }
10822         pausing = FALSE;
10823         ModeHighlight();
10824     } else {
10825         nextGameMode = gameMode;
10826     }
10827
10828     if (appData.noChessProgram) {
10829         gameMode = nextGameMode;
10830         ModeHighlight();
10831         endingGame = 0; /* [HGM] crash */
10832         return;
10833     }
10834
10835     if (first.reuse) {
10836         /* Put first chess program into idle state */
10837         if (first.pr != NoProc &&
10838             (gameMode == MachinePlaysWhite ||
10839              gameMode == MachinePlaysBlack ||
10840              gameMode == TwoMachinesPlay ||
10841              gameMode == IcsPlayingWhite ||
10842              gameMode == IcsPlayingBlack ||
10843              gameMode == BeginningOfGame)) {
10844             SendToProgram("force\n", &first);
10845             if (first.usePing) {
10846               char buf[MSG_SIZ];
10847               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10848               SendToProgram(buf, &first);
10849             }
10850         }
10851     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10852         /* Kill off first chess program */
10853         if (first.isr != NULL)
10854           RemoveInputSource(first.isr);
10855         first.isr = NULL;
10856
10857         if (first.pr != NoProc) {
10858             ExitAnalyzeMode();
10859             DoSleep( appData.delayBeforeQuit );
10860             SendToProgram("quit\n", &first);
10861             DoSleep( appData.delayAfterQuit );
10862             DestroyChildProcess(first.pr, first.useSigterm);
10863             first.reload = TRUE;
10864         }
10865         first.pr = NoProc;
10866     }
10867     if (second.reuse) {
10868         /* Put second chess program into idle state */
10869         if (second.pr != NoProc &&
10870             gameMode == TwoMachinesPlay) {
10871             SendToProgram("force\n", &second);
10872             if (second.usePing) {
10873               char buf[MSG_SIZ];
10874               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10875               SendToProgram(buf, &second);
10876             }
10877         }
10878     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10879         /* Kill off second chess program */
10880         if (second.isr != NULL)
10881           RemoveInputSource(second.isr);
10882         second.isr = NULL;
10883
10884         if (second.pr != NoProc) {
10885             DoSleep( appData.delayBeforeQuit );
10886             SendToProgram("quit\n", &second);
10887             DoSleep( appData.delayAfterQuit );
10888             DestroyChildProcess(second.pr, second.useSigterm);
10889             second.reload = TRUE;
10890         }
10891         second.pr = NoProc;
10892     }
10893
10894     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10895         char resChar = '=';
10896         switch (result) {
10897         case WhiteWins:
10898           resChar = '+';
10899           if (first.twoMachinesColor[0] == 'w') {
10900             first.matchWins++;
10901           } else {
10902             second.matchWins++;
10903           }
10904           break;
10905         case BlackWins:
10906           resChar = '-';
10907           if (first.twoMachinesColor[0] == 'b') {
10908             first.matchWins++;
10909           } else {
10910             second.matchWins++;
10911           }
10912           break;
10913         case GameUnfinished:
10914           resChar = ' ';
10915         default:
10916           break;
10917         }
10918
10919         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10920         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10921             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10922             ReserveGame(nextGame, resChar); // sets nextGame
10923             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10924             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10925         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10926
10927         if (nextGame <= appData.matchGames && !abortMatch) {
10928             gameMode = nextGameMode;
10929             matchGame = nextGame; // this will be overruled in tourney mode!
10930             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10931             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10932             endingGame = 0; /* [HGM] crash */
10933             return;
10934         } else {
10935             gameMode = nextGameMode;
10936             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10937                      first.tidy, second.tidy,
10938                      first.matchWins, second.matchWins,
10939                      appData.matchGames - (first.matchWins + second.matchWins));
10940             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10941             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10942             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10943             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10944                 first.twoMachinesColor = "black\n";
10945                 second.twoMachinesColor = "white\n";
10946             } else {
10947                 first.twoMachinesColor = "white\n";
10948                 second.twoMachinesColor = "black\n";
10949             }
10950         }
10951     }
10952     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10953         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10954       ExitAnalyzeMode();
10955     gameMode = nextGameMode;
10956     ModeHighlight();
10957     endingGame = 0;  /* [HGM] crash */
10958     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10959         if(matchMode == TRUE) { // match through command line: exit with or without popup
10960             if(ranking) {
10961                 ToNrEvent(forwardMostMove);
10962                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10963                 else ExitEvent(0);
10964             } else DisplayFatalError(buf, 0, 0);
10965         } else { // match through menu; just stop, with or without popup
10966             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10967             ModeHighlight();
10968             if(ranking){
10969                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10970             } else DisplayNote(buf);
10971       }
10972       if(ranking) free(ranking);
10973     }
10974 }
10975
10976 /* Assumes program was just initialized (initString sent).
10977    Leaves program in force mode. */
10978 void
10979 FeedMovesToProgram (ChessProgramState *cps, int upto)
10980 {
10981     int i;
10982
10983     if (appData.debugMode)
10984       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10985               startedFromSetupPosition ? "position and " : "",
10986               backwardMostMove, upto, cps->which);
10987     if(currentlyInitializedVariant != gameInfo.variant) {
10988       char buf[MSG_SIZ];
10989         // [HGM] variantswitch: make engine aware of new variant
10990         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10991                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10992         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10993         SendToProgram(buf, cps);
10994         currentlyInitializedVariant = gameInfo.variant;
10995     }
10996     SendToProgram("force\n", cps);
10997     if (startedFromSetupPosition) {
10998         SendBoard(cps, backwardMostMove);
10999     if (appData.debugMode) {
11000         fprintf(debugFP, "feedMoves\n");
11001     }
11002     }
11003     for (i = backwardMostMove; i < upto; i++) {
11004         SendMoveToProgram(i, cps);
11005     }
11006 }
11007
11008
11009 int
11010 ResurrectChessProgram ()
11011 {
11012      /* The chess program may have exited.
11013         If so, restart it and feed it all the moves made so far. */
11014     static int doInit = 0;
11015
11016     if (appData.noChessProgram) return 1;
11017
11018     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11019         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
11020         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11021         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11022     } else {
11023         if (first.pr != NoProc) return 1;
11024         StartChessProgram(&first);
11025     }
11026     InitChessProgram(&first, FALSE);
11027     FeedMovesToProgram(&first, currentMove);
11028
11029     if (!first.sendTime) {
11030         /* can't tell gnuchess what its clock should read,
11031            so we bow to its notion. */
11032         ResetClocks();
11033         timeRemaining[0][currentMove] = whiteTimeRemaining;
11034         timeRemaining[1][currentMove] = blackTimeRemaining;
11035     }
11036
11037     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11038                 appData.icsEngineAnalyze) && first.analysisSupport) {
11039       SendToProgram("analyze\n", &first);
11040       first.analyzing = TRUE;
11041     }
11042     return 1;
11043 }
11044
11045 /*
11046  * Button procedures
11047  */
11048 void
11049 Reset (int redraw, int init)
11050 {
11051     int i;
11052
11053     if (appData.debugMode) {
11054         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11055                 redraw, init, gameMode);
11056     }
11057     CleanupTail(); // [HGM] vari: delete any stored variations
11058     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11059     pausing = pauseExamInvalid = FALSE;
11060     startedFromSetupPosition = blackPlaysFirst = FALSE;
11061     firstMove = TRUE;
11062     whiteFlag = blackFlag = FALSE;
11063     userOfferedDraw = FALSE;
11064     hintRequested = bookRequested = FALSE;
11065     first.maybeThinking = FALSE;
11066     second.maybeThinking = FALSE;
11067     first.bookSuspend = FALSE; // [HGM] book
11068     second.bookSuspend = FALSE;
11069     thinkOutput[0] = NULLCHAR;
11070     lastHint[0] = NULLCHAR;
11071     ClearGameInfo(&gameInfo);
11072     gameInfo.variant = StringToVariant(appData.variant);
11073     ics_user_moved = ics_clock_paused = FALSE;
11074     ics_getting_history = H_FALSE;
11075     ics_gamenum = -1;
11076     white_holding[0] = black_holding[0] = NULLCHAR;
11077     ClearProgramStats();
11078     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11079
11080     ResetFrontEnd();
11081     ClearHighlights();
11082     flipView = appData.flipView;
11083     ClearPremoveHighlights();
11084     gotPremove = FALSE;
11085     alarmSounded = FALSE;
11086
11087     GameEnds(EndOfFile, NULL, GE_PLAYER);
11088     if(appData.serverMovesName != NULL) {
11089         /* [HGM] prepare to make moves file for broadcasting */
11090         clock_t t = clock();
11091         if(serverMoves != NULL) fclose(serverMoves);
11092         serverMoves = fopen(appData.serverMovesName, "r");
11093         if(serverMoves != NULL) {
11094             fclose(serverMoves);
11095             /* delay 15 sec before overwriting, so all clients can see end */
11096             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11097         }
11098         serverMoves = fopen(appData.serverMovesName, "w");
11099     }
11100
11101     ExitAnalyzeMode();
11102     gameMode = BeginningOfGame;
11103     ModeHighlight();
11104     if(appData.icsActive) gameInfo.variant = VariantNormal;
11105     currentMove = forwardMostMove = backwardMostMove = 0;
11106     MarkTargetSquares(1);
11107     InitPosition(redraw);
11108     for (i = 0; i < MAX_MOVES; i++) {
11109         if (commentList[i] != NULL) {
11110             free(commentList[i]);
11111             commentList[i] = NULL;
11112         }
11113     }
11114     ResetClocks();
11115     timeRemaining[0][0] = whiteTimeRemaining;
11116     timeRemaining[1][0] = blackTimeRemaining;
11117
11118     if (first.pr == NoProc) {
11119         StartChessProgram(&first);
11120     }
11121     if (init) {
11122             InitChessProgram(&first, startedFromSetupPosition);
11123     }
11124     DisplayTitle("");
11125     DisplayMessage("", "");
11126     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11127     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11128     ClearMap();        // [HGM] exclude: invalidate map
11129 }
11130
11131 void
11132 AutoPlayGameLoop ()
11133 {
11134     for (;;) {
11135         if (!AutoPlayOneMove())
11136           return;
11137         if (matchMode || appData.timeDelay == 0)
11138           continue;
11139         if (appData.timeDelay < 0)
11140           return;
11141         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11142         break;
11143     }
11144 }
11145
11146 void
11147 AnalyzeNextGame()
11148 {
11149     ReloadGame(1); // next game
11150 }
11151
11152 int
11153 AutoPlayOneMove ()
11154 {
11155     int fromX, fromY, toX, toY;
11156
11157     if (appData.debugMode) {
11158       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11159     }
11160
11161     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11162       return FALSE;
11163
11164     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11165       pvInfoList[currentMove].depth = programStats.depth;
11166       pvInfoList[currentMove].score = programStats.score;
11167       pvInfoList[currentMove].time  = 0;
11168       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11169     }
11170
11171     if (currentMove >= forwardMostMove) {
11172       if(gameMode == AnalyzeFile) {
11173           if(appData.loadGameIndex == -1) {
11174             GameEnds(EndOfFile, NULL, GE_FILE);
11175           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11176           } else {
11177           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11178         }
11179       }
11180 //      gameMode = EndOfGame;
11181 //      ModeHighlight();
11182
11183       /* [AS] Clear current move marker at the end of a game */
11184       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11185
11186       return FALSE;
11187     }
11188
11189     toX = moveList[currentMove][2] - AAA;
11190     toY = moveList[currentMove][3] - ONE;
11191
11192     if (moveList[currentMove][1] == '@') {
11193         if (appData.highlightLastMove) {
11194             SetHighlights(-1, -1, toX, toY);
11195         }
11196     } else {
11197         fromX = moveList[currentMove][0] - AAA;
11198         fromY = moveList[currentMove][1] - ONE;
11199
11200         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11201
11202         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11203
11204         if (appData.highlightLastMove) {
11205             SetHighlights(fromX, fromY, toX, toY);
11206         }
11207     }
11208     DisplayMove(currentMove);
11209     SendMoveToProgram(currentMove++, &first);
11210     DisplayBothClocks();
11211     DrawPosition(FALSE, boards[currentMove]);
11212     // [HGM] PV info: always display, routine tests if empty
11213     DisplayComment(currentMove - 1, commentList[currentMove]);
11214     return TRUE;
11215 }
11216
11217
11218 int
11219 LoadGameOneMove (ChessMove readAhead)
11220 {
11221     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11222     char promoChar = NULLCHAR;
11223     ChessMove moveType;
11224     char move[MSG_SIZ];
11225     char *p, *q;
11226
11227     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11228         gameMode != AnalyzeMode && gameMode != Training) {
11229         gameFileFP = NULL;
11230         return FALSE;
11231     }
11232
11233     yyboardindex = forwardMostMove;
11234     if (readAhead != EndOfFile) {
11235       moveType = readAhead;
11236     } else {
11237       if (gameFileFP == NULL)
11238           return FALSE;
11239       moveType = (ChessMove) Myylex();
11240     }
11241
11242     done = FALSE;
11243     switch (moveType) {
11244       case Comment:
11245         if (appData.debugMode)
11246           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11247         p = yy_text;
11248
11249         /* append the comment but don't display it */
11250         AppendComment(currentMove, p, FALSE);
11251         return TRUE;
11252
11253       case WhiteCapturesEnPassant:
11254       case BlackCapturesEnPassant:
11255       case WhitePromotion:
11256       case BlackPromotion:
11257       case WhiteNonPromotion:
11258       case BlackNonPromotion:
11259       case NormalMove:
11260       case WhiteKingSideCastle:
11261       case WhiteQueenSideCastle:
11262       case BlackKingSideCastle:
11263       case BlackQueenSideCastle:
11264       case WhiteKingSideCastleWild:
11265       case WhiteQueenSideCastleWild:
11266       case BlackKingSideCastleWild:
11267       case BlackQueenSideCastleWild:
11268       /* PUSH Fabien */
11269       case WhiteHSideCastleFR:
11270       case WhiteASideCastleFR:
11271       case BlackHSideCastleFR:
11272       case BlackASideCastleFR:
11273       /* POP Fabien */
11274         if (appData.debugMode)
11275           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11276         fromX = currentMoveString[0] - AAA;
11277         fromY = currentMoveString[1] - ONE;
11278         toX = currentMoveString[2] - AAA;
11279         toY = currentMoveString[3] - ONE;
11280         promoChar = currentMoveString[4];
11281         break;
11282
11283       case WhiteDrop:
11284       case BlackDrop:
11285         if (appData.debugMode)
11286           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11287         fromX = moveType == WhiteDrop ?
11288           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11289         (int) CharToPiece(ToLower(currentMoveString[0]));
11290         fromY = DROP_RANK;
11291         toX = currentMoveString[2] - AAA;
11292         toY = currentMoveString[3] - ONE;
11293         break;
11294
11295       case WhiteWins:
11296       case BlackWins:
11297       case GameIsDrawn:
11298       case GameUnfinished:
11299         if (appData.debugMode)
11300           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11301         p = strchr(yy_text, '{');
11302         if (p == NULL) p = strchr(yy_text, '(');
11303         if (p == NULL) {
11304             p = yy_text;
11305             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11306         } else {
11307             q = strchr(p, *p == '{' ? '}' : ')');
11308             if (q != NULL) *q = NULLCHAR;
11309             p++;
11310         }
11311         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11312         GameEnds(moveType, p, GE_FILE);
11313         done = TRUE;
11314         if (cmailMsgLoaded) {
11315             ClearHighlights();
11316             flipView = WhiteOnMove(currentMove);
11317             if (moveType == GameUnfinished) flipView = !flipView;
11318             if (appData.debugMode)
11319               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11320         }
11321         break;
11322
11323       case EndOfFile:
11324         if (appData.debugMode)
11325           fprintf(debugFP, "Parser hit end of file\n");
11326         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11327           case MT_NONE:
11328           case MT_CHECK:
11329             break;
11330           case MT_CHECKMATE:
11331           case MT_STAINMATE:
11332             if (WhiteOnMove(currentMove)) {
11333                 GameEnds(BlackWins, "Black mates", GE_FILE);
11334             } else {
11335                 GameEnds(WhiteWins, "White mates", GE_FILE);
11336             }
11337             break;
11338           case MT_STALEMATE:
11339             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11340             break;
11341         }
11342         done = TRUE;
11343         break;
11344
11345       case MoveNumberOne:
11346         if (lastLoadGameStart == GNUChessGame) {
11347             /* GNUChessGames have numbers, but they aren't move numbers */
11348             if (appData.debugMode)
11349               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11350                       yy_text, (int) moveType);
11351             return LoadGameOneMove(EndOfFile); /* tail recursion */
11352         }
11353         /* else fall thru */
11354
11355       case XBoardGame:
11356       case GNUChessGame:
11357       case PGNTag:
11358         /* Reached start of next game in file */
11359         if (appData.debugMode)
11360           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11361         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11362           case MT_NONE:
11363           case MT_CHECK:
11364             break;
11365           case MT_CHECKMATE:
11366           case MT_STAINMATE:
11367             if (WhiteOnMove(currentMove)) {
11368                 GameEnds(BlackWins, "Black mates", GE_FILE);
11369             } else {
11370                 GameEnds(WhiteWins, "White mates", GE_FILE);
11371             }
11372             break;
11373           case MT_STALEMATE:
11374             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11375             break;
11376         }
11377         done = TRUE;
11378         break;
11379
11380       case PositionDiagram:     /* should not happen; ignore */
11381       case ElapsedTime:         /* ignore */
11382       case NAG:                 /* ignore */
11383         if (appData.debugMode)
11384           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11385                   yy_text, (int) moveType);
11386         return LoadGameOneMove(EndOfFile); /* tail recursion */
11387
11388       case IllegalMove:
11389         if (appData.testLegality) {
11390             if (appData.debugMode)
11391               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11392             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11393                     (forwardMostMove / 2) + 1,
11394                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11395             DisplayError(move, 0);
11396             done = TRUE;
11397         } else {
11398             if (appData.debugMode)
11399               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11400                       yy_text, currentMoveString);
11401             fromX = currentMoveString[0] - AAA;
11402             fromY = currentMoveString[1] - ONE;
11403             toX = currentMoveString[2] - AAA;
11404             toY = currentMoveString[3] - ONE;
11405             promoChar = currentMoveString[4];
11406         }
11407         break;
11408
11409       case AmbiguousMove:
11410         if (appData.debugMode)
11411           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11412         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11413                 (forwardMostMove / 2) + 1,
11414                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11415         DisplayError(move, 0);
11416         done = TRUE;
11417         break;
11418
11419       default:
11420       case ImpossibleMove:
11421         if (appData.debugMode)
11422           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11423         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11424                 (forwardMostMove / 2) + 1,
11425                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11426         DisplayError(move, 0);
11427         done = TRUE;
11428         break;
11429     }
11430
11431     if (done) {
11432         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11433             DrawPosition(FALSE, boards[currentMove]);
11434             DisplayBothClocks();
11435             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11436               DisplayComment(currentMove - 1, commentList[currentMove]);
11437         }
11438         (void) StopLoadGameTimer();
11439         gameFileFP = NULL;
11440         cmailOldMove = forwardMostMove;
11441         return FALSE;
11442     } else {
11443         /* currentMoveString is set as a side-effect of yylex */
11444
11445         thinkOutput[0] = NULLCHAR;
11446         MakeMove(fromX, fromY, toX, toY, promoChar);
11447         currentMove = forwardMostMove;
11448         return TRUE;
11449     }
11450 }
11451
11452 /* Load the nth game from the given file */
11453 int
11454 LoadGameFromFile (char *filename, int n, char *title, int useList)
11455 {
11456     FILE *f;
11457     char buf[MSG_SIZ];
11458
11459     if (strcmp(filename, "-") == 0) {
11460         f = stdin;
11461         title = "stdin";
11462     } else {
11463         f = fopen(filename, "rb");
11464         if (f == NULL) {
11465           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11466             DisplayError(buf, errno);
11467             return FALSE;
11468         }
11469     }
11470     if (fseek(f, 0, 0) == -1) {
11471         /* f is not seekable; probably a pipe */
11472         useList = FALSE;
11473     }
11474     if (useList && n == 0) {
11475         int error = GameListBuild(f);
11476         if (error) {
11477             DisplayError(_("Cannot build game list"), error);
11478         } else if (!ListEmpty(&gameList) &&
11479                    ((ListGame *) gameList.tailPred)->number > 1) {
11480             GameListPopUp(f, title);
11481             return TRUE;
11482         }
11483         GameListDestroy();
11484         n = 1;
11485     }
11486     if (n == 0) n = 1;
11487     return LoadGame(f, n, title, FALSE);
11488 }
11489
11490
11491 void
11492 MakeRegisteredMove ()
11493 {
11494     int fromX, fromY, toX, toY;
11495     char promoChar;
11496     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11497         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11498           case CMAIL_MOVE:
11499           case CMAIL_DRAW:
11500             if (appData.debugMode)
11501               fprintf(debugFP, "Restoring %s for game %d\n",
11502                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11503
11504             thinkOutput[0] = NULLCHAR;
11505             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11506             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11507             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11508             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11509             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11510             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11511             MakeMove(fromX, fromY, toX, toY, promoChar);
11512             ShowMove(fromX, fromY, toX, toY);
11513
11514             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11515               case MT_NONE:
11516               case MT_CHECK:
11517                 break;
11518
11519               case MT_CHECKMATE:
11520               case MT_STAINMATE:
11521                 if (WhiteOnMove(currentMove)) {
11522                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11523                 } else {
11524                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11525                 }
11526                 break;
11527
11528               case MT_STALEMATE:
11529                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11530                 break;
11531             }
11532
11533             break;
11534
11535           case CMAIL_RESIGN:
11536             if (WhiteOnMove(currentMove)) {
11537                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11538             } else {
11539                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11540             }
11541             break;
11542
11543           case CMAIL_ACCEPT:
11544             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11545             break;
11546
11547           default:
11548             break;
11549         }
11550     }
11551
11552     return;
11553 }
11554
11555 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11556 int
11557 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11558 {
11559     int retVal;
11560
11561     if (gameNumber > nCmailGames) {
11562         DisplayError(_("No more games in this message"), 0);
11563         return FALSE;
11564     }
11565     if (f == lastLoadGameFP) {
11566         int offset = gameNumber - lastLoadGameNumber;
11567         if (offset == 0) {
11568             cmailMsg[0] = NULLCHAR;
11569             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11570                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11571                 nCmailMovesRegistered--;
11572             }
11573             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11574             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11575                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11576             }
11577         } else {
11578             if (! RegisterMove()) return FALSE;
11579         }
11580     }
11581
11582     retVal = LoadGame(f, gameNumber, title, useList);
11583
11584     /* Make move registered during previous look at this game, if any */
11585     MakeRegisteredMove();
11586
11587     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11588         commentList[currentMove]
11589           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11590         DisplayComment(currentMove - 1, commentList[currentMove]);
11591     }
11592
11593     return retVal;
11594 }
11595
11596 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11597 int
11598 ReloadGame (int offset)
11599 {
11600     int gameNumber = lastLoadGameNumber + offset;
11601     if (lastLoadGameFP == NULL) {
11602         DisplayError(_("No game has been loaded yet"), 0);
11603         return FALSE;
11604     }
11605     if (gameNumber <= 0) {
11606         DisplayError(_("Can't back up any further"), 0);
11607         return FALSE;
11608     }
11609     if (cmailMsgLoaded) {
11610         return CmailLoadGame(lastLoadGameFP, gameNumber,
11611                              lastLoadGameTitle, lastLoadGameUseList);
11612     } else {
11613         return LoadGame(lastLoadGameFP, gameNumber,
11614                         lastLoadGameTitle, lastLoadGameUseList);
11615     }
11616 }
11617
11618 int keys[EmptySquare+1];
11619
11620 int
11621 PositionMatches (Board b1, Board b2)
11622 {
11623     int r, f, sum=0;
11624     switch(appData.searchMode) {
11625         case 1: return CompareWithRights(b1, b2);
11626         case 2:
11627             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11628                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11629             }
11630             return TRUE;
11631         case 3:
11632             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11633               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11634                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11635             }
11636             return sum==0;
11637         case 4:
11638             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11639                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11640             }
11641             return sum==0;
11642     }
11643     return TRUE;
11644 }
11645
11646 #define Q_PROMO  4
11647 #define Q_EP     3
11648 #define Q_BCASTL 2
11649 #define Q_WCASTL 1
11650
11651 int pieceList[256], quickBoard[256];
11652 ChessSquare pieceType[256] = { EmptySquare };
11653 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11654 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11655 int soughtTotal, turn;
11656 Boolean epOK, flipSearch;
11657
11658 typedef struct {
11659     unsigned char piece, to;
11660 } Move;
11661
11662 #define DSIZE (250000)
11663
11664 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11665 Move *moveDatabase = initialSpace;
11666 unsigned int movePtr, dataSize = DSIZE;
11667
11668 int
11669 MakePieceList (Board board, int *counts)
11670 {
11671     int r, f, n=Q_PROMO, total=0;
11672     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11673     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11674         int sq = f + (r<<4);
11675         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11676             quickBoard[sq] = ++n;
11677             pieceList[n] = sq;
11678             pieceType[n] = board[r][f];
11679             counts[board[r][f]]++;
11680             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11681             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11682             total++;
11683         }
11684     }
11685     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11686     return total;
11687 }
11688
11689 void
11690 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11691 {
11692     int sq = fromX + (fromY<<4);
11693     int piece = quickBoard[sq];
11694     quickBoard[sq] = 0;
11695     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11696     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11697         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11698         moveDatabase[movePtr++].piece = Q_WCASTL;
11699         quickBoard[sq] = piece;
11700         piece = quickBoard[from]; quickBoard[from] = 0;
11701         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11702     } else
11703     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11704         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11705         moveDatabase[movePtr++].piece = Q_BCASTL;
11706         quickBoard[sq] = piece;
11707         piece = quickBoard[from]; quickBoard[from] = 0;
11708         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11709     } else
11710     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11711         quickBoard[(fromY<<4)+toX] = 0;
11712         moveDatabase[movePtr].piece = Q_EP;
11713         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11714         moveDatabase[movePtr].to = sq;
11715     } else
11716     if(promoPiece != pieceType[piece]) {
11717         moveDatabase[movePtr++].piece = Q_PROMO;
11718         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11719     }
11720     moveDatabase[movePtr].piece = piece;
11721     quickBoard[sq] = piece;
11722     movePtr++;
11723 }
11724
11725 int
11726 PackGame (Board board)
11727 {
11728     Move *newSpace = NULL;
11729     moveDatabase[movePtr].piece = 0; // terminate previous game
11730     if(movePtr > dataSize) {
11731         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11732         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11733         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11734         if(newSpace) {
11735             int i;
11736             Move *p = moveDatabase, *q = newSpace;
11737             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11738             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11739             moveDatabase = newSpace;
11740         } else { // calloc failed, we must be out of memory. Too bad...
11741             dataSize = 0; // prevent calloc events for all subsequent games
11742             return 0;     // and signal this one isn't cached
11743         }
11744     }
11745     movePtr++;
11746     MakePieceList(board, counts);
11747     return movePtr;
11748 }
11749
11750 int
11751 QuickCompare (Board board, int *minCounts, int *maxCounts)
11752 {   // compare according to search mode
11753     int r, f;
11754     switch(appData.searchMode)
11755     {
11756       case 1: // exact position match
11757         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11758         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11759             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11760         }
11761         break;
11762       case 2: // can have extra material on empty squares
11763         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11764             if(board[r][f] == EmptySquare) continue;
11765             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11766         }
11767         break;
11768       case 3: // material with exact Pawn structure
11769         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11770             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11771             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11772         } // fall through to material comparison
11773       case 4: // exact material
11774         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11775         break;
11776       case 6: // material range with given imbalance
11777         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11778         // fall through to range comparison
11779       case 5: // material range
11780         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11781     }
11782     return TRUE;
11783 }
11784
11785 int
11786 QuickScan (Board board, Move *move)
11787 {   // reconstruct game,and compare all positions in it
11788     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11789     do {
11790         int piece = move->piece;
11791         int to = move->to, from = pieceList[piece];
11792         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11793           if(!piece) return -1;
11794           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11795             piece = (++move)->piece;
11796             from = pieceList[piece];
11797             counts[pieceType[piece]]--;
11798             pieceType[piece] = (ChessSquare) move->to;
11799             counts[move->to]++;
11800           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11801             counts[pieceType[quickBoard[to]]]--;
11802             quickBoard[to] = 0; total--;
11803             move++;
11804             continue;
11805           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11806             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11807             from  = pieceList[piece]; // so this must be King
11808             quickBoard[from] = 0;
11809             pieceList[piece] = to;
11810             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11811             quickBoard[from] = 0; // rook
11812             quickBoard[to] = piece;
11813             to = move->to; piece = move->piece;
11814             goto aftercastle;
11815           }
11816         }
11817         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11818         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11819         quickBoard[from] = 0;
11820       aftercastle:
11821         quickBoard[to] = piece;
11822         pieceList[piece] = to;
11823         cnt++; turn ^= 3;
11824         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11825            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11826            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11827                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11828           ) {
11829             static int lastCounts[EmptySquare+1];
11830             int i;
11831             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11832             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11833         } else stretch = 0;
11834         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11835         move++;
11836     } while(1);
11837 }
11838
11839 void
11840 InitSearch ()
11841 {
11842     int r, f;
11843     flipSearch = FALSE;
11844     CopyBoard(soughtBoard, boards[currentMove]);
11845     soughtTotal = MakePieceList(soughtBoard, maxSought);
11846     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11847     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11848     CopyBoard(reverseBoard, boards[currentMove]);
11849     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11850         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11851         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11852         reverseBoard[r][f] = piece;
11853     }
11854     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11855     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11856     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11857                  || (boards[currentMove][CASTLING][2] == NoRights ||
11858                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11859                  && (boards[currentMove][CASTLING][5] == NoRights ||
11860                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11861       ) {
11862         flipSearch = TRUE;
11863         CopyBoard(flipBoard, soughtBoard);
11864         CopyBoard(rotateBoard, reverseBoard);
11865         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11866             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11867             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11868         }
11869     }
11870     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11871     if(appData.searchMode >= 5) {
11872         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11873         MakePieceList(soughtBoard, minSought);
11874         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11875     }
11876     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11877         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11878 }
11879
11880 GameInfo dummyInfo;
11881 static int creatingBook;
11882
11883 int
11884 GameContainsPosition (FILE *f, ListGame *lg)
11885 {
11886     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11887     int fromX, fromY, toX, toY;
11888     char promoChar;
11889     static int initDone=FALSE;
11890
11891     // weed out games based on numerical tag comparison
11892     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11893     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11894     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11895     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11896     if(!initDone) {
11897         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11898         initDone = TRUE;
11899     }
11900     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11901     else CopyBoard(boards[scratch], initialPosition); // default start position
11902     if(lg->moves) {
11903         turn = btm + 1;
11904         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11905         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11906     }
11907     if(btm) plyNr++;
11908     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11909     fseek(f, lg->offset, 0);
11910     yynewfile(f);
11911     while(1) {
11912         yyboardindex = scratch;
11913         quickFlag = plyNr+1;
11914         next = Myylex();
11915         quickFlag = 0;
11916         switch(next) {
11917             case PGNTag:
11918                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11919             default:
11920                 continue;
11921
11922             case XBoardGame:
11923             case GNUChessGame:
11924                 if(plyNr) return -1; // after we have seen moves, this is for new game
11925               continue;
11926
11927             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11928             case ImpossibleMove:
11929             case WhiteWins: // game ends here with these four
11930             case BlackWins:
11931             case GameIsDrawn:
11932             case GameUnfinished:
11933                 return -1;
11934
11935             case IllegalMove:
11936                 if(appData.testLegality) return -1;
11937             case WhiteCapturesEnPassant:
11938             case BlackCapturesEnPassant:
11939             case WhitePromotion:
11940             case BlackPromotion:
11941             case WhiteNonPromotion:
11942             case BlackNonPromotion:
11943             case NormalMove:
11944             case WhiteKingSideCastle:
11945             case WhiteQueenSideCastle:
11946             case BlackKingSideCastle:
11947             case BlackQueenSideCastle:
11948             case WhiteKingSideCastleWild:
11949             case WhiteQueenSideCastleWild:
11950             case BlackKingSideCastleWild:
11951             case BlackQueenSideCastleWild:
11952             case WhiteHSideCastleFR:
11953             case WhiteASideCastleFR:
11954             case BlackHSideCastleFR:
11955             case BlackASideCastleFR:
11956                 fromX = currentMoveString[0] - AAA;
11957                 fromY = currentMoveString[1] - ONE;
11958                 toX = currentMoveString[2] - AAA;
11959                 toY = currentMoveString[3] - ONE;
11960                 promoChar = currentMoveString[4];
11961                 break;
11962             case WhiteDrop:
11963             case BlackDrop:
11964                 fromX = next == WhiteDrop ?
11965                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11966                   (int) CharToPiece(ToLower(currentMoveString[0]));
11967                 fromY = DROP_RANK;
11968                 toX = currentMoveString[2] - AAA;
11969                 toY = currentMoveString[3] - ONE;
11970                 promoChar = 0;
11971                 break;
11972         }
11973         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11974         plyNr++;
11975         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11976         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11977         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11978         if(appData.findMirror) {
11979             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11980             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11981         }
11982     }
11983 }
11984
11985 /* Load the nth game from open file f */
11986 int
11987 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11988 {
11989     ChessMove cm;
11990     char buf[MSG_SIZ];
11991     int gn = gameNumber;
11992     ListGame *lg = NULL;
11993     int numPGNTags = 0;
11994     int err, pos = -1;
11995     GameMode oldGameMode;
11996     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11997
11998     if (appData.debugMode)
11999         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12000
12001     if (gameMode == Training )
12002         SetTrainingModeOff();
12003
12004     oldGameMode = gameMode;
12005     if (gameMode != BeginningOfGame) {
12006       Reset(FALSE, TRUE);
12007     }
12008
12009     gameFileFP = f;
12010     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12011         fclose(lastLoadGameFP);
12012     }
12013
12014     if (useList) {
12015         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12016
12017         if (lg) {
12018             fseek(f, lg->offset, 0);
12019             GameListHighlight(gameNumber);
12020             pos = lg->position;
12021             gn = 1;
12022         }
12023         else {
12024             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
12025               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12026             else
12027             DisplayError(_("Game number out of range"), 0);
12028             return FALSE;
12029         }
12030     } else {
12031         GameListDestroy();
12032         if (fseek(f, 0, 0) == -1) {
12033             if (f == lastLoadGameFP ?
12034                 gameNumber == lastLoadGameNumber + 1 :
12035                 gameNumber == 1) {
12036                 gn = 1;
12037             } else {
12038                 DisplayError(_("Can't seek on game file"), 0);
12039                 return FALSE;
12040             }
12041         }
12042     }
12043     lastLoadGameFP = f;
12044     lastLoadGameNumber = gameNumber;
12045     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12046     lastLoadGameUseList = useList;
12047
12048     yynewfile(f);
12049
12050     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12051       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12052                 lg->gameInfo.black);
12053             DisplayTitle(buf);
12054     } else if (*title != NULLCHAR) {
12055         if (gameNumber > 1) {
12056           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12057             DisplayTitle(buf);
12058         } else {
12059             DisplayTitle(title);
12060         }
12061     }
12062
12063     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12064         gameMode = PlayFromGameFile;
12065         ModeHighlight();
12066     }
12067
12068     currentMove = forwardMostMove = backwardMostMove = 0;
12069     CopyBoard(boards[0], initialPosition);
12070     StopClocks();
12071
12072     /*
12073      * Skip the first gn-1 games in the file.
12074      * Also skip over anything that precedes an identifiable
12075      * start of game marker, to avoid being confused by
12076      * garbage at the start of the file.  Currently
12077      * recognized start of game markers are the move number "1",
12078      * the pattern "gnuchess .* game", the pattern
12079      * "^[#;%] [^ ]* game file", and a PGN tag block.
12080      * A game that starts with one of the latter two patterns
12081      * will also have a move number 1, possibly
12082      * following a position diagram.
12083      * 5-4-02: Let's try being more lenient and allowing a game to
12084      * start with an unnumbered move.  Does that break anything?
12085      */
12086     cm = lastLoadGameStart = EndOfFile;
12087     while (gn > 0) {
12088         yyboardindex = forwardMostMove;
12089         cm = (ChessMove) Myylex();
12090         switch (cm) {
12091           case EndOfFile:
12092             if (cmailMsgLoaded) {
12093                 nCmailGames = CMAIL_MAX_GAMES - gn;
12094             } else {
12095                 Reset(TRUE, TRUE);
12096                 DisplayError(_("Game not found in file"), 0);
12097             }
12098             return FALSE;
12099
12100           case GNUChessGame:
12101           case XBoardGame:
12102             gn--;
12103             lastLoadGameStart = cm;
12104             break;
12105
12106           case MoveNumberOne:
12107             switch (lastLoadGameStart) {
12108               case GNUChessGame:
12109               case XBoardGame:
12110               case PGNTag:
12111                 break;
12112               case MoveNumberOne:
12113               case EndOfFile:
12114                 gn--;           /* count this game */
12115                 lastLoadGameStart = cm;
12116                 break;
12117               default:
12118                 /* impossible */
12119                 break;
12120             }
12121             break;
12122
12123           case PGNTag:
12124             switch (lastLoadGameStart) {
12125               case GNUChessGame:
12126               case PGNTag:
12127               case MoveNumberOne:
12128               case EndOfFile:
12129                 gn--;           /* count this game */
12130                 lastLoadGameStart = cm;
12131                 break;
12132               case XBoardGame:
12133                 lastLoadGameStart = cm; /* game counted already */
12134                 break;
12135               default:
12136                 /* impossible */
12137                 break;
12138             }
12139             if (gn > 0) {
12140                 do {
12141                     yyboardindex = forwardMostMove;
12142                     cm = (ChessMove) Myylex();
12143                 } while (cm == PGNTag || cm == Comment);
12144             }
12145             break;
12146
12147           case WhiteWins:
12148           case BlackWins:
12149           case GameIsDrawn:
12150             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12151                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12152                     != CMAIL_OLD_RESULT) {
12153                     nCmailResults ++ ;
12154                     cmailResult[  CMAIL_MAX_GAMES
12155                                 - gn - 1] = CMAIL_OLD_RESULT;
12156                 }
12157             }
12158             break;
12159
12160           case NormalMove:
12161             /* Only a NormalMove can be at the start of a game
12162              * without a position diagram. */
12163             if (lastLoadGameStart == EndOfFile ) {
12164               gn--;
12165               lastLoadGameStart = MoveNumberOne;
12166             }
12167             break;
12168
12169           default:
12170             break;
12171         }
12172     }
12173
12174     if (appData.debugMode)
12175       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12176
12177     if (cm == XBoardGame) {
12178         /* Skip any header junk before position diagram and/or move 1 */
12179         for (;;) {
12180             yyboardindex = forwardMostMove;
12181             cm = (ChessMove) Myylex();
12182
12183             if (cm == EndOfFile ||
12184                 cm == GNUChessGame || cm == XBoardGame) {
12185                 /* Empty game; pretend end-of-file and handle later */
12186                 cm = EndOfFile;
12187                 break;
12188             }
12189
12190             if (cm == MoveNumberOne || cm == PositionDiagram ||
12191                 cm == PGNTag || cm == Comment)
12192               break;
12193         }
12194     } else if (cm == GNUChessGame) {
12195         if (gameInfo.event != NULL) {
12196             free(gameInfo.event);
12197         }
12198         gameInfo.event = StrSave(yy_text);
12199     }
12200
12201     startedFromSetupPosition = FALSE;
12202     while (cm == PGNTag) {
12203         if (appData.debugMode)
12204           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12205         err = ParsePGNTag(yy_text, &gameInfo);
12206         if (!err) numPGNTags++;
12207
12208         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12209         if(gameInfo.variant != oldVariant) {
12210             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12211             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12212             InitPosition(TRUE);
12213             oldVariant = gameInfo.variant;
12214             if (appData.debugMode)
12215               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12216         }
12217
12218
12219         if (gameInfo.fen != NULL) {
12220           Board initial_position;
12221           startedFromSetupPosition = TRUE;
12222           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12223             Reset(TRUE, TRUE);
12224             DisplayError(_("Bad FEN position in file"), 0);
12225             return FALSE;
12226           }
12227           CopyBoard(boards[0], initial_position);
12228           if (blackPlaysFirst) {
12229             currentMove = forwardMostMove = backwardMostMove = 1;
12230             CopyBoard(boards[1], initial_position);
12231             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12232             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12233             timeRemaining[0][1] = whiteTimeRemaining;
12234             timeRemaining[1][1] = blackTimeRemaining;
12235             if (commentList[0] != NULL) {
12236               commentList[1] = commentList[0];
12237               commentList[0] = NULL;
12238             }
12239           } else {
12240             currentMove = forwardMostMove = backwardMostMove = 0;
12241           }
12242           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12243           {   int i;
12244               initialRulePlies = FENrulePlies;
12245               for( i=0; i< nrCastlingRights; i++ )
12246                   initialRights[i] = initial_position[CASTLING][i];
12247           }
12248           yyboardindex = forwardMostMove;
12249           free(gameInfo.fen);
12250           gameInfo.fen = NULL;
12251         }
12252
12253         yyboardindex = forwardMostMove;
12254         cm = (ChessMove) Myylex();
12255
12256         /* Handle comments interspersed among the tags */
12257         while (cm == Comment) {
12258             char *p;
12259             if (appData.debugMode)
12260               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12261             p = yy_text;
12262             AppendComment(currentMove, p, FALSE);
12263             yyboardindex = forwardMostMove;
12264             cm = (ChessMove) Myylex();
12265         }
12266     }
12267
12268     /* don't rely on existence of Event tag since if game was
12269      * pasted from clipboard the Event tag may not exist
12270      */
12271     if (numPGNTags > 0){
12272         char *tags;
12273         if (gameInfo.variant == VariantNormal) {
12274           VariantClass v = StringToVariant(gameInfo.event);
12275           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12276           if(v < VariantShogi) gameInfo.variant = v;
12277         }
12278         if (!matchMode) {
12279           if( appData.autoDisplayTags ) {
12280             tags = PGNTags(&gameInfo);
12281             TagsPopUp(tags, CmailMsg());
12282             free(tags);
12283           }
12284         }
12285     } else {
12286         /* Make something up, but don't display it now */
12287         SetGameInfo();
12288         TagsPopDown();
12289     }
12290
12291     if (cm == PositionDiagram) {
12292         int i, j;
12293         char *p;
12294         Board initial_position;
12295
12296         if (appData.debugMode)
12297           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12298
12299         if (!startedFromSetupPosition) {
12300             p = yy_text;
12301             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12302               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12303                 switch (*p) {
12304                   case '{':
12305                   case '[':
12306                   case '-':
12307                   case ' ':
12308                   case '\t':
12309                   case '\n':
12310                   case '\r':
12311                     break;
12312                   default:
12313                     initial_position[i][j++] = CharToPiece(*p);
12314                     break;
12315                 }
12316             while (*p == ' ' || *p == '\t' ||
12317                    *p == '\n' || *p == '\r') p++;
12318
12319             if (strncmp(p, "black", strlen("black"))==0)
12320               blackPlaysFirst = TRUE;
12321             else
12322               blackPlaysFirst = FALSE;
12323             startedFromSetupPosition = TRUE;
12324
12325             CopyBoard(boards[0], initial_position);
12326             if (blackPlaysFirst) {
12327                 currentMove = forwardMostMove = backwardMostMove = 1;
12328                 CopyBoard(boards[1], initial_position);
12329                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12330                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12331                 timeRemaining[0][1] = whiteTimeRemaining;
12332                 timeRemaining[1][1] = blackTimeRemaining;
12333                 if (commentList[0] != NULL) {
12334                     commentList[1] = commentList[0];
12335                     commentList[0] = NULL;
12336                 }
12337             } else {
12338                 currentMove = forwardMostMove = backwardMostMove = 0;
12339             }
12340         }
12341         yyboardindex = forwardMostMove;
12342         cm = (ChessMove) Myylex();
12343     }
12344
12345   if(!creatingBook) {
12346     if (first.pr == NoProc) {
12347         StartChessProgram(&first);
12348     }
12349     InitChessProgram(&first, FALSE);
12350     SendToProgram("force\n", &first);
12351     if (startedFromSetupPosition) {
12352         SendBoard(&first, forwardMostMove);
12353     if (appData.debugMode) {
12354         fprintf(debugFP, "Load Game\n");
12355     }
12356         DisplayBothClocks();
12357     }
12358   }
12359
12360     /* [HGM] server: flag to write setup moves in broadcast file as one */
12361     loadFlag = appData.suppressLoadMoves;
12362
12363     while (cm == Comment) {
12364         char *p;
12365         if (appData.debugMode)
12366           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12367         p = yy_text;
12368         AppendComment(currentMove, p, FALSE);
12369         yyboardindex = forwardMostMove;
12370         cm = (ChessMove) Myylex();
12371     }
12372
12373     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12374         cm == WhiteWins || cm == BlackWins ||
12375         cm == GameIsDrawn || cm == GameUnfinished) {
12376         DisplayMessage("", _("No moves in game"));
12377         if (cmailMsgLoaded) {
12378             if (appData.debugMode)
12379               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12380             ClearHighlights();
12381             flipView = FALSE;
12382         }
12383         DrawPosition(FALSE, boards[currentMove]);
12384         DisplayBothClocks();
12385         gameMode = EditGame;
12386         ModeHighlight();
12387         gameFileFP = NULL;
12388         cmailOldMove = 0;
12389         return TRUE;
12390     }
12391
12392     // [HGM] PV info: routine tests if comment empty
12393     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12394         DisplayComment(currentMove - 1, commentList[currentMove]);
12395     }
12396     if (!matchMode && appData.timeDelay != 0)
12397       DrawPosition(FALSE, boards[currentMove]);
12398
12399     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12400       programStats.ok_to_send = 1;
12401     }
12402
12403     /* if the first token after the PGN tags is a move
12404      * and not move number 1, retrieve it from the parser
12405      */
12406     if (cm != MoveNumberOne)
12407         LoadGameOneMove(cm);
12408
12409     /* load the remaining moves from the file */
12410     while (LoadGameOneMove(EndOfFile)) {
12411       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12412       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12413     }
12414
12415     /* rewind to the start of the game */
12416     currentMove = backwardMostMove;
12417
12418     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12419
12420     if (oldGameMode == AnalyzeFile ||
12421         oldGameMode == AnalyzeMode) {
12422       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12423       AnalyzeFileEvent();
12424     }
12425
12426     if(creatingBook) return TRUE;
12427     if (!matchMode && pos > 0) {
12428         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12429     } else
12430     if (matchMode || appData.timeDelay == 0) {
12431       ToEndEvent();
12432     } else if (appData.timeDelay > 0) {
12433       AutoPlayGameLoop();
12434     }
12435
12436     if (appData.debugMode)
12437         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12438
12439     loadFlag = 0; /* [HGM] true game starts */
12440     return TRUE;
12441 }
12442
12443 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12444 int
12445 ReloadPosition (int offset)
12446 {
12447     int positionNumber = lastLoadPositionNumber + offset;
12448     if (lastLoadPositionFP == NULL) {
12449         DisplayError(_("No position has been loaded yet"), 0);
12450         return FALSE;
12451     }
12452     if (positionNumber <= 0) {
12453         DisplayError(_("Can't back up any further"), 0);
12454         return FALSE;
12455     }
12456     return LoadPosition(lastLoadPositionFP, positionNumber,
12457                         lastLoadPositionTitle);
12458 }
12459
12460 /* Load the nth position from the given file */
12461 int
12462 LoadPositionFromFile (char *filename, int n, char *title)
12463 {
12464     FILE *f;
12465     char buf[MSG_SIZ];
12466
12467     if (strcmp(filename, "-") == 0) {
12468         return LoadPosition(stdin, n, "stdin");
12469     } else {
12470         f = fopen(filename, "rb");
12471         if (f == NULL) {
12472             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12473             DisplayError(buf, errno);
12474             return FALSE;
12475         } else {
12476             return LoadPosition(f, n, title);
12477         }
12478     }
12479 }
12480
12481 /* Load the nth position from the given open file, and close it */
12482 int
12483 LoadPosition (FILE *f, int positionNumber, char *title)
12484 {
12485     char *p, line[MSG_SIZ];
12486     Board initial_position;
12487     int i, j, fenMode, pn;
12488
12489     if (gameMode == Training )
12490         SetTrainingModeOff();
12491
12492     if (gameMode != BeginningOfGame) {
12493         Reset(FALSE, TRUE);
12494     }
12495     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12496         fclose(lastLoadPositionFP);
12497     }
12498     if (positionNumber == 0) positionNumber = 1;
12499     lastLoadPositionFP = f;
12500     lastLoadPositionNumber = positionNumber;
12501     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12502     if (first.pr == NoProc && !appData.noChessProgram) {
12503       StartChessProgram(&first);
12504       InitChessProgram(&first, FALSE);
12505     }
12506     pn = positionNumber;
12507     if (positionNumber < 0) {
12508         /* Negative position number means to seek to that byte offset */
12509         if (fseek(f, -positionNumber, 0) == -1) {
12510             DisplayError(_("Can't seek on position file"), 0);
12511             return FALSE;
12512         };
12513         pn = 1;
12514     } else {
12515         if (fseek(f, 0, 0) == -1) {
12516             if (f == lastLoadPositionFP ?
12517                 positionNumber == lastLoadPositionNumber + 1 :
12518                 positionNumber == 1) {
12519                 pn = 1;
12520             } else {
12521                 DisplayError(_("Can't seek on position file"), 0);
12522                 return FALSE;
12523             }
12524         }
12525     }
12526     /* See if this file is FEN or old-style xboard */
12527     if (fgets(line, MSG_SIZ, f) == NULL) {
12528         DisplayError(_("Position not found in file"), 0);
12529         return FALSE;
12530     }
12531     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12532     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12533
12534     if (pn >= 2) {
12535         if (fenMode || line[0] == '#') pn--;
12536         while (pn > 0) {
12537             /* skip positions before number pn */
12538             if (fgets(line, MSG_SIZ, f) == NULL) {
12539                 Reset(TRUE, TRUE);
12540                 DisplayError(_("Position not found in file"), 0);
12541                 return FALSE;
12542             }
12543             if (fenMode || line[0] == '#') pn--;
12544         }
12545     }
12546
12547     if (fenMode) {
12548         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12549             DisplayError(_("Bad FEN position in file"), 0);
12550             return FALSE;
12551         }
12552     } else {
12553         (void) fgets(line, MSG_SIZ, f);
12554         (void) fgets(line, MSG_SIZ, f);
12555
12556         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12557             (void) fgets(line, MSG_SIZ, f);
12558             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12559                 if (*p == ' ')
12560                   continue;
12561                 initial_position[i][j++] = CharToPiece(*p);
12562             }
12563         }
12564
12565         blackPlaysFirst = FALSE;
12566         if (!feof(f)) {
12567             (void) fgets(line, MSG_SIZ, f);
12568             if (strncmp(line, "black", strlen("black"))==0)
12569               blackPlaysFirst = TRUE;
12570         }
12571     }
12572     startedFromSetupPosition = TRUE;
12573
12574     CopyBoard(boards[0], initial_position);
12575     if (blackPlaysFirst) {
12576         currentMove = forwardMostMove = backwardMostMove = 1;
12577         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12578         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12579         CopyBoard(boards[1], initial_position);
12580         DisplayMessage("", _("Black to play"));
12581     } else {
12582         currentMove = forwardMostMove = backwardMostMove = 0;
12583         DisplayMessage("", _("White to play"));
12584     }
12585     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12586     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12587         SendToProgram("force\n", &first);
12588         SendBoard(&first, forwardMostMove);
12589     }
12590     if (appData.debugMode) {
12591 int i, j;
12592   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12593   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12594         fprintf(debugFP, "Load Position\n");
12595     }
12596
12597     if (positionNumber > 1) {
12598       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12599         DisplayTitle(line);
12600     } else {
12601         DisplayTitle(title);
12602     }
12603     gameMode = EditGame;
12604     ModeHighlight();
12605     ResetClocks();
12606     timeRemaining[0][1] = whiteTimeRemaining;
12607     timeRemaining[1][1] = blackTimeRemaining;
12608     DrawPosition(FALSE, boards[currentMove]);
12609
12610     return TRUE;
12611 }
12612
12613
12614 void
12615 CopyPlayerNameIntoFileName (char **dest, char *src)
12616 {
12617     while (*src != NULLCHAR && *src != ',') {
12618         if (*src == ' ') {
12619             *(*dest)++ = '_';
12620             src++;
12621         } else {
12622             *(*dest)++ = *src++;
12623         }
12624     }
12625 }
12626
12627 char *
12628 DefaultFileName (char *ext)
12629 {
12630     static char def[MSG_SIZ];
12631     char *p;
12632
12633     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12634         p = def;
12635         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12636         *p++ = '-';
12637         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12638         *p++ = '.';
12639         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12640     } else {
12641         def[0] = NULLCHAR;
12642     }
12643     return def;
12644 }
12645
12646 /* Save the current game to the given file */
12647 int
12648 SaveGameToFile (char *filename, int append)
12649 {
12650     FILE *f;
12651     char buf[MSG_SIZ];
12652     int result, i, t,tot=0;
12653
12654     if (strcmp(filename, "-") == 0) {
12655         return SaveGame(stdout, 0, NULL);
12656     } else {
12657         for(i=0; i<10; i++) { // upto 10 tries
12658              f = fopen(filename, append ? "a" : "w");
12659              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12660              if(f || errno != 13) break;
12661              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12662              tot += t;
12663         }
12664         if (f == NULL) {
12665             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12666             DisplayError(buf, errno);
12667             return FALSE;
12668         } else {
12669             safeStrCpy(buf, lastMsg, MSG_SIZ);
12670             DisplayMessage(_("Waiting for access to save file"), "");
12671             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12672             DisplayMessage(_("Saving game"), "");
12673             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12674             result = SaveGame(f, 0, NULL);
12675             DisplayMessage(buf, "");
12676             return result;
12677         }
12678     }
12679 }
12680
12681 char *
12682 SavePart (char *str)
12683 {
12684     static char buf[MSG_SIZ];
12685     char *p;
12686
12687     p = strchr(str, ' ');
12688     if (p == NULL) return str;
12689     strncpy(buf, str, p - str);
12690     buf[p - str] = NULLCHAR;
12691     return buf;
12692 }
12693
12694 #define PGN_MAX_LINE 75
12695
12696 #define PGN_SIDE_WHITE  0
12697 #define PGN_SIDE_BLACK  1
12698
12699 static int
12700 FindFirstMoveOutOfBook (int side)
12701 {
12702     int result = -1;
12703
12704     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12705         int index = backwardMostMove;
12706         int has_book_hit = 0;
12707
12708         if( (index % 2) != side ) {
12709             index++;
12710         }
12711
12712         while( index < forwardMostMove ) {
12713             /* Check to see if engine is in book */
12714             int depth = pvInfoList[index].depth;
12715             int score = pvInfoList[index].score;
12716             int in_book = 0;
12717
12718             if( depth <= 2 ) {
12719                 in_book = 1;
12720             }
12721             else if( score == 0 && depth == 63 ) {
12722                 in_book = 1; /* Zappa */
12723             }
12724             else if( score == 2 && depth == 99 ) {
12725                 in_book = 1; /* Abrok */
12726             }
12727
12728             has_book_hit += in_book;
12729
12730             if( ! in_book ) {
12731                 result = index;
12732
12733                 break;
12734             }
12735
12736             index += 2;
12737         }
12738     }
12739
12740     return result;
12741 }
12742
12743 void
12744 GetOutOfBookInfo (char * buf)
12745 {
12746     int oob[2];
12747     int i;
12748     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12749
12750     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12751     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12752
12753     *buf = '\0';
12754
12755     if( oob[0] >= 0 || oob[1] >= 0 ) {
12756         for( i=0; i<2; i++ ) {
12757             int idx = oob[i];
12758
12759             if( idx >= 0 ) {
12760                 if( i > 0 && oob[0] >= 0 ) {
12761                     strcat( buf, "   " );
12762                 }
12763
12764                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12765                 sprintf( buf+strlen(buf), "%s%.2f",
12766                     pvInfoList[idx].score >= 0 ? "+" : "",
12767                     pvInfoList[idx].score / 100.0 );
12768             }
12769         }
12770     }
12771 }
12772
12773 /* Save game in PGN style and close the file */
12774 int
12775 SaveGamePGN (FILE *f)
12776 {
12777     int i, offset, linelen, newblock;
12778 //    char *movetext;
12779     char numtext[32];
12780     int movelen, numlen, blank;
12781     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12782
12783     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12784
12785     PrintPGNTags(f, &gameInfo);
12786
12787     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12788
12789     if (backwardMostMove > 0 || startedFromSetupPosition) {
12790         char *fen = PositionToFEN(backwardMostMove, NULL);
12791         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12792         fprintf(f, "\n{--------------\n");
12793         PrintPosition(f, backwardMostMove);
12794         fprintf(f, "--------------}\n");
12795         free(fen);
12796     }
12797     else {
12798         /* [AS] Out of book annotation */
12799         if( appData.saveOutOfBookInfo ) {
12800             char buf[64];
12801
12802             GetOutOfBookInfo( buf );
12803
12804             if( buf[0] != '\0' ) {
12805                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12806             }
12807         }
12808
12809         fprintf(f, "\n");
12810     }
12811
12812     i = backwardMostMove;
12813     linelen = 0;
12814     newblock = TRUE;
12815
12816     while (i < forwardMostMove) {
12817         /* Print comments preceding this move */
12818         if (commentList[i] != NULL) {
12819             if (linelen > 0) fprintf(f, "\n");
12820             fprintf(f, "%s", commentList[i]);
12821             linelen = 0;
12822             newblock = TRUE;
12823         }
12824
12825         /* Format move number */
12826         if ((i % 2) == 0)
12827           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12828         else
12829           if (newblock)
12830             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12831           else
12832             numtext[0] = NULLCHAR;
12833
12834         numlen = strlen(numtext);
12835         newblock = FALSE;
12836
12837         /* Print move number */
12838         blank = linelen > 0 && numlen > 0;
12839         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12840             fprintf(f, "\n");
12841             linelen = 0;
12842             blank = 0;
12843         }
12844         if (blank) {
12845             fprintf(f, " ");
12846             linelen++;
12847         }
12848         fprintf(f, "%s", numtext);
12849         linelen += numlen;
12850
12851         /* Get move */
12852         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12853         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12854
12855         /* Print move */
12856         blank = linelen > 0 && movelen > 0;
12857         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12858             fprintf(f, "\n");
12859             linelen = 0;
12860             blank = 0;
12861         }
12862         if (blank) {
12863             fprintf(f, " ");
12864             linelen++;
12865         }
12866         fprintf(f, "%s", move_buffer);
12867         linelen += movelen;
12868
12869         /* [AS] Add PV info if present */
12870         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12871             /* [HGM] add time */
12872             char buf[MSG_SIZ]; int seconds;
12873
12874             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12875
12876             if( seconds <= 0)
12877               buf[0] = 0;
12878             else
12879               if( seconds < 30 )
12880                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12881               else
12882                 {
12883                   seconds = (seconds + 4)/10; // round to full seconds
12884                   if( seconds < 60 )
12885                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12886                   else
12887                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12888                 }
12889
12890             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12891                       pvInfoList[i].score >= 0 ? "+" : "",
12892                       pvInfoList[i].score / 100.0,
12893                       pvInfoList[i].depth,
12894                       buf );
12895
12896             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12897
12898             /* Print score/depth */
12899             blank = linelen > 0 && movelen > 0;
12900             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12901                 fprintf(f, "\n");
12902                 linelen = 0;
12903                 blank = 0;
12904             }
12905             if (blank) {
12906                 fprintf(f, " ");
12907                 linelen++;
12908             }
12909             fprintf(f, "%s", move_buffer);
12910             linelen += movelen;
12911         }
12912
12913         i++;
12914     }
12915
12916     /* Start a new line */
12917     if (linelen > 0) fprintf(f, "\n");
12918
12919     /* Print comments after last move */
12920     if (commentList[i] != NULL) {
12921         fprintf(f, "%s\n", commentList[i]);
12922     }
12923
12924     /* Print result */
12925     if (gameInfo.resultDetails != NULL &&
12926         gameInfo.resultDetails[0] != NULLCHAR) {
12927         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12928                 PGNResult(gameInfo.result));
12929     } else {
12930         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12931     }
12932
12933     fclose(f);
12934     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12935     return TRUE;
12936 }
12937
12938 /* Save game in old style and close the file */
12939 int
12940 SaveGameOldStyle (FILE *f)
12941 {
12942     int i, offset;
12943     time_t tm;
12944
12945     tm = time((time_t *) NULL);
12946
12947     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12948     PrintOpponents(f);
12949
12950     if (backwardMostMove > 0 || startedFromSetupPosition) {
12951         fprintf(f, "\n[--------------\n");
12952         PrintPosition(f, backwardMostMove);
12953         fprintf(f, "--------------]\n");
12954     } else {
12955         fprintf(f, "\n");
12956     }
12957
12958     i = backwardMostMove;
12959     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12960
12961     while (i < forwardMostMove) {
12962         if (commentList[i] != NULL) {
12963             fprintf(f, "[%s]\n", commentList[i]);
12964         }
12965
12966         if ((i % 2) == 1) {
12967             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12968             i++;
12969         } else {
12970             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12971             i++;
12972             if (commentList[i] != NULL) {
12973                 fprintf(f, "\n");
12974                 continue;
12975             }
12976             if (i >= forwardMostMove) {
12977                 fprintf(f, "\n");
12978                 break;
12979             }
12980             fprintf(f, "%s\n", parseList[i]);
12981             i++;
12982         }
12983     }
12984
12985     if (commentList[i] != NULL) {
12986         fprintf(f, "[%s]\n", commentList[i]);
12987     }
12988
12989     /* This isn't really the old style, but it's close enough */
12990     if (gameInfo.resultDetails != NULL &&
12991         gameInfo.resultDetails[0] != NULLCHAR) {
12992         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12993                 gameInfo.resultDetails);
12994     } else {
12995         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12996     }
12997
12998     fclose(f);
12999     return TRUE;
13000 }
13001
13002 /* Save the current game to open file f and close the file */
13003 int
13004 SaveGame (FILE *f, int dummy, char *dummy2)
13005 {
13006     if (gameMode == EditPosition) EditPositionDone(TRUE);
13007     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13008     if (appData.oldSaveStyle)
13009       return SaveGameOldStyle(f);
13010     else
13011       return SaveGamePGN(f);
13012 }
13013
13014 /* Save the current position to the given file */
13015 int
13016 SavePositionToFile (char *filename)
13017 {
13018     FILE *f;
13019     char buf[MSG_SIZ];
13020
13021     if (strcmp(filename, "-") == 0) {
13022         return SavePosition(stdout, 0, NULL);
13023     } else {
13024         f = fopen(filename, "a");
13025         if (f == NULL) {
13026             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13027             DisplayError(buf, errno);
13028             return FALSE;
13029         } else {
13030             safeStrCpy(buf, lastMsg, MSG_SIZ);
13031             DisplayMessage(_("Waiting for access to save file"), "");
13032             flock(fileno(f), LOCK_EX); // [HGM] lock
13033             DisplayMessage(_("Saving position"), "");
13034             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13035             SavePosition(f, 0, NULL);
13036             DisplayMessage(buf, "");
13037             return TRUE;
13038         }
13039     }
13040 }
13041
13042 /* Save the current position to the given open file and close the file */
13043 int
13044 SavePosition (FILE *f, int dummy, char *dummy2)
13045 {
13046     time_t tm;
13047     char *fen;
13048
13049     if (gameMode == EditPosition) EditPositionDone(TRUE);
13050     if (appData.oldSaveStyle) {
13051         tm = time((time_t *) NULL);
13052
13053         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13054         PrintOpponents(f);
13055         fprintf(f, "[--------------\n");
13056         PrintPosition(f, currentMove);
13057         fprintf(f, "--------------]\n");
13058     } else {
13059         fen = PositionToFEN(currentMove, NULL);
13060         fprintf(f, "%s\n", fen);
13061         free(fen);
13062     }
13063     fclose(f);
13064     return TRUE;
13065 }
13066
13067 void
13068 ReloadCmailMsgEvent (int unregister)
13069 {
13070 #if !WIN32
13071     static char *inFilename = NULL;
13072     static char *outFilename;
13073     int i;
13074     struct stat inbuf, outbuf;
13075     int status;
13076
13077     /* Any registered moves are unregistered if unregister is set, */
13078     /* i.e. invoked by the signal handler */
13079     if (unregister) {
13080         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13081             cmailMoveRegistered[i] = FALSE;
13082             if (cmailCommentList[i] != NULL) {
13083                 free(cmailCommentList[i]);
13084                 cmailCommentList[i] = NULL;
13085             }
13086         }
13087         nCmailMovesRegistered = 0;
13088     }
13089
13090     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13091         cmailResult[i] = CMAIL_NOT_RESULT;
13092     }
13093     nCmailResults = 0;
13094
13095     if (inFilename == NULL) {
13096         /* Because the filenames are static they only get malloced once  */
13097         /* and they never get freed                                      */
13098         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13099         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13100
13101         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13102         sprintf(outFilename, "%s.out", appData.cmailGameName);
13103     }
13104
13105     status = stat(outFilename, &outbuf);
13106     if (status < 0) {
13107         cmailMailedMove = FALSE;
13108     } else {
13109         status = stat(inFilename, &inbuf);
13110         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13111     }
13112
13113     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13114        counts the games, notes how each one terminated, etc.
13115
13116        It would be nice to remove this kludge and instead gather all
13117        the information while building the game list.  (And to keep it
13118        in the game list nodes instead of having a bunch of fixed-size
13119        parallel arrays.)  Note this will require getting each game's
13120        termination from the PGN tags, as the game list builder does
13121        not process the game moves.  --mann
13122        */
13123     cmailMsgLoaded = TRUE;
13124     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13125
13126     /* Load first game in the file or popup game menu */
13127     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13128
13129 #endif /* !WIN32 */
13130     return;
13131 }
13132
13133 int
13134 RegisterMove ()
13135 {
13136     FILE *f;
13137     char string[MSG_SIZ];
13138
13139     if (   cmailMailedMove
13140         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13141         return TRUE;            /* Allow free viewing  */
13142     }
13143
13144     /* Unregister move to ensure that we don't leave RegisterMove        */
13145     /* with the move registered when the conditions for registering no   */
13146     /* longer hold                                                       */
13147     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13148         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13149         nCmailMovesRegistered --;
13150
13151         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13152           {
13153               free(cmailCommentList[lastLoadGameNumber - 1]);
13154               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13155           }
13156     }
13157
13158     if (cmailOldMove == -1) {
13159         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13160         return FALSE;
13161     }
13162
13163     if (currentMove > cmailOldMove + 1) {
13164         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13165         return FALSE;
13166     }
13167
13168     if (currentMove < cmailOldMove) {
13169         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13170         return FALSE;
13171     }
13172
13173     if (forwardMostMove > currentMove) {
13174         /* Silently truncate extra moves */
13175         TruncateGame();
13176     }
13177
13178     if (   (currentMove == cmailOldMove + 1)
13179         || (   (currentMove == cmailOldMove)
13180             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13181                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13182         if (gameInfo.result != GameUnfinished) {
13183             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13184         }
13185
13186         if (commentList[currentMove] != NULL) {
13187             cmailCommentList[lastLoadGameNumber - 1]
13188               = StrSave(commentList[currentMove]);
13189         }
13190         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13191
13192         if (appData.debugMode)
13193           fprintf(debugFP, "Saving %s for game %d\n",
13194                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13195
13196         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13197
13198         f = fopen(string, "w");
13199         if (appData.oldSaveStyle) {
13200             SaveGameOldStyle(f); /* also closes the file */
13201
13202             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13203             f = fopen(string, "w");
13204             SavePosition(f, 0, NULL); /* also closes the file */
13205         } else {
13206             fprintf(f, "{--------------\n");
13207             PrintPosition(f, currentMove);
13208             fprintf(f, "--------------}\n\n");
13209
13210             SaveGame(f, 0, NULL); /* also closes the file*/
13211         }
13212
13213         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13214         nCmailMovesRegistered ++;
13215     } else if (nCmailGames == 1) {
13216         DisplayError(_("You have not made a move yet"), 0);
13217         return FALSE;
13218     }
13219
13220     return TRUE;
13221 }
13222
13223 void
13224 MailMoveEvent ()
13225 {
13226 #if !WIN32
13227     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13228     FILE *commandOutput;
13229     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13230     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13231     int nBuffers;
13232     int i;
13233     int archived;
13234     char *arcDir;
13235
13236     if (! cmailMsgLoaded) {
13237         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13238         return;
13239     }
13240
13241     if (nCmailGames == nCmailResults) {
13242         DisplayError(_("No unfinished games"), 0);
13243         return;
13244     }
13245
13246 #if CMAIL_PROHIBIT_REMAIL
13247     if (cmailMailedMove) {
13248       snprintf(msg, MSG_SIZ, _("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);
13249         DisplayError(msg, 0);
13250         return;
13251     }
13252 #endif
13253
13254     if (! (cmailMailedMove || RegisterMove())) return;
13255
13256     if (   cmailMailedMove
13257         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13258       snprintf(string, MSG_SIZ, partCommandString,
13259                appData.debugMode ? " -v" : "", appData.cmailGameName);
13260         commandOutput = popen(string, "r");
13261
13262         if (commandOutput == NULL) {
13263             DisplayError(_("Failed to invoke cmail"), 0);
13264         } else {
13265             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13266                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13267             }
13268             if (nBuffers > 1) {
13269                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13270                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13271                 nBytes = MSG_SIZ - 1;
13272             } else {
13273                 (void) memcpy(msg, buffer, nBytes);
13274             }
13275             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13276
13277             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13278                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13279
13280                 archived = TRUE;
13281                 for (i = 0; i < nCmailGames; i ++) {
13282                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13283                         archived = FALSE;
13284                     }
13285                 }
13286                 if (   archived
13287                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13288                         != NULL)) {
13289                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13290                            arcDir,
13291                            appData.cmailGameName,
13292                            gameInfo.date);
13293                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13294                     cmailMsgLoaded = FALSE;
13295                 }
13296             }
13297
13298             DisplayInformation(msg);
13299             pclose(commandOutput);
13300         }
13301     } else {
13302         if ((*cmailMsg) != '\0') {
13303             DisplayInformation(cmailMsg);
13304         }
13305     }
13306
13307     return;
13308 #endif /* !WIN32 */
13309 }
13310
13311 char *
13312 CmailMsg ()
13313 {
13314 #if WIN32
13315     return NULL;
13316 #else
13317     int  prependComma = 0;
13318     char number[5];
13319     char string[MSG_SIZ];       /* Space for game-list */
13320     int  i;
13321
13322     if (!cmailMsgLoaded) return "";
13323
13324     if (cmailMailedMove) {
13325       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13326     } else {
13327         /* Create a list of games left */
13328       snprintf(string, MSG_SIZ, "[");
13329         for (i = 0; i < nCmailGames; i ++) {
13330             if (! (   cmailMoveRegistered[i]
13331                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13332                 if (prependComma) {
13333                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13334                 } else {
13335                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13336                     prependComma = 1;
13337                 }
13338
13339                 strcat(string, number);
13340             }
13341         }
13342         strcat(string, "]");
13343
13344         if (nCmailMovesRegistered + nCmailResults == 0) {
13345             switch (nCmailGames) {
13346               case 1:
13347                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13348                 break;
13349
13350               case 2:
13351                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13352                 break;
13353
13354               default:
13355                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13356                          nCmailGames);
13357                 break;
13358             }
13359         } else {
13360             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13361               case 1:
13362                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13363                          string);
13364                 break;
13365
13366               case 0:
13367                 if (nCmailResults == nCmailGames) {
13368                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13369                 } else {
13370                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13371                 }
13372                 break;
13373
13374               default:
13375                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13376                          string);
13377             }
13378         }
13379     }
13380     return cmailMsg;
13381 #endif /* WIN32 */
13382 }
13383
13384 void
13385 ResetGameEvent ()
13386 {
13387     if (gameMode == Training)
13388       SetTrainingModeOff();
13389
13390     Reset(TRUE, TRUE);
13391     cmailMsgLoaded = FALSE;
13392     if (appData.icsActive) {
13393       SendToICS(ics_prefix);
13394       SendToICS("refresh\n");
13395     }
13396 }
13397
13398 void
13399 ExitEvent (int status)
13400 {
13401     exiting++;
13402     if (exiting > 2) {
13403       /* Give up on clean exit */
13404       exit(status);
13405     }
13406     if (exiting > 1) {
13407       /* Keep trying for clean exit */
13408       return;
13409     }
13410
13411     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13412
13413     if (telnetISR != NULL) {
13414       RemoveInputSource(telnetISR);
13415     }
13416     if (icsPR != NoProc) {
13417       DestroyChildProcess(icsPR, TRUE);
13418     }
13419
13420     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13421     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13422
13423     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13424     /* make sure this other one finishes before killing it!                  */
13425     if(endingGame) { int count = 0;
13426         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13427         while(endingGame && count++ < 10) DoSleep(1);
13428         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13429     }
13430
13431     /* Kill off chess programs */
13432     if (first.pr != NoProc) {
13433         ExitAnalyzeMode();
13434
13435         DoSleep( appData.delayBeforeQuit );
13436         SendToProgram("quit\n", &first);
13437         DoSleep( appData.delayAfterQuit );
13438         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13439     }
13440     if (second.pr != NoProc) {
13441         DoSleep( appData.delayBeforeQuit );
13442         SendToProgram("quit\n", &second);
13443         DoSleep( appData.delayAfterQuit );
13444         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13445     }
13446     if (first.isr != NULL) {
13447         RemoveInputSource(first.isr);
13448     }
13449     if (second.isr != NULL) {
13450         RemoveInputSource(second.isr);
13451     }
13452
13453     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13454     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13455
13456     ShutDownFrontEnd();
13457     exit(status);
13458 }
13459
13460 void
13461 PauseEngine (ChessProgramState *cps)
13462 {
13463     SendToProgram("pause\n", cps);
13464     cps->pause = 2;
13465 }
13466
13467 void
13468 UnPauseEngine (ChessProgramState *cps)
13469 {
13470     SendToProgram("resume\n", cps);
13471     cps->pause = 1;
13472 }
13473
13474 void
13475 PauseEvent ()
13476 {
13477     if (appData.debugMode)
13478         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13479     if (pausing) {
13480         pausing = FALSE;
13481         ModeHighlight();
13482         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13483             StartClocks();
13484             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13485                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13486                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13487             }
13488             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13489             HandleMachineMove(stashedInputMove, stalledEngine);
13490             stalledEngine = NULL;
13491             return;
13492         }
13493         if (gameMode == MachinePlaysWhite ||
13494             gameMode == TwoMachinesPlay   ||
13495             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13496             if(first.pause)  UnPauseEngine(&first);
13497             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13498             if(second.pause) UnPauseEngine(&second);
13499             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13500             StartClocks();
13501         } else {
13502             DisplayBothClocks();
13503         }
13504         if (gameMode == PlayFromGameFile) {
13505             if (appData.timeDelay >= 0)
13506                 AutoPlayGameLoop();
13507         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13508             Reset(FALSE, TRUE);
13509             SendToICS(ics_prefix);
13510             SendToICS("refresh\n");
13511         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13512             ForwardInner(forwardMostMove);
13513         }
13514         pauseExamInvalid = FALSE;
13515     } else {
13516         switch (gameMode) {
13517           default:
13518             return;
13519           case IcsExamining:
13520             pauseExamForwardMostMove = forwardMostMove;
13521             pauseExamInvalid = FALSE;
13522             /* fall through */
13523           case IcsObserving:
13524           case IcsPlayingWhite:
13525           case IcsPlayingBlack:
13526             pausing = TRUE;
13527             ModeHighlight();
13528             return;
13529           case PlayFromGameFile:
13530             (void) StopLoadGameTimer();
13531             pausing = TRUE;
13532             ModeHighlight();
13533             break;
13534           case BeginningOfGame:
13535             if (appData.icsActive) return;
13536             /* else fall through */
13537           case MachinePlaysWhite:
13538           case MachinePlaysBlack:
13539           case TwoMachinesPlay:
13540             if (forwardMostMove == 0)
13541               return;           /* don't pause if no one has moved */
13542             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13543                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13544                 if(onMove->pause) {           // thinking engine can be paused
13545                     PauseEngine(onMove);      // do it
13546                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13547                         PauseEngine(onMove->other);
13548                     else
13549                         SendToProgram("easy\n", onMove->other);
13550                     StopClocks();
13551                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13552             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13553                 if(first.pause) {
13554                     PauseEngine(&first);
13555                     StopClocks();
13556                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13557             } else { // human on move, pause pondering by either method
13558                 if(first.pause)
13559                     PauseEngine(&first);
13560                 else if(appData.ponderNextMove)
13561                     SendToProgram("easy\n", &first);
13562                 StopClocks();
13563             }
13564             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13565           case AnalyzeMode:
13566             pausing = TRUE;
13567             ModeHighlight();
13568             break;
13569         }
13570     }
13571 }
13572
13573 void
13574 EditCommentEvent ()
13575 {
13576     char title[MSG_SIZ];
13577
13578     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13579       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13580     } else {
13581       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13582                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13583                parseList[currentMove - 1]);
13584     }
13585
13586     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13587 }
13588
13589
13590 void
13591 EditTagsEvent ()
13592 {
13593     char *tags = PGNTags(&gameInfo);
13594     bookUp = FALSE;
13595     EditTagsPopUp(tags, NULL);
13596     free(tags);
13597 }
13598
13599 void
13600 ToggleSecond ()
13601 {
13602   if(second.analyzing) {
13603     SendToProgram("exit\n", &second);
13604     second.analyzing = FALSE;
13605   } else {
13606     if (second.pr == NoProc) StartChessProgram(&second);
13607     InitChessProgram(&second, FALSE);
13608     FeedMovesToProgram(&second, currentMove);
13609
13610     SendToProgram("analyze\n", &second);
13611     second.analyzing = TRUE;
13612   }
13613 }
13614
13615 /* Toggle ShowThinking */
13616 void
13617 ToggleShowThinking()
13618 {
13619   appData.showThinking = !appData.showThinking;
13620   ShowThinkingEvent();
13621 }
13622
13623 int
13624 AnalyzeModeEvent ()
13625 {
13626     char buf[MSG_SIZ];
13627
13628     if (!first.analysisSupport) {
13629       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13630       DisplayError(buf, 0);
13631       return 0;
13632     }
13633     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13634     if (appData.icsActive) {
13635         if (gameMode != IcsObserving) {
13636           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13637             DisplayError(buf, 0);
13638             /* secure check */
13639             if (appData.icsEngineAnalyze) {
13640                 if (appData.debugMode)
13641                     fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13642                 ExitAnalyzeMode();
13643                 ModeHighlight();
13644             }
13645             return 0;
13646         }
13647         /* if enable, user wants to disable icsEngineAnalyze */
13648         if (appData.icsEngineAnalyze) {
13649                 ExitAnalyzeMode();
13650                 ModeHighlight();
13651                 return 0;
13652         }
13653         appData.icsEngineAnalyze = TRUE;
13654         if (appData.debugMode)
13655             fprintf(debugFP, _("ICS engine analyze starting... \n"));
13656     }
13657
13658     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13659     if (appData.noChessProgram || gameMode == AnalyzeMode)
13660       return 0;
13661
13662     if (gameMode != AnalyzeFile) {
13663         if (!appData.icsEngineAnalyze) {
13664                EditGameEvent();
13665                if (gameMode != EditGame) return 0;
13666         }
13667         if (!appData.showThinking) ToggleShowThinking();
13668         ResurrectChessProgram();
13669         SendToProgram("analyze\n", &first);
13670         first.analyzing = TRUE;
13671         /*first.maybeThinking = TRUE;*/
13672         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13673         EngineOutputPopUp();
13674     }
13675     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13676     pausing = FALSE;
13677     ModeHighlight();
13678     SetGameInfo();
13679
13680     StartAnalysisClock();
13681     GetTimeMark(&lastNodeCountTime);
13682     lastNodeCount = 0;
13683     return 1;
13684 }
13685
13686 void
13687 AnalyzeFileEvent ()
13688 {
13689     if (appData.noChessProgram || gameMode == AnalyzeFile)
13690       return;
13691
13692     if (!first.analysisSupport) {
13693       char buf[MSG_SIZ];
13694       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13695       DisplayError(buf, 0);
13696       return;
13697     }
13698
13699     if (gameMode != AnalyzeMode) {
13700         keepInfo = 1; // mere annotating should not alter PGN tags
13701         EditGameEvent();
13702         keepInfo = 0;
13703         if (gameMode != EditGame) return;
13704         if (!appData.showThinking) ToggleShowThinking();
13705         ResurrectChessProgram();
13706         SendToProgram("analyze\n", &first);
13707         first.analyzing = TRUE;
13708         /*first.maybeThinking = TRUE;*/
13709         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13710         EngineOutputPopUp();
13711     }
13712     gameMode = AnalyzeFile;
13713     pausing = FALSE;
13714     ModeHighlight();
13715
13716     StartAnalysisClock();
13717     GetTimeMark(&lastNodeCountTime);
13718     lastNodeCount = 0;
13719     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13720     AnalysisPeriodicEvent(1);
13721 }
13722
13723 void
13724 MachineWhiteEvent ()
13725 {
13726     char buf[MSG_SIZ];
13727     char *bookHit = NULL;
13728
13729     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13730       return;
13731
13732
13733     if (gameMode == PlayFromGameFile ||
13734         gameMode == TwoMachinesPlay  ||
13735         gameMode == Training         ||
13736         gameMode == AnalyzeMode      ||
13737         gameMode == EndOfGame)
13738         EditGameEvent();
13739
13740     if (gameMode == EditPosition)
13741         EditPositionDone(TRUE);
13742
13743     if (!WhiteOnMove(currentMove)) {
13744         DisplayError(_("It is not White's turn"), 0);
13745         return;
13746     }
13747
13748     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13749       ExitAnalyzeMode();
13750
13751     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13752         gameMode == AnalyzeFile)
13753         TruncateGame();
13754
13755     ResurrectChessProgram();    /* in case it isn't running */
13756     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13757         gameMode = MachinePlaysWhite;
13758         ResetClocks();
13759     } else
13760     gameMode = MachinePlaysWhite;
13761     pausing = FALSE;
13762     ModeHighlight();
13763     SetGameInfo();
13764     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13765     DisplayTitle(buf);
13766     if (first.sendName) {
13767       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13768       SendToProgram(buf, &first);
13769     }
13770     if (first.sendTime) {
13771       if (first.useColors) {
13772         SendToProgram("black\n", &first); /*gnu kludge*/
13773       }
13774       SendTimeRemaining(&first, TRUE);
13775     }
13776     if (first.useColors) {
13777       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13778     }
13779     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13780     SetMachineThinkingEnables();
13781     first.maybeThinking = TRUE;
13782     StartClocks();
13783     firstMove = FALSE;
13784
13785     if (appData.autoFlipView && !flipView) {
13786       flipView = !flipView;
13787       DrawPosition(FALSE, NULL);
13788       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13789     }
13790
13791     if(bookHit) { // [HGM] book: simulate book reply
13792         static char bookMove[MSG_SIZ]; // a bit generous?
13793
13794         programStats.nodes = programStats.depth = programStats.time =
13795         programStats.score = programStats.got_only_move = 0;
13796         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13797
13798         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13799         strcat(bookMove, bookHit);
13800         HandleMachineMove(bookMove, &first);
13801     }
13802 }
13803
13804 void
13805 MachineBlackEvent ()
13806 {
13807   char buf[MSG_SIZ];
13808   char *bookHit = NULL;
13809
13810     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13811         return;
13812
13813
13814     if (gameMode == PlayFromGameFile ||
13815         gameMode == TwoMachinesPlay  ||
13816         gameMode == Training         ||
13817         gameMode == AnalyzeMode      ||
13818         gameMode == EndOfGame)
13819         EditGameEvent();
13820
13821     if (gameMode == EditPosition)
13822         EditPositionDone(TRUE);
13823
13824     if (WhiteOnMove(currentMove)) {
13825         DisplayError(_("It is not Black's turn"), 0);
13826         return;
13827     }
13828
13829     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13830       ExitAnalyzeMode();
13831
13832     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13833         gameMode == AnalyzeFile)
13834         TruncateGame();
13835
13836     ResurrectChessProgram();    /* in case it isn't running */
13837     gameMode = MachinePlaysBlack;
13838     pausing = FALSE;
13839     ModeHighlight();
13840     SetGameInfo();
13841     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13842     DisplayTitle(buf);
13843     if (first.sendName) {
13844       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13845       SendToProgram(buf, &first);
13846     }
13847     if (first.sendTime) {
13848       if (first.useColors) {
13849         SendToProgram("white\n", &first); /*gnu kludge*/
13850       }
13851       SendTimeRemaining(&first, FALSE);
13852     }
13853     if (first.useColors) {
13854       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13855     }
13856     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13857     SetMachineThinkingEnables();
13858     first.maybeThinking = TRUE;
13859     StartClocks();
13860
13861     if (appData.autoFlipView && flipView) {
13862       flipView = !flipView;
13863       DrawPosition(FALSE, NULL);
13864       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13865     }
13866     if(bookHit) { // [HGM] book: simulate book reply
13867         static char bookMove[MSG_SIZ]; // a bit generous?
13868
13869         programStats.nodes = programStats.depth = programStats.time =
13870         programStats.score = programStats.got_only_move = 0;
13871         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13872
13873         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13874         strcat(bookMove, bookHit);
13875         HandleMachineMove(bookMove, &first);
13876     }
13877 }
13878
13879
13880 void
13881 DisplayTwoMachinesTitle ()
13882 {
13883     char buf[MSG_SIZ];
13884     if (appData.matchGames > 0) {
13885         if(appData.tourneyFile[0]) {
13886           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13887                    gameInfo.white, _("vs."), gameInfo.black,
13888                    nextGame+1, appData.matchGames+1,
13889                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13890         } else
13891         if (first.twoMachinesColor[0] == 'w') {
13892           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13893                    gameInfo.white, _("vs."),  gameInfo.black,
13894                    first.matchWins, second.matchWins,
13895                    matchGame - 1 - (first.matchWins + second.matchWins));
13896         } else {
13897           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13898                    gameInfo.white, _("vs."), gameInfo.black,
13899                    second.matchWins, first.matchWins,
13900                    matchGame - 1 - (first.matchWins + second.matchWins));
13901         }
13902     } else {
13903       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13904     }
13905     DisplayTitle(buf);
13906 }
13907
13908 void
13909 SettingsMenuIfReady ()
13910 {
13911   if (second.lastPing != second.lastPong) {
13912     DisplayMessage("", _("Waiting for second chess program"));
13913     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13914     return;
13915   }
13916   ThawUI();
13917   DisplayMessage("", "");
13918   SettingsPopUp(&second);
13919 }
13920
13921 int
13922 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13923 {
13924     char buf[MSG_SIZ];
13925     if (cps->pr == NoProc) {
13926         StartChessProgram(cps);
13927         if (cps->protocolVersion == 1) {
13928           retry();
13929         } else {
13930           /* kludge: allow timeout for initial "feature" command */
13931           FreezeUI();
13932           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13933           DisplayMessage("", buf);
13934           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13935         }
13936         return 1;
13937     }
13938     return 0;
13939 }
13940
13941 void
13942 TwoMachinesEvent P((void))
13943 {
13944     int i;
13945     char buf[MSG_SIZ];
13946     ChessProgramState *onmove;
13947     char *bookHit = NULL;
13948     static int stalling = 0;
13949     TimeMark now;
13950     long wait;
13951
13952     if (appData.noChessProgram) return;
13953
13954     switch (gameMode) {
13955       case TwoMachinesPlay:
13956         return;
13957       case MachinePlaysWhite:
13958       case MachinePlaysBlack:
13959         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13960             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13961             return;
13962         }
13963         /* fall through */
13964       case BeginningOfGame:
13965       case PlayFromGameFile:
13966       case EndOfGame:
13967         EditGameEvent();
13968         if (gameMode != EditGame) return;
13969         break;
13970       case EditPosition:
13971         EditPositionDone(TRUE);
13972         break;
13973       case AnalyzeMode:
13974       case AnalyzeFile:
13975         ExitAnalyzeMode();
13976         break;
13977       case EditGame:
13978       default:
13979         break;
13980     }
13981
13982 //    forwardMostMove = currentMove;
13983     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13984
13985     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13986
13987     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13988     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13989       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13990       return;
13991     }
13992
13993     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13994         DisplayError("second engine does not play this", 0);
13995         return;
13996     }
13997
13998     if(!stalling) {
13999       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14000       SendToProgram("force\n", &second);
14001       stalling = 1;
14002       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14003       return;
14004     }
14005     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14006     if(appData.matchPause>10000 || appData.matchPause<10)
14007                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14008     wait = SubtractTimeMarks(&now, &pauseStart);
14009     if(wait < appData.matchPause) {
14010         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14011         return;
14012     }
14013     // we are now committed to starting the game
14014     stalling = 0;
14015     DisplayMessage("", "");
14016     if (startedFromSetupPosition) {
14017         SendBoard(&second, backwardMostMove);
14018     if (appData.debugMode) {
14019         fprintf(debugFP, "Two Machines\n");
14020     }
14021     }
14022     for (i = backwardMostMove; i < forwardMostMove; i++) {
14023         SendMoveToProgram(i, &second);
14024     }
14025
14026     gameMode = TwoMachinesPlay;
14027     pausing = FALSE;
14028     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14029     SetGameInfo();
14030     DisplayTwoMachinesTitle();
14031     firstMove = TRUE;
14032     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14033         onmove = &first;
14034     } else {
14035         onmove = &second;
14036     }
14037     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14038     SendToProgram(first.computerString, &first);
14039     if (first.sendName) {
14040       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14041       SendToProgram(buf, &first);
14042     }
14043     SendToProgram(second.computerString, &second);
14044     if (second.sendName) {
14045       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14046       SendToProgram(buf, &second);
14047     }
14048
14049     ResetClocks();
14050     if (!first.sendTime || !second.sendTime) {
14051         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14052         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14053     }
14054     if (onmove->sendTime) {
14055       if (onmove->useColors) {
14056         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14057       }
14058       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14059     }
14060     if (onmove->useColors) {
14061       SendToProgram(onmove->twoMachinesColor, onmove);
14062     }
14063     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14064 //    SendToProgram("go\n", onmove);
14065     onmove->maybeThinking = TRUE;
14066     SetMachineThinkingEnables();
14067
14068     StartClocks();
14069
14070     if(bookHit) { // [HGM] book: simulate book reply
14071         static char bookMove[MSG_SIZ]; // a bit generous?
14072
14073         programStats.nodes = programStats.depth = programStats.time =
14074         programStats.score = programStats.got_only_move = 0;
14075         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14076
14077         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14078         strcat(bookMove, bookHit);
14079         savedMessage = bookMove; // args for deferred call
14080         savedState = onmove;
14081         ScheduleDelayedEvent(DeferredBookMove, 1);
14082     }
14083 }
14084
14085 void
14086 TrainingEvent ()
14087 {
14088     if (gameMode == Training) {
14089       SetTrainingModeOff();
14090       gameMode = PlayFromGameFile;
14091       DisplayMessage("", _("Training mode off"));
14092     } else {
14093       gameMode = Training;
14094       animateTraining = appData.animate;
14095
14096       /* make sure we are not already at the end of the game */
14097       if (currentMove < forwardMostMove) {
14098         SetTrainingModeOn();
14099         DisplayMessage("", _("Training mode on"));
14100       } else {
14101         gameMode = PlayFromGameFile;
14102         DisplayError(_("Already at end of game"), 0);
14103       }
14104     }
14105     ModeHighlight();
14106 }
14107
14108 void
14109 IcsClientEvent ()
14110 {
14111     if (!appData.icsActive) return;
14112     switch (gameMode) {
14113       case IcsPlayingWhite:
14114       case IcsPlayingBlack:
14115       case IcsObserving:
14116       case IcsIdle:
14117       case BeginningOfGame:
14118       case IcsExamining:
14119         return;
14120
14121       case EditGame:
14122         break;
14123
14124       case EditPosition:
14125         EditPositionDone(TRUE);
14126         break;
14127
14128       case AnalyzeMode:
14129       case AnalyzeFile:
14130         ExitAnalyzeMode();
14131         break;
14132
14133       default:
14134         EditGameEvent();
14135         break;
14136     }
14137
14138     gameMode = IcsIdle;
14139     ModeHighlight();
14140     return;
14141 }
14142
14143 void
14144 EditGameEvent ()
14145 {
14146     int i;
14147
14148     switch (gameMode) {
14149       case Training:
14150         SetTrainingModeOff();
14151         break;
14152       case MachinePlaysWhite:
14153       case MachinePlaysBlack:
14154       case BeginningOfGame:
14155         SendToProgram("force\n", &first);
14156         SetUserThinkingEnables();
14157         break;
14158       case PlayFromGameFile:
14159         (void) StopLoadGameTimer();
14160         if (gameFileFP != NULL) {
14161             gameFileFP = NULL;
14162         }
14163         break;
14164       case EditPosition:
14165         EditPositionDone(TRUE);
14166         break;
14167       case AnalyzeMode:
14168       case AnalyzeFile:
14169         ExitAnalyzeMode();
14170         SendToProgram("force\n", &first);
14171         break;
14172       case TwoMachinesPlay:
14173         GameEnds(EndOfFile, NULL, GE_PLAYER);
14174         ResurrectChessProgram();
14175         SetUserThinkingEnables();
14176         break;
14177       case EndOfGame:
14178         ResurrectChessProgram();
14179         break;
14180       case IcsPlayingBlack:
14181       case IcsPlayingWhite:
14182         DisplayError(_("Warning: You are still playing a game"), 0);
14183         break;
14184       case IcsObserving:
14185         DisplayError(_("Warning: You are still observing a game"), 0);
14186         break;
14187       case IcsExamining:
14188         DisplayError(_("Warning: You are still examining a game"), 0);
14189         break;
14190       case IcsIdle:
14191         break;
14192       case EditGame:
14193       default:
14194         return;
14195     }
14196
14197     pausing = FALSE;
14198     StopClocks();
14199     first.offeredDraw = second.offeredDraw = 0;
14200
14201     if (gameMode == PlayFromGameFile) {
14202         whiteTimeRemaining = timeRemaining[0][currentMove];
14203         blackTimeRemaining = timeRemaining[1][currentMove];
14204         DisplayTitle("");
14205     }
14206
14207     if (gameMode == MachinePlaysWhite ||
14208         gameMode == MachinePlaysBlack ||
14209         gameMode == TwoMachinesPlay ||
14210         gameMode == EndOfGame) {
14211         i = forwardMostMove;
14212         while (i > currentMove) {
14213             SendToProgram("undo\n", &first);
14214             i--;
14215         }
14216         if(!adjustedClock) {
14217         whiteTimeRemaining = timeRemaining[0][currentMove];
14218         blackTimeRemaining = timeRemaining[1][currentMove];
14219         DisplayBothClocks();
14220         }
14221         if (whiteFlag || blackFlag) {
14222             whiteFlag = blackFlag = 0;
14223         }
14224         DisplayTitle("");
14225     }
14226
14227     gameMode = EditGame;
14228     ModeHighlight();
14229     SetGameInfo();
14230 }
14231
14232
14233 void
14234 EditPositionEvent ()
14235 {
14236     if (gameMode == EditPosition) {
14237         EditGameEvent();
14238         return;
14239     }
14240
14241     EditGameEvent();
14242     if (gameMode != EditGame) return;
14243
14244     gameMode = EditPosition;
14245     ModeHighlight();
14246     SetGameInfo();
14247     if (currentMove > 0)
14248       CopyBoard(boards[0], boards[currentMove]);
14249
14250     blackPlaysFirst = !WhiteOnMove(currentMove);
14251     ResetClocks();
14252     currentMove = forwardMostMove = backwardMostMove = 0;
14253     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14254     DisplayMove(-1);
14255     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14256 }
14257
14258 void
14259 ExitAnalyzeMode ()
14260 {
14261     /* [DM] icsEngineAnalyze - possible call from other functions */
14262     if (appData.icsEngineAnalyze) {
14263         appData.icsEngineAnalyze = FALSE;
14264
14265         DisplayMessage("",_("Close ICS engine analyze..."));
14266     }
14267     if (first.analysisSupport && first.analyzing) {
14268       SendToBoth("exit\n");
14269       first.analyzing = second.analyzing = FALSE;
14270     }
14271     thinkOutput[0] = NULLCHAR;
14272 }
14273
14274 void
14275 EditPositionDone (Boolean fakeRights)
14276 {
14277     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14278
14279     startedFromSetupPosition = TRUE;
14280     InitChessProgram(&first, FALSE);
14281     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14282       boards[0][EP_STATUS] = EP_NONE;
14283       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14284       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14285         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14286         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14287       } else boards[0][CASTLING][2] = NoRights;
14288       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14289         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14290         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14291       } else boards[0][CASTLING][5] = NoRights;
14292       if(gameInfo.variant == VariantSChess) {
14293         int i;
14294         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14295           boards[0][VIRGIN][i] = 0;
14296           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14297           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14298         }
14299       }
14300     }
14301     SendToProgram("force\n", &first);
14302     if (blackPlaysFirst) {
14303         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14304         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14305         currentMove = forwardMostMove = backwardMostMove = 1;
14306         CopyBoard(boards[1], boards[0]);
14307     } else {
14308         currentMove = forwardMostMove = backwardMostMove = 0;
14309     }
14310     SendBoard(&first, forwardMostMove);
14311     if (appData.debugMode) {
14312         fprintf(debugFP, "EditPosDone\n");
14313     }
14314     DisplayTitle("");
14315     DisplayMessage("", "");
14316     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14317     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14318     gameMode = EditGame;
14319     ModeHighlight();
14320     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14321     ClearHighlights(); /* [AS] */
14322 }
14323
14324 /* Pause for `ms' milliseconds */
14325 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14326 void
14327 TimeDelay (long ms)
14328 {
14329     TimeMark m1, m2;
14330
14331     GetTimeMark(&m1);
14332     do {
14333         GetTimeMark(&m2);
14334     } while (SubtractTimeMarks(&m2, &m1) < ms);
14335 }
14336
14337 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14338 void
14339 SendMultiLineToICS (char *buf)
14340 {
14341     char temp[MSG_SIZ+1], *p;
14342     int len;
14343
14344     len = strlen(buf);
14345     if (len > MSG_SIZ)
14346       len = MSG_SIZ;
14347
14348     strncpy(temp, buf, len);
14349     temp[len] = 0;
14350
14351     p = temp;
14352     while (*p) {
14353         if (*p == '\n' || *p == '\r')
14354           *p = ' ';
14355         ++p;
14356     }
14357
14358     strcat(temp, "\n");
14359     SendToICS(temp);
14360     SendToPlayer(temp, strlen(temp));
14361 }
14362
14363 void
14364 SetWhiteToPlayEvent ()
14365 {
14366     if (gameMode == EditPosition) {
14367         blackPlaysFirst = FALSE;
14368         DisplayBothClocks();    /* works because currentMove is 0 */
14369     } else if (gameMode == IcsExamining) {
14370         SendToICS(ics_prefix);
14371         SendToICS("tomove white\n");
14372     }
14373 }
14374
14375 void
14376 SetBlackToPlayEvent ()
14377 {
14378     if (gameMode == EditPosition) {
14379         blackPlaysFirst = TRUE;
14380         currentMove = 1;        /* kludge */
14381         DisplayBothClocks();
14382         currentMove = 0;
14383     } else if (gameMode == IcsExamining) {
14384         SendToICS(ics_prefix);
14385         SendToICS("tomove black\n");
14386     }
14387 }
14388
14389 void
14390 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14391 {
14392     char buf[MSG_SIZ];
14393     ChessSquare piece = boards[0][y][x];
14394
14395     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14396
14397     switch (selection) {
14398       case ClearBoard:
14399         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14400             SendToICS(ics_prefix);
14401             SendToICS("bsetup clear\n");
14402         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14403             SendToICS(ics_prefix);
14404             SendToICS("clearboard\n");
14405         } else {
14406             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14407                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14408                 for (y = 0; y < BOARD_HEIGHT; y++) {
14409                     if (gameMode == IcsExamining) {
14410                         if (boards[currentMove][y][x] != EmptySquare) {
14411                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14412                                     AAA + x, ONE + y);
14413                             SendToICS(buf);
14414                         }
14415                     } else {
14416                         boards[0][y][x] = p;
14417                     }
14418                 }
14419             }
14420         }
14421         if (gameMode == EditPosition) {
14422             DrawPosition(FALSE, boards[0]);
14423         }
14424         break;
14425
14426       case WhitePlay:
14427         SetWhiteToPlayEvent();
14428         break;
14429
14430       case BlackPlay:
14431         SetBlackToPlayEvent();
14432         break;
14433
14434       case EmptySquare:
14435         if (gameMode == IcsExamining) {
14436             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14437             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14438             SendToICS(buf);
14439         } else {
14440             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14441                 if(x == BOARD_LEFT-2) {
14442                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14443                     boards[0][y][1] = 0;
14444                 } else
14445                 if(x == BOARD_RGHT+1) {
14446                     if(y >= gameInfo.holdingsSize) break;
14447                     boards[0][y][BOARD_WIDTH-2] = 0;
14448                 } else break;
14449             }
14450             boards[0][y][x] = EmptySquare;
14451             DrawPosition(FALSE, boards[0]);
14452         }
14453         break;
14454
14455       case PromotePiece:
14456         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14457            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14458             selection = (ChessSquare) (PROMOTED piece);
14459         } else if(piece == EmptySquare) selection = WhiteSilver;
14460         else selection = (ChessSquare)((int)piece - 1);
14461         goto defaultlabel;
14462
14463       case DemotePiece:
14464         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14465            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14466             selection = (ChessSquare) (DEMOTED piece);
14467         } else if(piece == EmptySquare) selection = BlackSilver;
14468         else selection = (ChessSquare)((int)piece + 1);
14469         goto defaultlabel;
14470
14471       case WhiteQueen:
14472       case BlackQueen:
14473         if(gameInfo.variant == VariantShatranj ||
14474            gameInfo.variant == VariantXiangqi  ||
14475            gameInfo.variant == VariantCourier  ||
14476            gameInfo.variant == VariantMakruk     )
14477             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14478         goto defaultlabel;
14479
14480       case WhiteKing:
14481       case BlackKing:
14482         if(gameInfo.variant == VariantXiangqi)
14483             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14484         if(gameInfo.variant == VariantKnightmate)
14485             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14486       default:
14487         defaultlabel:
14488         if (gameMode == IcsExamining) {
14489             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14490             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14491                      PieceToChar(selection), AAA + x, ONE + y);
14492             SendToICS(buf);
14493         } else {
14494             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14495                 int n;
14496                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14497                     n = PieceToNumber(selection - BlackPawn);
14498                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14499                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14500                     boards[0][BOARD_HEIGHT-1-n][1]++;
14501                 } else
14502                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14503                     n = PieceToNumber(selection);
14504                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14505                     boards[0][n][BOARD_WIDTH-1] = selection;
14506                     boards[0][n][BOARD_WIDTH-2]++;
14507                 }
14508             } else
14509             boards[0][y][x] = selection;
14510             DrawPosition(TRUE, boards[0]);
14511             ClearHighlights();
14512             fromX = fromY = -1;
14513         }
14514         break;
14515     }
14516 }
14517
14518
14519 void
14520 DropMenuEvent (ChessSquare selection, int x, int y)
14521 {
14522     ChessMove moveType;
14523
14524     switch (gameMode) {
14525       case IcsPlayingWhite:
14526       case MachinePlaysBlack:
14527         if (!WhiteOnMove(currentMove)) {
14528             DisplayMoveError(_("It is Black's turn"));
14529             return;
14530         }
14531         moveType = WhiteDrop;
14532         break;
14533       case IcsPlayingBlack:
14534       case MachinePlaysWhite:
14535         if (WhiteOnMove(currentMove)) {
14536             DisplayMoveError(_("It is White's turn"));
14537             return;
14538         }
14539         moveType = BlackDrop;
14540         break;
14541       case EditGame:
14542         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14543         break;
14544       default:
14545         return;
14546     }
14547
14548     if (moveType == BlackDrop && selection < BlackPawn) {
14549       selection = (ChessSquare) ((int) selection
14550                                  + (int) BlackPawn - (int) WhitePawn);
14551     }
14552     if (boards[currentMove][y][x] != EmptySquare) {
14553         DisplayMoveError(_("That square is occupied"));
14554         return;
14555     }
14556
14557     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14558 }
14559
14560 void
14561 AcceptEvent ()
14562 {
14563     /* Accept a pending offer of any kind from opponent */
14564
14565     if (appData.icsActive) {
14566         SendToICS(ics_prefix);
14567         SendToICS("accept\n");
14568     } else if (cmailMsgLoaded) {
14569         if (currentMove == cmailOldMove &&
14570             commentList[cmailOldMove] != NULL &&
14571             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14572                    "Black offers a draw" : "White offers a draw")) {
14573             TruncateGame();
14574             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14575             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14576         } else {
14577             DisplayError(_("There is no pending offer on this move"), 0);
14578             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14579         }
14580     } else {
14581         /* Not used for offers from chess program */
14582     }
14583 }
14584
14585 void
14586 DeclineEvent ()
14587 {
14588     /* Decline a pending offer of any kind from opponent */
14589
14590     if (appData.icsActive) {
14591         SendToICS(ics_prefix);
14592         SendToICS("decline\n");
14593     } else if (cmailMsgLoaded) {
14594         if (currentMove == cmailOldMove &&
14595             commentList[cmailOldMove] != NULL &&
14596             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14597                    "Black offers a draw" : "White offers a draw")) {
14598 #ifdef NOTDEF
14599             AppendComment(cmailOldMove, "Draw declined", TRUE);
14600             DisplayComment(cmailOldMove - 1, "Draw declined");
14601 #endif /*NOTDEF*/
14602         } else {
14603             DisplayError(_("There is no pending offer on this move"), 0);
14604         }
14605     } else {
14606         /* Not used for offers from chess program */
14607     }
14608 }
14609
14610 void
14611 RematchEvent ()
14612 {
14613     /* Issue ICS rematch command */
14614     if (appData.icsActive) {
14615         SendToICS(ics_prefix);
14616         SendToICS("rematch\n");
14617     }
14618 }
14619
14620 void
14621 CallFlagEvent ()
14622 {
14623     /* Call your opponent's flag (claim a win on time) */
14624     if (appData.icsActive) {
14625         SendToICS(ics_prefix);
14626         SendToICS("flag\n");
14627     } else {
14628         switch (gameMode) {
14629           default:
14630             return;
14631           case MachinePlaysWhite:
14632             if (whiteFlag) {
14633                 if (blackFlag)
14634                   GameEnds(GameIsDrawn, "Both players ran out of time",
14635                            GE_PLAYER);
14636                 else
14637                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14638             } else {
14639                 DisplayError(_("Your opponent is not out of time"), 0);
14640             }
14641             break;
14642           case MachinePlaysBlack:
14643             if (blackFlag) {
14644                 if (whiteFlag)
14645                   GameEnds(GameIsDrawn, "Both players ran out of time",
14646                            GE_PLAYER);
14647                 else
14648                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14649             } else {
14650                 DisplayError(_("Your opponent is not out of time"), 0);
14651             }
14652             break;
14653         }
14654     }
14655 }
14656
14657 void
14658 ClockClick (int which)
14659 {       // [HGM] code moved to back-end from winboard.c
14660         if(which) { // black clock
14661           if (gameMode == EditPosition || gameMode == IcsExamining) {
14662             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14663             SetBlackToPlayEvent();
14664           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14665           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14666           } else if (shiftKey) {
14667             AdjustClock(which, -1);
14668           } else if (gameMode == IcsPlayingWhite ||
14669                      gameMode == MachinePlaysBlack) {
14670             CallFlagEvent();
14671           }
14672         } else { // white clock
14673           if (gameMode == EditPosition || gameMode == IcsExamining) {
14674             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14675             SetWhiteToPlayEvent();
14676           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14677           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14678           } else if (shiftKey) {
14679             AdjustClock(which, -1);
14680           } else if (gameMode == IcsPlayingBlack ||
14681                    gameMode == MachinePlaysWhite) {
14682             CallFlagEvent();
14683           }
14684         }
14685 }
14686
14687 void
14688 DrawEvent ()
14689 {
14690     /* Offer draw or accept pending draw offer from opponent */
14691
14692     if (appData.icsActive) {
14693         /* Note: tournament rules require draw offers to be
14694            made after you make your move but before you punch
14695            your clock.  Currently ICS doesn't let you do that;
14696            instead, you immediately punch your clock after making
14697            a move, but you can offer a draw at any time. */
14698
14699         SendToICS(ics_prefix);
14700         SendToICS("draw\n");
14701         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14702     } else if (cmailMsgLoaded) {
14703         if (currentMove == cmailOldMove &&
14704             commentList[cmailOldMove] != NULL &&
14705             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14706                    "Black offers a draw" : "White offers a draw")) {
14707             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14708             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14709         } else if (currentMove == cmailOldMove + 1) {
14710             char *offer = WhiteOnMove(cmailOldMove) ?
14711               "White offers a draw" : "Black offers a draw";
14712             AppendComment(currentMove, offer, TRUE);
14713             DisplayComment(currentMove - 1, offer);
14714             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14715         } else {
14716             DisplayError(_("You must make your move before offering a draw"), 0);
14717             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14718         }
14719     } else if (first.offeredDraw) {
14720         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14721     } else {
14722         if (first.sendDrawOffers) {
14723             SendToProgram("draw\n", &first);
14724             userOfferedDraw = TRUE;
14725         }
14726     }
14727 }
14728
14729 void
14730 AdjournEvent ()
14731 {
14732     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14733
14734     if (appData.icsActive) {
14735         SendToICS(ics_prefix);
14736         SendToICS("adjourn\n");
14737     } else {
14738         /* Currently GNU Chess doesn't offer or accept Adjourns */
14739     }
14740 }
14741
14742
14743 void
14744 AbortEvent ()
14745 {
14746     /* Offer Abort or accept pending Abort offer from opponent */
14747
14748     if (appData.icsActive) {
14749         SendToICS(ics_prefix);
14750         SendToICS("abort\n");
14751     } else {
14752         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14753     }
14754 }
14755
14756 void
14757 ResignEvent ()
14758 {
14759     /* Resign.  You can do this even if it's not your turn. */
14760
14761     if (appData.icsActive) {
14762         SendToICS(ics_prefix);
14763         SendToICS("resign\n");
14764     } else {
14765         switch (gameMode) {
14766           case MachinePlaysWhite:
14767             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14768             break;
14769           case MachinePlaysBlack:
14770             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14771             break;
14772           case EditGame:
14773             if (cmailMsgLoaded) {
14774                 TruncateGame();
14775                 if (WhiteOnMove(cmailOldMove)) {
14776                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14777                 } else {
14778                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14779                 }
14780                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14781             }
14782             break;
14783           default:
14784             break;
14785         }
14786     }
14787 }
14788
14789
14790 void
14791 StopObservingEvent ()
14792 {
14793     /* Stop observing current games */
14794     SendToICS(ics_prefix);
14795     SendToICS("unobserve\n");
14796 }
14797
14798 void
14799 StopExaminingEvent ()
14800 {
14801     /* Stop observing current game */
14802     SendToICS(ics_prefix);
14803     SendToICS("unexamine\n");
14804 }
14805
14806 void
14807 ForwardInner (int target)
14808 {
14809     int limit; int oldSeekGraphUp = seekGraphUp;
14810
14811     if (appData.debugMode)
14812         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14813                 target, currentMove, forwardMostMove);
14814
14815     if (gameMode == EditPosition)
14816       return;
14817
14818     seekGraphUp = FALSE;
14819     MarkTargetSquares(1);
14820
14821     if (gameMode == PlayFromGameFile && !pausing)
14822       PauseEvent();
14823
14824     if (gameMode == IcsExamining && pausing)
14825       limit = pauseExamForwardMostMove;
14826     else
14827       limit = forwardMostMove;
14828
14829     if (target > limit) target = limit;
14830
14831     if (target > 0 && moveList[target - 1][0]) {
14832         int fromX, fromY, toX, toY;
14833         toX = moveList[target - 1][2] - AAA;
14834         toY = moveList[target - 1][3] - ONE;
14835         if (moveList[target - 1][1] == '@') {
14836             if (appData.highlightLastMove) {
14837                 SetHighlights(-1, -1, toX, toY);
14838             }
14839         } else {
14840             fromX = moveList[target - 1][0] - AAA;
14841             fromY = moveList[target - 1][1] - ONE;
14842             if (target == currentMove + 1) {
14843                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14844             }
14845             if (appData.highlightLastMove) {
14846                 SetHighlights(fromX, fromY, toX, toY);
14847             }
14848         }
14849     }
14850     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14851         gameMode == Training || gameMode == PlayFromGameFile ||
14852         gameMode == AnalyzeFile) {
14853         while (currentMove < target) {
14854             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14855             SendMoveToProgram(currentMove++, &first);
14856         }
14857     } else {
14858         currentMove = target;
14859     }
14860
14861     if (gameMode == EditGame || gameMode == EndOfGame) {
14862         whiteTimeRemaining = timeRemaining[0][currentMove];
14863         blackTimeRemaining = timeRemaining[1][currentMove];
14864     }
14865     DisplayBothClocks();
14866     DisplayMove(currentMove - 1);
14867     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14868     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14869     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14870         DisplayComment(currentMove - 1, commentList[currentMove]);
14871     }
14872     ClearMap(); // [HGM] exclude: invalidate map
14873 }
14874
14875
14876 void
14877 ForwardEvent ()
14878 {
14879     if (gameMode == IcsExamining && !pausing) {
14880         SendToICS(ics_prefix);
14881         SendToICS("forward\n");
14882     } else {
14883         ForwardInner(currentMove + 1);
14884     }
14885 }
14886
14887 void
14888 ToEndEvent ()
14889 {
14890     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14891         /* to optimze, we temporarily turn off analysis mode while we feed
14892          * the remaining moves to the engine. Otherwise we get analysis output
14893          * after each move.
14894          */
14895         if (first.analysisSupport) {
14896           SendToProgram("exit\nforce\n", &first);
14897           first.analyzing = FALSE;
14898         }
14899     }
14900
14901     if (gameMode == IcsExamining && !pausing) {
14902         SendToICS(ics_prefix);
14903         SendToICS("forward 999999\n");
14904     } else {
14905         ForwardInner(forwardMostMove);
14906     }
14907
14908     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14909         /* we have fed all the moves, so reactivate analysis mode */
14910         SendToProgram("analyze\n", &first);
14911         first.analyzing = TRUE;
14912         /*first.maybeThinking = TRUE;*/
14913         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14914     }
14915 }
14916
14917 void
14918 BackwardInner (int target)
14919 {
14920     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14921
14922     if (appData.debugMode)
14923         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14924                 target, currentMove, forwardMostMove);
14925
14926     if (gameMode == EditPosition) return;
14927     seekGraphUp = FALSE;
14928     MarkTargetSquares(1);
14929     if (currentMove <= backwardMostMove) {
14930         ClearHighlights();
14931         DrawPosition(full_redraw, boards[currentMove]);
14932         return;
14933     }
14934     if (gameMode == PlayFromGameFile && !pausing)
14935       PauseEvent();
14936
14937     if (moveList[target][0]) {
14938         int fromX, fromY, toX, toY;
14939         toX = moveList[target][2] - AAA;
14940         toY = moveList[target][3] - ONE;
14941         if (moveList[target][1] == '@') {
14942             if (appData.highlightLastMove) {
14943                 SetHighlights(-1, -1, toX, toY);
14944             }
14945         } else {
14946             fromX = moveList[target][0] - AAA;
14947             fromY = moveList[target][1] - ONE;
14948             if (target == currentMove - 1) {
14949                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14950             }
14951             if (appData.highlightLastMove) {
14952                 SetHighlights(fromX, fromY, toX, toY);
14953             }
14954         }
14955     }
14956     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14957         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14958         while (currentMove > target) {
14959             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14960                 // null move cannot be undone. Reload program with move history before it.
14961                 int i;
14962                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14963                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14964                 }
14965                 SendBoard(&first, i);
14966               if(second.analyzing) SendBoard(&second, i);
14967                 for(currentMove=i; currentMove<target; currentMove++) {
14968                     SendMoveToProgram(currentMove, &first);
14969                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14970                 }
14971                 break;
14972             }
14973             SendToBoth("undo\n");
14974             currentMove--;
14975         }
14976     } else {
14977         currentMove = target;
14978     }
14979
14980     if (gameMode == EditGame || gameMode == EndOfGame) {
14981         whiteTimeRemaining = timeRemaining[0][currentMove];
14982         blackTimeRemaining = timeRemaining[1][currentMove];
14983     }
14984     DisplayBothClocks();
14985     DisplayMove(currentMove - 1);
14986     DrawPosition(full_redraw, boards[currentMove]);
14987     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14988     // [HGM] PV info: routine tests if comment empty
14989     DisplayComment(currentMove - 1, commentList[currentMove]);
14990     ClearMap(); // [HGM] exclude: invalidate map
14991 }
14992
14993 void
14994 BackwardEvent ()
14995 {
14996     if (gameMode == IcsExamining && !pausing) {
14997         SendToICS(ics_prefix);
14998         SendToICS("backward\n");
14999     } else {
15000         BackwardInner(currentMove - 1);
15001     }
15002 }
15003
15004 void
15005 ToStartEvent ()
15006 {
15007     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15008         /* to optimize, we temporarily turn off analysis mode while we undo
15009          * all the moves. Otherwise we get analysis output after each undo.
15010          */
15011         if (first.analysisSupport) {
15012           SendToProgram("exit\nforce\n", &first);
15013           first.analyzing = FALSE;
15014         }
15015     }
15016
15017     if (gameMode == IcsExamining && !pausing) {
15018         SendToICS(ics_prefix);
15019         SendToICS("backward 999999\n");
15020     } else {
15021         BackwardInner(backwardMostMove);
15022     }
15023
15024     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15025         /* we have fed all the moves, so reactivate analysis mode */
15026         SendToProgram("analyze\n", &first);
15027         first.analyzing = TRUE;
15028         /*first.maybeThinking = TRUE;*/
15029         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15030     }
15031 }
15032
15033 void
15034 ToNrEvent (int to)
15035 {
15036   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15037   if (to >= forwardMostMove) to = forwardMostMove;
15038   if (to <= backwardMostMove) to = backwardMostMove;
15039   if (to < currentMove) {
15040     BackwardInner(to);
15041   } else {
15042     ForwardInner(to);
15043   }
15044 }
15045
15046 void
15047 RevertEvent (Boolean annotate)
15048 {
15049     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15050         return;
15051     }
15052     if (gameMode != IcsExamining) {
15053         DisplayError(_("You are not examining a game"), 0);
15054         return;
15055     }
15056     if (pausing) {
15057         DisplayError(_("You can't revert while pausing"), 0);
15058         return;
15059     }
15060     SendToICS(ics_prefix);
15061     SendToICS("revert\n");
15062 }
15063
15064 void
15065 RetractMoveEvent ()
15066 {
15067     switch (gameMode) {
15068       case MachinePlaysWhite:
15069       case MachinePlaysBlack:
15070         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15071             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15072             return;
15073         }
15074         if (forwardMostMove < 2) return;
15075         currentMove = forwardMostMove = forwardMostMove - 2;
15076         whiteTimeRemaining = timeRemaining[0][currentMove];
15077         blackTimeRemaining = timeRemaining[1][currentMove];
15078         DisplayBothClocks();
15079         DisplayMove(currentMove - 1);
15080         ClearHighlights();/*!! could figure this out*/
15081         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15082         SendToProgram("remove\n", &first);
15083         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15084         break;
15085
15086       case BeginningOfGame:
15087       default:
15088         break;
15089
15090       case IcsPlayingWhite:
15091       case IcsPlayingBlack:
15092         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15093             SendToICS(ics_prefix);
15094             SendToICS("takeback 2\n");
15095         } else {
15096             SendToICS(ics_prefix);
15097             SendToICS("takeback 1\n");
15098         }
15099         break;
15100     }
15101 }
15102
15103 void
15104 MoveNowEvent ()
15105 {
15106     ChessProgramState *cps;
15107
15108     switch (gameMode) {
15109       case MachinePlaysWhite:
15110         if (!WhiteOnMove(forwardMostMove)) {
15111             DisplayError(_("It is your turn"), 0);
15112             return;
15113         }
15114         cps = &first;
15115         break;
15116       case MachinePlaysBlack:
15117         if (WhiteOnMove(forwardMostMove)) {
15118             DisplayError(_("It is your turn"), 0);
15119             return;
15120         }
15121         cps = &first;
15122         break;
15123       case TwoMachinesPlay:
15124         if (WhiteOnMove(forwardMostMove) ==
15125             (first.twoMachinesColor[0] == 'w')) {
15126             cps = &first;
15127         } else {
15128             cps = &second;
15129         }
15130         break;
15131       case BeginningOfGame:
15132       default:
15133         return;
15134     }
15135     SendToProgram("?\n", cps);
15136 }
15137
15138 void
15139 TruncateGameEvent ()
15140 {
15141     EditGameEvent();
15142     if (gameMode != EditGame) return;
15143     TruncateGame();
15144 }
15145
15146 void
15147 TruncateGame ()
15148 {
15149     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15150     if (forwardMostMove > currentMove) {
15151         if (gameInfo.resultDetails != NULL) {
15152             free(gameInfo.resultDetails);
15153             gameInfo.resultDetails = NULL;
15154             gameInfo.result = GameUnfinished;
15155         }
15156         forwardMostMove = currentMove;
15157         HistorySet(parseList, backwardMostMove, forwardMostMove,
15158                    currentMove-1);
15159     }
15160 }
15161
15162 void
15163 HintEvent ()
15164 {
15165     if (appData.noChessProgram) return;
15166     switch (gameMode) {
15167       case MachinePlaysWhite:
15168         if (WhiteOnMove(forwardMostMove)) {
15169             DisplayError(_("Wait until your turn"), 0);
15170             return;
15171         }
15172         break;
15173       case BeginningOfGame:
15174       case MachinePlaysBlack:
15175         if (!WhiteOnMove(forwardMostMove)) {
15176             DisplayError(_("Wait until your turn"), 0);
15177             return;
15178         }
15179         break;
15180       default:
15181         DisplayError(_("No hint available"), 0);
15182         return;
15183     }
15184     SendToProgram("hint\n", &first);
15185     hintRequested = TRUE;
15186 }
15187
15188 void
15189 CreateBookEvent ()
15190 {
15191     ListGame * lg = (ListGame *) gameList.head;
15192     FILE *f;
15193     int nItem;
15194     static int secondTime = FALSE;
15195
15196     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15197         DisplayError(_("Game list not loaded or empty"), 0);
15198         return;
15199     }
15200
15201     if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15202         fclose(f);
15203         secondTime++;
15204         DisplayNote(_("Book file exists! Try again for overwrite."));
15205         return;
15206     }
15207
15208     creatingBook = TRUE;
15209     secondTime = FALSE;
15210
15211     /* Get list size */
15212     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15213         LoadGame(f, nItem, "", TRUE);
15214         AddGameToBook(TRUE);
15215         lg = (ListGame *) lg->node.succ;
15216     }
15217
15218     creatingBook = FALSE;
15219     FlushBook();
15220 }
15221
15222 void
15223 BookEvent ()
15224 {
15225     if (appData.noChessProgram) return;
15226     switch (gameMode) {
15227       case MachinePlaysWhite:
15228         if (WhiteOnMove(forwardMostMove)) {
15229             DisplayError(_("Wait until your turn"), 0);
15230             return;
15231         }
15232         break;
15233       case BeginningOfGame:
15234       case MachinePlaysBlack:
15235         if (!WhiteOnMove(forwardMostMove)) {
15236             DisplayError(_("Wait until your turn"), 0);
15237             return;
15238         }
15239         break;
15240       case EditPosition:
15241         EditPositionDone(TRUE);
15242         break;
15243       case TwoMachinesPlay:
15244         return;
15245       default:
15246         break;
15247     }
15248     SendToProgram("bk\n", &first);
15249     bookOutput[0] = NULLCHAR;
15250     bookRequested = TRUE;
15251 }
15252
15253 void
15254 AboutGameEvent ()
15255 {
15256     char *tags = PGNTags(&gameInfo);
15257     TagsPopUp(tags, CmailMsg());
15258     free(tags);
15259 }
15260
15261 /* end button procedures */
15262
15263 void
15264 PrintPosition (FILE *fp, int move)
15265 {
15266     int i, j;
15267
15268     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15269         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15270             char c = PieceToChar(boards[move][i][j]);
15271             fputc(c == 'x' ? '.' : c, fp);
15272             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15273         }
15274     }
15275     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15276       fprintf(fp, "white to play\n");
15277     else
15278       fprintf(fp, "black to play\n");
15279 }
15280
15281 void
15282 PrintOpponents (FILE *fp)
15283 {
15284     if (gameInfo.white != NULL) {
15285         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15286     } else {
15287         fprintf(fp, "\n");
15288     }
15289 }
15290
15291 /* Find last component of program's own name, using some heuristics */
15292 void
15293 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15294 {
15295     char *p, *q, c;
15296     int local = (strcmp(host, "localhost") == 0);
15297     while (!local && (p = strchr(prog, ';')) != NULL) {
15298         p++;
15299         while (*p == ' ') p++;
15300         prog = p;
15301     }
15302     if (*prog == '"' || *prog == '\'') {
15303         q = strchr(prog + 1, *prog);
15304     } else {
15305         q = strchr(prog, ' ');
15306     }
15307     if (q == NULL) q = prog + strlen(prog);
15308     p = q;
15309     while (p >= prog && *p != '/' && *p != '\\') p--;
15310     p++;
15311     if(p == prog && *p == '"') p++;
15312     c = *q; *q = 0;
15313     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15314     memcpy(buf, p, q - p);
15315     buf[q - p] = NULLCHAR;
15316     if (!local) {
15317         strcat(buf, "@");
15318         strcat(buf, host);
15319     }
15320 }
15321
15322 char *
15323 TimeControlTagValue ()
15324 {
15325     char buf[MSG_SIZ];
15326     if (!appData.clockMode) {
15327       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15328     } else if (movesPerSession > 0) {
15329       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15330     } else if (timeIncrement == 0) {
15331       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15332     } else {
15333       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15334     }
15335     return StrSave(buf);
15336 }
15337
15338 void
15339 SetGameInfo ()
15340 {
15341     /* This routine is used only for certain modes */
15342     VariantClass v = gameInfo.variant;
15343     ChessMove r = GameUnfinished;
15344     char *p = NULL;
15345
15346     if(keepInfo) return;
15347
15348     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15349         r = gameInfo.result;
15350         p = gameInfo.resultDetails;
15351         gameInfo.resultDetails = NULL;
15352     }
15353     ClearGameInfo(&gameInfo);
15354     gameInfo.variant = v;
15355
15356     switch (gameMode) {
15357       case MachinePlaysWhite:
15358         gameInfo.event = StrSave( appData.pgnEventHeader );
15359         gameInfo.site = StrSave(HostName());
15360         gameInfo.date = PGNDate();
15361         gameInfo.round = StrSave("-");
15362         gameInfo.white = StrSave(first.tidy);
15363         gameInfo.black = StrSave(UserName());
15364         gameInfo.timeControl = TimeControlTagValue();
15365         break;
15366
15367       case MachinePlaysBlack:
15368         gameInfo.event = StrSave( appData.pgnEventHeader );
15369         gameInfo.site = StrSave(HostName());
15370         gameInfo.date = PGNDate();
15371         gameInfo.round = StrSave("-");
15372         gameInfo.white = StrSave(UserName());
15373         gameInfo.black = StrSave(first.tidy);
15374         gameInfo.timeControl = TimeControlTagValue();
15375         break;
15376
15377       case TwoMachinesPlay:
15378         gameInfo.event = StrSave( appData.pgnEventHeader );
15379         gameInfo.site = StrSave(HostName());
15380         gameInfo.date = PGNDate();
15381         if (roundNr > 0) {
15382             char buf[MSG_SIZ];
15383             snprintf(buf, MSG_SIZ, "%d", roundNr);
15384             gameInfo.round = StrSave(buf);
15385         } else {
15386             gameInfo.round = StrSave("-");
15387         }
15388         if (first.twoMachinesColor[0] == 'w') {
15389             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15390             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15391         } else {
15392             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15393             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15394         }
15395         gameInfo.timeControl = TimeControlTagValue();
15396         break;
15397
15398       case EditGame:
15399         gameInfo.event = StrSave("Edited game");
15400         gameInfo.site = StrSave(HostName());
15401         gameInfo.date = PGNDate();
15402         gameInfo.round = StrSave("-");
15403         gameInfo.white = StrSave("-");
15404         gameInfo.black = StrSave("-");
15405         gameInfo.result = r;
15406         gameInfo.resultDetails = p;
15407         break;
15408
15409       case EditPosition:
15410         gameInfo.event = StrSave("Edited position");
15411         gameInfo.site = StrSave(HostName());
15412         gameInfo.date = PGNDate();
15413         gameInfo.round = StrSave("-");
15414         gameInfo.white = StrSave("-");
15415         gameInfo.black = StrSave("-");
15416         break;
15417
15418       case IcsPlayingWhite:
15419       case IcsPlayingBlack:
15420       case IcsObserving:
15421       case IcsExamining:
15422         break;
15423
15424       case PlayFromGameFile:
15425         gameInfo.event = StrSave("Game from non-PGN file");
15426         gameInfo.site = StrSave(HostName());
15427         gameInfo.date = PGNDate();
15428         gameInfo.round = StrSave("-");
15429         gameInfo.white = StrSave("?");
15430         gameInfo.black = StrSave("?");
15431         break;
15432
15433       default:
15434         break;
15435     }
15436 }
15437
15438 void
15439 ReplaceComment (int index, char *text)
15440 {
15441     int len;
15442     char *p;
15443     float score;
15444
15445     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15446        pvInfoList[index-1].depth == len &&
15447        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15448        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15449     while (*text == '\n') text++;
15450     len = strlen(text);
15451     while (len > 0 && text[len - 1] == '\n') len--;
15452
15453     if (commentList[index] != NULL)
15454       free(commentList[index]);
15455
15456     if (len == 0) {
15457         commentList[index] = NULL;
15458         return;
15459     }
15460   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15461       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15462       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15463     commentList[index] = (char *) malloc(len + 2);
15464     strncpy(commentList[index], text, len);
15465     commentList[index][len] = '\n';
15466     commentList[index][len + 1] = NULLCHAR;
15467   } else {
15468     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15469     char *p;
15470     commentList[index] = (char *) malloc(len + 7);
15471     safeStrCpy(commentList[index], "{\n", 3);
15472     safeStrCpy(commentList[index]+2, text, len+1);
15473     commentList[index][len+2] = NULLCHAR;
15474     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15475     strcat(commentList[index], "\n}\n");
15476   }
15477 }
15478
15479 void
15480 CrushCRs (char *text)
15481 {
15482   char *p = text;
15483   char *q = text;
15484   char ch;
15485
15486   do {
15487     ch = *p++;
15488     if (ch == '\r') continue;
15489     *q++ = ch;
15490   } while (ch != '\0');
15491 }
15492
15493 void
15494 AppendComment (int index, char *text, Boolean addBraces)
15495 /* addBraces  tells if we should add {} */
15496 {
15497     int oldlen, len;
15498     char *old;
15499
15500 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15501     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15502
15503     CrushCRs(text);
15504     while (*text == '\n') text++;
15505     len = strlen(text);
15506     while (len > 0 && text[len - 1] == '\n') len--;
15507     text[len] = NULLCHAR;
15508
15509     if (len == 0) return;
15510
15511     if (commentList[index] != NULL) {
15512       Boolean addClosingBrace = addBraces;
15513         old = commentList[index];
15514         oldlen = strlen(old);
15515         while(commentList[index][oldlen-1] ==  '\n')
15516           commentList[index][--oldlen] = NULLCHAR;
15517         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15518         safeStrCpy(commentList[index], old, oldlen + len + 6);
15519         free(old);
15520         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15521         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15522           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15523           while (*text == '\n') { text++; len--; }
15524           commentList[index][--oldlen] = NULLCHAR;
15525       }
15526         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15527         else          strcat(commentList[index], "\n");
15528         strcat(commentList[index], text);
15529         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15530         else          strcat(commentList[index], "\n");
15531     } else {
15532         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15533         if(addBraces)
15534           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15535         else commentList[index][0] = NULLCHAR;
15536         strcat(commentList[index], text);
15537         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15538         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15539     }
15540 }
15541
15542 static char *
15543 FindStr (char * text, char * sub_text)
15544 {
15545     char * result = strstr( text, sub_text );
15546
15547     if( result != NULL ) {
15548         result += strlen( sub_text );
15549     }
15550
15551     return result;
15552 }
15553
15554 /* [AS] Try to extract PV info from PGN comment */
15555 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15556 char *
15557 GetInfoFromComment (int index, char * text)
15558 {
15559     char * sep = text, *p;
15560
15561     if( text != NULL && index > 0 ) {
15562         int score = 0;
15563         int depth = 0;
15564         int time = -1, sec = 0, deci;
15565         char * s_eval = FindStr( text, "[%eval " );
15566         char * s_emt = FindStr( text, "[%emt " );
15567
15568         if( s_eval != NULL || s_emt != NULL ) {
15569             /* New style */
15570             char delim;
15571
15572             if( s_eval != NULL ) {
15573                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15574                     return text;
15575                 }
15576
15577                 if( delim != ']' ) {
15578                     return text;
15579                 }
15580             }
15581
15582             if( s_emt != NULL ) {
15583             }
15584                 return text;
15585         }
15586         else {
15587             /* We expect something like: [+|-]nnn.nn/dd */
15588             int score_lo = 0;
15589
15590             if(*text != '{') return text; // [HGM] braces: must be normal comment
15591
15592             sep = strchr( text, '/' );
15593             if( sep == NULL || sep < (text+4) ) {
15594                 return text;
15595             }
15596
15597             p = text;
15598             if(p[1] == '(') { // comment starts with PV
15599                p = strchr(p, ')'); // locate end of PV
15600                if(p == NULL || sep < p+5) return text;
15601                // at this point we have something like "{(.*) +0.23/6 ..."
15602                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15603                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15604                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15605             }
15606             time = -1; sec = -1; deci = -1;
15607             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15608                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15609                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15610                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15611                 return text;
15612             }
15613
15614             if( score_lo < 0 || score_lo >= 100 ) {
15615                 return text;
15616             }
15617
15618             if(sec >= 0) time = 600*time + 10*sec; else
15619             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15620
15621             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15622
15623             /* [HGM] PV time: now locate end of PV info */
15624             while( *++sep >= '0' && *sep <= '9'); // strip depth
15625             if(time >= 0)
15626             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15627             if(sec >= 0)
15628             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15629             if(deci >= 0)
15630             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15631             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15632         }
15633
15634         if( depth <= 0 ) {
15635             return text;
15636         }
15637
15638         if( time < 0 ) {
15639             time = -1;
15640         }
15641
15642         pvInfoList[index-1].depth = depth;
15643         pvInfoList[index-1].score = score;
15644         pvInfoList[index-1].time  = 10*time; // centi-sec
15645         if(*sep == '}') *sep = 0; else *--sep = '{';
15646         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15647     }
15648     return sep;
15649 }
15650
15651 void
15652 SendToProgram (char *message, ChessProgramState *cps)
15653 {
15654     int count, outCount, error;
15655     char buf[MSG_SIZ];
15656
15657     if (cps->pr == NoProc) return;
15658     Attention(cps);
15659
15660     if (appData.debugMode) {
15661         TimeMark now;
15662         GetTimeMark(&now);
15663         fprintf(debugFP, "%ld >%-6s: %s",
15664                 SubtractTimeMarks(&now, &programStartTime),
15665                 cps->which, message);
15666         if(serverFP)
15667             fprintf(serverFP, "%ld >%-6s: %s",
15668                 SubtractTimeMarks(&now, &programStartTime),
15669                 cps->which, message), fflush(serverFP);
15670     }
15671
15672     count = strlen(message);
15673     outCount = OutputToProcess(cps->pr, message, count, &error);
15674     if (outCount < count && !exiting
15675                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15676       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15677       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15678         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15679             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15680                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15681                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15682                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15683             } else {
15684                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15685                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15686                 gameInfo.result = res;
15687             }
15688             gameInfo.resultDetails = StrSave(buf);
15689         }
15690         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15691         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15692     }
15693 }
15694
15695 void
15696 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15697 {
15698     char *end_str;
15699     char buf[MSG_SIZ];
15700     ChessProgramState *cps = (ChessProgramState *)closure;
15701
15702     if (isr != cps->isr) return; /* Killed intentionally */
15703     if (count <= 0) {
15704         if (count == 0) {
15705             RemoveInputSource(cps->isr);
15706             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15707                     _(cps->which), cps->program);
15708             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15709             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15710                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15711                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15712                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15713                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15714                 } else {
15715                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15716                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15717                     gameInfo.result = res;
15718                 }
15719                 gameInfo.resultDetails = StrSave(buf);
15720             }
15721             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15722             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15723         } else {
15724             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15725                     _(cps->which), cps->program);
15726             RemoveInputSource(cps->isr);
15727
15728             /* [AS] Program is misbehaving badly... kill it */
15729             if( count == -2 ) {
15730                 DestroyChildProcess( cps->pr, 9 );
15731                 cps->pr = NoProc;
15732             }
15733
15734             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15735         }
15736         return;
15737     }
15738
15739     if ((end_str = strchr(message, '\r')) != NULL)
15740       *end_str = NULLCHAR;
15741     if ((end_str = strchr(message, '\n')) != NULL)
15742       *end_str = NULLCHAR;
15743
15744     if (appData.debugMode) {
15745         TimeMark now; int print = 1;
15746         char *quote = ""; char c; int i;
15747
15748         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15749                 char start = message[0];
15750                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15751                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15752                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15753                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15754                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15755                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15756                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15757                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15758                    sscanf(message, "hint: %c", &c)!=1 &&
15759                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15760                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15761                     print = (appData.engineComments >= 2);
15762                 }
15763                 message[0] = start; // restore original message
15764         }
15765         if(print) {
15766                 GetTimeMark(&now);
15767                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15768                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15769                         quote,
15770                         message);
15771                 if(serverFP)
15772                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15773                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15774                         quote,
15775                         message), fflush(serverFP);
15776         }
15777     }
15778
15779     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15780     if (appData.icsEngineAnalyze) {
15781         if (strstr(message, "whisper") != NULL ||
15782              strstr(message, "kibitz") != NULL ||
15783             strstr(message, "tellics") != NULL) return;
15784     }
15785
15786     HandleMachineMove(message, cps);
15787 }
15788
15789
15790 void
15791 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15792 {
15793     char buf[MSG_SIZ];
15794     int seconds;
15795
15796     if( timeControl_2 > 0 ) {
15797         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15798             tc = timeControl_2;
15799         }
15800     }
15801     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15802     inc /= cps->timeOdds;
15803     st  /= cps->timeOdds;
15804
15805     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15806
15807     if (st > 0) {
15808       /* Set exact time per move, normally using st command */
15809       if (cps->stKludge) {
15810         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15811         seconds = st % 60;
15812         if (seconds == 0) {
15813           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15814         } else {
15815           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15816         }
15817       } else {
15818         snprintf(buf, MSG_SIZ, "st %d\n", st);
15819       }
15820     } else {
15821       /* Set conventional or incremental time control, using level command */
15822       if (seconds == 0) {
15823         /* Note old gnuchess bug -- minutes:seconds used to not work.
15824            Fixed in later versions, but still avoid :seconds
15825            when seconds is 0. */
15826         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15827       } else {
15828         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15829                  seconds, inc/1000.);
15830       }
15831     }
15832     SendToProgram(buf, cps);
15833
15834     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15835     /* Orthogonally, limit search to given depth */
15836     if (sd > 0) {
15837       if (cps->sdKludge) {
15838         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15839       } else {
15840         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15841       }
15842       SendToProgram(buf, cps);
15843     }
15844
15845     if(cps->nps >= 0) { /* [HGM] nps */
15846         if(cps->supportsNPS == FALSE)
15847           cps->nps = -1; // don't use if engine explicitly says not supported!
15848         else {
15849           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15850           SendToProgram(buf, cps);
15851         }
15852     }
15853 }
15854
15855 ChessProgramState *
15856 WhitePlayer ()
15857 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15858 {
15859     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15860        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15861         return &second;
15862     return &first;
15863 }
15864
15865 void
15866 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15867 {
15868     char message[MSG_SIZ];
15869     long time, otime;
15870
15871     /* Note: this routine must be called when the clocks are stopped
15872        or when they have *just* been set or switched; otherwise
15873        it will be off by the time since the current tick started.
15874     */
15875     if (machineWhite) {
15876         time = whiteTimeRemaining / 10;
15877         otime = blackTimeRemaining / 10;
15878     } else {
15879         time = blackTimeRemaining / 10;
15880         otime = whiteTimeRemaining / 10;
15881     }
15882     /* [HGM] translate opponent's time by time-odds factor */
15883     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15884
15885     if (time <= 0) time = 1;
15886     if (otime <= 0) otime = 1;
15887
15888     snprintf(message, MSG_SIZ, "time %ld\n", time);
15889     SendToProgram(message, cps);
15890
15891     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15892     SendToProgram(message, cps);
15893 }
15894
15895 int
15896 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15897 {
15898   char buf[MSG_SIZ];
15899   int len = strlen(name);
15900   int val;
15901
15902   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15903     (*p) += len + 1;
15904     sscanf(*p, "%d", &val);
15905     *loc = (val != 0);
15906     while (**p && **p != ' ')
15907       (*p)++;
15908     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15909     SendToProgram(buf, cps);
15910     return TRUE;
15911   }
15912   return FALSE;
15913 }
15914
15915 int
15916 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15917 {
15918   char buf[MSG_SIZ];
15919   int len = strlen(name);
15920   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15921     (*p) += len + 1;
15922     sscanf(*p, "%d", loc);
15923     while (**p && **p != ' ') (*p)++;
15924     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15925     SendToProgram(buf, cps);
15926     return TRUE;
15927   }
15928   return FALSE;
15929 }
15930
15931 int
15932 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15933 {
15934   char buf[MSG_SIZ];
15935   int len = strlen(name);
15936   if (strncmp((*p), name, len) == 0
15937       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15938     (*p) += len + 2;
15939     sscanf(*p, "%[^\"]", loc);
15940     while (**p && **p != '\"') (*p)++;
15941     if (**p == '\"') (*p)++;
15942     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15943     SendToProgram(buf, cps);
15944     return TRUE;
15945   }
15946   return FALSE;
15947 }
15948
15949 int
15950 ParseOption (Option *opt, ChessProgramState *cps)
15951 // [HGM] options: process the string that defines an engine option, and determine
15952 // name, type, default value, and allowed value range
15953 {
15954         char *p, *q, buf[MSG_SIZ];
15955         int n, min = (-1)<<31, max = 1<<31, def;
15956
15957         if(p = strstr(opt->name, " -spin ")) {
15958             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15959             if(max < min) max = min; // enforce consistency
15960             if(def < min) def = min;
15961             if(def > max) def = max;
15962             opt->value = def;
15963             opt->min = min;
15964             opt->max = max;
15965             opt->type = Spin;
15966         } else if((p = strstr(opt->name, " -slider "))) {
15967             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15968             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15969             if(max < min) max = min; // enforce consistency
15970             if(def < min) def = min;
15971             if(def > max) def = max;
15972             opt->value = def;
15973             opt->min = min;
15974             opt->max = max;
15975             opt->type = Spin; // Slider;
15976         } else if((p = strstr(opt->name, " -string "))) {
15977             opt->textValue = p+9;
15978             opt->type = TextBox;
15979         } else if((p = strstr(opt->name, " -file "))) {
15980             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15981             opt->textValue = p+7;
15982             opt->type = FileName; // FileName;
15983         } else if((p = strstr(opt->name, " -path "))) {
15984             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15985             opt->textValue = p+7;
15986             opt->type = PathName; // PathName;
15987         } else if(p = strstr(opt->name, " -check ")) {
15988             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15989             opt->value = (def != 0);
15990             opt->type = CheckBox;
15991         } else if(p = strstr(opt->name, " -combo ")) {
15992             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15993             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15994             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15995             opt->value = n = 0;
15996             while(q = StrStr(q, " /// ")) {
15997                 n++; *q = 0;    // count choices, and null-terminate each of them
15998                 q += 5;
15999                 if(*q == '*') { // remember default, which is marked with * prefix
16000                     q++;
16001                     opt->value = n;
16002                 }
16003                 cps->comboList[cps->comboCnt++] = q;
16004             }
16005             cps->comboList[cps->comboCnt++] = NULL;
16006             opt->max = n + 1;
16007             opt->type = ComboBox;
16008         } else if(p = strstr(opt->name, " -button")) {
16009             opt->type = Button;
16010         } else if(p = strstr(opt->name, " -save")) {
16011             opt->type = SaveButton;
16012         } else return FALSE;
16013         *p = 0; // terminate option name
16014         // now look if the command-line options define a setting for this engine option.
16015         if(cps->optionSettings && cps->optionSettings[0])
16016             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16017         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16018           snprintf(buf, MSG_SIZ, "option %s", p);
16019                 if(p = strstr(buf, ",")) *p = 0;
16020                 if(q = strchr(buf, '=')) switch(opt->type) {
16021                     case ComboBox:
16022                         for(n=0; n<opt->max; n++)
16023                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16024                         break;
16025                     case TextBox:
16026                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16027                         break;
16028                     case Spin:
16029                     case CheckBox:
16030                         opt->value = atoi(q+1);
16031                     default:
16032                         break;
16033                 }
16034                 strcat(buf, "\n");
16035                 SendToProgram(buf, cps);
16036         }
16037         return TRUE;
16038 }
16039
16040 void
16041 FeatureDone (ChessProgramState *cps, int val)
16042 {
16043   DelayedEventCallback cb = GetDelayedEvent();
16044   if ((cb == InitBackEnd3 && cps == &first) ||
16045       (cb == SettingsMenuIfReady && cps == &second) ||
16046       (cb == LoadEngine) ||
16047       (cb == TwoMachinesEventIfReady)) {
16048     CancelDelayedEvent();
16049     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16050   }
16051   cps->initDone = val;
16052   if(val) cps->reload = FALSE;
16053 }
16054
16055 /* Parse feature command from engine */
16056 void
16057 ParseFeatures (char *args, ChessProgramState *cps)
16058 {
16059   char *p = args;
16060   char *q;
16061   int val;
16062   char buf[MSG_SIZ];
16063
16064   for (;;) {
16065     while (*p == ' ') p++;
16066     if (*p == NULLCHAR) return;
16067
16068     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16069     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16070     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16071     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16072     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16073     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16074     if (BoolFeature(&p, "reuse", &val, cps)) {
16075       /* Engine can disable reuse, but can't enable it if user said no */
16076       if (!val) cps->reuse = FALSE;
16077       continue;
16078     }
16079     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16080     if (StringFeature(&p, "myname", cps->tidy, cps)) {
16081       if (gameMode == TwoMachinesPlay) {
16082         DisplayTwoMachinesTitle();
16083       } else {
16084         DisplayTitle("");
16085       }
16086       continue;
16087     }
16088     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16089     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16090     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16091     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16092     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16093     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16094     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16095     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16096     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16097     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16098     if (IntFeature(&p, "done", &val, cps)) {
16099       FeatureDone(cps, val);
16100       continue;
16101     }
16102     /* Added by Tord: */
16103     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16104     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16105     /* End of additions by Tord */
16106
16107     /* [HGM] added features: */
16108     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16109     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16110     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16111     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16112     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16113     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16114     if (StringFeature(&p, "option", buf, cps)) {
16115         if(cps->reload) continue; // we are reloading because of xreuse
16116         FREE(cps->option[cps->nrOptions].name);
16117         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16118         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16119         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16120           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16121             SendToProgram(buf, cps);
16122             continue;
16123         }
16124         if(cps->nrOptions >= MAX_OPTIONS) {
16125             cps->nrOptions--;
16126             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16127             DisplayError(buf, 0);
16128         }
16129         continue;
16130     }
16131     /* End of additions by HGM */
16132
16133     /* unknown feature: complain and skip */
16134     q = p;
16135     while (*q && *q != '=') q++;
16136     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16137     SendToProgram(buf, cps);
16138     p = q;
16139     if (*p == '=') {
16140       p++;
16141       if (*p == '\"') {
16142         p++;
16143         while (*p && *p != '\"') p++;
16144         if (*p == '\"') p++;
16145       } else {
16146         while (*p && *p != ' ') p++;
16147       }
16148     }
16149   }
16150
16151 }
16152
16153 void
16154 PeriodicUpdatesEvent (int newState)
16155 {
16156     if (newState == appData.periodicUpdates)
16157       return;
16158
16159     appData.periodicUpdates=newState;
16160
16161     /* Display type changes, so update it now */
16162 //    DisplayAnalysis();
16163
16164     /* Get the ball rolling again... */
16165     if (newState) {
16166         AnalysisPeriodicEvent(1);
16167         StartAnalysisClock();
16168     }
16169 }
16170
16171 void
16172 PonderNextMoveEvent (int newState)
16173 {
16174     if (newState == appData.ponderNextMove) return;
16175     if (gameMode == EditPosition) EditPositionDone(TRUE);
16176     if (newState) {
16177         SendToProgram("hard\n", &first);
16178         if (gameMode == TwoMachinesPlay) {
16179             SendToProgram("hard\n", &second);
16180         }
16181     } else {
16182         SendToProgram("easy\n", &first);
16183         thinkOutput[0] = NULLCHAR;
16184         if (gameMode == TwoMachinesPlay) {
16185             SendToProgram("easy\n", &second);
16186         }
16187     }
16188     appData.ponderNextMove = newState;
16189 }
16190
16191 void
16192 NewSettingEvent (int option, int *feature, char *command, int value)
16193 {
16194     char buf[MSG_SIZ];
16195
16196     if (gameMode == EditPosition) EditPositionDone(TRUE);
16197     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16198     if(feature == NULL || *feature) SendToProgram(buf, &first);
16199     if (gameMode == TwoMachinesPlay) {
16200         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16201     }
16202 }
16203
16204 void
16205 ShowThinkingEvent ()
16206 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16207 {
16208     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16209     int newState = appData.showThinking
16210         // [HGM] thinking: other features now need thinking output as well
16211         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16212
16213     if (oldState == newState) return;
16214     oldState = newState;
16215     if (gameMode == EditPosition) EditPositionDone(TRUE);
16216     if (oldState) {
16217         SendToProgram("post\n", &first);
16218         if (gameMode == TwoMachinesPlay) {
16219             SendToProgram("post\n", &second);
16220         }
16221     } else {
16222         SendToProgram("nopost\n", &first);
16223         thinkOutput[0] = NULLCHAR;
16224         if (gameMode == TwoMachinesPlay) {
16225             SendToProgram("nopost\n", &second);
16226         }
16227     }
16228 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16229 }
16230
16231 void
16232 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16233 {
16234   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16235   if (pr == NoProc) return;
16236   AskQuestion(title, question, replyPrefix, pr);
16237 }
16238
16239 void
16240 TypeInEvent (char firstChar)
16241 {
16242     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16243         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16244         gameMode == AnalyzeMode || gameMode == EditGame ||
16245         gameMode == EditPosition || gameMode == IcsExamining ||
16246         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16247         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16248                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16249                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16250         gameMode == Training) PopUpMoveDialog(firstChar);
16251 }
16252
16253 void
16254 TypeInDoneEvent (char *move)
16255 {
16256         Board board;
16257         int n, fromX, fromY, toX, toY;
16258         char promoChar;
16259         ChessMove moveType;
16260
16261         // [HGM] FENedit
16262         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16263                 EditPositionPasteFEN(move);
16264                 return;
16265         }
16266         // [HGM] movenum: allow move number to be typed in any mode
16267         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16268           ToNrEvent(2*n-1);
16269           return;
16270         }
16271         // undocumented kludge: allow command-line option to be typed in!
16272         // (potentially fatal, and does not implement the effect of the option.)
16273         // should only be used for options that are values on which future decisions will be made,
16274         // and definitely not on options that would be used during initialization.
16275         if(strstr(move, "!!! -") == move) {
16276             ParseArgsFromString(move+4);
16277             return;
16278         }
16279
16280       if (gameMode != EditGame && currentMove != forwardMostMove &&
16281         gameMode != Training) {
16282         DisplayMoveError(_("Displayed move is not current"));
16283       } else {
16284         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16285           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16286         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16287         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16288           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16289           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16290         } else {
16291           DisplayMoveError(_("Could not parse move"));
16292         }
16293       }
16294 }
16295
16296 void
16297 DisplayMove (int moveNumber)
16298 {
16299     char message[MSG_SIZ];
16300     char res[MSG_SIZ];
16301     char cpThinkOutput[MSG_SIZ];
16302
16303     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16304
16305     if (moveNumber == forwardMostMove - 1 ||
16306         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16307
16308         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16309
16310         if (strchr(cpThinkOutput, '\n')) {
16311             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16312         }
16313     } else {
16314         *cpThinkOutput = NULLCHAR;
16315     }
16316
16317     /* [AS] Hide thinking from human user */
16318     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16319         *cpThinkOutput = NULLCHAR;
16320         if( thinkOutput[0] != NULLCHAR ) {
16321             int i;
16322
16323             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16324                 cpThinkOutput[i] = '.';
16325             }
16326             cpThinkOutput[i] = NULLCHAR;
16327             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16328         }
16329     }
16330
16331     if (moveNumber == forwardMostMove - 1 &&
16332         gameInfo.resultDetails != NULL) {
16333         if (gameInfo.resultDetails[0] == NULLCHAR) {
16334           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16335         } else {
16336           snprintf(res, MSG_SIZ, " {%s} %s",
16337                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16338         }
16339     } else {
16340         res[0] = NULLCHAR;
16341     }
16342
16343     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16344         DisplayMessage(res, cpThinkOutput);
16345     } else {
16346       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16347                 WhiteOnMove(moveNumber) ? " " : ".. ",
16348                 parseList[moveNumber], res);
16349         DisplayMessage(message, cpThinkOutput);
16350     }
16351 }
16352
16353 void
16354 DisplayComment (int moveNumber, char *text)
16355 {
16356     char title[MSG_SIZ];
16357
16358     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16359       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16360     } else {
16361       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16362               WhiteOnMove(moveNumber) ? " " : ".. ",
16363               parseList[moveNumber]);
16364     }
16365     if (text != NULL && (appData.autoDisplayComment || commentUp))
16366         CommentPopUp(title, text);
16367 }
16368
16369 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16370  * might be busy thinking or pondering.  It can be omitted if your
16371  * gnuchess is configured to stop thinking immediately on any user
16372  * input.  However, that gnuchess feature depends on the FIONREAD
16373  * ioctl, which does not work properly on some flavors of Unix.
16374  */
16375 void
16376 Attention (ChessProgramState *cps)
16377 {
16378 #if ATTENTION
16379     if (!cps->useSigint) return;
16380     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16381     switch (gameMode) {
16382       case MachinePlaysWhite:
16383       case MachinePlaysBlack:
16384       case TwoMachinesPlay:
16385       case IcsPlayingWhite:
16386       case IcsPlayingBlack:
16387       case AnalyzeMode:
16388       case AnalyzeFile:
16389         /* Skip if we know it isn't thinking */
16390         if (!cps->maybeThinking) return;
16391         if (appData.debugMode)
16392           fprintf(debugFP, "Interrupting %s\n", cps->which);
16393         InterruptChildProcess(cps->pr);
16394         cps->maybeThinking = FALSE;
16395         break;
16396       default:
16397         break;
16398     }
16399 #endif /*ATTENTION*/
16400 }
16401
16402 int
16403 CheckFlags ()
16404 {
16405     if (whiteTimeRemaining <= 0) {
16406         if (!whiteFlag) {
16407             whiteFlag = TRUE;
16408             if (appData.icsActive) {
16409                 if (appData.autoCallFlag &&
16410                     gameMode == IcsPlayingBlack && !blackFlag) {
16411                   SendToICS(ics_prefix);
16412                   SendToICS("flag\n");
16413                 }
16414             } else {
16415                 if (blackFlag) {
16416                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16417                 } else {
16418                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16419                     if (appData.autoCallFlag) {
16420                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16421                         return TRUE;
16422                     }
16423                 }
16424             }
16425         }
16426     }
16427     if (blackTimeRemaining <= 0) {
16428         if (!blackFlag) {
16429             blackFlag = TRUE;
16430             if (appData.icsActive) {
16431                 if (appData.autoCallFlag &&
16432                     gameMode == IcsPlayingWhite && !whiteFlag) {
16433                   SendToICS(ics_prefix);
16434                   SendToICS("flag\n");
16435                 }
16436             } else {
16437                 if (whiteFlag) {
16438                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16439                 } else {
16440                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16441                     if (appData.autoCallFlag) {
16442                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16443                         return TRUE;
16444                     }
16445                 }
16446             }
16447         }
16448     }
16449     return FALSE;
16450 }
16451
16452 void
16453 CheckTimeControl ()
16454 {
16455     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16456         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16457
16458     /*
16459      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16460      */
16461     if ( !WhiteOnMove(forwardMostMove) ) {
16462         /* White made time control */
16463         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16464         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16465         /* [HGM] time odds: correct new time quota for time odds! */
16466                                             / WhitePlayer()->timeOdds;
16467         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16468     } else {
16469         lastBlack -= blackTimeRemaining;
16470         /* Black made time control */
16471         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16472                                             / WhitePlayer()->other->timeOdds;
16473         lastWhite = whiteTimeRemaining;
16474     }
16475 }
16476
16477 void
16478 DisplayBothClocks ()
16479 {
16480     int wom = gameMode == EditPosition ?
16481       !blackPlaysFirst : WhiteOnMove(currentMove);
16482     DisplayWhiteClock(whiteTimeRemaining, wom);
16483     DisplayBlackClock(blackTimeRemaining, !wom);
16484 }
16485
16486
16487 /* Timekeeping seems to be a portability nightmare.  I think everyone
16488    has ftime(), but I'm really not sure, so I'm including some ifdefs
16489    to use other calls if you don't.  Clocks will be less accurate if
16490    you have neither ftime nor gettimeofday.
16491 */
16492
16493 /* VS 2008 requires the #include outside of the function */
16494 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16495 #include <sys/timeb.h>
16496 #endif
16497
16498 /* Get the current time as a TimeMark */
16499 void
16500 GetTimeMark (TimeMark *tm)
16501 {
16502 #if HAVE_GETTIMEOFDAY
16503
16504     struct timeval timeVal;
16505     struct timezone timeZone;
16506
16507     gettimeofday(&timeVal, &timeZone);
16508     tm->sec = (long) timeVal.tv_sec;
16509     tm->ms = (int) (timeVal.tv_usec / 1000L);
16510
16511 #else /*!HAVE_GETTIMEOFDAY*/
16512 #if HAVE_FTIME
16513
16514 // include <sys/timeb.h> / moved to just above start of function
16515     struct timeb timeB;
16516
16517     ftime(&timeB);
16518     tm->sec = (long) timeB.time;
16519     tm->ms = (int) timeB.millitm;
16520
16521 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16522     tm->sec = (long) time(NULL);
16523     tm->ms = 0;
16524 #endif
16525 #endif
16526 }
16527
16528 /* Return the difference in milliseconds between two
16529    time marks.  We assume the difference will fit in a long!
16530 */
16531 long
16532 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16533 {
16534     return 1000L*(tm2->sec - tm1->sec) +
16535            (long) (tm2->ms - tm1->ms);
16536 }
16537
16538
16539 /*
16540  * Code to manage the game clocks.
16541  *
16542  * In tournament play, black starts the clock and then white makes a move.
16543  * We give the human user a slight advantage if he is playing white---the
16544  * clocks don't run until he makes his first move, so it takes zero time.
16545  * Also, we don't account for network lag, so we could get out of sync
16546  * with GNU Chess's clock -- but then, referees are always right.
16547  */
16548
16549 static TimeMark tickStartTM;
16550 static long intendedTickLength;
16551
16552 long
16553 NextTickLength (long timeRemaining)
16554 {
16555     long nominalTickLength, nextTickLength;
16556
16557     if (timeRemaining > 0L && timeRemaining <= 10000L)
16558       nominalTickLength = 100L;
16559     else
16560       nominalTickLength = 1000L;
16561     nextTickLength = timeRemaining % nominalTickLength;
16562     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16563
16564     return nextTickLength;
16565 }
16566
16567 /* Adjust clock one minute up or down */
16568 void
16569 AdjustClock (Boolean which, int dir)
16570 {
16571     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16572     if(which) blackTimeRemaining += 60000*dir;
16573     else      whiteTimeRemaining += 60000*dir;
16574     DisplayBothClocks();
16575     adjustedClock = TRUE;
16576 }
16577
16578 /* Stop clocks and reset to a fresh time control */
16579 void
16580 ResetClocks ()
16581 {
16582     (void) StopClockTimer();
16583     if (appData.icsActive) {
16584         whiteTimeRemaining = blackTimeRemaining = 0;
16585     } else if (searchTime) {
16586         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16587         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16588     } else { /* [HGM] correct new time quote for time odds */
16589         whiteTC = blackTC = fullTimeControlString;
16590         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16591         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16592     }
16593     if (whiteFlag || blackFlag) {
16594         DisplayTitle("");
16595         whiteFlag = blackFlag = FALSE;
16596     }
16597     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16598     DisplayBothClocks();
16599     adjustedClock = FALSE;
16600 }
16601
16602 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16603
16604 /* Decrement running clock by amount of time that has passed */
16605 void
16606 DecrementClocks ()
16607 {
16608     long timeRemaining;
16609     long lastTickLength, fudge;
16610     TimeMark now;
16611
16612     if (!appData.clockMode) return;
16613     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16614
16615     GetTimeMark(&now);
16616
16617     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16618
16619     /* Fudge if we woke up a little too soon */
16620     fudge = intendedTickLength - lastTickLength;
16621     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16622
16623     if (WhiteOnMove(forwardMostMove)) {
16624         if(whiteNPS >= 0) lastTickLength = 0;
16625         timeRemaining = whiteTimeRemaining -= lastTickLength;
16626         if(timeRemaining < 0 && !appData.icsActive) {
16627             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16628             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16629                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16630                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16631             }
16632         }
16633         DisplayWhiteClock(whiteTimeRemaining - fudge,
16634                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16635     } else {
16636         if(blackNPS >= 0) lastTickLength = 0;
16637         timeRemaining = blackTimeRemaining -= lastTickLength;
16638         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16639             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16640             if(suddenDeath) {
16641                 blackStartMove = forwardMostMove;
16642                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16643             }
16644         }
16645         DisplayBlackClock(blackTimeRemaining - fudge,
16646                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16647     }
16648     if (CheckFlags()) return;
16649
16650     if(twoBoards) { // count down secondary board's clocks as well
16651         activePartnerTime -= lastTickLength;
16652         partnerUp = 1;
16653         if(activePartner == 'W')
16654             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16655         else
16656             DisplayBlackClock(activePartnerTime, TRUE);
16657         partnerUp = 0;
16658     }
16659
16660     tickStartTM = now;
16661     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16662     StartClockTimer(intendedTickLength);
16663
16664     /* if the time remaining has fallen below the alarm threshold, sound the
16665      * alarm. if the alarm has sounded and (due to a takeback or time control
16666      * with increment) the time remaining has increased to a level above the
16667      * threshold, reset the alarm so it can sound again.
16668      */
16669
16670     if (appData.icsActive && appData.icsAlarm) {
16671
16672         /* make sure we are dealing with the user's clock */
16673         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16674                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16675            )) return;
16676
16677         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16678             alarmSounded = FALSE;
16679         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16680             PlayAlarmSound();
16681             alarmSounded = TRUE;
16682         }
16683     }
16684 }
16685
16686
16687 /* A player has just moved, so stop the previously running
16688    clock and (if in clock mode) start the other one.
16689    We redisplay both clocks in case we're in ICS mode, because
16690    ICS gives us an update to both clocks after every move.
16691    Note that this routine is called *after* forwardMostMove
16692    is updated, so the last fractional tick must be subtracted
16693    from the color that is *not* on move now.
16694 */
16695 void
16696 SwitchClocks (int newMoveNr)
16697 {
16698     long lastTickLength;
16699     TimeMark now;
16700     int flagged = FALSE;
16701
16702     GetTimeMark(&now);
16703
16704     if (StopClockTimer() && appData.clockMode) {
16705         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16706         if (!WhiteOnMove(forwardMostMove)) {
16707             if(blackNPS >= 0) lastTickLength = 0;
16708             blackTimeRemaining -= lastTickLength;
16709            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16710 //         if(pvInfoList[forwardMostMove].time == -1)
16711                  pvInfoList[forwardMostMove].time =               // use GUI time
16712                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16713         } else {
16714            if(whiteNPS >= 0) lastTickLength = 0;
16715            whiteTimeRemaining -= lastTickLength;
16716            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16717 //         if(pvInfoList[forwardMostMove].time == -1)
16718                  pvInfoList[forwardMostMove].time =
16719                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16720         }
16721         flagged = CheckFlags();
16722     }
16723     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16724     CheckTimeControl();
16725
16726     if (flagged || !appData.clockMode) return;
16727
16728     switch (gameMode) {
16729       case MachinePlaysBlack:
16730       case MachinePlaysWhite:
16731       case BeginningOfGame:
16732         if (pausing) return;
16733         break;
16734
16735       case EditGame:
16736       case PlayFromGameFile:
16737       case IcsExamining:
16738         return;
16739
16740       default:
16741         break;
16742     }
16743
16744     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16745         if(WhiteOnMove(forwardMostMove))
16746              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16747         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16748     }
16749
16750     tickStartTM = now;
16751     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16752       whiteTimeRemaining : blackTimeRemaining);
16753     StartClockTimer(intendedTickLength);
16754 }
16755
16756
16757 /* Stop both clocks */
16758 void
16759 StopClocks ()
16760 {
16761     long lastTickLength;
16762     TimeMark now;
16763
16764     if (!StopClockTimer()) return;
16765     if (!appData.clockMode) return;
16766
16767     GetTimeMark(&now);
16768
16769     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16770     if (WhiteOnMove(forwardMostMove)) {
16771         if(whiteNPS >= 0) lastTickLength = 0;
16772         whiteTimeRemaining -= lastTickLength;
16773         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16774     } else {
16775         if(blackNPS >= 0) lastTickLength = 0;
16776         blackTimeRemaining -= lastTickLength;
16777         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16778     }
16779     CheckFlags();
16780 }
16781
16782 /* Start clock of player on move.  Time may have been reset, so
16783    if clock is already running, stop and restart it. */
16784 void
16785 StartClocks ()
16786 {
16787     (void) StopClockTimer(); /* in case it was running already */
16788     DisplayBothClocks();
16789     if (CheckFlags()) return;
16790
16791     if (!appData.clockMode) return;
16792     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16793
16794     GetTimeMark(&tickStartTM);
16795     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16796       whiteTimeRemaining : blackTimeRemaining);
16797
16798    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16799     whiteNPS = blackNPS = -1;
16800     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16801        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16802         whiteNPS = first.nps;
16803     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16804        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16805         blackNPS = first.nps;
16806     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16807         whiteNPS = second.nps;
16808     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16809         blackNPS = second.nps;
16810     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16811
16812     StartClockTimer(intendedTickLength);
16813 }
16814
16815 char *
16816 TimeString (long ms)
16817 {
16818     long second, minute, hour, day;
16819     char *sign = "";
16820     static char buf[32];
16821
16822     if (ms > 0 && ms <= 9900) {
16823       /* convert milliseconds to tenths, rounding up */
16824       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16825
16826       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16827       return buf;
16828     }
16829
16830     /* convert milliseconds to seconds, rounding up */
16831     /* use floating point to avoid strangeness of integer division
16832        with negative dividends on many machines */
16833     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16834
16835     if (second < 0) {
16836         sign = "-";
16837         second = -second;
16838     }
16839
16840     day = second / (60 * 60 * 24);
16841     second = second % (60 * 60 * 24);
16842     hour = second / (60 * 60);
16843     second = second % (60 * 60);
16844     minute = second / 60;
16845     second = second % 60;
16846
16847     if (day > 0)
16848       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16849               sign, day, hour, minute, second);
16850     else if (hour > 0)
16851       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16852     else
16853       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16854
16855     return buf;
16856 }
16857
16858
16859 /*
16860  * This is necessary because some C libraries aren't ANSI C compliant yet.
16861  */
16862 char *
16863 StrStr (char *string, char *match)
16864 {
16865     int i, length;
16866
16867     length = strlen(match);
16868
16869     for (i = strlen(string) - length; i >= 0; i--, string++)
16870       if (!strncmp(match, string, length))
16871         return string;
16872
16873     return NULL;
16874 }
16875
16876 char *
16877 StrCaseStr (char *string, char *match)
16878 {
16879     int i, j, length;
16880
16881     length = strlen(match);
16882
16883     for (i = strlen(string) - length; i >= 0; i--, string++) {
16884         for (j = 0; j < length; j++) {
16885             if (ToLower(match[j]) != ToLower(string[j]))
16886               break;
16887         }
16888         if (j == length) return string;
16889     }
16890
16891     return NULL;
16892 }
16893
16894 #ifndef _amigados
16895 int
16896 StrCaseCmp (char *s1, char *s2)
16897 {
16898     char c1, c2;
16899
16900     for (;;) {
16901         c1 = ToLower(*s1++);
16902         c2 = ToLower(*s2++);
16903         if (c1 > c2) return 1;
16904         if (c1 < c2) return -1;
16905         if (c1 == NULLCHAR) return 0;
16906     }
16907 }
16908
16909
16910 int
16911 ToLower (int c)
16912 {
16913     return isupper(c) ? tolower(c) : c;
16914 }
16915
16916
16917 int
16918 ToUpper (int c)
16919 {
16920     return islower(c) ? toupper(c) : c;
16921 }
16922 #endif /* !_amigados    */
16923
16924 char *
16925 StrSave (char *s)
16926 {
16927   char *ret;
16928
16929   if ((ret = (char *) malloc(strlen(s) + 1)))
16930     {
16931       safeStrCpy(ret, s, strlen(s)+1);
16932     }
16933   return ret;
16934 }
16935
16936 char *
16937 StrSavePtr (char *s, char **savePtr)
16938 {
16939     if (*savePtr) {
16940         free(*savePtr);
16941     }
16942     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16943       safeStrCpy(*savePtr, s, strlen(s)+1);
16944     }
16945     return(*savePtr);
16946 }
16947
16948 char *
16949 PGNDate ()
16950 {
16951     time_t clock;
16952     struct tm *tm;
16953     char buf[MSG_SIZ];
16954
16955     clock = time((time_t *)NULL);
16956     tm = localtime(&clock);
16957     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16958             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16959     return StrSave(buf);
16960 }
16961
16962
16963 char *
16964 PositionToFEN (int move, char *overrideCastling)
16965 {
16966     int i, j, fromX, fromY, toX, toY;
16967     int whiteToPlay;
16968     char buf[MSG_SIZ];
16969     char *p, *q;
16970     int emptycount;
16971     ChessSquare piece;
16972
16973     whiteToPlay = (gameMode == EditPosition) ?
16974       !blackPlaysFirst : (move % 2 == 0);
16975     p = buf;
16976
16977     /* Piece placement data */
16978     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16979         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16980         emptycount = 0;
16981         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16982             if (boards[move][i][j] == EmptySquare) {
16983                 emptycount++;
16984             } else { ChessSquare piece = boards[move][i][j];
16985                 if (emptycount > 0) {
16986                     if(emptycount<10) /* [HGM] can be >= 10 */
16987                         *p++ = '0' + emptycount;
16988                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16989                     emptycount = 0;
16990                 }
16991                 if(PieceToChar(piece) == '+') {
16992                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16993                     *p++ = '+';
16994                     piece = (ChessSquare)(DEMOTED piece);
16995                 }
16996                 *p++ = PieceToChar(piece);
16997                 if(p[-1] == '~') {
16998                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16999                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17000                     *p++ = '~';
17001                 }
17002             }
17003         }
17004         if (emptycount > 0) {
17005             if(emptycount<10) /* [HGM] can be >= 10 */
17006                 *p++ = '0' + emptycount;
17007             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17008             emptycount = 0;
17009         }
17010         *p++ = '/';
17011     }
17012     *(p - 1) = ' ';
17013
17014     /* [HGM] print Crazyhouse or Shogi holdings */
17015     if( gameInfo.holdingsWidth ) {
17016         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17017         q = p;
17018         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17019             piece = boards[move][i][BOARD_WIDTH-1];
17020             if( piece != EmptySquare )
17021               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17022                   *p++ = PieceToChar(piece);
17023         }
17024         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17025             piece = boards[move][BOARD_HEIGHT-i-1][0];
17026             if( piece != EmptySquare )
17027               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17028                   *p++ = PieceToChar(piece);
17029         }
17030
17031         if( q == p ) *p++ = '-';
17032         *p++ = ']';
17033         *p++ = ' ';
17034     }
17035
17036     /* Active color */
17037     *p++ = whiteToPlay ? 'w' : 'b';
17038     *p++ = ' ';
17039
17040   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17041     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17042   } else {
17043   if(nrCastlingRights) {
17044      q = p;
17045      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17046        /* [HGM] write directly from rights */
17047            if(boards[move][CASTLING][2] != NoRights &&
17048               boards[move][CASTLING][0] != NoRights   )
17049                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17050            if(boards[move][CASTLING][2] != NoRights &&
17051               boards[move][CASTLING][1] != NoRights   )
17052                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17053            if(boards[move][CASTLING][5] != NoRights &&
17054               boards[move][CASTLING][3] != NoRights   )
17055                 *p++ = boards[move][CASTLING][3] + AAA;
17056            if(boards[move][CASTLING][5] != NoRights &&
17057               boards[move][CASTLING][4] != NoRights   )
17058                 *p++ = boards[move][CASTLING][4] + AAA;
17059      } else {
17060
17061         /* [HGM] write true castling rights */
17062         if( nrCastlingRights == 6 ) {
17063             int q, k=0;
17064             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17065                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17066             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17067                  boards[move][CASTLING][2] != NoRights  );
17068             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17069                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17070                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17071                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17072                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17073             }
17074             if(q) *p++ = 'Q';
17075             k = 0;
17076             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17077                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17078             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17079                  boards[move][CASTLING][5] != NoRights  );
17080             if(gameInfo.variant == VariantSChess) {
17081                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17082                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17083                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17084                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17085             }
17086             if(q) *p++ = 'q';
17087         }
17088      }
17089      if (q == p) *p++ = '-'; /* No castling rights */
17090      *p++ = ' ';
17091   }
17092
17093   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17094      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17095     /* En passant target square */
17096     if (move > backwardMostMove) {
17097         fromX = moveList[move - 1][0] - AAA;
17098         fromY = moveList[move - 1][1] - ONE;
17099         toX = moveList[move - 1][2] - AAA;
17100         toY = moveList[move - 1][3] - ONE;
17101         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17102             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17103             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17104             fromX == toX) {
17105             /* 2-square pawn move just happened */
17106             *p++ = toX + AAA;
17107             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17108         } else {
17109             *p++ = '-';
17110         }
17111     } else if(move == backwardMostMove) {
17112         // [HGM] perhaps we should always do it like this, and forget the above?
17113         if((signed char)boards[move][EP_STATUS] >= 0) {
17114             *p++ = boards[move][EP_STATUS] + AAA;
17115             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17116         } else {
17117             *p++ = '-';
17118         }
17119     } else {
17120         *p++ = '-';
17121     }
17122     *p++ = ' ';
17123   }
17124   }
17125
17126     /* [HGM] find reversible plies */
17127     {   int i = 0, j=move;
17128
17129         if (appData.debugMode) { int k;
17130             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17131             for(k=backwardMostMove; k<=forwardMostMove; k++)
17132                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17133
17134         }
17135
17136         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17137         if( j == backwardMostMove ) i += initialRulePlies;
17138         sprintf(p, "%d ", i);
17139         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17140     }
17141     /* Fullmove number */
17142     sprintf(p, "%d", (move / 2) + 1);
17143
17144     return StrSave(buf);
17145 }
17146
17147 Boolean
17148 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17149 {
17150     int i, j;
17151     char *p, c;
17152     int emptycount, virgin[BOARD_FILES];
17153     ChessSquare piece;
17154
17155     p = fen;
17156
17157     /* [HGM] by default clear Crazyhouse holdings, if present */
17158     if(gameInfo.holdingsWidth) {
17159        for(i=0; i<BOARD_HEIGHT; i++) {
17160            board[i][0]             = EmptySquare; /* black holdings */
17161            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17162            board[i][1]             = (ChessSquare) 0; /* black counts */
17163            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17164        }
17165     }
17166
17167     /* Piece placement data */
17168     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17169         j = 0;
17170         for (;;) {
17171             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17172                 if (*p == '/') p++;
17173                 emptycount = gameInfo.boardWidth - j;
17174                 while (emptycount--)
17175                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17176                 break;
17177 #if(BOARD_FILES >= 10)
17178             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17179                 p++; emptycount=10;
17180                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17181                 while (emptycount--)
17182                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17183 #endif
17184             } else if (isdigit(*p)) {
17185                 emptycount = *p++ - '0';
17186                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17187                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17188                 while (emptycount--)
17189                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17190             } else if (*p == '+' || isalpha(*p)) {
17191                 if (j >= gameInfo.boardWidth) return FALSE;
17192                 if(*p=='+') {
17193                     piece = CharToPiece(*++p);
17194                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17195                     piece = (ChessSquare) (PROMOTED piece ); p++;
17196                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17197                 } else piece = CharToPiece(*p++);
17198
17199                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17200                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17201                     piece = (ChessSquare) (PROMOTED piece);
17202                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17203                     p++;
17204                 }
17205                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17206             } else {
17207                 return FALSE;
17208             }
17209         }
17210     }
17211     while (*p == '/' || *p == ' ') p++;
17212
17213     /* [HGM] look for Crazyhouse holdings here */
17214     while(*p==' ') p++;
17215     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17216         if(*p == '[') p++;
17217         if(*p == '-' ) p++; /* empty holdings */ else {
17218             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17219             /* if we would allow FEN reading to set board size, we would   */
17220             /* have to add holdings and shift the board read so far here   */
17221             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17222                 p++;
17223                 if((int) piece >= (int) BlackPawn ) {
17224                     i = (int)piece - (int)BlackPawn;
17225                     i = PieceToNumber((ChessSquare)i);
17226                     if( i >= gameInfo.holdingsSize ) return FALSE;
17227                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17228                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17229                 } else {
17230                     i = (int)piece - (int)WhitePawn;
17231                     i = PieceToNumber((ChessSquare)i);
17232                     if( i >= gameInfo.holdingsSize ) return FALSE;
17233                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17234                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17235                 }
17236             }
17237         }
17238         if(*p == ']') p++;
17239     }
17240
17241     while(*p == ' ') p++;
17242
17243     /* Active color */
17244     c = *p++;
17245     if(appData.colorNickNames) {
17246       if( c == appData.colorNickNames[0] ) c = 'w'; else
17247       if( c == appData.colorNickNames[1] ) c = 'b';
17248     }
17249     switch (c) {
17250       case 'w':
17251         *blackPlaysFirst = FALSE;
17252         break;
17253       case 'b':
17254         *blackPlaysFirst = TRUE;
17255         break;
17256       default:
17257         return FALSE;
17258     }
17259
17260     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17261     /* return the extra info in global variiables             */
17262
17263     /* set defaults in case FEN is incomplete */
17264     board[EP_STATUS] = EP_UNKNOWN;
17265     for(i=0; i<nrCastlingRights; i++ ) {
17266         board[CASTLING][i] =
17267             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17268     }   /* assume possible unless obviously impossible */
17269     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17270     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17271     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17272                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17273     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17274     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17275     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17276                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17277     FENrulePlies = 0;
17278
17279     while(*p==' ') p++;
17280     if(nrCastlingRights) {
17281       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17282       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17283           /* castling indicator present, so default becomes no castlings */
17284           for(i=0; i<nrCastlingRights; i++ ) {
17285                  board[CASTLING][i] = NoRights;
17286           }
17287       }
17288       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17289              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17290              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17291              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17292         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17293
17294         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17295             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17296             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17297         }
17298         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17299             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17300         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17301                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17302         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17303                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17304         switch(c) {
17305           case'K':
17306               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17307               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17308               board[CASTLING][2] = whiteKingFile;
17309               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17310               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17311               break;
17312           case'Q':
17313               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17314               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17315               board[CASTLING][2] = whiteKingFile;
17316               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17317               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17318               break;
17319           case'k':
17320               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17321               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17322               board[CASTLING][5] = blackKingFile;
17323               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17324               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17325               break;
17326           case'q':
17327               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17328               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17329               board[CASTLING][5] = blackKingFile;
17330               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17331               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17332           case '-':
17333               break;
17334           default: /* FRC castlings */
17335               if(c >= 'a') { /* black rights */
17336                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17337                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17338                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17339                   if(i == BOARD_RGHT) break;
17340                   board[CASTLING][5] = i;
17341                   c -= AAA;
17342                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17343                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17344                   if(c > i)
17345                       board[CASTLING][3] = c;
17346                   else
17347                       board[CASTLING][4] = c;
17348               } else { /* white rights */
17349                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17350                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17351                     if(board[0][i] == WhiteKing) break;
17352                   if(i == BOARD_RGHT) break;
17353                   board[CASTLING][2] = i;
17354                   c -= AAA - 'a' + 'A';
17355                   if(board[0][c] >= WhiteKing) break;
17356                   if(c > i)
17357                       board[CASTLING][0] = c;
17358                   else
17359                       board[CASTLING][1] = c;
17360               }
17361         }
17362       }
17363       for(i=0; i<nrCastlingRights; i++)
17364         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17365       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17366     if (appData.debugMode) {
17367         fprintf(debugFP, "FEN castling rights:");
17368         for(i=0; i<nrCastlingRights; i++)
17369         fprintf(debugFP, " %d", board[CASTLING][i]);
17370         fprintf(debugFP, "\n");
17371     }
17372
17373       while(*p==' ') p++;
17374     }
17375
17376     /* read e.p. field in games that know e.p. capture */
17377     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17378        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17379       if(*p=='-') {
17380         p++; board[EP_STATUS] = EP_NONE;
17381       } else {
17382          char c = *p++ - AAA;
17383
17384          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17385          if(*p >= '0' && *p <='9') p++;
17386          board[EP_STATUS] = c;
17387       }
17388     }
17389
17390
17391     if(sscanf(p, "%d", &i) == 1) {
17392         FENrulePlies = i; /* 50-move ply counter */
17393         /* (The move number is still ignored)    */
17394     }
17395
17396     return TRUE;
17397 }
17398
17399 void
17400 EditPositionPasteFEN (char *fen)
17401 {
17402   if (fen != NULL) {
17403     Board initial_position;
17404
17405     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17406       DisplayError(_("Bad FEN position in clipboard"), 0);
17407       return ;
17408     } else {
17409       int savedBlackPlaysFirst = blackPlaysFirst;
17410       EditPositionEvent();
17411       blackPlaysFirst = savedBlackPlaysFirst;
17412       CopyBoard(boards[0], initial_position);
17413       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17414       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17415       DisplayBothClocks();
17416       DrawPosition(FALSE, boards[currentMove]);
17417     }
17418   }
17419 }
17420
17421 static char cseq[12] = "\\   ";
17422
17423 Boolean
17424 set_cont_sequence (char *new_seq)
17425 {
17426     int len;
17427     Boolean ret;
17428
17429     // handle bad attempts to set the sequence
17430         if (!new_seq)
17431                 return 0; // acceptable error - no debug
17432
17433     len = strlen(new_seq);
17434     ret = (len > 0) && (len < sizeof(cseq));
17435     if (ret)
17436       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17437     else if (appData.debugMode)
17438       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17439     return ret;
17440 }
17441
17442 /*
17443     reformat a source message so words don't cross the width boundary.  internal
17444     newlines are not removed.  returns the wrapped size (no null character unless
17445     included in source message).  If dest is NULL, only calculate the size required
17446     for the dest buffer.  lp argument indicats line position upon entry, and it's
17447     passed back upon exit.
17448 */
17449 int
17450 wrap (char *dest, char *src, int count, int width, int *lp)
17451 {
17452     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17453
17454     cseq_len = strlen(cseq);
17455     old_line = line = *lp;
17456     ansi = len = clen = 0;
17457
17458     for (i=0; i < count; i++)
17459     {
17460         if (src[i] == '\033')
17461             ansi = 1;
17462
17463         // if we hit the width, back up
17464         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17465         {
17466             // store i & len in case the word is too long
17467             old_i = i, old_len = len;
17468
17469             // find the end of the last word
17470             while (i && src[i] != ' ' && src[i] != '\n')
17471             {
17472                 i--;
17473                 len--;
17474             }
17475
17476             // word too long?  restore i & len before splitting it
17477             if ((old_i-i+clen) >= width)
17478             {
17479                 i = old_i;
17480                 len = old_len;
17481             }
17482
17483             // extra space?
17484             if (i && src[i-1] == ' ')
17485                 len--;
17486
17487             if (src[i] != ' ' && src[i] != '\n')
17488             {
17489                 i--;
17490                 if (len)
17491                     len--;
17492             }
17493
17494             // now append the newline and continuation sequence
17495             if (dest)
17496                 dest[len] = '\n';
17497             len++;
17498             if (dest)
17499                 strncpy(dest+len, cseq, cseq_len);
17500             len += cseq_len;
17501             line = cseq_len;
17502             clen = cseq_len;
17503             continue;
17504         }
17505
17506         if (dest)
17507             dest[len] = src[i];
17508         len++;
17509         if (!ansi)
17510             line++;
17511         if (src[i] == '\n')
17512             line = 0;
17513         if (src[i] == 'm')
17514             ansi = 0;
17515     }
17516     if (dest && appData.debugMode)
17517     {
17518         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17519             count, width, line, len, *lp);
17520         show_bytes(debugFP, src, count);
17521         fprintf(debugFP, "\ndest: ");
17522         show_bytes(debugFP, dest, len);
17523         fprintf(debugFP, "\n");
17524     }
17525     *lp = dest ? line : old_line;
17526
17527     return len;
17528 }
17529
17530 // [HGM] vari: routines for shelving variations
17531 Boolean modeRestore = FALSE;
17532
17533 void
17534 PushInner (int firstMove, int lastMove)
17535 {
17536         int i, j, nrMoves = lastMove - firstMove;
17537
17538         // push current tail of game on stack
17539         savedResult[storedGames] = gameInfo.result;
17540         savedDetails[storedGames] = gameInfo.resultDetails;
17541         gameInfo.resultDetails = NULL;
17542         savedFirst[storedGames] = firstMove;
17543         savedLast [storedGames] = lastMove;
17544         savedFramePtr[storedGames] = framePtr;
17545         framePtr -= nrMoves; // reserve space for the boards
17546         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17547             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17548             for(j=0; j<MOVE_LEN; j++)
17549                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17550             for(j=0; j<2*MOVE_LEN; j++)
17551                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17552             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17553             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17554             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17555             pvInfoList[firstMove+i-1].depth = 0;
17556             commentList[framePtr+i] = commentList[firstMove+i];
17557             commentList[firstMove+i] = NULL;
17558         }
17559
17560         storedGames++;
17561         forwardMostMove = firstMove; // truncate game so we can start variation
17562 }
17563
17564 void
17565 PushTail (int firstMove, int lastMove)
17566 {
17567         if(appData.icsActive) { // only in local mode
17568                 forwardMostMove = currentMove; // mimic old ICS behavior
17569                 return;
17570         }
17571         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17572
17573         PushInner(firstMove, lastMove);
17574         if(storedGames == 1) GreyRevert(FALSE);
17575         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17576 }
17577
17578 void
17579 PopInner (Boolean annotate)
17580 {
17581         int i, j, nrMoves;
17582         char buf[8000], moveBuf[20];
17583
17584         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17585         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17586         nrMoves = savedLast[storedGames] - currentMove;
17587         if(annotate) {
17588                 int cnt = 10;
17589                 if(!WhiteOnMove(currentMove))
17590                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17591                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17592                 for(i=currentMove; i<forwardMostMove; i++) {
17593                         if(WhiteOnMove(i))
17594                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17595                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17596                         strcat(buf, moveBuf);
17597                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17598                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17599                 }
17600                 strcat(buf, ")");
17601         }
17602         for(i=1; i<=nrMoves; i++) { // copy last variation back
17603             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17604             for(j=0; j<MOVE_LEN; j++)
17605                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17606             for(j=0; j<2*MOVE_LEN; j++)
17607                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17608             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17609             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17610             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17611             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17612             commentList[currentMove+i] = commentList[framePtr+i];
17613             commentList[framePtr+i] = NULL;
17614         }
17615         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17616         framePtr = savedFramePtr[storedGames];
17617         gameInfo.result = savedResult[storedGames];
17618         if(gameInfo.resultDetails != NULL) {
17619             free(gameInfo.resultDetails);
17620       }
17621         gameInfo.resultDetails = savedDetails[storedGames];
17622         forwardMostMove = currentMove + nrMoves;
17623 }
17624
17625 Boolean
17626 PopTail (Boolean annotate)
17627 {
17628         if(appData.icsActive) return FALSE; // only in local mode
17629         if(!storedGames) return FALSE; // sanity
17630         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17631
17632         PopInner(annotate);
17633         if(currentMove < forwardMostMove) ForwardEvent(); else
17634         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17635
17636         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17637         return TRUE;
17638 }
17639
17640 void
17641 CleanupTail ()
17642 {       // remove all shelved variations
17643         int i;
17644         for(i=0; i<storedGames; i++) {
17645             if(savedDetails[i])
17646                 free(savedDetails[i]);
17647             savedDetails[i] = NULL;
17648         }
17649         for(i=framePtr; i<MAX_MOVES; i++) {
17650                 if(commentList[i]) free(commentList[i]);
17651                 commentList[i] = NULL;
17652         }
17653         framePtr = MAX_MOVES-1;
17654         storedGames = 0;
17655 }
17656
17657 void
17658 LoadVariation (int index, char *text)
17659 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17660         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17661         int level = 0, move;
17662
17663         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17664         // first find outermost bracketing variation
17665         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17666             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17667                 if(*p == '{') wait = '}'; else
17668                 if(*p == '[') wait = ']'; else
17669                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17670                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17671             }
17672             if(*p == wait) wait = NULLCHAR; // closing ]} found
17673             p++;
17674         }
17675         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17676         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17677         end[1] = NULLCHAR; // clip off comment beyond variation
17678         ToNrEvent(currentMove-1);
17679         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17680         // kludge: use ParsePV() to append variation to game
17681         move = currentMove;
17682         ParsePV(start, TRUE, TRUE);
17683         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17684         ClearPremoveHighlights();
17685         CommentPopDown();
17686         ToNrEvent(currentMove+1);
17687 }
17688
17689 void
17690 LoadTheme ()
17691 {
17692     char *p, *q, buf[MSG_SIZ];
17693     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17694         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17695         ParseArgsFromString(buf);
17696         ActivateTheme(TRUE); // also redo colors
17697         return;
17698     }
17699     p = nickName;
17700     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17701     {
17702         int len;
17703         q = appData.themeNames;
17704         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17705       if(appData.useBitmaps) {
17706         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17707                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17708                 appData.liteBackTextureMode,
17709                 appData.darkBackTextureMode );
17710       } else {
17711         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17712                 Col2Text(2),   // lightSquareColor
17713                 Col2Text(3) ); // darkSquareColor
17714       }
17715       if(appData.useBorder) {
17716         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17717                 appData.border);
17718       } else {
17719         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17720       }
17721       if(appData.useFont) {
17722         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17723                 appData.renderPiecesWithFont,
17724                 appData.fontToPieceTable,
17725                 Col2Text(9),    // appData.fontBackColorWhite
17726                 Col2Text(10) ); // appData.fontForeColorBlack
17727       } else {
17728         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17729                 appData.pieceDirectory);
17730         if(!appData.pieceDirectory[0])
17731           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17732                 Col2Text(0),   // whitePieceColor
17733                 Col2Text(1) ); // blackPieceColor
17734       }
17735       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17736                 Col2Text(4),   // highlightSquareColor
17737                 Col2Text(5) ); // premoveHighlightColor
17738         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17739         if(insert != q) insert[-1] = NULLCHAR;
17740         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17741         if(q)   free(q);
17742     }
17743     ActivateTheme(FALSE);
17744 }