4537da1fa716241e197da0509789e247fd041b5a
[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, 2014, 2015, 2016 Free
9  * Software Foundation, Inc.
10  *
11  * Enhancements Copyright 2005 Alessandro Scotti
12  *
13  * The following terms apply to Digital Equipment Corporation's copyright
14  * interest in XBoard:
15  * ------------------------------------------------------------------------
16  * All Rights Reserved
17  *
18  * Permission to use, copy, modify, and distribute this software and its
19  * documentation for any purpose and without fee is hereby granted,
20  * provided that the above copyright notice appear in all copies and that
21  * both that copyright notice and this permission notice appear in
22  * supporting documentation, and that the name of Digital not be
23  * used in advertising or publicity pertaining to distribution of the
24  * software without specific, written prior permission.
25  *
26  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32  * SOFTWARE.
33  * ------------------------------------------------------------------------
34  *
35  * The following terms apply to the enhanced version of XBoard
36  * distributed by the Free Software Foundation:
37  * ------------------------------------------------------------------------
38  *
39  * GNU XBoard is free software: you can redistribute it and/or modify
40  * it under the terms of the GNU General Public License as published by
41  * the Free Software Foundation, either version 3 of the License, or (at
42  * your option) any later version.
43  *
44  * GNU XBoard is distributed in the hope that it will be useful, but
45  * WITHOUT ANY WARRANTY; without even the implied warranty of
46  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47  * General Public License for more details.
48  *
49  * You should have received a copy of the GNU General Public License
50  * along with this program. If not, see http://www.gnu.org/licenses/.  *
51  *
52  *------------------------------------------------------------------------
53  ** See the file ChangeLog for a revision history.  */
54
55 /* [AS] Also useful here for debugging */
56 #ifdef WIN32
57 #include <windows.h>
58
59     int flock(int f, int code);
60 #   define LOCK_EX 2
61 #   define SLASH '\\'
62
63 #   ifdef ARC_64BIT
64 #       define EGBB_NAME "egbbdll64.dll"
65 #   else
66 #       define EGBB_NAME "egbbdll.dll"
67 #   endif
68
69 #else
70
71 #   include <sys/file.h>
72 #   define SLASH '/'
73
74 #   include <dlfcn.h>
75 #   ifdef ARC_64BIT
76 #       define EGBB_NAME "egbbso64.so"
77 #   else
78 #       define EGBB_NAME "egbbso.so"
79 #   endif
80     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
81 #   define CDECL
82 #   define HMODULE void *
83 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 #   define GetProcAddress dlsym
85
86 #endif
87
88 #include "config.h"
89
90 #include <assert.h>
91 #include <stdio.h>
92 #include <ctype.h>
93 #include <errno.h>
94 #include <sys/types.h>
95 #include <sys/stat.h>
96 #include <math.h>
97 #include <ctype.h>
98
99 #if STDC_HEADERS
100 # include <stdlib.h>
101 # include <string.h>
102 # include <stdarg.h>
103 #else /* not STDC_HEADERS */
104 # if HAVE_STRING_H
105 #  include <string.h>
106 # else /* not HAVE_STRING_H */
107 #  include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
110
111 #if HAVE_SYS_FCNTL_H
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
114 # if HAVE_FCNTL_H
115 #  include <fcntl.h>
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
118
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
121 # include <time.h>
122 #else
123 # if HAVE_SYS_TIME_H
124 #  include <sys/time.h>
125 # else
126 #  include <time.h>
127 # endif
128 #endif
129
130 #if defined(_amigados) && !defined(__GNUC__)
131 struct timezone {
132     int tz_minuteswest;
133     int tz_dsttime;
134 };
135 extern int gettimeofday(struct timeval *, struct timezone *);
136 #endif
137
138 #if HAVE_UNISTD_H
139 # include <unistd.h>
140 #endif
141
142 #include "common.h"
143 #include "frontend.h"
144 #include "backend.h"
145 #include "parser.h"
146 #include "moves.h"
147 #if ZIPPY
148 # include "zippy.h"
149 #endif
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
153 #include "gettext.h"
154
155 #ifdef ENABLE_NLS
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
159 #else
160 # ifdef WIN32
161 #   define _(s) T_(s)
162 #   define N_(s) s
163 # else
164 #   define _(s) (s)
165 #   define N_(s) s
166 #   define T_(s) s
167 # endif
168 #endif
169
170
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173                          char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175                       char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188                    /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264 int deadRanks, handSize, handOffsets;
265
266 extern int tinyLayout, smallLayout;
267 ChessProgramStats programStats;
268 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
269 int endPV = -1;
270 static int exiting = 0; /* [HGM] moved to top */
271 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
272 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
273 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
274 int partnerHighlight[2];
275 Boolean partnerBoardValid = 0;
276 char partnerStatus[MSG_SIZ];
277 Boolean partnerUp;
278 Boolean originalFlip;
279 Boolean twoBoards = 0;
280 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
281 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
282 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
283 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
284 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
285 int opponentKibitzes;
286 int lastSavedGame; /* [HGM] save: ID of game */
287 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
288 extern int chatCount;
289 int chattingPartner;
290 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
291 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
292 char lastMsg[MSG_SIZ];
293 char lastTalker[MSG_SIZ];
294 ChessSquare pieceSweep = EmptySquare;
295 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
296 int promoDefaultAltered;
297 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
298 static int initPing = -1;
299 int border;       /* [HGM] width of board rim, needed to size seek graph  */
300 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
301 int solvingTime, totalTime;
302
303 /* States for ics_getting_history */
304 #define H_FALSE 0
305 #define H_REQUESTED 1
306 #define H_GOT_REQ_HEADER 2
307 #define H_GOT_UNREQ_HEADER 3
308 #define H_GETTING_MOVES 4
309 #define H_GOT_UNWANTED_HEADER 5
310
311 /* whosays values for GameEnds */
312 #define GE_ICS 0
313 #define GE_ENGINE 1
314 #define GE_PLAYER 2
315 #define GE_FILE 3
316 #define GE_XBOARD 4
317 #define GE_ENGINE1 5
318 #define GE_ENGINE2 6
319
320 /* Maximum number of games in a cmail message */
321 #define CMAIL_MAX_GAMES 20
322
323 /* Different types of move when calling RegisterMove */
324 #define CMAIL_MOVE   0
325 #define CMAIL_RESIGN 1
326 #define CMAIL_DRAW   2
327 #define CMAIL_ACCEPT 3
328
329 /* Different types of result to remember for each game */
330 #define CMAIL_NOT_RESULT 0
331 #define CMAIL_OLD_RESULT 1
332 #define CMAIL_NEW_RESULT 2
333
334 /* Telnet protocol constants */
335 #define TN_WILL 0373
336 #define TN_WONT 0374
337 #define TN_DO   0375
338 #define TN_DONT 0376
339 #define TN_IAC  0377
340 #define TN_ECHO 0001
341 #define TN_SGA  0003
342 #define TN_PORT 23
343
344 char*
345 safeStrCpy (char *dst, const char *src, size_t count)
346 { // [HGM] made safe
347   int i;
348   assert( dst != NULL );
349   assert( src != NULL );
350   assert( count > 0 );
351
352   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
353   if(  i == count && dst[count-1] != NULLCHAR)
354     {
355       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
356       if(appData.debugMode)
357         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
358     }
359
360   return dst;
361 }
362
363 /* Some compiler can't cast u64 to double
364  * This function do the job for us:
365
366  * We use the highest bit for cast, this only
367  * works if the highest bit is not
368  * in use (This should not happen)
369  *
370  * We used this for all compiler
371  */
372 double
373 u64ToDouble (u64 value)
374 {
375   double r;
376   u64 tmp = value & u64Const(0x7fffffffffffffff);
377   r = (double)(s64)tmp;
378   if (value & u64Const(0x8000000000000000))
379        r +=  9.2233720368547758080e18; /* 2^63 */
380  return r;
381 }
382
383 /* Fake up flags for now, as we aren't keeping track of castling
384    availability yet. [HGM] Change of logic: the flag now only
385    indicates the type of castlings allowed by the rule of the game.
386    The actual rights themselves are maintained in the array
387    castlingRights, as part of the game history, and are not probed
388    by this function.
389  */
390 int
391 PosFlags (int index)
392 {
393   int flags = F_ALL_CASTLE_OK;
394   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
395   switch (gameInfo.variant) {
396   case VariantSuicide:
397     flags &= ~F_ALL_CASTLE_OK;
398   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
399     flags |= F_IGNORE_CHECK;
400   case VariantLosers:
401     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
402     break;
403   case VariantAtomic:
404     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
405     break;
406   case VariantKriegspiel:
407     flags |= F_KRIEGSPIEL_CAPTURE;
408     break;
409   case VariantCapaRandom:
410   case VariantFischeRandom:
411     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
412   case VariantNoCastle:
413   case VariantShatranj:
414   case VariantCourier:
415   case VariantMakruk:
416   case VariantASEAN:
417   case VariantGrand:
418     flags &= ~F_ALL_CASTLE_OK;
419     break;
420   case VariantChu:
421   case VariantChuChess:
422   case VariantLion:
423     flags |= F_NULL_MOVE;
424     break;
425   default:
426     break;
427   }
428   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
429   return flags;
430 }
431
432 FILE *gameFileFP, *debugFP, *serverFP;
433 char *currentDebugFile; // [HGM] debug split: to remember name
434
435 /*
436     [AS] Note: sometimes, the sscanf() function is used to parse the input
437     into a fixed-size buffer. Because of this, we must be prepared to
438     receive strings as long as the size of the input buffer, which is currently
439     set to 4K for Windows and 8K for the rest.
440     So, we must either allocate sufficiently large buffers here, or
441     reduce the size of the input buffer in the input reading part.
442 */
443
444 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
445 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
446 char thinkOutput1[MSG_SIZ*10];
447 char promoRestrict[MSG_SIZ];
448
449 ChessProgramState first, second, pairing;
450
451 /* premove variables */
452 int premoveToX = 0;
453 int premoveToY = 0;
454 int premoveFromX = 0;
455 int premoveFromY = 0;
456 int premovePromoChar = 0;
457 int gotPremove = 0;
458 Boolean alarmSounded;
459 /* end premove variables */
460
461 char *ics_prefix = "$";
462 enum ICS_TYPE ics_type = ICS_GENERIC;
463
464 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
465 int pauseExamForwardMostMove = 0;
466 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
467 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
468 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
469 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
470 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
471 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
472 int whiteFlag = FALSE, blackFlag = FALSE;
473 int userOfferedDraw = FALSE;
474 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
475 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
476 int cmailMoveType[CMAIL_MAX_GAMES];
477 long ics_clock_paused = 0;
478 ProcRef icsPR = NoProc, cmailPR = NoProc;
479 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
480 GameMode gameMode = BeginningOfGame;
481 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
482 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
483 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
484 int hiddenThinkOutputState = 0; /* [AS] */
485 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
486 int adjudicateLossPlies = 6;
487 char white_holding[64], black_holding[64];
488 TimeMark lastNodeCountTime;
489 long lastNodeCount=0;
490 int shiftKey, controlKey; // [HGM] set by mouse handler
491
492 int have_sent_ICS_logon = 0;
493 int movesPerSession;
494 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
495 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
496 Boolean adjustedClock;
497 long timeControl_2; /* [AS] Allow separate time controls */
498 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
499 long timeRemaining[2][MAX_MOVES];
500 int matchGame = 0, nextGame = 0, roundNr = 0;
501 Boolean waitingForGame = FALSE, startingEngine = FALSE;
502 TimeMark programStartTime, pauseStart;
503 char ics_handle[MSG_SIZ];
504 int have_set_title = 0;
505
506 /* animateTraining preserves the state of appData.animate
507  * when Training mode is activated. This allows the
508  * response to be animated when appData.animate == TRUE and
509  * appData.animateDragging == TRUE.
510  */
511 Boolean animateTraining;
512
513 GameInfo gameInfo;
514
515 AppData appData;
516
517 Board boards[MAX_MOVES];
518 /* [HGM] Following 7 needed for accurate legality tests: */
519 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
520 unsigned char initialRights[BOARD_FILES];
521 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
522 int   initialRulePlies, FENrulePlies;
523 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
524 int loadFlag = 0;
525 Boolean shuffleOpenings;
526 int mute; // mute all sounds
527
528 // [HGM] vari: next 12 to save and restore variations
529 #define MAX_VARIATIONS 10
530 int framePtr = MAX_MOVES-1; // points to free stack entry
531 int storedGames = 0;
532 int savedFirst[MAX_VARIATIONS];
533 int savedLast[MAX_VARIATIONS];
534 int savedFramePtr[MAX_VARIATIONS];
535 char *savedDetails[MAX_VARIATIONS];
536 ChessMove savedResult[MAX_VARIATIONS];
537
538 void PushTail P((int firstMove, int lastMove));
539 Boolean PopTail P((Boolean annotate));
540 void PushInner P((int firstMove, int lastMove));
541 void PopInner P((Boolean annotate));
542 void CleanupTail P((void));
543
544 ChessSquare  FIDEArray[2][BOARD_FILES] = {
545     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
548         BlackKing, BlackBishop, BlackKnight, BlackRook }
549 };
550
551 ChessSquare twoKingsArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
553         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
555         BlackKing, BlackKing, BlackKnight, BlackRook }
556 };
557
558 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
559     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
560         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
561     { BlackRook, BlackMan, BlackBishop, BlackQueen,
562         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
563 };
564
565 ChessSquare SpartanArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
567         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
568     { BlackAlfil, BlackDragon, BlackKing, BlackTower,
569         BlackTower, BlackKing, BlackAngel, BlackAlfil }
570 };
571
572 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
573     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
574         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
575     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
576         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
577 };
578
579 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
580     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
581         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
582     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
583         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
584 };
585
586 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
587     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
588         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
589     { BlackRook, BlackKnight, BlackMan, BlackFerz,
590         BlackKing, BlackMan, BlackKnight, BlackRook }
591 };
592
593 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
594     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
595         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
596     { BlackRook, BlackKnight, BlackMan, BlackFerz,
597         BlackKing, BlackMan, BlackKnight, BlackRook }
598 };
599
600 ChessSquare  lionArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
602         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackLion, BlackBishop, BlackQueen,
604         BlackKing, BlackBishop, BlackKnight, BlackRook }
605 };
606
607
608 #if (BOARD_FILES>=10)
609 ChessSquare ShogiArray[2][BOARD_FILES] = {
610     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
611         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
612     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
613         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
614 };
615
616 ChessSquare XiangqiArray[2][BOARD_FILES] = {
617     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
618         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
619     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
620         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
621 };
622
623 ChessSquare CapablancaArray[2][BOARD_FILES] = {
624     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
625         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
626     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
627         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
628 };
629
630 ChessSquare GreatArray[2][BOARD_FILES] = {
631     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
632         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
633     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
634         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
635 };
636
637 ChessSquare JanusArray[2][BOARD_FILES] = {
638     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
639         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
640     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
641         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
642 };
643
644 ChessSquare GrandArray[2][BOARD_FILES] = {
645     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
646         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
647     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
648         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
649 };
650
651 ChessSquare ChuChessArray[2][BOARD_FILES] = {
652     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
653         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
654     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
655         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
656 };
657
658 #ifdef GOTHIC
659 ChessSquare GothicArray[2][BOARD_FILES] = {
660     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
661         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
662     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
663         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
664 };
665 #else // !GOTHIC
666 #define GothicArray CapablancaArray
667 #endif // !GOTHIC
668
669 #ifdef FALCON
670 ChessSquare FalconArray[2][BOARD_FILES] = {
671     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
672         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
673     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
674         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
675 };
676 #else // !FALCON
677 #define FalconArray CapablancaArray
678 #endif // !FALCON
679
680 #else // !(BOARD_FILES>=10)
681 #define XiangqiPosition FIDEArray
682 #define CapablancaArray FIDEArray
683 #define GothicArray FIDEArray
684 #define GreatArray FIDEArray
685 #endif // !(BOARD_FILES>=10)
686
687 #if (BOARD_FILES>=12)
688 ChessSquare CourierArray[2][BOARD_FILES] = {
689     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
690         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
691     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
692         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
693 };
694 ChessSquare ChuArray[6][BOARD_FILES] = {
695     { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
696       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
697     { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
698       BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
699     { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
700       WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
701     { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
702       BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
703     { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
704       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
705     { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
706       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
707 };
708 #else // !(BOARD_FILES>=12)
709 #define CourierArray CapablancaArray
710 #define ChuArray CapablancaArray
711 #endif // !(BOARD_FILES>=12)
712
713
714 Board initialPosition;
715
716
717 /* Convert str to a rating. Checks for special cases of "----",
718
719    "++++", etc. Also strips ()'s */
720 int
721 string_to_rating (char *str)
722 {
723   while(*str && !isdigit(*str)) ++str;
724   if (!*str)
725     return 0;   /* One of the special "no rating" cases */
726   else
727     return atoi(str);
728 }
729
730 void
731 ClearProgramStats ()
732 {
733     /* Init programStats */
734     programStats.movelist[0] = 0;
735     programStats.depth = 0;
736     programStats.nr_moves = 0;
737     programStats.moves_left = 0;
738     programStats.nodes = 0;
739     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
740     programStats.score = 0;
741     programStats.got_only_move = 0;
742     programStats.got_fail = 0;
743     programStats.line_is_book = 0;
744 }
745
746 void
747 CommonEngineInit ()
748 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
749     if (appData.firstPlaysBlack) {
750         first.twoMachinesColor = "black\n";
751         second.twoMachinesColor = "white\n";
752     } else {
753         first.twoMachinesColor = "white\n";
754         second.twoMachinesColor = "black\n";
755     }
756
757     first.other = &second;
758     second.other = &first;
759
760     { float norm = 1;
761         if(appData.timeOddsMode) {
762             norm = appData.timeOdds[0];
763             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
764         }
765         first.timeOdds  = appData.timeOdds[0]/norm;
766         second.timeOdds = appData.timeOdds[1]/norm;
767     }
768
769     if(programVersion) free(programVersion);
770     if (appData.noChessProgram) {
771         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
772         sprintf(programVersion, "%s", PACKAGE_STRING);
773     } else {
774       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
775       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
776       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
777     }
778 }
779
780 void
781 UnloadEngine (ChessProgramState *cps)
782 {
783         /* Kill off first chess program */
784         if (cps->isr != NULL)
785           RemoveInputSource(cps->isr);
786         cps->isr = NULL;
787
788         if (cps->pr != NoProc) {
789             ExitAnalyzeMode();
790             DoSleep( appData.delayBeforeQuit );
791             SendToProgram("quit\n", cps);
792             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
793         }
794         cps->pr = NoProc;
795         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
796 }
797
798 void
799 ClearOptions (ChessProgramState *cps)
800 {
801     int i;
802     cps->nrOptions = cps->comboCnt = 0;
803     for(i=0; i<MAX_OPTIONS; i++) {
804         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
805         cps->option[i].textValue = 0;
806     }
807 }
808
809 char *engineNames[] = {
810   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
811      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
812 N_("first"),
813   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
814      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
815 N_("second")
816 };
817
818 void
819 InitEngine (ChessProgramState *cps, int n)
820 {   // [HGM] all engine initialiation put in a function that does one engine
821
822     ClearOptions(cps);
823
824     cps->which = engineNames[n];
825     cps->maybeThinking = FALSE;
826     cps->pr = NoProc;
827     cps->isr = NULL;
828     cps->sendTime = 2;
829     cps->sendDrawOffers = 1;
830
831     cps->program = appData.chessProgram[n];
832     cps->host = appData.host[n];
833     cps->dir = appData.directory[n];
834     cps->initString = appData.engInitString[n];
835     cps->computerString = appData.computerString[n];
836     cps->useSigint  = TRUE;
837     cps->useSigterm = TRUE;
838     cps->reuse = appData.reuse[n];
839     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
840     cps->useSetboard = FALSE;
841     cps->useSAN = FALSE;
842     cps->usePing = FALSE;
843     cps->lastPing = 0;
844     cps->lastPong = 0;
845     cps->usePlayother = FALSE;
846     cps->useColors = TRUE;
847     cps->useUsermove = FALSE;
848     cps->sendICS = FALSE;
849     cps->sendName = appData.icsActive;
850     cps->sdKludge = FALSE;
851     cps->stKludge = FALSE;
852     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
853     TidyProgramName(cps->program, cps->host, cps->tidy);
854     cps->matchWins = 0;
855     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
856     cps->analysisSupport = 2; /* detect */
857     cps->analyzing = FALSE;
858     cps->initDone = FALSE;
859     cps->reload = FALSE;
860     cps->pseudo = appData.pseudo[n];
861
862     /* New features added by Tord: */
863     cps->useFEN960 = FALSE;
864     cps->useOOCastle = TRUE;
865     /* End of new features added by Tord. */
866     cps->fenOverride  = appData.fenOverride[n];
867
868     /* [HGM] time odds: set factor for each machine */
869     cps->timeOdds  = appData.timeOdds[n];
870
871     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
872     cps->accumulateTC = appData.accumulateTC[n];
873     cps->maxNrOfSessions = 1;
874
875     /* [HGM] debug */
876     cps->debug = FALSE;
877
878     cps->drawDepth = appData.drawDepth[n];
879     cps->supportsNPS = UNKNOWN;
880     cps->memSize = FALSE;
881     cps->maxCores = FALSE;
882     ASSIGN(cps->egtFormats, "");
883
884     /* [HGM] options */
885     cps->optionSettings  = appData.engOptions[n];
886
887     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
888     cps->isUCI = appData.isUCI[n]; /* [AS] */
889     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
890     cps->highlight = 0;
891
892     if (appData.protocolVersion[n] > PROTOVER
893         || appData.protocolVersion[n] < 1)
894       {
895         char buf[MSG_SIZ];
896         int len;
897
898         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
899                        appData.protocolVersion[n]);
900         if( (len >= MSG_SIZ) && appData.debugMode )
901           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
902
903         DisplayFatalError(buf, 0, 2);
904       }
905     else
906       {
907         cps->protocolVersion = appData.protocolVersion[n];
908       }
909
910     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
911     ParseFeatures(appData.featureDefaults, cps);
912 }
913
914 ChessProgramState *savCps;
915
916 GameMode oldMode;
917
918 void
919 LoadEngine ()
920 {
921     int i;
922     if(WaitForEngine(savCps, LoadEngine)) return;
923     CommonEngineInit(); // recalculate time odds
924     if(gameInfo.variant != StringToVariant(appData.variant)) {
925         // we changed variant when loading the engine; this forces us to reset
926         Reset(TRUE, savCps != &first);
927         oldMode = BeginningOfGame; // to prevent restoring old mode
928     }
929     InitChessProgram(savCps, FALSE);
930     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
931     DisplayMessage("", "");
932     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
933     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
934     ThawUI();
935     SetGNUMode();
936     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
937 }
938
939 void
940 ReplaceEngine (ChessProgramState *cps, int n)
941 {
942     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
943     keepInfo = 1;
944     if(oldMode != BeginningOfGame) EditGameEvent();
945     keepInfo = 0;
946     UnloadEngine(cps);
947     appData.noChessProgram = FALSE;
948     appData.clockMode = TRUE;
949     InitEngine(cps, n);
950     UpdateLogos(TRUE);
951     if(n) return; // only startup first engine immediately; second can wait
952     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
953     LoadEngine();
954 }
955
956 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
957 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
958
959 static char resetOptions[] =
960         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
961         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
962         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
963         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
964
965 void
966 FloatToFront(char **list, char *engineLine)
967 {
968     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
969     int i=0;
970     if(appData.recentEngines <= 0) return;
971     TidyProgramName(engineLine, "localhost", tidy+1);
972     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
973     strncpy(buf+1, *list, MSG_SIZ-50);
974     if(p = strstr(buf, tidy)) { // tidy name appears in list
975         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
976         while(*p++ = *++q); // squeeze out
977     }
978     strcat(tidy, buf+1); // put list behind tidy name
979     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
980     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
981     ASSIGN(*list, tidy+1);
982 }
983
984 char *insert, *wbOptions, *currentEngine[2]; // point in ChessProgramNames were we should insert new engine
985
986 void
987 Load (ChessProgramState *cps, int i)
988 {
989     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
990     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
991         ASSIGN(currentEngine[i], engineLine);
992         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
993         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
994         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
995         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
996         appData.firstProtocolVersion = PROTOVER;
997         ParseArgsFromString(buf);
998         SwapEngines(i);
999         ReplaceEngine(cps, i);
1000         FloatToFront(&appData.recentEngineList, engineLine);
1001         if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1002         return;
1003     }
1004     p = engineName;
1005     while(q = strchr(p, SLASH)) p = q+1;
1006     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1007     if(engineDir[0] != NULLCHAR) {
1008         ASSIGN(appData.directory[i], engineDir); p = engineName;
1009     } else if(p != engineName) { // derive directory from engine path, when not given
1010         p[-1] = 0;
1011         ASSIGN(appData.directory[i], engineName);
1012         p[-1] = SLASH;
1013         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1014     } else { ASSIGN(appData.directory[i], "."); }
1015     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1016     if(params[0]) {
1017         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1018         snprintf(command, MSG_SIZ, "%s %s", p, params);
1019         p = command;
1020     }
1021     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1022     ASSIGN(appData.chessProgram[i], p);
1023     appData.isUCI[i] = isUCI;
1024     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1025     appData.hasOwnBookUCI[i] = hasBook;
1026     if(!nickName[0]) useNick = FALSE;
1027     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1028     if(addToList) {
1029         int len;
1030         char quote;
1031         q = firstChessProgramNames;
1032         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1033         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1034         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s",
1035                         quote, p, quote, appData.directory[i],
1036                         useNick ? " -fn \"" : "",
1037                         useNick ? nickName : "",
1038                         useNick ? "\"" : "",
1039                         v1 ? " -firstProtocolVersion 1" : "",
1040                         hasBook ? "" : " -fNoOwnBookUCI",
1041                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1042                         storeVariant ? " -variant " : "",
1043                         storeVariant ? VariantName(gameInfo.variant) : "");
1044         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " %s", wbOptions);
1045         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 2);
1046         if(insert != q) insert[-1] = NULLCHAR;
1047         snprintf(firstChessProgramNames, len, "%s\n%s\n%s", q, buf, insert);
1048         if(q)   free(q);
1049         FloatToFront(&appData.recentEngineList, buf);
1050         ASSIGN(currentEngine[i], buf);
1051     }
1052     ReplaceEngine(cps, i);
1053 }
1054
1055 void
1056 InitTimeControls ()
1057 {
1058     int matched, min, sec;
1059     /*
1060      * Parse timeControl resource
1061      */
1062     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1063                           appData.movesPerSession)) {
1064         char buf[MSG_SIZ];
1065         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1066         DisplayFatalError(buf, 0, 2);
1067     }
1068
1069     /*
1070      * Parse searchTime resource
1071      */
1072     if (*appData.searchTime != NULLCHAR) {
1073         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1074         if (matched == 1) {
1075             searchTime = min * 60;
1076         } else if (matched == 2) {
1077             searchTime = min * 60 + sec;
1078         } else {
1079             char buf[MSG_SIZ];
1080             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1081             DisplayFatalError(buf, 0, 2);
1082         }
1083     }
1084 }
1085
1086 void
1087 InitBackEnd1 ()
1088 {
1089
1090     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1091     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1092
1093     GetTimeMark(&programStartTime);
1094     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1095     appData.seedBase = random() + (random()<<15);
1096     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1097
1098     ClearProgramStats();
1099     programStats.ok_to_send = 1;
1100     programStats.seen_stat = 0;
1101
1102     /*
1103      * Initialize game list
1104      */
1105     ListNew(&gameList);
1106
1107
1108     /*
1109      * Internet chess server status
1110      */
1111     if (appData.icsActive) {
1112         appData.matchMode = FALSE;
1113         appData.matchGames = 0;
1114 #if ZIPPY
1115         appData.noChessProgram = !appData.zippyPlay;
1116 #else
1117         appData.zippyPlay = FALSE;
1118         appData.zippyTalk = FALSE;
1119         appData.noChessProgram = TRUE;
1120 #endif
1121         if (*appData.icsHelper != NULLCHAR) {
1122             appData.useTelnet = TRUE;
1123             appData.telnetProgram = appData.icsHelper;
1124         }
1125     } else {
1126         appData.zippyTalk = appData.zippyPlay = FALSE;
1127     }
1128
1129     /* [AS] Initialize pv info list [HGM] and game state */
1130     {
1131         int i, j;
1132
1133         for( i=0; i<=framePtr; i++ ) {
1134             pvInfoList[i].depth = -1;
1135             boards[i][EP_STATUS] = EP_NONE;
1136             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1137         }
1138     }
1139
1140     InitTimeControls();
1141
1142     /* [AS] Adjudication threshold */
1143     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1144
1145     InitEngine(&first, 0);
1146     InitEngine(&second, 1);
1147     CommonEngineInit();
1148
1149     pairing.which = "pairing"; // pairing engine
1150     pairing.pr = NoProc;
1151     pairing.isr = NULL;
1152     pairing.program = appData.pairingEngine;
1153     pairing.host = "localhost";
1154     pairing.dir = ".";
1155
1156     if (appData.icsActive) {
1157         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1158     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1159         appData.clockMode = FALSE;
1160         first.sendTime = second.sendTime = 0;
1161     }
1162
1163 #if ZIPPY
1164     /* Override some settings from environment variables, for backward
1165        compatibility.  Unfortunately it's not feasible to have the env
1166        vars just set defaults, at least in xboard.  Ugh.
1167     */
1168     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1169       ZippyInit();
1170     }
1171 #endif
1172
1173     if (!appData.icsActive) {
1174       char buf[MSG_SIZ];
1175       int len;
1176
1177       /* Check for variants that are supported only in ICS mode,
1178          or not at all.  Some that are accepted here nevertheless
1179          have bugs; see comments below.
1180       */
1181       VariantClass variant = StringToVariant(appData.variant);
1182       switch (variant) {
1183       case VariantBughouse:     /* need four players and two boards */
1184       case VariantKriegspiel:   /* need to hide pieces and move details */
1185         /* case VariantFischeRandom: (Fabien: moved below) */
1186         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1187         if( (len >= MSG_SIZ) && appData.debugMode )
1188           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1189
1190         DisplayFatalError(buf, 0, 2);
1191         return;
1192
1193       case VariantUnknown:
1194       case VariantLoadable:
1195       case Variant29:
1196       case Variant30:
1197       case Variant31:
1198       case Variant32:
1199       case Variant33:
1200       case Variant34:
1201       case Variant35:
1202       case Variant36:
1203       default:
1204         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1205         if( (len >= MSG_SIZ) && appData.debugMode )
1206           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1207
1208         DisplayFatalError(buf, 0, 2);
1209         return;
1210
1211       case VariantNormal:     /* definitely works! */
1212         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1213           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1214           return;
1215         }
1216       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1217       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1218       case VariantGothic:     /* [HGM] should work */
1219       case VariantCapablanca: /* [HGM] should work */
1220       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1221       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1222       case VariantChu:        /* [HGM] experimental */
1223       case VariantKnightmate: /* [HGM] should work */
1224       case VariantCylinder:   /* [HGM] untested */
1225       case VariantFalcon:     /* [HGM] untested */
1226       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1227                                  offboard interposition not understood */
1228       case VariantWildCastle: /* pieces not automatically shuffled */
1229       case VariantNoCastle:   /* pieces not automatically shuffled */
1230       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1231       case VariantLosers:     /* should work except for win condition,
1232                                  and doesn't know captures are mandatory */
1233       case VariantSuicide:    /* should work except for win condition,
1234                                  and doesn't know captures are mandatory */
1235       case VariantGiveaway:   /* should work except for win condition,
1236                                  and doesn't know captures are mandatory */
1237       case VariantTwoKings:   /* should work */
1238       case VariantAtomic:     /* should work except for win condition */
1239       case Variant3Check:     /* should work except for win condition */
1240       case VariantShatranj:   /* should work except for all win conditions */
1241       case VariantMakruk:     /* should work except for draw countdown */
1242       case VariantASEAN :     /* should work except for draw countdown */
1243       case VariantBerolina:   /* might work if TestLegality is off */
1244       case VariantCapaRandom: /* should work */
1245       case VariantJanus:      /* should work */
1246       case VariantSuper:      /* experimental */
1247       case VariantGreat:      /* experimental, requires legality testing to be off */
1248       case VariantSChess:     /* S-Chess, should work */
1249       case VariantGrand:      /* should work */
1250       case VariantSpartan:    /* should work */
1251       case VariantLion:       /* should work */
1252       case VariantChuChess:   /* should work */
1253         break;
1254       }
1255     }
1256
1257 }
1258
1259 int
1260 NextIntegerFromString (char ** str, long * value)
1261 {
1262     int result = -1;
1263     char * s = *str;
1264
1265     while( *s == ' ' || *s == '\t' ) {
1266         s++;
1267     }
1268
1269     *value = 0;
1270
1271     if( *s >= '0' && *s <= '9' ) {
1272         while( *s >= '0' && *s <= '9' ) {
1273             *value = *value * 10 + (*s - '0');
1274             s++;
1275         }
1276
1277         result = 0;
1278     }
1279
1280     *str = s;
1281
1282     return result;
1283 }
1284
1285 int
1286 NextTimeControlFromString (char ** str, long * value)
1287 {
1288     long temp;
1289     int result = NextIntegerFromString( str, &temp );
1290
1291     if( result == 0 ) {
1292         *value = temp * 60; /* Minutes */
1293         if( **str == ':' ) {
1294             (*str)++;
1295             result = NextIntegerFromString( str, &temp );
1296             *value += temp; /* Seconds */
1297         }
1298     }
1299
1300     return result;
1301 }
1302
1303 int
1304 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1305 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1306     int result = -1, type = 0; long temp, temp2;
1307
1308     if(**str != ':') return -1; // old params remain in force!
1309     (*str)++;
1310     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1311     if( NextIntegerFromString( str, &temp ) ) return -1;
1312     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1313
1314     if(**str != '/') {
1315         /* time only: incremental or sudden-death time control */
1316         if(**str == '+') { /* increment follows; read it */
1317             (*str)++;
1318             if(**str == '!') type = *(*str)++; // Bronstein TC
1319             if(result = NextIntegerFromString( str, &temp2)) return -1;
1320             *inc = temp2 * 1000;
1321             if(**str == '.') { // read fraction of increment
1322                 char *start = ++(*str);
1323                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1324                 temp2 *= 1000;
1325                 while(start++ < *str) temp2 /= 10;
1326                 *inc += temp2;
1327             }
1328         } else *inc = 0;
1329         *moves = 0; *tc = temp * 1000; *incType = type;
1330         return 0;
1331     }
1332
1333     (*str)++; /* classical time control */
1334     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1335
1336     if(result == 0) {
1337         *moves = temp;
1338         *tc    = temp2 * 1000;
1339         *inc   = 0;
1340         *incType = type;
1341     }
1342     return result;
1343 }
1344
1345 int
1346 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1347 {   /* [HGM] get time to add from the multi-session time-control string */
1348     int incType, moves=1; /* kludge to force reading of first session */
1349     long time, increment;
1350     char *s = tcString;
1351
1352     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1353     do {
1354         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1355         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1356         if(movenr == -1) return time;    /* last move before new session     */
1357         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1358         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1359         if(!moves) return increment;     /* current session is incremental   */
1360         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1361     } while(movenr >= -1);               /* try again for next session       */
1362
1363     return 0; // no new time quota on this move
1364 }
1365
1366 int
1367 ParseTimeControl (char *tc, float ti, int mps)
1368 {
1369   long tc1;
1370   long tc2;
1371   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1372   int min, sec=0;
1373
1374   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1375   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1376       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1377   if(ti > 0) {
1378
1379     if(mps)
1380       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1381     else
1382       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1383   } else {
1384     if(mps)
1385       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1386     else
1387       snprintf(buf, MSG_SIZ, ":%s", mytc);
1388   }
1389   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1390
1391   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1392     return FALSE;
1393   }
1394
1395   if( *tc == '/' ) {
1396     /* Parse second time control */
1397     tc++;
1398
1399     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1400       return FALSE;
1401     }
1402
1403     if( tc2 == 0 ) {
1404       return FALSE;
1405     }
1406
1407     timeControl_2 = tc2 * 1000;
1408   }
1409   else {
1410     timeControl_2 = 0;
1411   }
1412
1413   if( tc1 == 0 ) {
1414     return FALSE;
1415   }
1416
1417   timeControl = tc1 * 1000;
1418
1419   if (ti >= 0) {
1420     timeIncrement = ti * 1000;  /* convert to ms */
1421     movesPerSession = 0;
1422   } else {
1423     timeIncrement = 0;
1424     movesPerSession = mps;
1425   }
1426   return TRUE;
1427 }
1428
1429 void
1430 InitBackEnd2 ()
1431 {
1432     if (appData.debugMode) {
1433 #    ifdef __GIT_VERSION
1434       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1435 #    else
1436       fprintf(debugFP, "Version: %s\n", programVersion);
1437 #    endif
1438     }
1439     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1440
1441     set_cont_sequence(appData.wrapContSeq);
1442     if (appData.matchGames > 0) {
1443         appData.matchMode = TRUE;
1444     } else if (appData.matchMode) {
1445         appData.matchGames = 1;
1446     }
1447     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1448         appData.matchGames = appData.sameColorGames;
1449     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1450         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1451         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1452     }
1453     Reset(TRUE, FALSE);
1454     if (appData.noChessProgram || first.protocolVersion == 1) {
1455       InitBackEnd3();
1456     } else {
1457       /* kludge: allow timeout for initial "feature" commands */
1458       FreezeUI();
1459       DisplayMessage("", _("Starting chess program"));
1460       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1461     }
1462 }
1463
1464 int
1465 CalculateIndex (int index, int gameNr)
1466 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1467     int res;
1468     if(index > 0) return index; // fixed nmber
1469     if(index == 0) return 1;
1470     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1471     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1472     return res;
1473 }
1474
1475 int
1476 LoadGameOrPosition (int gameNr)
1477 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1478     if (*appData.loadGameFile != NULLCHAR) {
1479         if (!LoadGameFromFile(appData.loadGameFile,
1480                 CalculateIndex(appData.loadGameIndex, gameNr),
1481                               appData.loadGameFile, FALSE)) {
1482             DisplayFatalError(_("Bad game file"), 0, 1);
1483             return 0;
1484         }
1485     } else if (*appData.loadPositionFile != NULLCHAR) {
1486         if (!LoadPositionFromFile(appData.loadPositionFile,
1487                 CalculateIndex(appData.loadPositionIndex, gameNr),
1488                                   appData.loadPositionFile)) {
1489             DisplayFatalError(_("Bad position file"), 0, 1);
1490             return 0;
1491         }
1492     }
1493     return 1;
1494 }
1495
1496 void
1497 ReserveGame (int gameNr, char resChar)
1498 {
1499     FILE *tf = fopen(appData.tourneyFile, "r+");
1500     char *p, *q, c, buf[MSG_SIZ];
1501     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1502     safeStrCpy(buf, lastMsg, MSG_SIZ);
1503     DisplayMessage(_("Pick new game"), "");
1504     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1505     ParseArgsFromFile(tf);
1506     p = q = appData.results;
1507     if(appData.debugMode) {
1508       char *r = appData.participants;
1509       fprintf(debugFP, "results = '%s'\n", p);
1510       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1511       fprintf(debugFP, "\n");
1512     }
1513     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1514     nextGame = q - p;
1515     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1516     safeStrCpy(q, p, strlen(p) + 2);
1517     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1518     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1519     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1520         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1521         q[nextGame] = '*';
1522     }
1523     fseek(tf, -(strlen(p)+4), SEEK_END);
1524     c = fgetc(tf);
1525     if(c != '"') // depending on DOS or Unix line endings we can be one off
1526          fseek(tf, -(strlen(p)+2), SEEK_END);
1527     else fseek(tf, -(strlen(p)+3), SEEK_END);
1528     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1529     DisplayMessage(buf, "");
1530     free(p); appData.results = q;
1531     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1532        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1533       int round = appData.defaultMatchGames * appData.tourneyType;
1534       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1535          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1536         UnloadEngine(&first);  // next game belongs to other pairing;
1537         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1538     }
1539     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1540 }
1541
1542 void
1543 MatchEvent (int mode)
1544 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1545         int dummy;
1546         if(matchMode) { // already in match mode: switch it off
1547             abortMatch = TRUE;
1548             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1549             return;
1550         }
1551 //      if(gameMode != BeginningOfGame) {
1552 //          DisplayError(_("You can only start a match from the initial position."), 0);
1553 //          return;
1554 //      }
1555         abortMatch = FALSE;
1556         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1557         /* Set up machine vs. machine match */
1558         nextGame = 0;
1559         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1560         if(appData.tourneyFile[0]) {
1561             ReserveGame(-1, 0);
1562             if(nextGame > appData.matchGames) {
1563                 char buf[MSG_SIZ];
1564                 if(strchr(appData.results, '*') == NULL) {
1565                     FILE *f;
1566                     appData.tourneyCycles++;
1567                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1568                         fclose(f);
1569                         NextTourneyGame(-1, &dummy);
1570                         ReserveGame(-1, 0);
1571                         if(nextGame <= appData.matchGames) {
1572                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1573                             matchMode = mode;
1574                             ScheduleDelayedEvent(NextMatchGame, 10000);
1575                             return;
1576                         }
1577                     }
1578                 }
1579                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1580                 DisplayError(buf, 0);
1581                 appData.tourneyFile[0] = 0;
1582                 return;
1583             }
1584         } else
1585         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1586             DisplayFatalError(_("Can't have a match with no chess programs"),
1587                               0, 2);
1588             return;
1589         }
1590         matchMode = mode;
1591         matchGame = roundNr = 1;
1592         first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1593         NextMatchGame();
1594 }
1595
1596 void
1597 InitBackEnd3 P((void))
1598 {
1599     GameMode initialMode;
1600     char buf[MSG_SIZ];
1601     int err, len;
1602
1603     ParseFeatures(appData.features[0], &first);
1604     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1605        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1606         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1607        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1608        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1609         char c, *q = first.variants, *p = strchr(q, ',');
1610         if(p) *p = NULLCHAR;
1611         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1612             int w, h, s;
1613             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1614                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1615             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1616             Reset(TRUE, FALSE);         // and re-initialize
1617         }
1618         if(p) *p = ',';
1619     }
1620
1621     InitChessProgram(&first, startedFromSetupPosition);
1622
1623     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1624         free(programVersion);
1625         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1626         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1627         FloatToFront(&appData.recentEngineList, currentEngine[0] ? currentEngine[0] : appData.firstChessProgram);
1628     }
1629
1630     if (appData.icsActive) {
1631 #ifdef WIN32
1632         /* [DM] Make a console window if needed [HGM] merged ifs */
1633         ConsoleCreate();
1634 #endif
1635         err = establish();
1636         if (err != 0)
1637           {
1638             if (*appData.icsCommPort != NULLCHAR)
1639               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1640                              appData.icsCommPort);
1641             else
1642               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1643                         appData.icsHost, appData.icsPort);
1644
1645             if( (len >= MSG_SIZ) && appData.debugMode )
1646               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1647
1648             DisplayFatalError(buf, err, 1);
1649             return;
1650         }
1651         SetICSMode();
1652         telnetISR =
1653           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1654         fromUserISR =
1655           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1656         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1657             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1658     } else if (appData.noChessProgram) {
1659         SetNCPMode();
1660     } else {
1661         SetGNUMode();
1662     }
1663
1664     if (*appData.cmailGameName != NULLCHAR) {
1665         SetCmailMode();
1666         OpenLoopback(&cmailPR);
1667         cmailISR =
1668           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1669     }
1670
1671     ThawUI();
1672     DisplayMessage("", "");
1673     if (StrCaseCmp(appData.initialMode, "") == 0) {
1674       initialMode = BeginningOfGame;
1675       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1676         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1677         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1678         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1679         ModeHighlight();
1680       }
1681     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1682       initialMode = TwoMachinesPlay;
1683     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1684       initialMode = AnalyzeFile;
1685     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1686       initialMode = AnalyzeMode;
1687     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1688       initialMode = MachinePlaysWhite;
1689     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1690       initialMode = MachinePlaysBlack;
1691     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1692       initialMode = EditGame;
1693     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1694       initialMode = EditPosition;
1695     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1696       initialMode = Training;
1697     } else {
1698       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1699       if( (len >= MSG_SIZ) && appData.debugMode )
1700         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1701
1702       DisplayFatalError(buf, 0, 2);
1703       return;
1704     }
1705
1706     if (appData.matchMode) {
1707         if(appData.tourneyFile[0]) { // start tourney from command line
1708             FILE *f;
1709             if(f = fopen(appData.tourneyFile, "r")) {
1710                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1711                 fclose(f);
1712                 appData.clockMode = TRUE;
1713                 SetGNUMode();
1714             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1715         }
1716         MatchEvent(TRUE);
1717     } else if (*appData.cmailGameName != NULLCHAR) {
1718         /* Set up cmail mode */
1719         ReloadCmailMsgEvent(TRUE);
1720     } else {
1721         /* Set up other modes */
1722         if (initialMode == AnalyzeFile) {
1723           if (*appData.loadGameFile == NULLCHAR) {
1724             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1725             return;
1726           }
1727         }
1728         if (*appData.loadGameFile != NULLCHAR) {
1729             (void) LoadGameFromFile(appData.loadGameFile,
1730                                     appData.loadGameIndex,
1731                                     appData.loadGameFile, TRUE);
1732         } else if (*appData.loadPositionFile != NULLCHAR) {
1733             (void) LoadPositionFromFile(appData.loadPositionFile,
1734                                         appData.loadPositionIndex,
1735                                         appData.loadPositionFile);
1736             /* [HGM] try to make self-starting even after FEN load */
1737             /* to allow automatic setup of fairy variants with wtm */
1738             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1739                 gameMode = BeginningOfGame;
1740                 setboardSpoiledMachineBlack = 1;
1741             }
1742             /* [HGM] loadPos: make that every new game uses the setup */
1743             /* from file as long as we do not switch variant          */
1744             if(!blackPlaysFirst) {
1745                 startedFromPositionFile = TRUE;
1746                 CopyBoard(filePosition, boards[0]);
1747                 CopyBoard(initialPosition, boards[0]);
1748             }
1749         } else if(*appData.fen != NULLCHAR) {
1750             if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1751                 startedFromPositionFile = TRUE;
1752                 Reset(TRUE, TRUE);
1753             }
1754         }
1755         if (initialMode == AnalyzeMode) {
1756           if (appData.noChessProgram) {
1757             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1758             return;
1759           }
1760           if (appData.icsActive) {
1761             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1762             return;
1763           }
1764           AnalyzeModeEvent();
1765         } else if (initialMode == AnalyzeFile) {
1766           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1767           ShowThinkingEvent();
1768           AnalyzeFileEvent();
1769           AnalysisPeriodicEvent(1);
1770         } else if (initialMode == MachinePlaysWhite) {
1771           if (appData.noChessProgram) {
1772             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1773                               0, 2);
1774             return;
1775           }
1776           if (appData.icsActive) {
1777             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1778                               0, 2);
1779             return;
1780           }
1781           MachineWhiteEvent();
1782         } else if (initialMode == MachinePlaysBlack) {
1783           if (appData.noChessProgram) {
1784             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1785                               0, 2);
1786             return;
1787           }
1788           if (appData.icsActive) {
1789             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1790                               0, 2);
1791             return;
1792           }
1793           MachineBlackEvent();
1794         } else if (initialMode == TwoMachinesPlay) {
1795           if (appData.noChessProgram) {
1796             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1797                               0, 2);
1798             return;
1799           }
1800           if (appData.icsActive) {
1801             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1802                               0, 2);
1803             return;
1804           }
1805           TwoMachinesEvent();
1806         } else if (initialMode == EditGame) {
1807           EditGameEvent();
1808         } else if (initialMode == EditPosition) {
1809           EditPositionEvent();
1810         } else if (initialMode == Training) {
1811           if (*appData.loadGameFile == NULLCHAR) {
1812             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1813             return;
1814           }
1815           TrainingEvent();
1816         }
1817     }
1818 }
1819
1820 void
1821 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1822 {
1823     DisplayBook(current+1);
1824
1825     MoveHistorySet( movelist, first, last, current, pvInfoList );
1826
1827     EvalGraphSet( first, last, current, pvInfoList );
1828
1829     MakeEngineOutputTitle();
1830 }
1831
1832 /*
1833  * Establish will establish a contact to a remote host.port.
1834  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1835  *  used to talk to the host.
1836  * Returns 0 if okay, error code if not.
1837  */
1838 int
1839 establish ()
1840 {
1841     char buf[MSG_SIZ];
1842
1843     if (*appData.icsCommPort != NULLCHAR) {
1844         /* Talk to the host through a serial comm port */
1845         return OpenCommPort(appData.icsCommPort, &icsPR);
1846
1847     } else if (*appData.gateway != NULLCHAR) {
1848         if (*appData.remoteShell == NULLCHAR) {
1849             /* Use the rcmd protocol to run telnet program on a gateway host */
1850             snprintf(buf, sizeof(buf), "%s %s %s",
1851                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1852             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1853
1854         } else {
1855             /* Use the rsh program to run telnet program on a gateway host */
1856             if (*appData.remoteUser == NULLCHAR) {
1857                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1858                         appData.gateway, appData.telnetProgram,
1859                         appData.icsHost, appData.icsPort);
1860             } else {
1861                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1862                         appData.remoteShell, appData.gateway,
1863                         appData.remoteUser, appData.telnetProgram,
1864                         appData.icsHost, appData.icsPort);
1865             }
1866             return StartChildProcess(buf, "", &icsPR);
1867
1868         }
1869     } else if (appData.useTelnet) {
1870         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1871
1872     } else {
1873         /* TCP socket interface differs somewhat between
1874            Unix and NT; handle details in the front end.
1875            */
1876         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1877     }
1878 }
1879
1880 void
1881 EscapeExpand (char *p, char *q)
1882 {       // [HGM] initstring: routine to shape up string arguments
1883         while(*p++ = *q++) if(p[-1] == '\\')
1884             switch(*q++) {
1885                 case 'n': p[-1] = '\n'; break;
1886                 case 'r': p[-1] = '\r'; break;
1887                 case 't': p[-1] = '\t'; break;
1888                 case '\\': p[-1] = '\\'; break;
1889                 case 0: *p = 0; return;
1890                 default: p[-1] = q[-1]; break;
1891             }
1892 }
1893
1894 void
1895 show_bytes (FILE *fp, char *buf, int count)
1896 {
1897     while (count--) {
1898         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1899             fprintf(fp, "\\%03o", *buf & 0xff);
1900         } else {
1901             putc(*buf, fp);
1902         }
1903         buf++;
1904     }
1905     fflush(fp);
1906 }
1907
1908 /* Returns an errno value */
1909 int
1910 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1911 {
1912     char buf[8192], *p, *q, *buflim;
1913     int left, newcount, outcount;
1914
1915     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1916         *appData.gateway != NULLCHAR) {
1917         if (appData.debugMode) {
1918             fprintf(debugFP, ">ICS: ");
1919             show_bytes(debugFP, message, count);
1920             fprintf(debugFP, "\n");
1921         }
1922         return OutputToProcess(pr, message, count, outError);
1923     }
1924
1925     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1926     p = message;
1927     q = buf;
1928     left = count;
1929     newcount = 0;
1930     while (left) {
1931         if (q >= buflim) {
1932             if (appData.debugMode) {
1933                 fprintf(debugFP, ">ICS: ");
1934                 show_bytes(debugFP, buf, newcount);
1935                 fprintf(debugFP, "\n");
1936             }
1937             outcount = OutputToProcess(pr, buf, newcount, outError);
1938             if (outcount < newcount) return -1; /* to be sure */
1939             q = buf;
1940             newcount = 0;
1941         }
1942         if (*p == '\n') {
1943             *q++ = '\r';
1944             newcount++;
1945         } else if (((unsigned char) *p) == TN_IAC) {
1946             *q++ = (char) TN_IAC;
1947             newcount ++;
1948         }
1949         *q++ = *p++;
1950         newcount++;
1951         left--;
1952     }
1953     if (appData.debugMode) {
1954         fprintf(debugFP, ">ICS: ");
1955         show_bytes(debugFP, buf, newcount);
1956         fprintf(debugFP, "\n");
1957     }
1958     outcount = OutputToProcess(pr, buf, newcount, outError);
1959     if (outcount < newcount) return -1; /* to be sure */
1960     return count;
1961 }
1962
1963 void
1964 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1965 {
1966     int outError, outCount;
1967     static int gotEof = 0;
1968     static FILE *ini;
1969
1970     /* Pass data read from player on to ICS */
1971     if (count > 0) {
1972         gotEof = 0;
1973         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1974         if (outCount < count) {
1975             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1976         }
1977         if(have_sent_ICS_logon == 2) {
1978           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1979             fprintf(ini, "%s", message);
1980             have_sent_ICS_logon = 3;
1981           } else
1982             have_sent_ICS_logon = 1;
1983         } else if(have_sent_ICS_logon == 3) {
1984             fprintf(ini, "%s", message);
1985             fclose(ini);
1986           have_sent_ICS_logon = 1;
1987         }
1988     } else if (count < 0) {
1989         RemoveInputSource(isr);
1990         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1991     } else if (gotEof++ > 0) {
1992         RemoveInputSource(isr);
1993         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1994     }
1995 }
1996
1997 void
1998 KeepAlive ()
1999 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
2000     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2001     connectionAlive = FALSE; // only sticks if no response to 'date' command.
2002     SendToICS("date\n");
2003     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2004 }
2005
2006 /* added routine for printf style output to ics */
2007 void
2008 ics_printf (char *format, ...)
2009 {
2010     char buffer[MSG_SIZ];
2011     va_list args;
2012
2013     va_start(args, format);
2014     vsnprintf(buffer, sizeof(buffer), format, args);
2015     buffer[sizeof(buffer)-1] = '\0';
2016     SendToICS(buffer);
2017     va_end(args);
2018 }
2019
2020 void
2021 SendToICS (char *s)
2022 {
2023     int count, outCount, outError;
2024
2025     if (icsPR == NoProc) return;
2026
2027     count = strlen(s);
2028     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2029     if (outCount < count) {
2030         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2031     }
2032 }
2033
2034 /* This is used for sending logon scripts to the ICS. Sending
2035    without a delay causes problems when using timestamp on ICC
2036    (at least on my machine). */
2037 void
2038 SendToICSDelayed (char *s, long msdelay)
2039 {
2040     int count, outCount, outError;
2041
2042     if (icsPR == NoProc) return;
2043
2044     count = strlen(s);
2045     if (appData.debugMode) {
2046         fprintf(debugFP, ">ICS: ");
2047         show_bytes(debugFP, s, count);
2048         fprintf(debugFP, "\n");
2049     }
2050     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2051                                       msdelay);
2052     if (outCount < count) {
2053         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2054     }
2055 }
2056
2057
2058 /* Remove all highlighting escape sequences in s
2059    Also deletes any suffix starting with '('
2060    */
2061 char *
2062 StripHighlightAndTitle (char *s)
2063 {
2064     static char retbuf[MSG_SIZ];
2065     char *p = retbuf;
2066
2067     while (*s != NULLCHAR) {
2068         while (*s == '\033') {
2069             while (*s != NULLCHAR && !isalpha(*s)) s++;
2070             if (*s != NULLCHAR) s++;
2071         }
2072         while (*s != NULLCHAR && *s != '\033') {
2073             if (*s == '(' || *s == '[') {
2074                 *p = NULLCHAR;
2075                 return retbuf;
2076             }
2077             *p++ = *s++;
2078         }
2079     }
2080     *p = NULLCHAR;
2081     return retbuf;
2082 }
2083
2084 /* Remove all highlighting escape sequences in s */
2085 char *
2086 StripHighlight (char *s)
2087 {
2088     static char retbuf[MSG_SIZ];
2089     char *p = retbuf;
2090
2091     while (*s != NULLCHAR) {
2092         while (*s == '\033') {
2093             while (*s != NULLCHAR && !isalpha(*s)) s++;
2094             if (*s != NULLCHAR) s++;
2095         }
2096         while (*s != NULLCHAR && *s != '\033') {
2097             *p++ = *s++;
2098         }
2099     }
2100     *p = NULLCHAR;
2101     return retbuf;
2102 }
2103
2104 char engineVariant[MSG_SIZ];
2105 char *variantNames[] = VARIANT_NAMES;
2106 char *
2107 VariantName (VariantClass v)
2108 {
2109     if(v == VariantUnknown || *engineVariant) return engineVariant;
2110     return variantNames[v];
2111 }
2112
2113
2114 /* Identify a variant from the strings the chess servers use or the
2115    PGN Variant tag names we use. */
2116 VariantClass
2117 StringToVariant (char *e)
2118 {
2119     char *p;
2120     int wnum = -1;
2121     VariantClass v = VariantNormal;
2122     int i, found = FALSE;
2123     char buf[MSG_SIZ], c;
2124     int len;
2125
2126     if (!e) return v;
2127
2128     /* [HGM] skip over optional board-size prefixes */
2129     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2130         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2131         while( *e++ != '_');
2132     }
2133
2134     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2135         v = VariantNormal;
2136         found = TRUE;
2137     } else
2138     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2139       if (p = StrCaseStr(e, variantNames[i])) {
2140         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2141         v = (VariantClass) i;
2142         found = TRUE;
2143         break;
2144       }
2145     }
2146
2147     if (!found) {
2148       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2149           || StrCaseStr(e, "wild/fr")
2150           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2151         v = VariantFischeRandom;
2152       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2153                  (i = 1, p = StrCaseStr(e, "w"))) {
2154         p += i;
2155         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2156         if (isdigit(*p)) {
2157           wnum = atoi(p);
2158         } else {
2159           wnum = -1;
2160         }
2161         switch (wnum) {
2162         case 0: /* FICS only, actually */
2163         case 1:
2164           /* Castling legal even if K starts on d-file */
2165           v = VariantWildCastle;
2166           break;
2167         case 2:
2168         case 3:
2169         case 4:
2170           /* Castling illegal even if K & R happen to start in
2171              normal positions. */
2172           v = VariantNoCastle;
2173           break;
2174         case 5:
2175         case 7:
2176         case 8:
2177         case 10:
2178         case 11:
2179         case 12:
2180         case 13:
2181         case 14:
2182         case 15:
2183         case 18:
2184         case 19:
2185           /* Castling legal iff K & R start in normal positions */
2186           v = VariantNormal;
2187           break;
2188         case 6:
2189         case 20:
2190         case 21:
2191           /* Special wilds for position setup; unclear what to do here */
2192           v = VariantLoadable;
2193           break;
2194         case 9:
2195           /* Bizarre ICC game */
2196           v = VariantTwoKings;
2197           break;
2198         case 16:
2199           v = VariantKriegspiel;
2200           break;
2201         case 17:
2202           v = VariantLosers;
2203           break;
2204         case 22:
2205           v = VariantFischeRandom;
2206           break;
2207         case 23:
2208           v = VariantCrazyhouse;
2209           break;
2210         case 24:
2211           v = VariantBughouse;
2212           break;
2213         case 25:
2214           v = Variant3Check;
2215           break;
2216         case 26:
2217           /* Not quite the same as FICS suicide! */
2218           v = VariantGiveaway;
2219           break;
2220         case 27:
2221           v = VariantAtomic;
2222           break;
2223         case 28:
2224           v = VariantShatranj;
2225           break;
2226
2227         /* Temporary names for future ICC types.  The name *will* change in
2228            the next xboard/WinBoard release after ICC defines it. */
2229         case 29:
2230           v = Variant29;
2231           break;
2232         case 30:
2233           v = Variant30;
2234           break;
2235         case 31:
2236           v = Variant31;
2237           break;
2238         case 32:
2239           v = Variant32;
2240           break;
2241         case 33:
2242           v = Variant33;
2243           break;
2244         case 34:
2245           v = Variant34;
2246           break;
2247         case 35:
2248           v = Variant35;
2249           break;
2250         case 36:
2251           v = Variant36;
2252           break;
2253         case 37:
2254           v = VariantShogi;
2255           break;
2256         case 38:
2257           v = VariantXiangqi;
2258           break;
2259         case 39:
2260           v = VariantCourier;
2261           break;
2262         case 40:
2263           v = VariantGothic;
2264           break;
2265         case 41:
2266           v = VariantCapablanca;
2267           break;
2268         case 42:
2269           v = VariantKnightmate;
2270           break;
2271         case 43:
2272           v = VariantFairy;
2273           break;
2274         case 44:
2275           v = VariantCylinder;
2276           break;
2277         case 45:
2278           v = VariantFalcon;
2279           break;
2280         case 46:
2281           v = VariantCapaRandom;
2282           break;
2283         case 47:
2284           v = VariantBerolina;
2285           break;
2286         case 48:
2287           v = VariantJanus;
2288           break;
2289         case 49:
2290           v = VariantSuper;
2291           break;
2292         case 50:
2293           v = VariantGreat;
2294           break;
2295         case -1:
2296           /* Found "wild" or "w" in the string but no number;
2297              must assume it's normal chess. */
2298           v = VariantNormal;
2299           break;
2300         default:
2301           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2302           if( (len >= MSG_SIZ) && appData.debugMode )
2303             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2304
2305           DisplayError(buf, 0);
2306           v = VariantUnknown;
2307           break;
2308         }
2309       }
2310     }
2311     if (appData.debugMode) {
2312       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2313               e, wnum, VariantName(v));
2314     }
2315     return v;
2316 }
2317
2318 static int leftover_start = 0, leftover_len = 0;
2319 char star_match[STAR_MATCH_N][MSG_SIZ];
2320
2321 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2322    advance *index beyond it, and set leftover_start to the new value of
2323    *index; else return FALSE.  If pattern contains the character '*', it
2324    matches any sequence of characters not containing '\r', '\n', or the
2325    character following the '*' (if any), and the matched sequence(s) are
2326    copied into star_match.
2327    */
2328 int
2329 looking_at ( char *buf, int *index, char *pattern)
2330 {
2331     char *bufp = &buf[*index], *patternp = pattern;
2332     int star_count = 0;
2333     char *matchp = star_match[0];
2334
2335     for (;;) {
2336         if (*patternp == NULLCHAR) {
2337             *index = leftover_start = bufp - buf;
2338             *matchp = NULLCHAR;
2339             return TRUE;
2340         }
2341         if (*bufp == NULLCHAR) return FALSE;
2342         if (*patternp == '*') {
2343             if (*bufp == *(patternp + 1)) {
2344                 *matchp = NULLCHAR;
2345                 matchp = star_match[++star_count];
2346                 patternp += 2;
2347                 bufp++;
2348                 continue;
2349             } else if (*bufp == '\n' || *bufp == '\r') {
2350                 patternp++;
2351                 if (*patternp == NULLCHAR)
2352                   continue;
2353                 else
2354                   return FALSE;
2355             } else {
2356                 *matchp++ = *bufp++;
2357                 continue;
2358             }
2359         }
2360         if (*patternp != *bufp) return FALSE;
2361         patternp++;
2362         bufp++;
2363     }
2364 }
2365
2366 void
2367 SendToPlayer (char *data, int length)
2368 {
2369     int error, outCount;
2370     outCount = OutputToProcess(NoProc, data, length, &error);
2371     if (outCount < length) {
2372         DisplayFatalError(_("Error writing to display"), error, 1);
2373     }
2374 }
2375
2376 void
2377 PackHolding (char packed[], char *holding)
2378 {
2379     char *p = holding;
2380     char *q = packed;
2381     int runlength = 0;
2382     int curr = 9999;
2383     do {
2384         if (*p == curr) {
2385             runlength++;
2386         } else {
2387             switch (runlength) {
2388               case 0:
2389                 break;
2390               case 1:
2391                 *q++ = curr;
2392                 break;
2393               case 2:
2394                 *q++ = curr;
2395                 *q++ = curr;
2396                 break;
2397               default:
2398                 sprintf(q, "%d", runlength);
2399                 while (*q) q++;
2400                 *q++ = curr;
2401                 break;
2402             }
2403             runlength = 1;
2404             curr = *p;
2405         }
2406     } while (*p++);
2407     *q = NULLCHAR;
2408 }
2409
2410 /* Telnet protocol requests from the front end */
2411 void
2412 TelnetRequest (unsigned char ddww, unsigned char option)
2413 {
2414     unsigned char msg[3];
2415     int outCount, outError;
2416
2417     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2418
2419     if (appData.debugMode) {
2420         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2421         switch (ddww) {
2422           case TN_DO:
2423             ddwwStr = "DO";
2424             break;
2425           case TN_DONT:
2426             ddwwStr = "DONT";
2427             break;
2428           case TN_WILL:
2429             ddwwStr = "WILL";
2430             break;
2431           case TN_WONT:
2432             ddwwStr = "WONT";
2433             break;
2434           default:
2435             ddwwStr = buf1;
2436             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2437             break;
2438         }
2439         switch (option) {
2440           case TN_ECHO:
2441             optionStr = "ECHO";
2442             break;
2443           default:
2444             optionStr = buf2;
2445             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2446             break;
2447         }
2448         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2449     }
2450     msg[0] = TN_IAC;
2451     msg[1] = ddww;
2452     msg[2] = option;
2453     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2454     if (outCount < 3) {
2455         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2456     }
2457 }
2458
2459 void
2460 DoEcho ()
2461 {
2462     if (!appData.icsActive) return;
2463     TelnetRequest(TN_DO, TN_ECHO);
2464 }
2465
2466 void
2467 DontEcho ()
2468 {
2469     if (!appData.icsActive) return;
2470     TelnetRequest(TN_DONT, TN_ECHO);
2471 }
2472
2473 void
2474 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2475 {
2476     /* put the holdings sent to us by the server on the board holdings area */
2477     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2478     char p;
2479     ChessSquare piece;
2480
2481     if(gameInfo.holdingsWidth < 2)  return;
2482     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2483         return; // prevent overwriting by pre-board holdings
2484
2485     if( (int)lowestPiece >= BlackPawn ) {
2486         holdingsColumn = 0;
2487         countsColumn = 1;
2488         holdingsStartRow = handSize-1;
2489         direction = -1;
2490     } else {
2491         holdingsColumn = BOARD_WIDTH-1;
2492         countsColumn = BOARD_WIDTH-2;
2493         holdingsStartRow = 0;
2494         direction = 1;
2495     }
2496
2497     for(i=0; i<BOARD_RANKS-1; i++) { /* clear holdings */
2498         board[i][holdingsColumn] = EmptySquare;
2499         board[i][countsColumn]   = (ChessSquare) 0;
2500     }
2501     while( (p=*holdings++) != NULLCHAR ) {
2502         piece = CharToPiece( ToUpper(p) );
2503         if(piece == EmptySquare) continue;
2504         /*j = (int) piece - (int) WhitePawn;*/
2505         j = PieceToNumber(piece);
2506         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2507         if(j < 0) continue;               /* should not happen */
2508         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2509         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2510         board[holdingsStartRow+j*direction][countsColumn]++;
2511     }
2512 }
2513
2514
2515 void
2516 VariantSwitch (Board board, VariantClass newVariant)
2517 {
2518    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2519    static Board oldBoard;
2520
2521    startedFromPositionFile = FALSE;
2522    if(gameInfo.variant == newVariant) return;
2523
2524    /* [HGM] This routine is called each time an assignment is made to
2525     * gameInfo.variant during a game, to make sure the board sizes
2526     * are set to match the new variant. If that means adding or deleting
2527     * holdings, we shift the playing board accordingly
2528     * This kludge is needed because in ICS observe mode, we get boards
2529     * of an ongoing game without knowing the variant, and learn about the
2530     * latter only later. This can be because of the move list we requested,
2531     * in which case the game history is refilled from the beginning anyway,
2532     * but also when receiving holdings of a crazyhouse game. In the latter
2533     * case we want to add those holdings to the already received position.
2534     */
2535
2536
2537    if (appData.debugMode) {
2538      fprintf(debugFP, "Switch board from %s to %s\n",
2539              VariantName(gameInfo.variant), VariantName(newVariant));
2540      setbuf(debugFP, NULL);
2541    }
2542    shuffleOpenings = 0;       /* [HGM] shuffle */
2543    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2544    switch(newVariant)
2545      {
2546      case VariantShogi:
2547        newWidth = 9;  newHeight = 9;
2548        gameInfo.holdingsSize = 7;
2549      case VariantBughouse:
2550      case VariantCrazyhouse:
2551        newHoldingsWidth = 2; break;
2552      case VariantGreat:
2553        newWidth = 10;
2554      case VariantSuper:
2555        newHoldingsWidth = 2;
2556        gameInfo.holdingsSize = 8;
2557        break;
2558      case VariantGothic:
2559      case VariantCapablanca:
2560      case VariantCapaRandom:
2561        newWidth = 10;
2562      default:
2563        newHoldingsWidth = gameInfo.holdingsSize = 0;
2564      };
2565
2566    if(newWidth  != gameInfo.boardWidth  ||
2567       newHeight != gameInfo.boardHeight ||
2568       newHoldingsWidth != gameInfo.holdingsWidth ) {
2569
2570      /* shift position to new playing area, if needed */
2571      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2572        for(i=0; i<BOARD_HEIGHT; i++)
2573          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2574            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2575              board[i][j];
2576        for(i=0; i<newHeight; i++) {
2577          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2578          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2579        }
2580      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2581        for(i=0; i<BOARD_HEIGHT; i++)
2582          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2583            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2584              board[i][j];
2585      }
2586      board[HOLDINGS_SET] = 0;
2587      gameInfo.boardWidth  = newWidth;
2588      gameInfo.boardHeight = newHeight;
2589      gameInfo.holdingsWidth = newHoldingsWidth;
2590      gameInfo.variant = newVariant;
2591      InitDrawingSizes(-2, 0);
2592    } else gameInfo.variant = newVariant;
2593    CopyBoard(oldBoard, board);   // remember correctly formatted board
2594      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2595    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2596 }
2597
2598 static int loggedOn = FALSE;
2599
2600 /*-- Game start info cache: --*/
2601 int gs_gamenum;
2602 char gs_kind[MSG_SIZ];
2603 static char player1Name[128] = "";
2604 static char player2Name[128] = "";
2605 static char cont_seq[] = "\n\\   ";
2606 static int player1Rating = -1;
2607 static int player2Rating = -1;
2608 /*----------------------------*/
2609
2610 ColorClass curColor = ColorNormal;
2611 int suppressKibitz = 0;
2612
2613 // [HGM] seekgraph
2614 Boolean soughtPending = FALSE;
2615 Boolean seekGraphUp;
2616 #define MAX_SEEK_ADS 200
2617 #define SQUARE 0x80
2618 char *seekAdList[MAX_SEEK_ADS];
2619 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2620 float tcList[MAX_SEEK_ADS];
2621 char colorList[MAX_SEEK_ADS];
2622 int nrOfSeekAds = 0;
2623 int minRating = 1010, maxRating = 2800;
2624 int hMargin = 10, vMargin = 20, h, w;
2625 extern int squareSize, lineGap;
2626
2627 void
2628 PlotSeekAd (int i)
2629 {
2630         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2631         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2632         if(r < minRating+100 && r >=0 ) r = minRating+100;
2633         if(r > maxRating) r = maxRating;
2634         if(tc < 1.f) tc = 1.f;
2635         if(tc > 95.f) tc = 95.f;
2636         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2637         y = ((double)r - minRating)/(maxRating - minRating)
2638             * (h-vMargin-squareSize/8-1) + vMargin;
2639         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2640         if(strstr(seekAdList[i], " u ")) color = 1;
2641         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2642            !strstr(seekAdList[i], "bullet") &&
2643            !strstr(seekAdList[i], "blitz") &&
2644            !strstr(seekAdList[i], "standard") ) color = 2;
2645         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2646         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2647 }
2648
2649 void
2650 PlotSingleSeekAd (int i)
2651 {
2652         PlotSeekAd(i);
2653 }
2654
2655 void
2656 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2657 {
2658         char buf[MSG_SIZ], *ext = "";
2659         VariantClass v = StringToVariant(type);
2660         if(strstr(type, "wild")) {
2661             ext = type + 4; // append wild number
2662             if(v == VariantFischeRandom) type = "chess960"; else
2663             if(v == VariantLoadable) type = "setup"; else
2664             type = VariantName(v);
2665         }
2666         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2667         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2668             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2669             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2670             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2671             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2672             seekNrList[nrOfSeekAds] = nr;
2673             zList[nrOfSeekAds] = 0;
2674             seekAdList[nrOfSeekAds++] = StrSave(buf);
2675             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2676         }
2677 }
2678
2679 void
2680 EraseSeekDot (int i)
2681 {
2682     int x = xList[i], y = yList[i], d=squareSize/4, k;
2683     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2684     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2685     // now replot every dot that overlapped
2686     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2687         int xx = xList[k], yy = yList[k];
2688         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2689             DrawSeekDot(xx, yy, colorList[k]);
2690     }
2691 }
2692
2693 void
2694 RemoveSeekAd (int nr)
2695 {
2696         int i;
2697         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2698             EraseSeekDot(i);
2699             if(seekAdList[i]) free(seekAdList[i]);
2700             seekAdList[i] = seekAdList[--nrOfSeekAds];
2701             seekNrList[i] = seekNrList[nrOfSeekAds];
2702             ratingList[i] = ratingList[nrOfSeekAds];
2703             colorList[i]  = colorList[nrOfSeekAds];
2704             tcList[i] = tcList[nrOfSeekAds];
2705             xList[i]  = xList[nrOfSeekAds];
2706             yList[i]  = yList[nrOfSeekAds];
2707             zList[i]  = zList[nrOfSeekAds];
2708             seekAdList[nrOfSeekAds] = NULL;
2709             break;
2710         }
2711 }
2712
2713 Boolean
2714 MatchSoughtLine (char *line)
2715 {
2716     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2717     int nr, base, inc, u=0; char dummy;
2718
2719     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2720        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2721        (u=1) &&
2722        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2723         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2724         // match: compact and save the line
2725         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2726         return TRUE;
2727     }
2728     return FALSE;
2729 }
2730
2731 int
2732 DrawSeekGraph ()
2733 {
2734     int i;
2735     if(!seekGraphUp) return FALSE;
2736     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2737     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2738
2739     DrawSeekBackground(0, 0, w, h);
2740     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2741     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2742     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2743         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2744         yy = h-1-yy;
2745         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2746         if(i%500 == 0) {
2747             char buf[MSG_SIZ];
2748             snprintf(buf, MSG_SIZ, "%d", i);
2749             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2750         }
2751     }
2752     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2753     for(i=1; i<100; i+=(i<10?1:5)) {
2754         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2755         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2756         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2757             char buf[MSG_SIZ];
2758             snprintf(buf, MSG_SIZ, "%d", i);
2759             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2760         }
2761     }
2762     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2763     return TRUE;
2764 }
2765
2766 int
2767 SeekGraphClick (ClickType click, int x, int y, int moving)
2768 {
2769     static int lastDown = 0, displayed = 0, lastSecond;
2770     if(y < 0) return FALSE;
2771     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2772         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2773         if(!seekGraphUp) return FALSE;
2774         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2775         DrawPosition(TRUE, NULL);
2776         return TRUE;
2777     }
2778     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2779         if(click == Release || moving) return FALSE;
2780         nrOfSeekAds = 0;
2781         soughtPending = TRUE;
2782         SendToICS(ics_prefix);
2783         SendToICS("sought\n"); // should this be "sought all"?
2784     } else { // issue challenge based on clicked ad
2785         int dist = 10000; int i, closest = 0, second = 0;
2786         for(i=0; i<nrOfSeekAds; i++) {
2787             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2788             if(d < dist) { dist = d; closest = i; }
2789             second += (d - zList[i] < 120); // count in-range ads
2790             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2791         }
2792         if(dist < 120) {
2793             char buf[MSG_SIZ];
2794             second = (second > 1);
2795             if(displayed != closest || second != lastSecond) {
2796                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2797                 lastSecond = second; displayed = closest;
2798             }
2799             if(click == Press) {
2800                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2801                 lastDown = closest;
2802                 return TRUE;
2803             } // on press 'hit', only show info
2804             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2805             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2806             SendToICS(ics_prefix);
2807             SendToICS(buf);
2808             return TRUE; // let incoming board of started game pop down the graph
2809         } else if(click == Release) { // release 'miss' is ignored
2810             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2811             if(moving == 2) { // right up-click
2812                 nrOfSeekAds = 0; // refresh graph
2813                 soughtPending = TRUE;
2814                 SendToICS(ics_prefix);
2815                 SendToICS("sought\n"); // should this be "sought all"?
2816             }
2817             return TRUE;
2818         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2819         // press miss or release hit 'pop down' seek graph
2820         seekGraphUp = FALSE;
2821         DrawPosition(TRUE, NULL);
2822     }
2823     return TRUE;
2824 }
2825
2826 void
2827 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2828 {
2829 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2830 #define STARTED_NONE 0
2831 #define STARTED_MOVES 1
2832 #define STARTED_BOARD 2
2833 #define STARTED_OBSERVE 3
2834 #define STARTED_HOLDINGS 4
2835 #define STARTED_CHATTER 5
2836 #define STARTED_COMMENT 6
2837 #define STARTED_MOVES_NOHIDE 7
2838
2839     static int started = STARTED_NONE;
2840     static char parse[20000];
2841     static int parse_pos = 0;
2842     static char buf[BUF_SIZE + 1];
2843     static int firstTime = TRUE, intfSet = FALSE;
2844     static ColorClass prevColor = ColorNormal;
2845     static int savingComment = FALSE;
2846     static int cmatch = 0; // continuation sequence match
2847     char *bp;
2848     char str[MSG_SIZ];
2849     int i, oldi;
2850     int buf_len;
2851     int next_out;
2852     int tkind;
2853     int backup;    /* [DM] For zippy color lines */
2854     char *p;
2855     char talker[MSG_SIZ]; // [HGM] chat
2856     int channel, collective=0;
2857
2858     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2859
2860     if (appData.debugMode) {
2861       if (!error) {
2862         fprintf(debugFP, "<ICS: ");
2863         show_bytes(debugFP, data, count);
2864         fprintf(debugFP, "\n");
2865       }
2866     }
2867
2868     if (appData.debugMode) { int f = forwardMostMove;
2869         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2870                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2871                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2872     }
2873     if (count > 0) {
2874         /* If last read ended with a partial line that we couldn't parse,
2875            prepend it to the new read and try again. */
2876         if (leftover_len > 0) {
2877             for (i=0; i<leftover_len; i++)
2878               buf[i] = buf[leftover_start + i];
2879         }
2880
2881     /* copy new characters into the buffer */
2882     bp = buf + leftover_len;
2883     buf_len=leftover_len;
2884     for (i=0; i<count; i++)
2885     {
2886         // ignore these
2887         if (data[i] == '\r')
2888             continue;
2889
2890         // join lines split by ICS?
2891         if (!appData.noJoin)
2892         {
2893             /*
2894                 Joining just consists of finding matches against the
2895                 continuation sequence, and discarding that sequence
2896                 if found instead of copying it.  So, until a match
2897                 fails, there's nothing to do since it might be the
2898                 complete sequence, and thus, something we don't want
2899                 copied.
2900             */
2901             if (data[i] == cont_seq[cmatch])
2902             {
2903                 cmatch++;
2904                 if (cmatch == strlen(cont_seq))
2905                 {
2906                     cmatch = 0; // complete match.  just reset the counter
2907
2908                     /*
2909                         it's possible for the ICS to not include the space
2910                         at the end of the last word, making our [correct]
2911                         join operation fuse two separate words.  the server
2912                         does this when the space occurs at the width setting.
2913                     */
2914                     if (!buf_len || buf[buf_len-1] != ' ')
2915                     {
2916                         *bp++ = ' ';
2917                         buf_len++;
2918                     }
2919                 }
2920                 continue;
2921             }
2922             else if (cmatch)
2923             {
2924                 /*
2925                     match failed, so we have to copy what matched before
2926                     falling through and copying this character.  In reality,
2927                     this will only ever be just the newline character, but
2928                     it doesn't hurt to be precise.
2929                 */
2930                 strncpy(bp, cont_seq, cmatch);
2931                 bp += cmatch;
2932                 buf_len += cmatch;
2933                 cmatch = 0;
2934             }
2935         }
2936
2937         // copy this char
2938         *bp++ = data[i];
2939         buf_len++;
2940     }
2941
2942         buf[buf_len] = NULLCHAR;
2943 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2944         next_out = 0;
2945         leftover_start = 0;
2946
2947         i = 0;
2948         while (i < buf_len) {
2949             /* Deal with part of the TELNET option negotiation
2950                protocol.  We refuse to do anything beyond the
2951                defaults, except that we allow the WILL ECHO option,
2952                which ICS uses to turn off password echoing when we are
2953                directly connected to it.  We reject this option
2954                if localLineEditing mode is on (always on in xboard)
2955                and we are talking to port 23, which might be a real
2956                telnet server that will try to keep WILL ECHO on permanently.
2957              */
2958             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2959                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2960                 unsigned char option;
2961                 oldi = i;
2962                 switch ((unsigned char) buf[++i]) {
2963                   case TN_WILL:
2964                     if (appData.debugMode)
2965                       fprintf(debugFP, "\n<WILL ");
2966                     switch (option = (unsigned char) buf[++i]) {
2967                       case TN_ECHO:
2968                         if (appData.debugMode)
2969                           fprintf(debugFP, "ECHO ");
2970                         /* Reply only if this is a change, according
2971                            to the protocol rules. */
2972                         if (remoteEchoOption) break;
2973                         if (appData.localLineEditing &&
2974                             atoi(appData.icsPort) == TN_PORT) {
2975                             TelnetRequest(TN_DONT, TN_ECHO);
2976                         } else {
2977                             EchoOff();
2978                             TelnetRequest(TN_DO, TN_ECHO);
2979                             remoteEchoOption = TRUE;
2980                         }
2981                         break;
2982                       default:
2983                         if (appData.debugMode)
2984                           fprintf(debugFP, "%d ", option);
2985                         /* Whatever this is, we don't want it. */
2986                         TelnetRequest(TN_DONT, option);
2987                         break;
2988                     }
2989                     break;
2990                   case TN_WONT:
2991                     if (appData.debugMode)
2992                       fprintf(debugFP, "\n<WONT ");
2993                     switch (option = (unsigned char) buf[++i]) {
2994                       case TN_ECHO:
2995                         if (appData.debugMode)
2996                           fprintf(debugFP, "ECHO ");
2997                         /* Reply only if this is a change, according
2998                            to the protocol rules. */
2999                         if (!remoteEchoOption) break;
3000                         EchoOn();
3001                         TelnetRequest(TN_DONT, TN_ECHO);
3002                         remoteEchoOption = FALSE;
3003                         break;
3004                       default:
3005                         if (appData.debugMode)
3006                           fprintf(debugFP, "%d ", (unsigned char) option);
3007                         /* Whatever this is, it must already be turned
3008                            off, because we never agree to turn on
3009                            anything non-default, so according to the
3010                            protocol rules, we don't reply. */
3011                         break;
3012                     }
3013                     break;
3014                   case TN_DO:
3015                     if (appData.debugMode)
3016                       fprintf(debugFP, "\n<DO ");
3017                     switch (option = (unsigned char) buf[++i]) {
3018                       default:
3019                         /* Whatever this is, we refuse to do it. */
3020                         if (appData.debugMode)
3021                           fprintf(debugFP, "%d ", option);
3022                         TelnetRequest(TN_WONT, option);
3023                         break;
3024                     }
3025                     break;
3026                   case TN_DONT:
3027                     if (appData.debugMode)
3028                       fprintf(debugFP, "\n<DONT ");
3029                     switch (option = (unsigned char) buf[++i]) {
3030                       default:
3031                         if (appData.debugMode)
3032                           fprintf(debugFP, "%d ", option);
3033                         /* Whatever this is, we are already not doing
3034                            it, because we never agree to do anything
3035                            non-default, so according to the protocol
3036                            rules, we don't reply. */
3037                         break;
3038                     }
3039                     break;
3040                   case TN_IAC:
3041                     if (appData.debugMode)
3042                       fprintf(debugFP, "\n<IAC ");
3043                     /* Doubled IAC; pass it through */
3044                     i--;
3045                     break;
3046                   default:
3047                     if (appData.debugMode)
3048                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3049                     /* Drop all other telnet commands on the floor */
3050                     break;
3051                 }
3052                 if (oldi > next_out)
3053                   SendToPlayer(&buf[next_out], oldi - next_out);
3054                 if (++i > next_out)
3055                   next_out = i;
3056                 continue;
3057             }
3058
3059             /* OK, this at least will *usually* work */
3060             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3061                 loggedOn = TRUE;
3062             }
3063
3064             if (loggedOn && !intfSet) {
3065                 if (ics_type == ICS_ICC) {
3066                   snprintf(str, MSG_SIZ,
3067                           "/set-quietly interface %s\n/set-quietly style 12\n",
3068                           programVersion);
3069                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3070                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3071                 } else if (ics_type == ICS_CHESSNET) {
3072                   snprintf(str, MSG_SIZ, "/style 12\n");
3073                 } else {
3074                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3075                   strcat(str, programVersion);
3076                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3077                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3078                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3079 #ifdef WIN32
3080                   strcat(str, "$iset nohighlight 1\n");
3081 #endif
3082                   strcat(str, "$iset lock 1\n$style 12\n");
3083                 }
3084                 SendToICS(str);
3085                 NotifyFrontendLogin();
3086                 intfSet = TRUE;
3087             }
3088
3089             if (started == STARTED_COMMENT) {
3090                 /* Accumulate characters in comment */
3091                 parse[parse_pos++] = buf[i];
3092                 if (buf[i] == '\n') {
3093                     parse[parse_pos] = NULLCHAR;
3094                     if(chattingPartner>=0) {
3095                         char mess[MSG_SIZ];
3096                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3097                         OutputChatMessage(chattingPartner, mess);
3098                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3099                             int p;
3100                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3101                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3102                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3103                                 OutputChatMessage(p, mess);
3104                                 break;
3105                             }
3106                         }
3107                         chattingPartner = -1;
3108                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3109                         collective = 0;
3110                     } else
3111                     if(!suppressKibitz) // [HGM] kibitz
3112                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3113                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3114                         int nrDigit = 0, nrAlph = 0, j;
3115                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3116                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3117                         parse[parse_pos] = NULLCHAR;
3118                         // try to be smart: if it does not look like search info, it should go to
3119                         // ICS interaction window after all, not to engine-output window.
3120                         for(j=0; j<parse_pos; j++) { // count letters and digits
3121                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3122                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3123                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3124                         }
3125                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3126                             int depth=0; float score;
3127                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3128                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3129                                 pvInfoList[forwardMostMove-1].depth = depth;
3130                                 pvInfoList[forwardMostMove-1].score = 100*score;
3131                             }
3132                             OutputKibitz(suppressKibitz, parse);
3133                         } else {
3134                             char tmp[MSG_SIZ];
3135                             if(gameMode == IcsObserving) // restore original ICS messages
3136                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3137                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3138                             else
3139                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3140                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3141                             SendToPlayer(tmp, strlen(tmp));
3142                         }
3143                         next_out = i+1; // [HGM] suppress printing in ICS window
3144                     }
3145                     started = STARTED_NONE;
3146                 } else {
3147                     /* Don't match patterns against characters in comment */
3148                     i++;
3149                     continue;
3150                 }
3151             }
3152             if (started == STARTED_CHATTER) {
3153                 if (buf[i] != '\n') {
3154                     /* Don't match patterns against characters in chatter */
3155                     i++;
3156                     continue;
3157                 }
3158                 started = STARTED_NONE;
3159                 if(suppressKibitz) next_out = i+1;
3160             }
3161
3162             /* Kludge to deal with rcmd protocol */
3163             if (firstTime && looking_at(buf, &i, "\001*")) {
3164                 DisplayFatalError(&buf[1], 0, 1);
3165                 continue;
3166             } else {
3167                 firstTime = FALSE;
3168             }
3169
3170             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3171                 ics_type = ICS_ICC;
3172                 ics_prefix = "/";
3173                 if (appData.debugMode)
3174                   fprintf(debugFP, "ics_type %d\n", ics_type);
3175                 continue;
3176             }
3177             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3178                 ics_type = ICS_FICS;
3179                 ics_prefix = "$";
3180                 if (appData.debugMode)
3181                   fprintf(debugFP, "ics_type %d\n", ics_type);
3182                 continue;
3183             }
3184             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3185                 ics_type = ICS_CHESSNET;
3186                 ics_prefix = "/";
3187                 if (appData.debugMode)
3188                   fprintf(debugFP, "ics_type %d\n", ics_type);
3189                 continue;
3190             }
3191
3192             if (!loggedOn &&
3193                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3194                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3195                  looking_at(buf, &i, "will be \"*\""))) {
3196               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3197               continue;
3198             }
3199
3200             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3201               char buf[MSG_SIZ];
3202               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3203               DisplayIcsInteractionTitle(buf);
3204               have_set_title = TRUE;
3205             }
3206
3207             /* skip finger notes */
3208             if (started == STARTED_NONE &&
3209                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3210                  (buf[i] == '1' && buf[i+1] == '0')) &&
3211                 buf[i+2] == ':' && buf[i+3] == ' ') {
3212               started = STARTED_CHATTER;
3213               i += 3;
3214               continue;
3215             }
3216
3217             oldi = i;
3218             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3219             if(appData.seekGraph) {
3220                 if(soughtPending && MatchSoughtLine(buf+i)) {
3221                     i = strstr(buf+i, "rated") - buf;
3222                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3223                     next_out = leftover_start = i;
3224                     started = STARTED_CHATTER;
3225                     suppressKibitz = TRUE;
3226                     continue;
3227                 }
3228                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3229                         && looking_at(buf, &i, "* ads displayed")) {
3230                     soughtPending = FALSE;
3231                     seekGraphUp = TRUE;
3232                     DrawSeekGraph();
3233                     continue;
3234                 }
3235                 if(appData.autoRefresh) {
3236                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3237                         int s = (ics_type == ICS_ICC); // ICC format differs
3238                         if(seekGraphUp)
3239                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3240                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3241                         looking_at(buf, &i, "*% "); // eat prompt
3242                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3243                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3244                         next_out = i; // suppress
3245                         continue;
3246                     }
3247                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3248                         char *p = star_match[0];
3249                         while(*p) {
3250                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3251                             while(*p && *p++ != ' '); // next
3252                         }
3253                         looking_at(buf, &i, "*% "); // eat prompt
3254                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3255                         next_out = i;
3256                         continue;
3257                     }
3258                 }
3259             }
3260
3261             /* skip formula vars */
3262             if (started == STARTED_NONE &&
3263                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3264               started = STARTED_CHATTER;
3265               i += 3;
3266               continue;
3267             }
3268
3269             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3270             if (appData.autoKibitz && started == STARTED_NONE &&
3271                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3272                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3273                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3274                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3275                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3276                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3277                         suppressKibitz = TRUE;
3278                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3279                         next_out = i;
3280                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3281                                 && (gameMode == IcsPlayingWhite)) ||
3282                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3283                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3284                             started = STARTED_CHATTER; // own kibitz we simply discard
3285                         else {
3286                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3287                             parse_pos = 0; parse[0] = NULLCHAR;
3288                             savingComment = TRUE;
3289                             suppressKibitz = gameMode != IcsObserving ? 2 :
3290                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3291                         }
3292                         continue;
3293                 } else
3294                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3295                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3296                          && atoi(star_match[0])) {
3297                     // suppress the acknowledgements of our own autoKibitz
3298                     char *p;
3299                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3300                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3301                     SendToPlayer(star_match[0], strlen(star_match[0]));
3302                     if(looking_at(buf, &i, "*% ")) // eat prompt
3303                         suppressKibitz = FALSE;
3304                     next_out = i;
3305                     continue;
3306                 }
3307             } // [HGM] kibitz: end of patch
3308
3309             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3310
3311             // [HGM] chat: intercept tells by users for which we have an open chat window
3312             channel = -1;
3313             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3314                                            looking_at(buf, &i, "* whispers:") ||
3315                                            looking_at(buf, &i, "* kibitzes:") ||
3316                                            looking_at(buf, &i, "* shouts:") ||
3317                                            looking_at(buf, &i, "* c-shouts:") ||
3318                                            looking_at(buf, &i, "--> * ") ||
3319                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3320                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3321                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3322                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3323                 int p;
3324                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3325                 chattingPartner = -1; collective = 0;
3326
3327                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3328                 for(p=0; p<MAX_CHAT; p++) {
3329                     collective = 1;
3330                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3331                     talker[0] = '['; strcat(talker, "] ");
3332                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3333                     chattingPartner = p; break;
3334                     }
3335                 } else
3336                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3337                 for(p=0; p<MAX_CHAT; p++) {
3338                     collective = 1;
3339                     if(!strcmp("kibitzes", chatPartner[p])) {
3340                         talker[0] = '['; strcat(talker, "] ");
3341                         chattingPartner = p; break;
3342                     }
3343                 } else
3344                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3345                 for(p=0; p<MAX_CHAT; p++) {
3346                     collective = 1;
3347                     if(!strcmp("whispers", chatPartner[p])) {
3348                         talker[0] = '['; strcat(talker, "] ");
3349                         chattingPartner = p; break;
3350                     }
3351                 } else
3352                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3353                   if(buf[i-8] == '-' && buf[i-3] == 't')
3354                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3355                     collective = 1;
3356                     if(!strcmp("c-shouts", chatPartner[p])) {
3357                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3358                         chattingPartner = p; break;
3359                     }
3360                   }
3361                   if(chattingPartner < 0)
3362                   for(p=0; p<MAX_CHAT; p++) {
3363                     collective = 1;
3364                     if(!strcmp("shouts", chatPartner[p])) {
3365                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3366                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3367                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3368                         chattingPartner = p; break;
3369                     }
3370                   }
3371                 }
3372                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3373                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3374                     talker[0] = 0;
3375                     Colorize(ColorTell, FALSE);
3376                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3377                     collective |= 2;
3378                     chattingPartner = p; break;
3379                 }
3380                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3381                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3382                     started = STARTED_COMMENT;
3383                     parse_pos = 0; parse[0] = NULLCHAR;
3384                     savingComment = 3 + chattingPartner; // counts as TRUE
3385                     if(collective == 3) i = oldi; else {
3386                         suppressKibitz = TRUE;
3387                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3388                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3389                         continue;
3390                     }
3391                 }
3392             } // [HGM] chat: end of patch
3393
3394           backup = i;
3395             if (appData.zippyTalk || appData.zippyPlay) {
3396                 /* [DM] Backup address for color zippy lines */
3397 #if ZIPPY
3398                if (loggedOn == TRUE)
3399                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3400                           (appData.zippyPlay && ZippyMatch(buf, &backup)))
3401                        ;
3402 #endif
3403             } // [DM] 'else { ' deleted
3404                 if (
3405                     /* Regular tells and says */
3406                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3407                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3408                     looking_at(buf, &i, "* says: ") ||
3409                     /* Don't color "message" or "messages" output */
3410                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3411                     looking_at(buf, &i, "*. * at *:*: ") ||
3412                     looking_at(buf, &i, "--* (*:*): ") ||
3413                     /* Message notifications (same color as tells) */
3414                     looking_at(buf, &i, "* has left a message ") ||
3415                     looking_at(buf, &i, "* just sent you a message:\n") ||
3416                     /* Whispers and kibitzes */
3417                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3418                     looking_at(buf, &i, "* kibitzes: ") ||
3419                     /* Channel tells */
3420                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3421
3422                   if (tkind == 1 && strchr(star_match[0], ':')) {
3423                       /* Avoid "tells you:" spoofs in channels */
3424                      tkind = 3;
3425                   }
3426                   if (star_match[0][0] == NULLCHAR ||
3427                       strchr(star_match[0], ' ') ||
3428                       (tkind == 3 && strchr(star_match[1], ' '))) {
3429                     /* Reject bogus matches */
3430                     i = oldi;
3431                   } else {
3432                     if (appData.colorize) {
3433                       if (oldi > next_out) {
3434                         SendToPlayer(&buf[next_out], oldi - next_out);
3435                         next_out = oldi;
3436                       }
3437                       switch (tkind) {
3438                       case 1:
3439                         Colorize(ColorTell, FALSE);
3440                         curColor = ColorTell;
3441                         break;
3442                       case 2:
3443                         Colorize(ColorKibitz, FALSE);
3444                         curColor = ColorKibitz;
3445                         break;
3446                       case 3:
3447                         p = strrchr(star_match[1], '(');
3448                         if (p == NULL) {
3449                           p = star_match[1];
3450                         } else {
3451                           p++;
3452                         }
3453                         if (atoi(p) == 1) {
3454                           Colorize(ColorChannel1, FALSE);
3455                           curColor = ColorChannel1;
3456                         } else {
3457                           Colorize(ColorChannel, FALSE);
3458                           curColor = ColorChannel;
3459                         }
3460                         break;
3461                       case 5:
3462                         curColor = ColorNormal;
3463                         break;
3464                       }
3465                     }
3466                     if (started == STARTED_NONE && appData.autoComment &&
3467                         (gameMode == IcsObserving ||
3468                          gameMode == IcsPlayingWhite ||
3469                          gameMode == IcsPlayingBlack)) {
3470                       parse_pos = i - oldi;
3471                       memcpy(parse, &buf[oldi], parse_pos);
3472                       parse[parse_pos] = NULLCHAR;
3473                       started = STARTED_COMMENT;
3474                       savingComment = TRUE;
3475                     } else if(collective != 3) {
3476                       started = STARTED_CHATTER;
3477                       savingComment = FALSE;
3478                     }
3479                     loggedOn = TRUE;
3480                     continue;
3481                   }
3482                 }
3483
3484                 if (looking_at(buf, &i, "* s-shouts: ") ||
3485                     looking_at(buf, &i, "* c-shouts: ")) {
3486                     if (appData.colorize) {
3487                         if (oldi > next_out) {
3488                             SendToPlayer(&buf[next_out], oldi - next_out);
3489                             next_out = oldi;
3490                         }
3491                         Colorize(ColorSShout, FALSE);
3492                         curColor = ColorSShout;
3493                     }
3494                     loggedOn = TRUE;
3495                     started = STARTED_CHATTER;
3496                     continue;
3497                 }
3498
3499                 if (looking_at(buf, &i, "--->")) {
3500                     loggedOn = TRUE;
3501                     continue;
3502                 }
3503
3504                 if (looking_at(buf, &i, "* shouts: ") ||
3505                     looking_at(buf, &i, "--> ")) {
3506                     if (appData.colorize) {
3507                         if (oldi > next_out) {
3508                             SendToPlayer(&buf[next_out], oldi - next_out);
3509                             next_out = oldi;
3510                         }
3511                         Colorize(ColorShout, FALSE);
3512                         curColor = ColorShout;
3513                     }
3514                     loggedOn = TRUE;
3515                     started = STARTED_CHATTER;
3516                     continue;
3517                 }
3518
3519                 if (looking_at( buf, &i, "Challenge:")) {
3520                     if (appData.colorize) {
3521                         if (oldi > next_out) {
3522                             SendToPlayer(&buf[next_out], oldi - next_out);
3523                             next_out = oldi;
3524                         }
3525                         Colorize(ColorChallenge, FALSE);
3526                         curColor = ColorChallenge;
3527                     }
3528                     loggedOn = TRUE;
3529                     continue;
3530                 }
3531
3532                 if (looking_at(buf, &i, "* offers you") ||
3533                     looking_at(buf, &i, "* offers to be") ||
3534                     looking_at(buf, &i, "* would like to") ||
3535                     looking_at(buf, &i, "* requests to") ||
3536                     looking_at(buf, &i, "Your opponent offers") ||
3537                     looking_at(buf, &i, "Your opponent requests")) {
3538
3539                     if (appData.colorize) {
3540                         if (oldi > next_out) {
3541                             SendToPlayer(&buf[next_out], oldi - next_out);
3542                             next_out = oldi;
3543                         }
3544                         Colorize(ColorRequest, FALSE);
3545                         curColor = ColorRequest;
3546                     }
3547                     continue;
3548                 }
3549
3550                 if (looking_at(buf, &i, "* (*) seeking")) {
3551                     if (appData.colorize) {
3552                         if (oldi > next_out) {
3553                             SendToPlayer(&buf[next_out], oldi - next_out);
3554                             next_out = oldi;
3555                         }
3556                         Colorize(ColorSeek, FALSE);
3557                         curColor = ColorSeek;
3558                     }
3559                     continue;
3560             }
3561
3562           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3563
3564             if (looking_at(buf, &i, "\\   ")) {
3565                 if (prevColor != ColorNormal) {
3566                     if (oldi > next_out) {
3567                         SendToPlayer(&buf[next_out], oldi - next_out);
3568                         next_out = oldi;
3569                     }
3570                     Colorize(prevColor, TRUE);
3571                     curColor = prevColor;
3572                 }
3573                 if (savingComment) {
3574                     parse_pos = i - oldi;
3575                     memcpy(parse, &buf[oldi], parse_pos);
3576                     parse[parse_pos] = NULLCHAR;
3577                     started = STARTED_COMMENT;
3578                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3579                         chattingPartner = savingComment - 3; // kludge to remember the box
3580                 } else {
3581                     started = STARTED_CHATTER;
3582                 }
3583                 continue;
3584             }
3585
3586             if (looking_at(buf, &i, "Black Strength :") ||
3587                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3588                 looking_at(buf, &i, "<10>") ||
3589                 looking_at(buf, &i, "#@#")) {
3590                 /* Wrong board style */
3591                 loggedOn = TRUE;
3592                 SendToICS(ics_prefix);
3593                 SendToICS("set style 12\n");
3594                 SendToICS(ics_prefix);
3595                 SendToICS("refresh\n");
3596                 continue;
3597             }
3598
3599             if (looking_at(buf, &i, "login:")) {
3600               if (!have_sent_ICS_logon) {
3601                 if(ICSInitScript())
3602                   have_sent_ICS_logon = 1;
3603                 else // no init script was found
3604                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3605               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3606                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3607               }
3608                 continue;
3609             }
3610
3611             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3612                 (looking_at(buf, &i, "\n<12> ") ||
3613                  looking_at(buf, &i, "<12> "))) {
3614                 loggedOn = TRUE;
3615                 if (oldi > next_out) {
3616                     SendToPlayer(&buf[next_out], oldi - next_out);
3617                 }
3618                 next_out = i;
3619                 started = STARTED_BOARD;
3620                 parse_pos = 0;
3621                 continue;
3622             }
3623
3624             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3625                 looking_at(buf, &i, "<b1> ")) {
3626                 if (oldi > next_out) {
3627                     SendToPlayer(&buf[next_out], oldi - next_out);
3628                 }
3629                 next_out = i;
3630                 started = STARTED_HOLDINGS;
3631                 parse_pos = 0;
3632                 continue;
3633             }
3634
3635             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3636                 loggedOn = TRUE;
3637                 /* Header for a move list -- first line */
3638
3639                 switch (ics_getting_history) {
3640                   case H_FALSE:
3641                     switch (gameMode) {
3642                       case IcsIdle:
3643                       case BeginningOfGame:
3644                         /* User typed "moves" or "oldmoves" while we
3645                            were idle.  Pretend we asked for these
3646                            moves and soak them up so user can step
3647                            through them and/or save them.
3648                            */
3649                         Reset(FALSE, TRUE);
3650                         gameMode = IcsObserving;
3651                         ModeHighlight();
3652                         ics_gamenum = -1;
3653                         ics_getting_history = H_GOT_UNREQ_HEADER;
3654                         break;
3655                       case EditGame: /*?*/
3656                       case EditPosition: /*?*/
3657                         /* Should above feature work in these modes too? */
3658                         /* For now it doesn't */
3659                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3660                         break;
3661                       default:
3662                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3663                         break;
3664                     }
3665                     break;
3666                   case H_REQUESTED:
3667                     /* Is this the right one? */
3668                     if (gameInfo.white && gameInfo.black &&
3669                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3670                         strcmp(gameInfo.black, star_match[2]) == 0) {
3671                         /* All is well */
3672                         ics_getting_history = H_GOT_REQ_HEADER;
3673                     }
3674                     break;
3675                   case H_GOT_REQ_HEADER:
3676                   case H_GOT_UNREQ_HEADER:
3677                   case H_GOT_UNWANTED_HEADER:
3678                   case H_GETTING_MOVES:
3679                     /* Should not happen */
3680                     DisplayError(_("Error gathering move list: two headers"), 0);
3681                     ics_getting_history = H_FALSE;
3682                     break;
3683                 }
3684
3685                 /* Save player ratings into gameInfo if needed */
3686                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3687                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3688                     (gameInfo.whiteRating == -1 ||
3689                      gameInfo.blackRating == -1)) {
3690
3691                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3692                     gameInfo.blackRating = string_to_rating(star_match[3]);
3693                     if (appData.debugMode)
3694                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3695                               gameInfo.whiteRating, gameInfo.blackRating);
3696                 }
3697                 continue;
3698             }
3699
3700             if (looking_at(buf, &i,
3701               "* * match, initial time: * minute*, increment: * second")) {
3702                 /* Header for a move list -- second line */
3703                 /* Initial board will follow if this is a wild game */
3704                 if (gameInfo.event != NULL) free(gameInfo.event);
3705                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3706                 gameInfo.event = StrSave(str);
3707                 /* [HGM] we switched variant. Translate boards if needed. */
3708                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3709                 continue;
3710             }
3711
3712             if (looking_at(buf, &i, "Move  ")) {
3713                 /* Beginning of a move list */
3714                 switch (ics_getting_history) {
3715                   case H_FALSE:
3716                     /* Normally should not happen */
3717                     /* Maybe user hit reset while we were parsing */
3718                     break;
3719                   case H_REQUESTED:
3720                     /* Happens if we are ignoring a move list that is not
3721                      * the one we just requested.  Common if the user
3722                      * tries to observe two games without turning off
3723                      * getMoveList */
3724                     break;
3725                   case H_GETTING_MOVES:
3726                     /* Should not happen */
3727                     DisplayError(_("Error gathering move list: nested"), 0);
3728                     ics_getting_history = H_FALSE;
3729                     break;
3730                   case H_GOT_REQ_HEADER:
3731                     ics_getting_history = H_GETTING_MOVES;
3732                     started = STARTED_MOVES;
3733                     parse_pos = 0;
3734                     if (oldi > next_out) {
3735                         SendToPlayer(&buf[next_out], oldi - next_out);
3736                     }
3737                     break;
3738                   case H_GOT_UNREQ_HEADER:
3739                     ics_getting_history = H_GETTING_MOVES;
3740                     started = STARTED_MOVES_NOHIDE;
3741                     parse_pos = 0;
3742                     break;
3743                   case H_GOT_UNWANTED_HEADER:
3744                     ics_getting_history = H_FALSE;
3745                     break;
3746                 }
3747                 continue;
3748             }
3749
3750             if (looking_at(buf, &i, "% ") ||
3751                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3752                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3753                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3754                     soughtPending = FALSE;
3755                     seekGraphUp = TRUE;
3756                     DrawSeekGraph();
3757                 }
3758                 if(suppressKibitz) next_out = i;
3759                 savingComment = FALSE;
3760                 suppressKibitz = 0;
3761                 switch (started) {
3762                   case STARTED_MOVES:
3763                   case STARTED_MOVES_NOHIDE:
3764                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3765                     parse[parse_pos + i - oldi] = NULLCHAR;
3766                     ParseGameHistory(parse);
3767 #if ZIPPY
3768                     if (appData.zippyPlay && first.initDone) {
3769                         FeedMovesToProgram(&first, forwardMostMove);
3770                         if (gameMode == IcsPlayingWhite) {
3771                             if (WhiteOnMove(forwardMostMove)) {
3772                                 if (first.sendTime) {
3773                                   if (first.useColors) {
3774                                     SendToProgram("black\n", &first);
3775                                   }
3776                                   SendTimeRemaining(&first, TRUE);
3777                                 }
3778                                 if (first.useColors) {
3779                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3780                                 }
3781                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3782                                 first.maybeThinking = TRUE;
3783                             } else {
3784                                 if (first.usePlayother) {
3785                                   if (first.sendTime) {
3786                                     SendTimeRemaining(&first, TRUE);
3787                                   }
3788                                   SendToProgram("playother\n", &first);
3789                                   firstMove = FALSE;
3790                                 } else {
3791                                   firstMove = TRUE;
3792                                 }
3793                             }
3794                         } else if (gameMode == IcsPlayingBlack) {
3795                             if (!WhiteOnMove(forwardMostMove)) {
3796                                 if (first.sendTime) {
3797                                   if (first.useColors) {
3798                                     SendToProgram("white\n", &first);
3799                                   }
3800                                   SendTimeRemaining(&first, FALSE);
3801                                 }
3802                                 if (first.useColors) {
3803                                   SendToProgram("black\n", &first);
3804                                 }
3805                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3806                                 first.maybeThinking = TRUE;
3807                             } else {
3808                                 if (first.usePlayother) {
3809                                   if (first.sendTime) {
3810                                     SendTimeRemaining(&first, FALSE);
3811                                   }
3812                                   SendToProgram("playother\n", &first);
3813                                   firstMove = FALSE;
3814                                 } else {
3815                                   firstMove = TRUE;
3816                                 }
3817                             }
3818                         }
3819                     }
3820 #endif
3821                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3822                         /* Moves came from oldmoves or moves command
3823                            while we weren't doing anything else.
3824                            */
3825                         currentMove = forwardMostMove;
3826                         ClearHighlights();/*!!could figure this out*/
3827                         flipView = appData.flipView;
3828                         DrawPosition(TRUE, boards[currentMove]);
3829                         DisplayBothClocks();
3830                         snprintf(str, MSG_SIZ, "%s %s %s",
3831                                 gameInfo.white, _("vs."),  gameInfo.black);
3832                         DisplayTitle(str);
3833                         gameMode = IcsIdle;
3834                     } else {
3835                         /* Moves were history of an active game */
3836                         if (gameInfo.resultDetails != NULL) {
3837                             free(gameInfo.resultDetails);
3838                             gameInfo.resultDetails = NULL;
3839                         }
3840                     }
3841                     HistorySet(parseList, backwardMostMove,
3842                                forwardMostMove, currentMove-1);
3843                     DisplayMove(currentMove - 1);
3844                     if (started == STARTED_MOVES) next_out = i;
3845                     started = STARTED_NONE;
3846                     ics_getting_history = H_FALSE;
3847                     break;
3848
3849                   case STARTED_OBSERVE:
3850                     started = STARTED_NONE;
3851                     SendToICS(ics_prefix);
3852                     SendToICS("refresh\n");
3853                     break;
3854
3855                   default:
3856                     break;
3857                 }
3858                 if(bookHit) { // [HGM] book: simulate book reply
3859                     static char bookMove[MSG_SIZ]; // a bit generous?
3860
3861                     programStats.nodes = programStats.depth = programStats.time =
3862                     programStats.score = programStats.got_only_move = 0;
3863                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3864
3865                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3866                     strcat(bookMove, bookHit);
3867                     HandleMachineMove(bookMove, &first);
3868                 }
3869                 continue;
3870             }
3871
3872             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3873                  started == STARTED_HOLDINGS ||
3874                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3875                 /* Accumulate characters in move list or board */
3876                 parse[parse_pos++] = buf[i];
3877             }
3878
3879             /* Start of game messages.  Mostly we detect start of game
3880                when the first board image arrives.  On some versions
3881                of the ICS, though, we need to do a "refresh" after starting
3882                to observe in order to get the current board right away. */
3883             if (looking_at(buf, &i, "Adding game * to observation list")) {
3884                 started = STARTED_OBSERVE;
3885                 continue;
3886             }
3887
3888             /* Handle auto-observe */
3889             if (appData.autoObserve &&
3890                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3891                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3892                 char *player;
3893                 /* Choose the player that was highlighted, if any. */
3894                 if (star_match[0][0] == '\033' ||
3895                     star_match[1][0] != '\033') {
3896                     player = star_match[0];
3897                 } else {
3898                     player = star_match[2];
3899                 }
3900                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3901                         ics_prefix, StripHighlightAndTitle(player));
3902                 SendToICS(str);
3903
3904                 /* Save ratings from notify string */
3905                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3906                 player1Rating = string_to_rating(star_match[1]);
3907                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3908                 player2Rating = string_to_rating(star_match[3]);
3909
3910                 if (appData.debugMode)
3911                   fprintf(debugFP,
3912                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3913                           player1Name, player1Rating,
3914                           player2Name, player2Rating);
3915
3916                 continue;
3917             }
3918
3919             /* Deal with automatic examine mode after a game,
3920                and with IcsObserving -> IcsExamining transition */
3921             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3922                 looking_at(buf, &i, "has made you an examiner of game *")) {
3923
3924                 int gamenum = atoi(star_match[0]);
3925                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3926                     gamenum == ics_gamenum) {
3927                     /* We were already playing or observing this game;
3928                        no need to refetch history */
3929                     gameMode = IcsExamining;
3930                     if (pausing) {
3931                         pauseExamForwardMostMove = forwardMostMove;
3932                     } else if (currentMove < forwardMostMove) {
3933                         ForwardInner(forwardMostMove);
3934                     }
3935                 } else {
3936                     /* I don't think this case really can happen */
3937                     SendToICS(ics_prefix);
3938                     SendToICS("refresh\n");
3939                 }
3940                 continue;
3941             }
3942
3943             /* Error messages */
3944 //          if (ics_user_moved) {
3945             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3946                 if (looking_at(buf, &i, "Illegal move") ||
3947                     looking_at(buf, &i, "Not a legal move") ||
3948                     looking_at(buf, &i, "Your king is in check") ||
3949                     looking_at(buf, &i, "It isn't your turn") ||
3950                     looking_at(buf, &i, "It is not your move")) {
3951                     /* Illegal move */
3952                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3953                         currentMove = forwardMostMove-1;
3954                         DisplayMove(currentMove - 1); /* before DMError */
3955                         DrawPosition(FALSE, boards[currentMove]);
3956                         SwitchClocks(forwardMostMove-1); // [HGM] race
3957                         DisplayBothClocks();
3958                     }
3959                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3960                     ics_user_moved = 0;
3961                     continue;
3962                 }
3963             }
3964
3965             if (looking_at(buf, &i, "still have time") ||
3966                 looking_at(buf, &i, "not out of time") ||
3967                 looking_at(buf, &i, "either player is out of time") ||
3968                 looking_at(buf, &i, "has timeseal; checking")) {
3969                 /* We must have called his flag a little too soon */
3970                 whiteFlag = blackFlag = FALSE;
3971                 continue;
3972             }
3973
3974             if (looking_at(buf, &i, "added * seconds to") ||
3975                 looking_at(buf, &i, "seconds were added to")) {
3976                 /* Update the clocks */
3977                 SendToICS(ics_prefix);
3978                 SendToICS("refresh\n");
3979                 continue;
3980             }
3981
3982             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3983                 ics_clock_paused = TRUE;
3984                 StopClocks();
3985                 continue;
3986             }
3987
3988             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3989                 ics_clock_paused = FALSE;
3990                 StartClocks();
3991                 continue;
3992             }
3993
3994             /* Grab player ratings from the Creating: message.
3995                Note we have to check for the special case when
3996                the ICS inserts things like [white] or [black]. */
3997             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3998                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3999                 /* star_matches:
4000                    0    player 1 name (not necessarily white)
4001                    1    player 1 rating
4002                    2    empty, white, or black (IGNORED)
4003                    3    player 2 name (not necessarily black)
4004                    4    player 2 rating
4005
4006                    The names/ratings are sorted out when the game
4007                    actually starts (below).
4008                 */
4009                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4010                 player1Rating = string_to_rating(star_match[1]);
4011                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4012                 player2Rating = string_to_rating(star_match[4]);
4013
4014                 if (appData.debugMode)
4015                   fprintf(debugFP,
4016                           "Ratings from 'Creating:' %s %d, %s %d\n",
4017                           player1Name, player1Rating,
4018                           player2Name, player2Rating);
4019
4020                 continue;
4021             }
4022
4023             /* Improved generic start/end-of-game messages */
4024             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4025                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4026                 /* If tkind == 0: */
4027                 /* star_match[0] is the game number */
4028                 /*           [1] is the white player's name */
4029                 /*           [2] is the black player's name */
4030                 /* For end-of-game: */
4031                 /*           [3] is the reason for the game end */
4032                 /*           [4] is a PGN end game-token, preceded by " " */
4033                 /* For start-of-game: */
4034                 /*           [3] begins with "Creating" or "Continuing" */
4035                 /*           [4] is " *" or empty (don't care). */
4036                 int gamenum = atoi(star_match[0]);
4037                 char *whitename, *blackname, *why, *endtoken;
4038                 ChessMove endtype = EndOfFile;
4039
4040                 if (tkind == 0) {
4041                   whitename = star_match[1];
4042                   blackname = star_match[2];
4043                   why = star_match[3];
4044                   endtoken = star_match[4];
4045                 } else {
4046                   whitename = star_match[1];
4047                   blackname = star_match[3];
4048                   why = star_match[5];
4049                   endtoken = star_match[6];
4050                 }
4051
4052                 /* Game start messages */
4053                 if (strncmp(why, "Creating ", 9) == 0 ||
4054                     strncmp(why, "Continuing ", 11) == 0) {
4055                     gs_gamenum = gamenum;
4056                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4057                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4058                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4059 #if ZIPPY
4060                     if (appData.zippyPlay) {
4061                         ZippyGameStart(whitename, blackname);
4062                     }
4063 #endif /*ZIPPY*/
4064                     partnerBoardValid = FALSE; // [HGM] bughouse
4065                     continue;
4066                 }
4067
4068                 /* Game end messages */
4069                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4070                     ics_gamenum != gamenum) {
4071                     continue;
4072                 }
4073                 while (endtoken[0] == ' ') endtoken++;
4074                 switch (endtoken[0]) {
4075                   case '*':
4076                   default:
4077                     endtype = GameUnfinished;
4078                     break;
4079                   case '0':
4080                     endtype = BlackWins;
4081                     break;
4082                   case '1':
4083                     if (endtoken[1] == '/')
4084                       endtype = GameIsDrawn;
4085                     else
4086                       endtype = WhiteWins;
4087                     break;
4088                 }
4089                 GameEnds(endtype, why, GE_ICS);
4090 #if ZIPPY
4091                 if (appData.zippyPlay && first.initDone) {
4092                     ZippyGameEnd(endtype, why);
4093                     if (first.pr == NoProc) {
4094                       /* Start the next process early so that we'll
4095                          be ready for the next challenge */
4096                       StartChessProgram(&first);
4097                     }
4098                     /* Send "new" early, in case this command takes
4099                        a long time to finish, so that we'll be ready
4100                        for the next challenge. */
4101                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4102                     Reset(TRUE, TRUE);
4103                 }
4104 #endif /*ZIPPY*/
4105                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4106                 continue;
4107             }
4108
4109             if (looking_at(buf, &i, "Removing game * from observation") ||
4110                 looking_at(buf, &i, "no longer observing game *") ||
4111                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4112                 if (gameMode == IcsObserving &&
4113                     atoi(star_match[0]) == ics_gamenum)
4114                   {
4115                       /* icsEngineAnalyze */
4116                       if (appData.icsEngineAnalyze) {
4117                             ExitAnalyzeMode();
4118                             ModeHighlight();
4119                       }
4120                       StopClocks();
4121                       gameMode = IcsIdle;
4122                       ics_gamenum = -1;
4123                       ics_user_moved = FALSE;
4124                   }
4125                 continue;
4126             }
4127
4128             if (looking_at(buf, &i, "no longer examining game *")) {
4129                 if (gameMode == IcsExamining &&
4130                     atoi(star_match[0]) == ics_gamenum)
4131                   {
4132                       gameMode = IcsIdle;
4133                       ics_gamenum = -1;
4134                       ics_user_moved = FALSE;
4135                   }
4136                 continue;
4137             }
4138
4139             /* Advance leftover_start past any newlines we find,
4140                so only partial lines can get reparsed */
4141             if (looking_at(buf, &i, "\n")) {
4142                 prevColor = curColor;
4143                 if (curColor != ColorNormal) {
4144                     if (oldi > next_out) {
4145                         SendToPlayer(&buf[next_out], oldi - next_out);
4146                         next_out = oldi;
4147                     }
4148                     Colorize(ColorNormal, FALSE);
4149                     curColor = ColorNormal;
4150                 }
4151                 if (started == STARTED_BOARD) {
4152                     started = STARTED_NONE;
4153                     parse[parse_pos] = NULLCHAR;
4154                     ParseBoard12(parse);
4155                     ics_user_moved = 0;
4156
4157                     /* Send premove here */
4158                     if (appData.premove) {
4159                       char str[MSG_SIZ];
4160                       if (currentMove == 0 &&
4161                           gameMode == IcsPlayingWhite &&
4162                           appData.premoveWhite) {
4163                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4164                         if (appData.debugMode)
4165                           fprintf(debugFP, "Sending premove:\n");
4166                         SendToICS(str);
4167                       } else if (currentMove == 1 &&
4168                                  gameMode == IcsPlayingBlack &&
4169                                  appData.premoveBlack) {
4170                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4171                         if (appData.debugMode)
4172                           fprintf(debugFP, "Sending premove:\n");
4173                         SendToICS(str);
4174                       } else if (gotPremove) {
4175                         int oldFMM = forwardMostMove;
4176                         gotPremove = 0;
4177                         ClearPremoveHighlights();
4178                         if (appData.debugMode)
4179                           fprintf(debugFP, "Sending premove:\n");
4180                           UserMoveEvent(premoveFromX, premoveFromY,
4181                                         premoveToX, premoveToY,
4182                                         premovePromoChar);
4183                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4184                           if(moveList[oldFMM-1][1] != '@')
4185                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4186                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4187                           else // (drop)
4188                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4189                         }
4190                       }
4191                     }
4192
4193                     /* Usually suppress following prompt */
4194                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4195                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4196                         if (looking_at(buf, &i, "*% ")) {
4197                             savingComment = FALSE;
4198                             suppressKibitz = 0;
4199                         }
4200                     }
4201                     next_out = i;
4202                 } else if (started == STARTED_HOLDINGS) {
4203                     int gamenum;
4204                     char new_piece[MSG_SIZ];
4205                     started = STARTED_NONE;
4206                     parse[parse_pos] = NULLCHAR;
4207                     if (appData.debugMode)
4208                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4209                                                         parse, currentMove);
4210                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4211                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4212                         new_piece[0] = NULLCHAR;
4213                         sscanf(parse, "game %d white [%s black [%s <- %s",
4214                                &gamenum, white_holding, black_holding,
4215                                new_piece);
4216                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4217                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4218                         if (gameInfo.variant == VariantNormal) {
4219                           /* [HGM] We seem to switch variant during a game!
4220                            * Presumably no holdings were displayed, so we have
4221                            * to move the position two files to the right to
4222                            * create room for them!
4223                            */
4224                           VariantClass newVariant;
4225                           switch(gameInfo.boardWidth) { // base guess on board width
4226                                 case 9:  newVariant = VariantShogi; break;
4227                                 case 10: newVariant = VariantGreat; break;
4228                                 default: newVariant = VariantCrazyhouse;
4229                                      if(strchr(white_holding, 'E') || strchr(black_holding, 'E') || 
4230                                         strchr(white_holding, 'H') || strchr(black_holding, 'H')   )
4231                                          newVariant = VariantSChess;
4232                           }
4233                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4234                           /* Get a move list just to see the header, which
4235                              will tell us whether this is really bug or zh */
4236                           if (ics_getting_history == H_FALSE) {
4237                             ics_getting_history = H_REQUESTED;
4238                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4239                             SendToICS(str);
4240                           }
4241                         }
4242                         /* [HGM] copy holdings to board holdings area */
4243                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4244                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4245                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4246 #if ZIPPY
4247                         if (appData.zippyPlay && first.initDone) {
4248                             ZippyHoldings(white_holding, black_holding,
4249                                           new_piece);
4250                         }
4251 #endif /*ZIPPY*/
4252                         if (tinyLayout || smallLayout) {
4253                             char wh[16], bh[16];
4254                             PackHolding(wh, white_holding);
4255                             PackHolding(bh, black_holding);
4256                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4257                                     gameInfo.white, gameInfo.black);
4258                         } else {
4259                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4260                                     gameInfo.white, white_holding, _("vs."),
4261                                     gameInfo.black, black_holding);
4262                         }
4263                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4264                         DrawPosition(FALSE, boards[currentMove]);
4265                         DisplayTitle(str);
4266                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4267                         sscanf(parse, "game %d white [%s black [%s <- %s",
4268                                &gamenum, white_holding, black_holding,
4269                                new_piece);
4270                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4271                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4272                         /* [HGM] copy holdings to partner-board holdings area */
4273                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4274                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4275                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4276                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4277                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4278                       }
4279                     }
4280                     /* Suppress following prompt */
4281                     if (looking_at(buf, &i, "*% ")) {
4282                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4283                         savingComment = FALSE;
4284                         suppressKibitz = 0;
4285                     }
4286                     next_out = i;
4287                 }
4288                 continue;
4289             }
4290
4291             i++;                /* skip unparsed character and loop back */
4292         }
4293
4294         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4295 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4296 //          SendToPlayer(&buf[next_out], i - next_out);
4297             started != STARTED_HOLDINGS && leftover_start > next_out) {
4298             SendToPlayer(&buf[next_out], leftover_start - next_out);
4299             next_out = i;
4300         }
4301
4302         leftover_len = buf_len - leftover_start;
4303         /* if buffer ends with something we couldn't parse,
4304            reparse it after appending the next read */
4305
4306     } else if (count == 0) {
4307         RemoveInputSource(isr);
4308         DisplayFatalError(_("Connection closed by ICS"), 0, 6666);
4309     } else {
4310         DisplayFatalError(_("Error reading from ICS"), error, 1);
4311     }
4312 }
4313
4314
4315 /* Board style 12 looks like this:
4316
4317    <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
4318
4319  * The "<12> " is stripped before it gets to this routine.  The two
4320  * trailing 0's (flip state and clock ticking) are later addition, and
4321  * some chess servers may not have them, or may have only the first.
4322  * Additional trailing fields may be added in the future.
4323  */
4324
4325 #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"
4326
4327 #define RELATION_OBSERVING_PLAYED    0
4328 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4329 #define RELATION_PLAYING_MYMOVE      1
4330 #define RELATION_PLAYING_NOTMYMOVE  -1
4331 #define RELATION_EXAMINING           2
4332 #define RELATION_ISOLATED_BOARD     -3
4333 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4334
4335 void
4336 ParseBoard12 (char *string)
4337 {
4338 #if ZIPPY
4339     int i, takeback;
4340     char *bookHit = NULL; // [HGM] book
4341 #endif
4342     GameMode newGameMode;
4343     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4344     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4345     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4346     char to_play, board_chars[200];
4347     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4348     char black[32], white[32];
4349     Board board;
4350     int prevMove = currentMove;
4351     int ticking = 2;
4352     ChessMove moveType;
4353     int fromX, fromY, toX, toY;
4354     char promoChar;
4355     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4356     Boolean weird = FALSE, reqFlag = FALSE;
4357
4358     fromX = fromY = toX = toY = -1;
4359
4360     newGame = FALSE;
4361
4362     if (appData.debugMode)
4363       fprintf(debugFP, "Parsing board: %s\n", string);
4364
4365     move_str[0] = NULLCHAR;
4366     elapsed_time[0] = NULLCHAR;
4367     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4368         int  i = 0, j;
4369         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4370             if(string[i] == ' ') { ranks++; files = 0; }
4371             else files++;
4372             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4373             i++;
4374         }
4375         for(j = 0; j <i; j++) board_chars[j] = string[j];
4376         board_chars[i] = '\0';
4377         string += i + 1;
4378     }
4379     n = sscanf(string, PATTERN, &to_play, &double_push,
4380                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4381                &gamenum, white, black, &relation, &basetime, &increment,
4382                &white_stren, &black_stren, &white_time, &black_time,
4383                &moveNum, str, elapsed_time, move_str, &ics_flip,
4384                &ticking);
4385
4386     if (n < 21) {
4387         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4388         DisplayError(str, 0);
4389         return;
4390     }
4391
4392     /* Convert the move number to internal form */
4393     moveNum = (moveNum - 1) * 2;
4394     if (to_play == 'B') moveNum++;
4395     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4396       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4397                         0, 1);
4398       return;
4399     }
4400
4401     switch (relation) {
4402       case RELATION_OBSERVING_PLAYED:
4403       case RELATION_OBSERVING_STATIC:
4404         if (gamenum == -1) {
4405             /* Old ICC buglet */
4406             relation = RELATION_OBSERVING_STATIC;
4407         }
4408         newGameMode = IcsObserving;
4409         break;
4410       case RELATION_PLAYING_MYMOVE:
4411       case RELATION_PLAYING_NOTMYMOVE:
4412         newGameMode =
4413           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4414             IcsPlayingWhite : IcsPlayingBlack;
4415         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4416         break;
4417       case RELATION_EXAMINING:
4418         newGameMode = IcsExamining;
4419         break;
4420       case RELATION_ISOLATED_BOARD:
4421       default:
4422         /* Just display this board.  If user was doing something else,
4423            we will forget about it until the next board comes. */
4424         newGameMode = IcsIdle;
4425         break;
4426       case RELATION_STARTING_POSITION:
4427         newGameMode = gameMode;
4428         break;
4429     }
4430
4431     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4432         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4433          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4434       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4435       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4436       static int lastBgGame = -1;
4437       char *toSqr;
4438       for (k = 0; k < ranks; k++) {
4439         for (j = 0; j < files; j++)
4440           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4441         if(gameInfo.holdingsWidth > 1) {
4442              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4443              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4444         }
4445       }
4446       CopyBoard(partnerBoard, board);
4447       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4448         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4449         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4450       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4451       if(toSqr = strchr(str, '-')) {
4452         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4453         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4454       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4455       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4456       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4457       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4458       if(twoBoards) {
4459           DisplayWhiteClock(white_time*fac, to_play == 'W');
4460           DisplayBlackClock(black_time*fac, to_play != 'W');
4461           activePartner = to_play;
4462           if(gamenum != lastBgGame) {
4463               char buf[MSG_SIZ];
4464               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4465               DisplayTitle(buf);
4466           }
4467           lastBgGame = gamenum;
4468           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4469                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4470       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4471                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4472       if(!twoBoards) DisplayMessage(partnerStatus, "");
4473         partnerBoardValid = TRUE;
4474       return;
4475     }
4476
4477     if(appData.dualBoard && appData.bgObserve) {
4478         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4479             SendToICS(ics_prefix), SendToICS("pobserve\n");
4480         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4481             char buf[MSG_SIZ];
4482             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4483             SendToICS(buf);
4484         }
4485     }
4486
4487     /* Modify behavior for initial board display on move listing
4488        of wild games.
4489        */
4490     switch (ics_getting_history) {
4491       case H_FALSE:
4492       case H_REQUESTED:
4493         break;
4494       case H_GOT_REQ_HEADER:
4495       case H_GOT_UNREQ_HEADER:
4496         /* This is the initial position of the current game */
4497         gamenum = ics_gamenum;
4498         moveNum = 0;            /* old ICS bug workaround */
4499         if (to_play == 'B') {
4500           startedFromSetupPosition = TRUE;
4501           blackPlaysFirst = TRUE;
4502           moveNum = 1;
4503           if (forwardMostMove == 0) forwardMostMove = 1;
4504           if (backwardMostMove == 0) backwardMostMove = 1;
4505           if (currentMove == 0) currentMove = 1;
4506         }
4507         newGameMode = gameMode;
4508         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4509         break;
4510       case H_GOT_UNWANTED_HEADER:
4511         /* This is an initial board that we don't want */
4512         return;
4513       case H_GETTING_MOVES:
4514         /* Should not happen */
4515         DisplayError(_("Error gathering move list: extra board"), 0);
4516         ics_getting_history = H_FALSE;
4517         return;
4518     }
4519
4520    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4521                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4522                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4523      /* [HGM] We seem to have switched variant unexpectedly
4524       * Try to guess new variant from board size
4525       */
4526           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4527           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4528           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4529           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4530           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4531           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4532           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4533           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4534           /* Get a move list just to see the header, which
4535              will tell us whether this is really bug or zh */
4536           if (ics_getting_history == H_FALSE) {
4537             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4538             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4539             SendToICS(str);
4540           }
4541     }
4542
4543     /* Take action if this is the first board of a new game, or of a
4544        different game than is currently being displayed.  */
4545     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4546         relation == RELATION_ISOLATED_BOARD) {
4547
4548         /* Forget the old game and get the history (if any) of the new one */
4549         if (gameMode != BeginningOfGame) {
4550           Reset(TRUE, TRUE);
4551         }
4552         newGame = TRUE;
4553         if (appData.autoRaiseBoard) BoardToTop();
4554         prevMove = -3;
4555         if (gamenum == -1) {
4556             newGameMode = IcsIdle;
4557         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4558                    appData.getMoveList && !reqFlag) {
4559             /* Need to get game history */
4560             ics_getting_history = H_REQUESTED;
4561             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4562             SendToICS(str);
4563         }
4564
4565         /* Initially flip the board to have black on the bottom if playing
4566            black or if the ICS flip flag is set, but let the user change
4567            it with the Flip View button. */
4568         flipView = appData.autoFlipView ?
4569           (newGameMode == IcsPlayingBlack) || ics_flip :
4570           appData.flipView;
4571
4572         /* Done with values from previous mode; copy in new ones */
4573         gameMode = newGameMode;
4574         ModeHighlight();
4575         ics_gamenum = gamenum;
4576         if (gamenum == gs_gamenum) {
4577             int klen = strlen(gs_kind);
4578             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4579             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4580             gameInfo.event = StrSave(str);
4581         } else {
4582             gameInfo.event = StrSave("ICS game");
4583         }
4584         gameInfo.site = StrSave(appData.icsHost);
4585         gameInfo.date = PGNDate();
4586         gameInfo.round = StrSave("-");
4587         gameInfo.white = StrSave(white);
4588         gameInfo.black = StrSave(black);
4589         timeControl = basetime * 60 * 1000;
4590         timeControl_2 = 0;
4591         timeIncrement = increment * 1000;
4592         movesPerSession = 0;
4593         gameInfo.timeControl = TimeControlTagValue();
4594         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4595   if (appData.debugMode) {
4596     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4597     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4598     setbuf(debugFP, NULL);
4599   }
4600
4601         gameInfo.outOfBook = NULL;
4602
4603         /* Do we have the ratings? */
4604         if (strcmp(player1Name, white) == 0 &&
4605             strcmp(player2Name, black) == 0) {
4606             if (appData.debugMode)
4607               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4608                       player1Rating, player2Rating);
4609             gameInfo.whiteRating = player1Rating;
4610             gameInfo.blackRating = player2Rating;
4611         } else if (strcmp(player2Name, white) == 0 &&
4612                    strcmp(player1Name, black) == 0) {
4613             if (appData.debugMode)
4614               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4615                       player2Rating, player1Rating);
4616             gameInfo.whiteRating = player2Rating;
4617             gameInfo.blackRating = player1Rating;
4618         }
4619         player1Name[0] = player2Name[0] = NULLCHAR;
4620
4621         /* Silence shouts if requested */
4622         if (appData.quietPlay &&
4623             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4624             SendToICS(ics_prefix);
4625             SendToICS("set shout 0\n");
4626         }
4627     }
4628
4629     /* Deal with midgame name changes */
4630     if (!newGame) {
4631         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4632             if (gameInfo.white) free(gameInfo.white);
4633             gameInfo.white = StrSave(white);
4634         }
4635         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4636             if (gameInfo.black) free(gameInfo.black);
4637             gameInfo.black = StrSave(black);
4638         }
4639     }
4640
4641     /* Throw away game result if anything actually changes in examine mode */
4642     if (gameMode == IcsExamining && !newGame) {
4643         gameInfo.result = GameUnfinished;
4644         if (gameInfo.resultDetails != NULL) {
4645             free(gameInfo.resultDetails);
4646             gameInfo.resultDetails = NULL;
4647         }
4648     }
4649
4650     /* In pausing && IcsExamining mode, we ignore boards coming
4651        in if they are in a different variation than we are. */
4652     if (pauseExamInvalid) return;
4653     if (pausing && gameMode == IcsExamining) {
4654         if (moveNum <= pauseExamForwardMostMove) {
4655             pauseExamInvalid = TRUE;
4656             forwardMostMove = pauseExamForwardMostMove;
4657             return;
4658         }
4659     }
4660
4661   if (appData.debugMode) {
4662     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4663   }
4664     /* Parse the board */
4665     for (k = 0; k < ranks; k++) {
4666       for (j = 0; j < files; j++)
4667         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4668       if(gameInfo.holdingsWidth > 1) {
4669            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4670            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4671       }
4672     }
4673     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4674       board[5][BOARD_RGHT+1] = WhiteAngel;
4675       board[6][BOARD_RGHT+1] = WhiteMarshall;
4676       board[1][0] = BlackMarshall;
4677       board[2][0] = BlackAngel;
4678       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4679     }
4680     CopyBoard(boards[moveNum], board);
4681     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4682     if (moveNum == 0) {
4683         startedFromSetupPosition =
4684           !CompareBoards(board, initialPosition);
4685         if(startedFromSetupPosition)
4686             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4687     }
4688
4689     /* [HGM] Set castling rights. Take the outermost Rooks,
4690        to make it also work for FRC opening positions. Note that board12
4691        is really defective for later FRC positions, as it has no way to
4692        indicate which Rook can castle if they are on the same side of King.
4693        For the initial position we grant rights to the outermost Rooks,
4694        and remember thos rights, and we then copy them on positions
4695        later in an FRC game. This means WB might not recognize castlings with
4696        Rooks that have moved back to their original position as illegal,
4697        but in ICS mode that is not its job anyway.
4698     */
4699     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4700     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4701
4702         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4703             if(board[0][i] == WhiteRook) j = i;
4704         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4705         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4706             if(board[0][i] == WhiteRook) j = i;
4707         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4708         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4709             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4710         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4711         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4712             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4713         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4714
4715         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4716         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4717         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4718             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4719         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4720             if(board[BOARD_HEIGHT-1][k] == bKing)
4721                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4722         if(gameInfo.variant == VariantTwoKings) {
4723             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4724             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4725             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4726         }
4727     } else { int r;
4728         r = boards[moveNum][CASTLING][0] = initialRights[0];
4729         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4730         r = boards[moveNum][CASTLING][1] = initialRights[1];
4731         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4732         r = boards[moveNum][CASTLING][3] = initialRights[3];
4733         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4734         r = boards[moveNum][CASTLING][4] = initialRights[4];
4735         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4736         /* wildcastle kludge: always assume King has rights */
4737         r = boards[moveNum][CASTLING][2] = initialRights[2];
4738         r = boards[moveNum][CASTLING][5] = initialRights[5];
4739     }
4740     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4741     boards[moveNum][EP_STATUS] = EP_NONE;
4742     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4743     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4744     if(double_push !=  -1) {
4745         int dir = WhiteOnMove(moveNum) ? 1 : -1, last = BOARD_HEIGHT-1;
4746         boards[moveNum][EP_FILE] = // also set new e.p. variables
4747         boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4748         boards[moveNum][EP_RANK] = (last + 3*dir)/2;
4749         boards[moveNum][LAST_TO] = 128*(last + dir) + boards[moveNum][EP_FILE];
4750     } else boards[moveNum][EP_FILE] = boards[moveNum][EP_RANK] = 100;
4751
4752
4753     if (ics_getting_history == H_GOT_REQ_HEADER ||
4754         ics_getting_history == H_GOT_UNREQ_HEADER) {
4755         /* This was an initial position from a move list, not
4756            the current position */
4757         return;
4758     }
4759
4760     /* Update currentMove and known move number limits */
4761     newMove = newGame || moveNum > forwardMostMove;
4762
4763     if (newGame) {
4764         forwardMostMove = backwardMostMove = currentMove = moveNum;
4765         if (gameMode == IcsExamining && moveNum == 0) {
4766           /* Workaround for ICS limitation: we are not told the wild
4767              type when starting to examine a game.  But if we ask for
4768              the move list, the move list header will tell us */
4769             ics_getting_history = H_REQUESTED;
4770             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4771             SendToICS(str);
4772         }
4773     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4774                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4775 #if ZIPPY
4776         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4777         /* [HGM] applied this also to an engine that is silently watching        */
4778         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4779             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4780             gameInfo.variant == currentlyInitializedVariant) {
4781           takeback = forwardMostMove - moveNum;
4782           for (i = 0; i < takeback; i++) {
4783             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4784             SendToProgram("undo\n", &first);
4785           }
4786         }
4787 #endif
4788
4789         forwardMostMove = moveNum;
4790         if (!pausing || currentMove > forwardMostMove)
4791           currentMove = forwardMostMove;
4792     } else {
4793         /* New part of history that is not contiguous with old part */
4794         if (pausing && gameMode == IcsExamining) {
4795             pauseExamInvalid = TRUE;
4796             forwardMostMove = pauseExamForwardMostMove;
4797             return;
4798         }
4799         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4800 #if ZIPPY
4801             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4802                 // [HGM] when we will receive the move list we now request, it will be
4803                 // fed to the engine from the first move on. So if the engine is not
4804                 // in the initial position now, bring it there.
4805                 InitChessProgram(&first, 0);
4806             }
4807 #endif
4808             ics_getting_history = H_REQUESTED;
4809             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4810             SendToICS(str);
4811         }
4812         forwardMostMove = backwardMostMove = currentMove = moveNum;
4813     }
4814
4815     /* Update the clocks */
4816     if (strchr(elapsed_time, '.')) {
4817       /* Time is in ms */
4818       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4819       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4820     } else {
4821       /* Time is in seconds */
4822       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4823       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4824     }
4825
4826
4827 #if ZIPPY
4828     if (appData.zippyPlay && newGame &&
4829         gameMode != IcsObserving && gameMode != IcsIdle &&
4830         gameMode != IcsExamining)
4831       ZippyFirstBoard(moveNum, basetime, increment);
4832 #endif
4833
4834     /* Put the move on the move list, first converting
4835        to canonical algebraic form. */
4836     if (moveNum > 0) {
4837   if (appData.debugMode) {
4838     int f = forwardMostMove;
4839     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4840             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4841             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4842     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4843     fprintf(debugFP, "moveNum = %d\n", moveNum);
4844     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4845     setbuf(debugFP, NULL);
4846   }
4847         if (moveNum <= backwardMostMove) {
4848             /* We don't know what the board looked like before
4849                this move.  Punt. */
4850           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4851             strcat(parseList[moveNum - 1], " ");
4852             strcat(parseList[moveNum - 1], elapsed_time);
4853             moveList[moveNum - 1][0] = NULLCHAR;
4854         } else if (strcmp(move_str, "none") == 0) {
4855             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4856             /* Again, we don't know what the board looked like;
4857                this is really the start of the game. */
4858             parseList[moveNum - 1][0] = NULLCHAR;
4859             moveList[moveNum - 1][0] = NULLCHAR;
4860             backwardMostMove = moveNum;
4861             startedFromSetupPosition = TRUE;
4862             fromX = fromY = toX = toY = -1;
4863         } else {
4864           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4865           //                 So we parse the long-algebraic move string in stead of the SAN move
4866           int valid; char buf[MSG_SIZ], *prom;
4867
4868           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4869                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4870           // str looks something like "Q/a1-a2"; kill the slash
4871           if(str[1] == '/')
4872             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4873           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4874           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4875                 strcat(buf, prom); // long move lacks promo specification!
4876           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4877                 if(appData.debugMode)
4878                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4879                 safeStrCpy(move_str, buf, MSG_SIZ);
4880           }
4881           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4882                                 &fromX, &fromY, &toX, &toY, &promoChar)
4883                || ParseOneMove(buf, moveNum - 1, &moveType,
4884                                 &fromX, &fromY, &toX, &toY, &promoChar);
4885           // end of long SAN patch
4886           if (valid) {
4887             (void) CoordsToAlgebraic(boards[moveNum - 1],
4888                                      PosFlags(moveNum - 1),
4889                                      fromY, fromX, toY, toX, promoChar,
4890                                      parseList[moveNum-1]);
4891             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4892               case MT_NONE:
4893               case MT_STALEMATE:
4894               default:
4895                 break;
4896               case MT_CHECK:
4897                 if(!IS_SHOGI(gameInfo.variant))
4898                     strcat(parseList[moveNum - 1], "+");
4899                 break;
4900               case MT_CHECKMATE:
4901               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4902                 strcat(parseList[moveNum - 1], "#");
4903                 break;
4904             }
4905             strcat(parseList[moveNum - 1], " ");
4906             strcat(parseList[moveNum - 1], elapsed_time);
4907             /* currentMoveString is set as a side-effect of ParseOneMove */
4908             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4909             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4910             strcat(moveList[moveNum - 1], "\n");
4911
4912             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4913                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4914               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4915                 ChessSquare old, new = boards[moveNum][k][j];
4916                   if(new == EmptySquare || fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4917                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4918                   if(old == new) continue;
4919                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4920                   else if(new == WhiteWazir || new == BlackWazir) {
4921                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4922                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4923                       else boards[moveNum][k][j] = old; // preserve type of Gold
4924                   } else if(old == WhitePawn || old == BlackPawn) // Pawn promotions (but not e.p.capture!)
4925                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4926               }
4927           } else {
4928             /* Move from ICS was illegal!?  Punt. */
4929             if (appData.debugMode) {
4930               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4931               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4932             }
4933             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4934             strcat(parseList[moveNum - 1], " ");
4935             strcat(parseList[moveNum - 1], elapsed_time);
4936             moveList[moveNum - 1][0] = NULLCHAR;
4937             fromX = fromY = toX = toY = -1;
4938           }
4939         }
4940   if (appData.debugMode) {
4941     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4942     setbuf(debugFP, NULL);
4943   }
4944
4945 #if ZIPPY
4946         /* Send move to chess program (BEFORE animating it). */
4947         if (appData.zippyPlay && !newGame && newMove &&
4948            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4949
4950             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4951                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4952                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4953                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4954                             move_str);
4955                     DisplayError(str, 0);
4956                 } else {
4957                     if (first.sendTime) {
4958                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4959                     }
4960                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4961                     if (firstMove && !bookHit) {
4962                         firstMove = FALSE;
4963                         if (first.useColors) {
4964                           SendToProgram(gameMode == IcsPlayingWhite ?
4965                                         "white\ngo\n" :
4966                                         "black\ngo\n", &first);
4967                         } else {
4968                           SendToProgram("go\n", &first);
4969                         }
4970                         first.maybeThinking = TRUE;
4971                     }
4972                 }
4973             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4974               if (moveList[moveNum - 1][0] == NULLCHAR) {
4975                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4976                 DisplayError(str, 0);
4977               } else {
4978                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4979                 SendMoveToProgram(moveNum - 1, &first);
4980               }
4981             }
4982         }
4983 #endif
4984     }
4985
4986     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4987         /* If move comes from a remote source, animate it.  If it
4988            isn't remote, it will have already been animated. */
4989         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4990             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4991         }
4992         if (!pausing && appData.highlightLastMove) {
4993             SetHighlights(fromX, fromY, toX, toY);
4994         }
4995     }
4996
4997     /* Start the clocks */
4998     whiteFlag = blackFlag = FALSE;
4999     appData.clockMode = !(basetime == 0 && increment == 0);
5000     if (ticking == 0) {
5001       ics_clock_paused = TRUE;
5002       StopClocks();
5003     } else if (ticking == 1) {
5004       ics_clock_paused = FALSE;
5005     }
5006     if (gameMode == IcsIdle ||
5007         relation == RELATION_OBSERVING_STATIC ||
5008         relation == RELATION_EXAMINING ||
5009         ics_clock_paused)
5010       DisplayBothClocks();
5011     else
5012       StartClocks();
5013
5014     /* Display opponents and material strengths */
5015     if (gameInfo.variant != VariantBughouse &&
5016         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5017         if (tinyLayout || smallLayout) {
5018             if(gameInfo.variant == VariantNormal)
5019               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5020                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5021                     basetime, increment);
5022             else
5023               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5024                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5025                     basetime, increment, (int) gameInfo.variant);
5026         } else {
5027             if(gameInfo.variant == VariantNormal)
5028               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5029                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5030                     basetime, increment);
5031             else
5032               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5033                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5034                     basetime, increment, VariantName(gameInfo.variant));
5035         }
5036         DisplayTitle(str);
5037   if (appData.debugMode) {
5038     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5039   }
5040     }
5041
5042
5043     /* Display the board */
5044     if (!pausing && !appData.noGUI) {
5045
5046       if (appData.premove)
5047           if (!gotPremove ||
5048              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5049              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5050               ClearPremoveHighlights();
5051
5052       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5053         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5054       DrawPosition(j, boards[currentMove]);
5055
5056       DisplayMove(moveNum - 1);
5057       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5058             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5059               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5060         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5061       }
5062     }
5063
5064     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5065 #if ZIPPY
5066     if(bookHit) { // [HGM] book: simulate book reply
5067         static char bookMove[MSG_SIZ]; // a bit generous?
5068
5069         programStats.nodes = programStats.depth = programStats.time =
5070         programStats.score = programStats.got_only_move = 0;
5071         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5072
5073         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5074         strcat(bookMove, bookHit);
5075         HandleMachineMove(bookMove, &first);
5076     }
5077 #endif
5078 }
5079
5080 void
5081 GetMoveListEvent ()
5082 {
5083     char buf[MSG_SIZ];
5084     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5085         ics_getting_history = H_REQUESTED;
5086         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5087         SendToICS(buf);
5088     }
5089 }
5090
5091 void
5092 SendToBoth (char *msg)
5093 {   // to make it easy to keep two engines in step in dual analysis
5094     SendToProgram(msg, &first);
5095     if(second.analyzing) SendToProgram(msg, &second);
5096 }
5097
5098 void
5099 AnalysisPeriodicEvent (int force)
5100 {
5101     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5102          && !force) || !appData.periodicUpdates)
5103       return;
5104
5105     /* Send . command to Crafty to collect stats */
5106     SendToBoth(".\n");
5107
5108     /* Don't send another until we get a response (this makes
5109        us stop sending to old Crafty's which don't understand
5110        the "." command (sending illegal cmds resets node count & time,
5111        which looks bad)) */
5112     programStats.ok_to_send = 0;
5113 }
5114
5115 void
5116 ics_update_width (int new_width)
5117 {
5118         ics_printf("set width %d\n", new_width);
5119 }
5120
5121 void
5122 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5123 {
5124     char buf[MSG_SIZ];
5125
5126     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5127         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5128             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5129             SendToProgram(buf, cps);
5130             return;
5131         }
5132         // null move in variant where engine does not understand it (for analysis purposes)
5133         SendBoard(cps, moveNum + 1); // send position after move in stead.
5134         return;
5135     }
5136     if (cps->useUsermove) {
5137       SendToProgram("usermove ", cps);
5138     }
5139     if (cps->useSAN) {
5140       char *space;
5141       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5142         int len = space - parseList[moveNum];
5143         memcpy(buf, parseList[moveNum], len);
5144         buf[len++] = '\n';
5145         buf[len] = NULLCHAR;
5146       } else {
5147         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5148       }
5149       SendToProgram(buf, cps);
5150     } else {
5151       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5152         AlphaRank(moveList[moveNum], 4);
5153         SendToProgram(moveList[moveNum], cps);
5154         AlphaRank(moveList[moveNum], 4); // and back
5155       } else
5156       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5157        * the engine. It would be nice to have a better way to identify castle
5158        * moves here. */
5159       if(appData.fischerCastling && cps->useOOCastle) {
5160         int fromX = moveList[moveNum][0] - AAA;
5161         int fromY = moveList[moveNum][1] - ONE;
5162         int toX = moveList[moveNum][2] - AAA;
5163         int toY = moveList[moveNum][3] - ONE;
5164         if((boards[moveNum][fromY][fromX] == WhiteKing
5165             && boards[moveNum][toY][toX] == WhiteRook)
5166            || (boards[moveNum][fromY][fromX] == BlackKing
5167                && boards[moveNum][toY][toX] == BlackRook)) {
5168           if(toX > fromX) SendToProgram("O-O\n", cps);
5169           else SendToProgram("O-O-O\n", cps);
5170         }
5171         else SendToProgram(moveList[moveNum], cps);
5172       } else
5173       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5174         char *m = moveList[moveNum];
5175         static char c[2];
5176         *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5177         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5178           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5179                                                m[2], m[3] - '0',
5180                                                m[5], m[6] - '0',
5181                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5182         else if(*c && m[8] != '\n') { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5183           *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5184           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
5185                                                m[7], m[8] - '0',
5186                                                m[7], m[8] - '0',
5187                                                m[5], m[6] - '0',
5188                                                m[5], m[6] - '0',
5189                                                m[2], m[3] - '0', c);
5190         } else
5191           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5192                                                m[5], m[6] - '0',
5193                                                m[5], m[6] - '0',
5194                                                m[2], m[3] - '0', c);
5195           SendToProgram(buf, cps);
5196       } else
5197       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5198         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5199           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5200           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5201                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5202         } else
5203           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5204                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5205         SendToProgram(buf, cps);
5206       }
5207       else SendToProgram(moveList[moveNum], cps);
5208       /* End of additions by Tord */
5209     }
5210
5211     /* [HGM] setting up the opening has brought engine in force mode! */
5212     /*       Send 'go' if we are in a mode where machine should play. */
5213     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5214         (gameMode == TwoMachinesPlay   ||
5215 #if ZIPPY
5216          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5217 #endif
5218          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5219         SendToProgram("go\n", cps);
5220   if (appData.debugMode) {
5221     fprintf(debugFP, "(extra)\n");
5222   }
5223     }
5224     setboardSpoiledMachineBlack = 0;
5225 }
5226
5227 void
5228 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5229 {
5230     char user_move[MSG_SIZ];
5231     char suffix[4];
5232
5233     if(gameInfo.variant == VariantSChess && promoChar) {
5234         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5235         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5236     } else suffix[0] = NULLCHAR;
5237
5238     switch (moveType) {
5239       default:
5240         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5241                 (int)moveType, fromX, fromY, toX, toY);
5242         DisplayError(user_move + strlen("say "), 0);
5243         break;
5244       case WhiteKingSideCastle:
5245       case BlackKingSideCastle:
5246       case WhiteQueenSideCastleWild:
5247       case BlackQueenSideCastleWild:
5248       /* PUSH Fabien */
5249       case WhiteHSideCastleFR:
5250       case BlackHSideCastleFR:
5251       /* POP Fabien */
5252         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5253         break;
5254       case WhiteQueenSideCastle:
5255       case BlackQueenSideCastle:
5256       case WhiteKingSideCastleWild:
5257       case BlackKingSideCastleWild:
5258       /* PUSH Fabien */
5259       case WhiteASideCastleFR:
5260       case BlackASideCastleFR:
5261       /* POP Fabien */
5262         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5263         break;
5264       case WhiteNonPromotion:
5265       case BlackNonPromotion:
5266         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5267         break;
5268       case WhitePromotion:
5269       case BlackPromotion:
5270         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5271            gameInfo.variant == VariantMakruk)
5272           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5273                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5274                 PieceToChar(WhiteFerz));
5275         else if(gameInfo.variant == VariantGreat)
5276           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5277                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5278                 PieceToChar(WhiteMan));
5279         else
5280           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5281                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5282                 promoChar);
5283         break;
5284       case WhiteDrop:
5285       case BlackDrop:
5286       drop:
5287         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5288                  ToUpper(PieceToChar((ChessSquare) fromX)),
5289                  AAA + toX, ONE + toY);
5290         break;
5291       case IllegalMove:  /* could be a variant we don't quite understand */
5292         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5293       case NormalMove:
5294       case WhiteCapturesEnPassant:
5295       case BlackCapturesEnPassant:
5296         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5297                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5298         break;
5299     }
5300     SendToICS(user_move);
5301     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5302         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5303 }
5304
5305 void
5306 UploadGameEvent ()
5307 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5308     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5309     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5310     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5311       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5312       return;
5313     }
5314     if(gameMode != IcsExamining) { // is this ever not the case?
5315         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5316
5317         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5318           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5319         } else { // on FICS we must first go to general examine mode
5320           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5321         }
5322         if(gameInfo.variant != VariantNormal) {
5323             // try figure out wild number, as xboard names are not always valid on ICS
5324             for(i=1; i<=36; i++) {
5325               snprintf(buf, MSG_SIZ, "wild/%d", i);
5326                 if(StringToVariant(buf) == gameInfo.variant) break;
5327             }
5328             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5329             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5330             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5331         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5332         SendToICS(ics_prefix);
5333         SendToICS(buf);
5334         if(startedFromSetupPosition || backwardMostMove != 0) {
5335           fen = PositionToFEN(backwardMostMove, NULL, 1);
5336           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5337             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5338             SendToICS(buf);
5339           } else { // FICS: everything has to set by separate bsetup commands
5340             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5341             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5342             SendToICS(buf);
5343             if(!WhiteOnMove(backwardMostMove)) {
5344                 SendToICS("bsetup tomove black\n");
5345             }
5346             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5347             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5348             SendToICS(buf);
5349             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5350             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5351             SendToICS(buf);
5352             i = boards[backwardMostMove][EP_STATUS];
5353             if(i >= 0) { // set e.p.
5354               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5355                 SendToICS(buf);
5356             }
5357             bsetup++;
5358           }
5359         }
5360       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5361             SendToICS("bsetup done\n"); // switch to normal examining.
5362     }
5363     for(i = backwardMostMove; i<last; i++) {
5364         char buf[20];
5365         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5366         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5367             int len = strlen(moveList[i]);
5368             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5369             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5370         }
5371         SendToICS(buf);
5372     }
5373     SendToICS(ics_prefix);
5374     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5375 }
5376
5377 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5378 int legNr = 1;
5379
5380 void
5381 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5382 {
5383     if (rf == DROP_RANK) {
5384       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5385       sprintf(move, "%c@%c%c\n",
5386                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5387     } else {
5388         if (promoChar == 'x' || promoChar == NULLCHAR) {
5389           sprintf(move, "%c%c%c%c\n",
5390                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5391           if(killX >= 0 && killY >= 0) {
5392             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5393             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5394           }
5395         } else {
5396             sprintf(move, "%c%c%c%c%c\n",
5397                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5398           if(killX >= 0 && killY >= 0) {
5399             sprintf(move+4, ";%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5400             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5401           }
5402         }
5403     }
5404 }
5405
5406 void
5407 ProcessICSInitScript (FILE *f)
5408 {
5409     char buf[MSG_SIZ];
5410
5411     while (fgets(buf, MSG_SIZ, f)) {
5412         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5413     }
5414
5415     fclose(f);
5416 }
5417
5418
5419 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5420 int dragging;
5421 static ClickType lastClickType;
5422
5423 int
5424 PieceInString (char *s, ChessSquare piece)
5425 {
5426   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5427   while((p = strchr(s, ID))) {
5428     if(!suffix || p[1] == suffix) return TRUE;
5429     s = p;
5430   }
5431   return FALSE;
5432 }
5433
5434 int
5435 Partner (ChessSquare *p)
5436 { // change piece into promotion partner if one shogi-promotes to the other
5437   ChessSquare partner = promoPartner[*p];
5438   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5439   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5440   *p = partner;
5441   return 1;
5442 }
5443
5444 void
5445 Sweep (int step)
5446 {
5447     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5448     static int toggleFlag;
5449     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5450     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5451     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5452     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5453     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5454     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5455     do {
5456         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5457         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5458         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5459         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5460         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5461         if(!step) step = -1;
5462     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5463             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5464             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5465             promoSweep == pawn ||
5466             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5467             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5468     if(toX >= 0) {
5469         int victim = boards[currentMove][toY][toX];
5470         boards[currentMove][toY][toX] = promoSweep;
5471         DrawPosition(FALSE, boards[currentMove]);
5472         boards[currentMove][toY][toX] = victim;
5473     } else
5474     ChangeDragPiece(promoSweep);
5475 }
5476
5477 int
5478 PromoScroll (int x, int y)
5479 {
5480   int step = 0;
5481
5482   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5483   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5484   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5485   if(!step) return FALSE;
5486   lastX = x; lastY = y;
5487   if((promoSweep < BlackPawn) == flipView) step = -step;
5488   if(step > 0) selectFlag = 1;
5489   if(!selectFlag) Sweep(step);
5490   return FALSE;
5491 }
5492
5493 void
5494 NextPiece (int step)
5495 {
5496     ChessSquare piece = boards[currentMove][toY][toX];
5497     do {
5498         pieceSweep -= step;
5499         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5500         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5501         if(!step) step = -1;
5502     } while(PieceToChar(pieceSweep) == '.');
5503     boards[currentMove][toY][toX] = pieceSweep;
5504     DrawPosition(FALSE, boards[currentMove]);
5505     boards[currentMove][toY][toX] = piece;
5506 }
5507 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5508 void
5509 AlphaRank (char *move, int n)
5510 {
5511 //    char *p = move, c; int x, y;
5512
5513     if (appData.debugMode) {
5514         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5515     }
5516
5517     if(move[1]=='*' &&
5518        move[2]>='0' && move[2]<='9' &&
5519        move[3]>='a' && move[3]<='x'    ) {
5520         move[1] = '@';
5521         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5522         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5523     } else
5524     if(move[0]>='0' && move[0]<='9' &&
5525        move[1]>='a' && move[1]<='x' &&
5526        move[2]>='0' && move[2]<='9' &&
5527        move[3]>='a' && move[3]<='x'    ) {
5528         /* input move, Shogi -> normal */
5529         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5530         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5531         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5532         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5533     } else
5534     if(move[1]=='@' &&
5535        move[3]>='0' && move[3]<='9' &&
5536        move[2]>='a' && move[2]<='x'    ) {
5537         move[1] = '*';
5538         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5539         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5540     } else
5541     if(
5542        move[0]>='a' && move[0]<='x' &&
5543        move[3]>='0' && move[3]<='9' &&
5544        move[2]>='a' && move[2]<='x'    ) {
5545          /* output move, normal -> Shogi */
5546         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5547         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5548         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5549         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5550         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5551     }
5552     if (appData.debugMode) {
5553         fprintf(debugFP, "   out = '%s'\n", move);
5554     }
5555 }
5556
5557 char yy_textstr[8000];
5558
5559 /* Parser for moves from gnuchess, ICS, or user typein box */
5560 Boolean
5561 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5562 {
5563     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5564
5565     switch (*moveType) {
5566       case WhitePromotion:
5567       case BlackPromotion:
5568       case WhiteNonPromotion:
5569       case BlackNonPromotion:
5570       case NormalMove:
5571       case FirstLeg:
5572       case WhiteCapturesEnPassant:
5573       case BlackCapturesEnPassant:
5574       case WhiteKingSideCastle:
5575       case WhiteQueenSideCastle:
5576       case BlackKingSideCastle:
5577       case BlackQueenSideCastle:
5578       case WhiteKingSideCastleWild:
5579       case WhiteQueenSideCastleWild:
5580       case BlackKingSideCastleWild:
5581       case BlackQueenSideCastleWild:
5582       /* Code added by Tord: */
5583       case WhiteHSideCastleFR:
5584       case WhiteASideCastleFR:
5585       case BlackHSideCastleFR:
5586       case BlackASideCastleFR:
5587       /* End of code added by Tord */
5588       case IllegalMove:         /* bug or odd chess variant */
5589         if(currentMoveString[1] == '@') { // illegal drop
5590           *fromX = WhiteOnMove(moveNum) ?
5591             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5592             (int) CharToPiece(ToLower(currentMoveString[0]));
5593           goto drop;
5594         }
5595         *fromX = currentMoveString[0] - AAA;
5596         *fromY = currentMoveString[1] - ONE;
5597         *toX = currentMoveString[2] - AAA;
5598         *toY = currentMoveString[3] - ONE;
5599         *promoChar = currentMoveString[4];
5600         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5601         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5602             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5603     if (appData.debugMode) {
5604         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5605     }
5606             *fromX = *fromY = *toX = *toY = 0;
5607             return FALSE;
5608         }
5609         if (appData.testLegality) {
5610           return (*moveType != IllegalMove);
5611         } else {
5612           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5613                          // [HGM] lion: if this is a double move we are less critical
5614                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5615         }
5616
5617       case WhiteDrop:
5618       case BlackDrop:
5619         *fromX = *moveType == WhiteDrop ?
5620           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5621           (int) CharToPiece(ToLower(currentMoveString[0]));
5622       drop:
5623         *fromY = DROP_RANK;
5624         *toX = currentMoveString[2] - AAA;
5625         *toY = currentMoveString[3] - ONE;
5626         *promoChar = NULLCHAR;
5627         return TRUE;
5628
5629       case AmbiguousMove:
5630       case ImpossibleMove:
5631       case EndOfFile:
5632       case ElapsedTime:
5633       case Comment:
5634       case PGNTag:
5635       case NAG:
5636       case WhiteWins:
5637       case BlackWins:
5638       case GameIsDrawn:
5639       default:
5640     if (appData.debugMode) {
5641         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5642     }
5643         /* bug? */
5644         *fromX = *fromY = *toX = *toY = 0;
5645         *promoChar = NULLCHAR;
5646         return FALSE;
5647     }
5648 }
5649
5650 Boolean pushed = FALSE;
5651 char *lastParseAttempt;
5652
5653 void
5654 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5655 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5656   int fromX, fromY, toX, toY; char promoChar;
5657   ChessMove moveType;
5658   Boolean valid;
5659   int nr = 0;
5660
5661   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5662   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5663     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5664     pushed = TRUE;
5665   }
5666   endPV = forwardMostMove;
5667   do {
5668     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5669     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5670     lastParseAttempt = pv;
5671     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5672     if(!valid && nr == 0 &&
5673        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5674         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5675         // Hande case where played move is different from leading PV move
5676         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5677         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5678         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5679         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5680           endPV += 2; // if position different, keep this
5681           moveList[endPV-1][0] = fromX + AAA;
5682           moveList[endPV-1][1] = fromY + ONE;
5683           moveList[endPV-1][2] = toX + AAA;
5684           moveList[endPV-1][3] = toY + ONE;
5685           parseList[endPV-1][0] = NULLCHAR;
5686           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5687         }
5688       }
5689     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5690     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5691     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5692     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5693         valid++; // allow comments in PV
5694         continue;
5695     }
5696     nr++;
5697     if(endPV+1 > framePtr) break; // no space, truncate
5698     if(!valid) break;
5699     endPV++;
5700     CopyBoard(boards[endPV], boards[endPV-1]);
5701     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5702     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5703     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5704     CoordsToAlgebraic(boards[endPV - 1],
5705                              PosFlags(endPV - 1),
5706                              fromY, fromX, toY, toX, promoChar,
5707                              parseList[endPV - 1]);
5708   } while(valid);
5709   if(atEnd == 2) return; // used hidden, for PV conversion
5710   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5711   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5712   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5713                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5714   DrawPosition(TRUE, boards[currentMove]);
5715 }
5716
5717 int
5718 MultiPV (ChessProgramState *cps, int kind)
5719 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5720         int i;
5721         for(i=0; i<cps->nrOptions; i++) {
5722             char *s = cps->option[i].name;
5723             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5724             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5725                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5726         }
5727         return -1;
5728 }
5729
5730 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5731 static int multi, pv_margin;
5732 static ChessProgramState *activeCps;
5733
5734 Boolean
5735 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5736 {
5737         int startPV, lineStart, origIndex = index;
5738         char *p, buf2[MSG_SIZ];
5739         ChessProgramState *cps = (pane ? &second : &first);
5740
5741         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5742         lastX = x; lastY = y;
5743         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5744         lineStart = startPV = index;
5745         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5746         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5747         index = startPV;
5748         do{ while(buf[index] && buf[index] != '\n') index++;
5749         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5750         buf[index] = 0;
5751         if(lineStart == 0 && gameMode == AnalyzeMode) {
5752             int n = 0;
5753             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5754             if(n == 0) { // click not on "fewer" or "more"
5755                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5756                     pv_margin = cps->option[multi].value;
5757                     activeCps = cps; // non-null signals margin adjustment
5758                 }
5759             } else if((multi = MultiPV(cps, 1)) >= 0) {
5760                 n += cps->option[multi].value; if(n < 1) n = 1;
5761                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5762                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5763                 cps->option[multi].value = n;
5764                 *start = *end = 0;
5765                 return FALSE;
5766             }
5767         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5768                 ExcludeClick(origIndex - lineStart);
5769                 return FALSE;
5770         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5771                 Collapse(origIndex - lineStart);
5772                 return FALSE;
5773         }
5774         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5775         *start = startPV; *end = index-1;
5776         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5777         return TRUE;
5778 }
5779
5780 char *
5781 PvToSAN (char *pv)
5782 {
5783         static char buf[10*MSG_SIZ];
5784         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5785         *buf = NULLCHAR;
5786         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5787         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5788         for(i = forwardMostMove; i<endPV; i++){
5789             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5790             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5791             k += strlen(buf+k);
5792         }
5793         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5794         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5795         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5796         endPV = savedEnd;
5797         return buf;
5798 }
5799
5800 Boolean
5801 LoadPV (int x, int y)
5802 { // called on right mouse click to load PV
5803   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5804   lastX = x; lastY = y;
5805   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5806   extendGame = FALSE;
5807   return TRUE;
5808 }
5809
5810 void
5811 UnLoadPV ()
5812 {
5813   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5814   if(activeCps) {
5815     if(pv_margin != activeCps->option[multi].value) {
5816       char buf[MSG_SIZ];
5817       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5818       SendToProgram(buf, activeCps);
5819       activeCps->option[multi].value = pv_margin;
5820     }
5821     activeCps = NULL;
5822     return;
5823   }
5824   if(endPV < 0) return;
5825   if(appData.autoCopyPV) CopyFENToClipboard();
5826   endPV = -1;
5827   if(extendGame && currentMove > forwardMostMove) {
5828         Boolean saveAnimate = appData.animate;
5829         if(pushed) {
5830             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5831                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5832             } else storedGames--; // abandon shelved tail of original game
5833         }
5834         pushed = FALSE;
5835         forwardMostMove = currentMove;
5836         currentMove = oldFMM;
5837         appData.animate = FALSE;
5838         ToNrEvent(forwardMostMove);
5839         appData.animate = saveAnimate;
5840   }
5841   currentMove = forwardMostMove;
5842   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5843   ClearPremoveHighlights();
5844   DrawPosition(TRUE, boards[currentMove]);
5845 }
5846
5847 void
5848 MovePV (int x, int y, int h)
5849 { // step through PV based on mouse coordinates (called on mouse move)
5850   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5851
5852   if(activeCps) { // adjusting engine's multi-pv margin
5853     if(x > lastX) pv_margin++; else
5854     if(x < lastX) pv_margin -= (pv_margin > 0);
5855     if(x != lastX) {
5856       char buf[MSG_SIZ];
5857       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5858       DisplayMessage(buf, "");
5859     }
5860     lastX = x;
5861     return;
5862   }
5863   // we must somehow check if right button is still down (might be released off board!)
5864   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5865   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5866   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5867   if(!step) return;
5868   lastX = x; lastY = y;
5869
5870   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5871   if(endPV < 0) return;
5872   if(y < margin) step = 1; else
5873   if(y > h - margin) step = -1;
5874   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5875   currentMove += step;
5876   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5877   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5878                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5879   DrawPosition(FALSE, boards[currentMove]);
5880 }
5881
5882
5883 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5884 // All positions will have equal probability, but the current method will not provide a unique
5885 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5886 #define DARK 1
5887 #define LITE 2
5888 #define ANY 3
5889
5890 int squaresLeft[4];
5891 int piecesLeft[(int)BlackPawn];
5892 int seed, nrOfShuffles;
5893
5894 void
5895 GetPositionNumber ()
5896 {       // sets global variable seed
5897         int i;
5898
5899         seed = appData.defaultFrcPosition;
5900         if(seed < 0) { // randomize based on time for negative FRC position numbers
5901                 for(i=0; i<50; i++) seed += random();
5902                 seed = random() ^ random() >> 8 ^ random() << 8;
5903                 if(seed<0) seed = -seed;
5904         }
5905 }
5906
5907 int
5908 put (Board board, int pieceType, int rank, int n, int shade)
5909 // put the piece on the (n-1)-th empty squares of the given shade
5910 {
5911         int i;
5912
5913         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5914                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5915                         board[rank][i] = (ChessSquare) pieceType;
5916                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5917                         squaresLeft[ANY]--;
5918                         piecesLeft[pieceType]--;
5919                         return i;
5920                 }
5921         }
5922         return -1;
5923 }
5924
5925
5926 void
5927 AddOnePiece (Board board, int pieceType, int rank, int shade)
5928 // calculate where the next piece goes, (any empty square), and put it there
5929 {
5930         int i;
5931
5932         i = seed % squaresLeft[shade];
5933         nrOfShuffles *= squaresLeft[shade];
5934         seed /= squaresLeft[shade];
5935         put(board, pieceType, rank, i, shade);
5936 }
5937
5938 void
5939 AddTwoPieces (Board board, int pieceType, int rank)
5940 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5941 {
5942         int i, n=squaresLeft[ANY], j=n-1, k;
5943
5944         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5945         i = seed % k;  // pick one
5946         nrOfShuffles *= k;
5947         seed /= k;
5948         while(i >= j) i -= j--;
5949         j = n - 1 - j; i += j;
5950         put(board, pieceType, rank, j, ANY);
5951         put(board, pieceType, rank, i, ANY);
5952 }
5953
5954 void
5955 SetUpShuffle (Board board, int number)
5956 {
5957         int i, p, first=1;
5958
5959         GetPositionNumber(); nrOfShuffles = 1;
5960
5961         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5962         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5963         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5964
5965         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5966
5967         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5968             p = (int) board[0][i];
5969             if(p < (int) BlackPawn) piecesLeft[p] ++;
5970             board[0][i] = EmptySquare;
5971         }
5972
5973         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5974             // shuffles restricted to allow normal castling put KRR first
5975             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5976                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5977             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5978                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5979             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5980                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5981             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5982                 put(board, WhiteRook, 0, 0, ANY);
5983             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5984         }
5985
5986         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5987             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5988             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5989                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5990                 while(piecesLeft[p] >= 2) {
5991                     AddOnePiece(board, p, 0, LITE);
5992                     AddOnePiece(board, p, 0, DARK);
5993                 }
5994                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5995             }
5996
5997         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5998             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5999             // but we leave King and Rooks for last, to possibly obey FRC restriction
6000             if(p == (int)WhiteRook) continue;
6001             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
6002             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
6003         }
6004
6005         // now everything is placed, except perhaps King (Unicorn) and Rooks
6006
6007         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
6008             // Last King gets castling rights
6009             while(piecesLeft[(int)WhiteUnicorn]) {
6010                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6011                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6012             }
6013
6014             while(piecesLeft[(int)WhiteKing]) {
6015                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6016                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6017             }
6018
6019
6020         } else {
6021             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6022             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6023         }
6024
6025         // Only Rooks can be left; simply place them all
6026         while(piecesLeft[(int)WhiteRook]) {
6027                 i = put(board, WhiteRook, 0, 0, ANY);
6028                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6029                         if(first) {
6030                                 first=0;
6031                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6032                         }
6033                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6034                 }
6035         }
6036         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6037             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6038         }
6039
6040         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6041 }
6042
6043 int
6044 ptclen (const char *s, char *escapes)
6045 {
6046     int n = 0;
6047     if(!*escapes) return strlen(s);
6048     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6049     return n;
6050 }
6051
6052 int
6053 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6054 /* [HGM] moved here from winboard.c because of its general usefulness */
6055 /*       Basically a safe strcpy that uses the last character as King */
6056 {
6057     int result = FALSE; int NrPieces;
6058     unsigned char partner[EmptySquare];
6059
6060     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6061                     && NrPieces >= 12 && !(NrPieces&1)) {
6062         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6063
6064         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6065         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6066             char *p, c=0;
6067             if(map[j] == '/') offs = WhitePBishop - i, j++;
6068             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6069             table[i+offs] = map[j++];
6070             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6071             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6072             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6073         }
6074         table[(int) WhiteKing]  = map[j++];
6075         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6076             char *p, c=0;
6077             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6078             i = WHITE_TO_BLACK ii;
6079             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6080             table[i+offs] = map[j++];
6081             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6082             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6083             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6084         }
6085         table[(int) BlackKing]  = map[j++];
6086
6087
6088         if(*escapes) { // set up promotion pairing
6089             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6090             // pieceToChar entirely filled, so we can look up specified partners
6091             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6092                 int c = table[i];
6093                 if(c == '^' || c == '-') { // has specified partner
6094                     int p;
6095                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6096                     if(c == '^') table[i] = '+';
6097                     if(p < EmptySquare) {
6098                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6099                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6100                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6101                     }
6102                 } else if(c == '*') {
6103                     table[i] = partner[i];
6104                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6105                 }
6106             }
6107         }
6108
6109         result = TRUE;
6110     }
6111
6112     return result;
6113 }
6114
6115 int
6116 SetCharTable (unsigned char *table, const char * map)
6117 {
6118     return SetCharTableEsc(table, map, "");
6119 }
6120
6121 void
6122 Prelude (Board board)
6123 {       // [HGM] superchess: random selection of exo-pieces
6124         int i, j, k; ChessSquare p;
6125         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6126
6127         GetPositionNumber(); // use FRC position number
6128
6129         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6130             SetCharTable(pieceToChar, appData.pieceToCharTable);
6131             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6132                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6133         }
6134
6135         j = seed%4;                 seed /= 4;
6136         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6137         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6138         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6139         j = seed%3 + (seed%3 >= j); seed /= 3;
6140         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6141         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6142         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6143         j = seed%3;                 seed /= 3;
6144         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6145         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6146         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6147         j = seed%2 + (seed%2 >= j); seed /= 2;
6148         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6149         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6150         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6151         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6152         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6153         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6154         put(board, exoPieces[0],    0, 0, ANY);
6155         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6156 }
6157
6158 void
6159 InitPosition (int redraw)
6160 {
6161     ChessSquare (* pieces)[BOARD_FILES];
6162     int i, j, pawnRow=1, pieceRows=1, overrule,
6163     oldx = gameInfo.boardWidth,
6164     oldy = gameInfo.boardHeight,
6165     oldh = gameInfo.holdingsWidth;
6166     static int oldv;
6167
6168     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6169
6170     /* [AS] Initialize pv info list [HGM] and game status */
6171     {
6172         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6173             pvInfoList[i].depth = 0;
6174             boards[i][EP_STATUS] = EP_NONE;
6175             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6176         }
6177
6178         initialRulePlies = 0; /* 50-move counter start */
6179
6180         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6181         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6182     }
6183
6184
6185     /* [HGM] logic here is completely changed. In stead of full positions */
6186     /* the initialized data only consist of the two backranks. The switch */
6187     /* selects which one we will use, which is than copied to the Board   */
6188     /* initialPosition, which for the rest is initialized by Pawns and    */
6189     /* empty squares. This initial position is then copied to boards[0],  */
6190     /* possibly after shuffling, so that it remains available.            */
6191
6192     gameInfo.holdingsWidth = 0; /* default board sizes */
6193     gameInfo.boardWidth    = 8;
6194     gameInfo.boardHeight   = 8;
6195     gameInfo.holdingsSize  = 0;
6196     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6197     for(i=0; i<BOARD_FILES-6; i++)
6198       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6199     initialPosition[EP_STATUS] = EP_NONE;
6200     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6201     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6202     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6203          SetCharTable(pieceNickName, appData.pieceNickNames);
6204     else SetCharTable(pieceNickName, "............");
6205     pieces = FIDEArray;
6206
6207     switch (gameInfo.variant) {
6208     case VariantFischeRandom:
6209       shuffleOpenings = TRUE;
6210       appData.fischerCastling = TRUE;
6211     default:
6212       break;
6213     case VariantShatranj:
6214       pieces = ShatranjArray;
6215       nrCastlingRights = 0;
6216       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6217       break;
6218     case VariantMakruk:
6219       pieces = makrukArray;
6220       nrCastlingRights = 0;
6221       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6222       break;
6223     case VariantASEAN:
6224       pieces = aseanArray;
6225       nrCastlingRights = 0;
6226       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6227       break;
6228     case VariantTwoKings:
6229       pieces = twoKingsArray;
6230       break;
6231     case VariantGrand:
6232       pieces = GrandArray;
6233       nrCastlingRights = 0;
6234       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6235       gameInfo.boardWidth = 10;
6236       gameInfo.boardHeight = 10;
6237       gameInfo.holdingsSize = 7;
6238       break;
6239     case VariantCapaRandom:
6240       shuffleOpenings = TRUE;
6241       appData.fischerCastling = TRUE;
6242     case VariantCapablanca:
6243       pieces = CapablancaArray;
6244       gameInfo.boardWidth = 10;
6245       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6246       break;
6247     case VariantGothic:
6248       pieces = GothicArray;
6249       gameInfo.boardWidth = 10;
6250       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6251       break;
6252     case VariantSChess:
6253       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6254       gameInfo.holdingsSize = 7;
6255       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6256       break;
6257     case VariantJanus:
6258       pieces = JanusArray;
6259       gameInfo.boardWidth = 10;
6260       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6261       nrCastlingRights = 6;
6262         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6263         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6264         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6265         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6266         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6267         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6268       break;
6269     case VariantFalcon:
6270       pieces = FalconArray;
6271       gameInfo.boardWidth = 10;
6272       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6273       break;
6274     case VariantXiangqi:
6275       pieces = XiangqiArray;
6276       gameInfo.boardWidth  = 9;
6277       gameInfo.boardHeight = 10;
6278       nrCastlingRights = 0;
6279       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6280       break;
6281     case VariantShogi:
6282       pieces = ShogiArray;
6283       gameInfo.boardWidth  = 9;
6284       gameInfo.boardHeight = 9;
6285       gameInfo.holdingsSize = 7;
6286       nrCastlingRights = 0;
6287       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6288       break;
6289     case VariantChu:
6290       pieces = ChuArray; pieceRows = 3;
6291       gameInfo.boardWidth  = 12;
6292       gameInfo.boardHeight = 12;
6293       nrCastlingRights = 0;
6294 //      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6295   //                                 "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6296       SetCharTableEsc(pieceToChar, "P.BRQSEXOG...HD..^DLI^HNV........^T..^L.C...A^AFT/^F^G^M.^E^X^O^I.^P.^B^R..M^S^C^VK"
6297                                    "p.brqsexog...hd..^dli^hnv........^t..^l.c...a^aft/^f^g^m.^e^x^o^i.^p.^b^r..m^s^c^vk", SUFFIXES);
6298       break;
6299     case VariantCourier:
6300       pieces = CourierArray;
6301       gameInfo.boardWidth  = 12;
6302       nrCastlingRights = 0;
6303       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6304       break;
6305     case VariantKnightmate:
6306       pieces = KnightmateArray;
6307       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6308       break;
6309     case VariantSpartan:
6310       pieces = SpartanArray;
6311       SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6312       break;
6313     case VariantLion:
6314       pieces = lionArray;
6315       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6316       break;
6317     case VariantChuChess:
6318       pieces = ChuChessArray;
6319       gameInfo.boardWidth = 10;
6320       gameInfo.boardHeight = 10;
6321       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6322       break;
6323     case VariantFairy:
6324       pieces = fairyArray;
6325       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6326       break;
6327     case VariantGreat:
6328       pieces = GreatArray;
6329       gameInfo.boardWidth = 10;
6330       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6331       gameInfo.holdingsSize = 8;
6332       break;
6333     case VariantSuper:
6334       pieces = FIDEArray;
6335       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6336       gameInfo.holdingsSize = 8;
6337       startedFromSetupPosition = TRUE;
6338       break;
6339     case VariantCrazyhouse:
6340     case VariantBughouse:
6341       pieces = FIDEArray;
6342       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6343       gameInfo.holdingsSize = 5;
6344       break;
6345     case VariantWildCastle:
6346       pieces = FIDEArray;
6347       /* !!?shuffle with kings guaranteed to be on d or e file */
6348       shuffleOpenings = 1;
6349       break;
6350     case VariantNoCastle:
6351       pieces = FIDEArray;
6352       nrCastlingRights = 0;
6353       /* !!?unconstrained back-rank shuffle */
6354       shuffleOpenings = 1;
6355       break;
6356     }
6357
6358     overrule = 0;
6359     if(appData.NrFiles >= 0) {
6360         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6361         gameInfo.boardWidth = appData.NrFiles;
6362     }
6363     if(appData.NrRanks >= 0) {
6364         gameInfo.boardHeight = appData.NrRanks;
6365     }
6366     if(appData.holdingsSize >= 0) {
6367         i = appData.holdingsSize;
6368 //        if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6369         gameInfo.holdingsSize = i;
6370     }
6371     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6372     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6373         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6374
6375     if(!handSize) handSize = BOARD_HEIGHT;
6376     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6377     if(pawnRow < 1) pawnRow = 1;
6378     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6379        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6380     if(gameInfo.variant == VariantChu) pawnRow = 3;
6381
6382     /* User pieceToChar list overrules defaults */
6383     if(appData.pieceToCharTable != NULL)
6384         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6385
6386     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6387
6388         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6389             s = (ChessSquare) 0; /* account holding counts in guard band */
6390         for( i=0; i<BOARD_HEIGHT; i++ )
6391             initialPosition[i][j] = s;
6392
6393         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6394         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6395         initialPosition[pawnRow][j] = WhitePawn;
6396         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6397         if(gameInfo.variant == VariantXiangqi) {
6398             if(j&1) {
6399                 initialPosition[pawnRow][j] =
6400                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6401                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6402                    initialPosition[2][j] = WhiteCannon;
6403                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6404                 }
6405             }
6406         }
6407         if(gameInfo.variant == VariantChu) {
6408              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6409                initialPosition[pawnRow+1][j] = WhiteCobra,
6410                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6411              for(i=1; i<pieceRows; i++) {
6412                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6413                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6414              }
6415         }
6416         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6417             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6418                initialPosition[0][j] = WhiteRook;
6419                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6420             }
6421         }
6422         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6423     }
6424     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6425     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6426
6427             j=BOARD_LEFT+1;
6428             initialPosition[1][j] = WhiteBishop;
6429             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6430             j=BOARD_RGHT-2;
6431             initialPosition[1][j] = WhiteRook;
6432             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6433     }
6434
6435     if( nrCastlingRights == -1) {
6436         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6437         /*       This sets default castling rights from none to normal corners   */
6438         /* Variants with other castling rights must set them themselves above    */
6439         nrCastlingRights = 6;
6440
6441         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6442         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6443         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6444         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6445         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6446         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6447      }
6448
6449      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6450      if(gameInfo.variant == VariantGreat) { // promotion commoners
6451         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6452         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6453         initialPosition[handSize-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6454         initialPosition[handSize-1-PieceToNumber(WhiteMan)][1] = 9;
6455      }
6456      if( gameInfo.variant == VariantSChess ) {
6457       initialPosition[1][0] = BlackMarshall;
6458       initialPosition[2][0] = BlackAngel;
6459       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6460       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6461       initialPosition[1][1] = initialPosition[2][1] =
6462       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6463      }
6464      initialPosition[CHECK_COUNT] = (gameInfo.variant == Variant3Check ? 0x303 : 0);
6465   if (appData.debugMode) {
6466     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6467   }
6468     if(shuffleOpenings) {
6469         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6470         startedFromSetupPosition = TRUE;
6471     }
6472     if(startedFromPositionFile) {
6473       /* [HGM] loadPos: use PositionFile for every new game */
6474       CopyBoard(initialPosition, filePosition);
6475       for(i=0; i<nrCastlingRights; i++)
6476           initialRights[i] = filePosition[CASTLING][i];
6477       startedFromSetupPosition = TRUE;
6478     }
6479     if(*appData.men) LoadPieceDesc(appData.men);
6480
6481     CopyBoard(boards[0], initialPosition);
6482
6483     if(oldx != gameInfo.boardWidth ||
6484        oldy != gameInfo.boardHeight ||
6485        oldv != gameInfo.variant ||
6486        oldh != gameInfo.holdingsWidth
6487                                          )
6488             InitDrawingSizes(-2 ,0);
6489
6490     oldv = gameInfo.variant;
6491     if (redraw)
6492       DrawPosition(TRUE, boards[currentMove]);
6493 }
6494
6495 void
6496 SendBoard (ChessProgramState *cps, int moveNum)
6497 {
6498     char message[MSG_SIZ];
6499
6500     if (cps->useSetboard) {
6501       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6502       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6503       SendToProgram(message, cps);
6504       free(fen);
6505
6506     } else {
6507       ChessSquare *bp;
6508       int i, j, left=0, right=BOARD_WIDTH;
6509       /* Kludge to set black to move, avoiding the troublesome and now
6510        * deprecated "black" command.
6511        */
6512       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6513         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
6514
6515       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6516
6517       SendToProgram("edit\n", cps);
6518       SendToProgram("#\n", cps);
6519       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6520         bp = &boards[moveNum][i][left];
6521         for (j = left; j < right; j++, bp++) {
6522           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6523           if ((int) *bp < (int) BlackPawn) {
6524             if(j == BOARD_RGHT+1)
6525                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6526             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6527             if(message[0] == '+' || message[0] == '~') {
6528               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6529                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6530                         AAA + j, ONE + i - '0');
6531             }
6532             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6533                 message[1] = BOARD_RGHT   - 1 - j + '1';
6534                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6535             }
6536             SendToProgram(message, cps);
6537           }
6538         }
6539       }
6540
6541       SendToProgram("c\n", cps);
6542       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6543         bp = &boards[moveNum][i][left];
6544         for (j = left; j < right; j++, bp++) {
6545           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6546           if (((int) *bp != (int) EmptySquare)
6547               && ((int) *bp >= (int) BlackPawn)) {
6548             if(j == BOARD_LEFT-2)
6549                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6550             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6551                     AAA + j, ONE + i - '0');
6552             if(message[0] == '+' || message[0] == '~') {
6553               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6554                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6555                         AAA + j, ONE + i - '0');
6556             }
6557             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6558                 message[1] = BOARD_RGHT   - 1 - j + '1';
6559                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6560             }
6561             SendToProgram(message, cps);
6562           }
6563         }
6564       }
6565
6566       SendToProgram(".\n", cps);
6567     }
6568     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6569 }
6570
6571 char exclusionHeader[MSG_SIZ];
6572 int exCnt, excludePtr;
6573 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6574 static Exclusion excluTab[200];
6575 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6576
6577 static void
6578 WriteMap (int s)
6579 {
6580     int j;
6581     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6582     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6583 }
6584
6585 static void
6586 ClearMap ()
6587 {
6588     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6589     excludePtr = 24; exCnt = 0;
6590     WriteMap(0);
6591 }
6592
6593 static void
6594 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6595 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6596     char buf[2*MOVE_LEN], *p;
6597     Exclusion *e = excluTab;
6598     int i;
6599     for(i=0; i<exCnt; i++)
6600         if(e[i].ff == fromX && e[i].fr == fromY &&
6601            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6602     if(i == exCnt) { // was not in exclude list; add it
6603         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6604         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6605             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6606             return; // abort
6607         }
6608         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6609         excludePtr++; e[i].mark = excludePtr++;
6610         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6611         exCnt++;
6612     }
6613     exclusionHeader[e[i].mark] = state;
6614 }
6615
6616 static int
6617 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6618 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6619     char buf[MSG_SIZ];
6620     int j, k;
6621     ChessMove moveType;
6622     if((signed char)promoChar == -1) { // kludge to indicate best move
6623         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6624             return 1; // if unparsable, abort
6625     }
6626     // update exclusion map (resolving toggle by consulting existing state)
6627     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6628     j = k%8; k >>= 3;
6629     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6630     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6631          excludeMap[k] |=   1<<j;
6632     else excludeMap[k] &= ~(1<<j);
6633     // update header
6634     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6635     // inform engine
6636     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6637     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6638     SendToBoth(buf);
6639     return (state == '+');
6640 }
6641
6642 static void
6643 ExcludeClick (int index)
6644 {
6645     int i, j;
6646     Exclusion *e = excluTab;
6647     if(index < 25) { // none, best or tail clicked
6648         if(index < 13) { // none: include all
6649             WriteMap(0); // clear map
6650             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6651             SendToBoth("include all\n"); // and inform engine
6652         } else if(index > 18) { // tail
6653             if(exclusionHeader[19] == '-') { // tail was excluded
6654                 SendToBoth("include all\n");
6655                 WriteMap(0); // clear map completely
6656                 // now re-exclude selected moves
6657                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6658                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6659             } else { // tail was included or in mixed state
6660                 SendToBoth("exclude all\n");
6661                 WriteMap(0xFF); // fill map completely
6662                 // now re-include selected moves
6663                 j = 0; // count them
6664                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6665                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6666                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6667             }
6668         } else { // best
6669             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6670         }
6671     } else {
6672         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6673             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6674             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6675             break;
6676         }
6677     }
6678 }
6679
6680 ChessSquare
6681 DefaultPromoChoice (int white)
6682 {
6683     ChessSquare result;
6684     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6685        gameInfo.variant == VariantMakruk)
6686         result = WhiteFerz; // no choice
6687     else if(gameInfo.variant == VariantASEAN)
6688         result = WhiteRook; // no choice
6689     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6690         result= WhiteKing; // in Suicide Q is the last thing we want
6691     else if(gameInfo.variant == VariantSpartan)
6692         result = white ? WhiteQueen : WhiteAngel;
6693     else result = WhiteQueen;
6694     if(!white) result = WHITE_TO_BLACK result;
6695     return result;
6696 }
6697
6698 static int autoQueen; // [HGM] oneclick
6699
6700 int
6701 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6702 {
6703     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6704     /* [HGM] add Shogi promotions */
6705     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6706     ChessSquare piece, partner;
6707     ChessMove moveType;
6708     Boolean premove;
6709
6710     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6711     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6712
6713     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6714       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6715         return FALSE;
6716
6717     if(legal[toY][toX] == 4) return FALSE;
6718
6719     piece = boards[currentMove][fromY][fromX];
6720     if(gameInfo.variant == VariantChu) {
6721         promotionZoneSize = (BOARD_HEIGHT - deadRanks)/3;
6722         if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6723         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6724     } else if(gameInfo.variant == VariantShogi) {
6725         promotionZoneSize = (BOARD_HEIGHT- deadRanks)/3 +(BOARD_HEIGHT == 8);
6726         highestPromotingPiece = (int)WhiteAlfil;
6727         if(PieceToChar(piece) != '+' || PieceToChar(CHUPROMOTED(piece)) == '+') highestPromotingPiece = piece;
6728     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6729         promotionZoneSize = 3;
6730     }
6731
6732     // Treat Lance as Pawn when it is not representing Amazon or Lance
6733     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6734         if(piece == WhiteLance) piece = WhitePawn; else
6735         if(piece == BlackLance) piece = BlackPawn;
6736     }
6737
6738     // next weed out all moves that do not touch the promotion zone at all
6739     if((int)piece >= BlackPawn) {
6740         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6741              return FALSE;
6742         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6743         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6744     } else {
6745         if(  toY < BOARD_HEIGHT - deadRanks - promotionZoneSize &&
6746            fromY < BOARD_HEIGHT - deadRanks - promotionZoneSize) return FALSE;
6747         if(fromY >= BOARD_HEIGHT - deadRanks - promotionZoneSize && gameInfo.variant == VariantChuChess)
6748              return FALSE;
6749     }
6750
6751     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6752
6753     // weed out mandatory Shogi promotions
6754     if(gameInfo.variant == VariantShogi) {
6755         if(piece >= BlackPawn) {
6756             if(toY == 0 && piece == BlackPawn ||
6757                toY == 0 && piece == BlackQueen ||
6758                toY <= 1 && piece == BlackKnight) {
6759                 *promoChoice = '+';
6760                 return FALSE;
6761             }
6762         } else {
6763             if(toY == BOARD_HEIGHT-deadRanks-1 && piece == WhitePawn ||
6764                toY == BOARD_HEIGHT-deadRanks-1 && piece == WhiteQueen ||
6765                toY >= BOARD_HEIGHT-deadRanks-2 && piece == WhiteKnight) {
6766                 *promoChoice = '+';
6767                 return FALSE;
6768             }
6769         }
6770     }
6771
6772     // weed out obviously illegal Pawn moves
6773     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6774         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6775         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6776         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6777         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6778         // note we are not allowed to test for valid (non-)capture, due to premove
6779     }
6780
6781     // we either have a choice what to promote to, or (in Shogi) whether to promote
6782     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6783        gameInfo.variant == VariantMakruk) {
6784         ChessSquare p=BlackFerz;  // no choice
6785         while(p < EmptySquare) {  //but make sure we use piece that exists
6786             *promoChoice = PieceToChar(p++);
6787             if(*promoChoice != '.') break;
6788         }
6789         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6790     }
6791     // no sense asking what we must promote to if it is going to explode...
6792     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6793         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6794         return FALSE;
6795     }
6796     // give caller the default choice even if we will not make it
6797     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6798     partner = piece; // pieces can promote if the pieceToCharTable says so
6799     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6800     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6801     if(        sweepSelect && gameInfo.variant != VariantGreat
6802                            && gameInfo.variant != VariantGrand
6803                            && gameInfo.variant != VariantSuper) return FALSE;
6804     if(autoQueen) return FALSE; // predetermined
6805
6806     // suppress promotion popup on illegal moves that are not premoves
6807     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6808               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6809     if(appData.testLegality && !premove) {
6810         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6811                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6812         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6813         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6814             return FALSE;
6815     }
6816
6817     return TRUE;
6818 }
6819
6820 int
6821 InPalace (int row, int column)
6822 {   /* [HGM] for Xiangqi */
6823     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6824          column < (BOARD_WIDTH + 4)/2 &&
6825          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6826     return FALSE;
6827 }
6828
6829 int
6830 PieceForSquare (int x, int y)
6831 {
6832   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) return -1;
6833   if(x == BOARD_RGHT+1 && handOffsets & 1) y += handSize - BOARD_HEIGHT;
6834   if(x == BOARD_LEFT-2 && !(handOffsets & 2)) y += handSize - BOARD_HEIGHT;
6835      return boards[currentMove][y][x];
6836 }
6837
6838 ChessSquare
6839 More (Board board, int col, int start, int end)
6840 {
6841     int k;
6842     for(k=start; k<end; k++) if(board[k][col]) return (col == 1 ? WhiteMonarch : BlackMonarch); // arrow image
6843     return EmptySquare;
6844 }
6845
6846 void
6847 DrawPosition (int repaint, Board board)
6848 {
6849     Board compactedBoard;
6850     if(handSize > BOARD_HEIGHT && board) {
6851         int k;
6852         CopyBoard(compactedBoard, board);
6853         if(handOffsets & 1) {
6854             for(k=0; k<BOARD_HEIGHT; k++) {
6855                 compactedBoard[k][BOARD_WIDTH-1] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-1];
6856                 compactedBoard[k][BOARD_WIDTH-2] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-2];
6857             }
6858             compactedBoard[0][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, 0, handSize-BOARD_HEIGHT+1);
6859         } else compactedBoard[BOARD_HEIGHT-1][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, BOARD_HEIGHT-1, handSize);
6860         if(!(handOffsets & 2)) {
6861             for(k=0; k<BOARD_HEIGHT; k++) {
6862                 compactedBoard[k][0] = board[k+handSize-BOARD_HEIGHT][0];
6863                 compactedBoard[k][1] = board[k+handSize-BOARD_HEIGHT][1];
6864             }
6865             compactedBoard[0][0] = More(board, 1, 0, handSize-BOARD_HEIGHT+1);
6866         } else compactedBoard[BOARD_HEIGHT-1][0] = More(board, 1, BOARD_HEIGHT-1, handSize);
6867         DrawPositionX(TRUE, compactedBoard);
6868     } else DrawPositionX(repaint, board);
6869 }
6870
6871 int
6872 OKToStartUserMove (int x, int y)
6873 {
6874     ChessSquare from_piece;
6875     int white_piece;
6876
6877     if (matchMode) return FALSE;
6878     if (gameMode == EditPosition) return TRUE;
6879
6880     if (x >= 0 && y >= 0)
6881       from_piece = boards[currentMove][y][x];
6882     else
6883       from_piece = EmptySquare;
6884
6885     if (from_piece == EmptySquare) return FALSE;
6886
6887     white_piece = (int)from_piece >= (int)WhitePawn &&
6888       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6889
6890     switch (gameMode) {
6891       case AnalyzeFile:
6892       case TwoMachinesPlay:
6893       case EndOfGame:
6894         return FALSE;
6895
6896       case IcsObserving:
6897       case IcsIdle:
6898         return FALSE;
6899
6900       case MachinePlaysWhite:
6901       case IcsPlayingBlack:
6902         if (appData.zippyPlay) return FALSE;
6903         if (white_piece) {
6904             DisplayMoveError(_("You are playing Black"));
6905             return FALSE;
6906         }
6907         break;
6908
6909       case MachinePlaysBlack:
6910       case IcsPlayingWhite:
6911         if (appData.zippyPlay) return FALSE;
6912         if (!white_piece) {
6913             DisplayMoveError(_("You are playing White"));
6914             return FALSE;
6915         }
6916         break;
6917
6918       case PlayFromGameFile:
6919             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6920       case EditGame:
6921       case AnalyzeMode:
6922         if (!white_piece && WhiteOnMove(currentMove)) {
6923             DisplayMoveError(_("It is White's turn"));
6924             return FALSE;
6925         }
6926         if (white_piece && !WhiteOnMove(currentMove)) {
6927             DisplayMoveError(_("It is Black's turn"));
6928             return FALSE;
6929         }
6930         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6931             /* Editing correspondence game history */
6932             /* Could disallow this or prompt for confirmation */
6933             cmailOldMove = -1;
6934         }
6935         break;
6936
6937       case BeginningOfGame:
6938         if (appData.icsActive) return FALSE;
6939         if (!appData.noChessProgram) {
6940             if (!white_piece) {
6941                 DisplayMoveError(_("You are playing White"));
6942                 return FALSE;
6943             }
6944         }
6945         break;
6946
6947       case Training:
6948         if (!white_piece && WhiteOnMove(currentMove)) {
6949             DisplayMoveError(_("It is White's turn"));
6950             return FALSE;
6951         }
6952         if (white_piece && !WhiteOnMove(currentMove)) {
6953             DisplayMoveError(_("It is Black's turn"));
6954             return FALSE;
6955         }
6956         break;
6957
6958       default:
6959       case IcsExamining:
6960         break;
6961     }
6962     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6963         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6964         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6965         && gameMode != AnalyzeFile && gameMode != Training) {
6966         DisplayMoveError(_("Displayed position is not current"));
6967         return FALSE;
6968     }
6969     return TRUE;
6970 }
6971
6972 Boolean
6973 OnlyMove (int *x, int *y, Boolean captures)
6974 {
6975     DisambiguateClosure cl;
6976     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6977     switch(gameMode) {
6978       case MachinePlaysBlack:
6979       case IcsPlayingWhite:
6980       case BeginningOfGame:
6981         if(!WhiteOnMove(currentMove)) return FALSE;
6982         break;
6983       case MachinePlaysWhite:
6984       case IcsPlayingBlack:
6985         if(WhiteOnMove(currentMove)) return FALSE;
6986         break;
6987       case EditGame:
6988         break;
6989       default:
6990         return FALSE;
6991     }
6992     cl.pieceIn = EmptySquare;
6993     cl.rfIn = *y;
6994     cl.ffIn = *x;
6995     cl.rtIn = -1;
6996     cl.ftIn = -1;
6997     cl.promoCharIn = NULLCHAR;
6998     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6999     if( cl.kind == NormalMove ||
7000         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7001         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7002         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7003       fromX = cl.ff;
7004       fromY = cl.rf;
7005       *x = cl.ft;
7006       *y = cl.rt;
7007       return TRUE;
7008     }
7009     if(cl.kind != ImpossibleMove) return FALSE;
7010     cl.pieceIn = EmptySquare;
7011     cl.rfIn = -1;
7012     cl.ffIn = -1;
7013     cl.rtIn = *y;
7014     cl.ftIn = *x;
7015     cl.promoCharIn = NULLCHAR;
7016     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
7017     if( cl.kind == NormalMove ||
7018         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7019         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7020         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7021       fromX = cl.ff;
7022       fromY = cl.rf;
7023       *x = cl.ft;
7024       *y = cl.rt;
7025       autoQueen = TRUE; // act as if autoQueen on when we click to-square
7026       return TRUE;
7027     }
7028     return FALSE;
7029 }
7030
7031 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
7032 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
7033 int lastLoadGameUseList = FALSE;
7034 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
7035 ChessMove lastLoadGameStart = EndOfFile;
7036 int doubleClick;
7037 Boolean addToBookFlag;
7038 static Board rightsBoard, nullBoard;
7039
7040 void
7041 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
7042 {
7043     ChessMove moveType;
7044     ChessSquare pup;
7045     int ff=fromX, rf=fromY, ft=toX, rt=toY;
7046
7047     /* Check if the user is playing in turn.  This is complicated because we
7048        let the user "pick up" a piece before it is his turn.  So the piece he
7049        tried to pick up may have been captured by the time he puts it down!
7050        Therefore we use the color the user is supposed to be playing in this
7051        test, not the color of the piece that is currently on the starting
7052        square---except in EditGame mode, where the user is playing both
7053        sides; fortunately there the capture race can't happen.  (It can
7054        now happen in IcsExamining mode, but that's just too bad.  The user
7055        will get a somewhat confusing message in that case.)
7056        */
7057
7058     switch (gameMode) {
7059       case AnalyzeFile:
7060       case TwoMachinesPlay:
7061       case EndOfGame:
7062       case IcsObserving:
7063       case IcsIdle:
7064         /* We switched into a game mode where moves are not accepted,
7065            perhaps while the mouse button was down. */
7066         return;
7067
7068       case MachinePlaysWhite:
7069         /* User is moving for Black */
7070         if (WhiteOnMove(currentMove)) {
7071             DisplayMoveError(_("It is White's turn"));
7072             return;
7073         }
7074         break;
7075
7076       case MachinePlaysBlack:
7077         /* User is moving for White */
7078         if (!WhiteOnMove(currentMove)) {
7079             DisplayMoveError(_("It is Black's turn"));
7080             return;
7081         }
7082         break;
7083
7084       case PlayFromGameFile:
7085             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7086       case EditGame:
7087       case IcsExamining:
7088       case BeginningOfGame:
7089       case AnalyzeMode:
7090       case Training:
7091         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7092         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7093             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7094             /* User is moving for Black */
7095             if (WhiteOnMove(currentMove)) {
7096                 DisplayMoveError(_("It is White's turn"));
7097                 return;
7098             }
7099         } else {
7100             /* User is moving for White */
7101             if (!WhiteOnMove(currentMove)) {
7102                 DisplayMoveError(_("It is Black's turn"));
7103                 return;
7104             }
7105         }
7106         break;
7107
7108       case IcsPlayingBlack:
7109         /* User is moving for Black */
7110         if (WhiteOnMove(currentMove)) {
7111             if (!appData.premove) {
7112                 DisplayMoveError(_("It is White's turn"));
7113             } else if (toX >= 0 && toY >= 0) {
7114                 premoveToX = toX;
7115                 premoveToY = toY;
7116                 premoveFromX = fromX;
7117                 premoveFromY = fromY;
7118                 premovePromoChar = promoChar;
7119                 gotPremove = 1;
7120                 if (appData.debugMode)
7121                     fprintf(debugFP, "Got premove: fromX %d,"
7122                             "fromY %d, toX %d, toY %d\n",
7123                             fromX, fromY, toX, toY);
7124             }
7125             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7126             return;
7127         }
7128         break;
7129
7130       case IcsPlayingWhite:
7131         /* User is moving for White */
7132         if (!WhiteOnMove(currentMove)) {
7133             if (!appData.premove) {
7134                 DisplayMoveError(_("It is Black's turn"));
7135             } else if (toX >= 0 && toY >= 0) {
7136                 premoveToX = toX;
7137                 premoveToY = toY;
7138                 premoveFromX = fromX;
7139                 premoveFromY = fromY;
7140                 premovePromoChar = promoChar;
7141                 gotPremove = 1;
7142                 if (appData.debugMode)
7143                     fprintf(debugFP, "Got premove: fromX %d,"
7144                             "fromY %d, toX %d, toY %d\n",
7145                             fromX, fromY, toX, toY);
7146             }
7147             DrawPosition(TRUE, boards[currentMove]);
7148             return;
7149         }
7150         break;
7151
7152       default:
7153         break;
7154
7155       case EditPosition:
7156         /* EditPosition, empty square, or different color piece;
7157            click-click move is possible */
7158         if (toX == -2 || toY == -2) {
7159             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7160             DrawPosition(FALSE, boards[currentMove]);
7161             return;
7162         } else if (toX >= 0 && toY >= 0) {
7163             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7164                 ChessSquare p = boards[0][rf][ff];
7165                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7166                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7167                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7168                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7169                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7170                     gatingPiece = p;
7171                 }
7172             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7173             boards[0][toY][toX] = boards[0][fromY][fromX];
7174             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7175                 if(boards[0][fromY][0] != EmptySquare) {
7176                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7177                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7178                 }
7179             } else
7180             if(fromX == BOARD_RGHT+1) {
7181                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7182                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7183                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7184                 }
7185             } else
7186             boards[0][fromY][fromX] = gatingPiece;
7187             ClearHighlights();
7188             DrawPosition(FALSE, boards[currentMove]);
7189             return;
7190         }
7191         return;
7192     }
7193
7194     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7195     pup = boards[currentMove][toY][toX];
7196
7197     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7198     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7199          if( pup != EmptySquare ) return;
7200          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7201            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7202                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7203            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7204            if(fromX == 0) fromY = handSize-1 - fromY; // black holdings upside-down
7205            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7206            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7207          fromY = DROP_RANK;
7208     }
7209
7210     /* [HGM] always test for legality, to get promotion info */
7211     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7212                                          fromY, fromX, toY, toX, promoChar);
7213
7214     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7215
7216     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7217
7218     /* [HGM] but possibly ignore an IllegalMove result */
7219     if (appData.testLegality) {
7220         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7221             DisplayMoveError(_("Illegal move"));
7222             return;
7223         }
7224     }
7225
7226     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7227         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7228              ClearPremoveHighlights(); // was included
7229         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7230         DrawPosition(FALSE, NULL);
7231         return;
7232     }
7233
7234     if(addToBookFlag) { // adding moves to book
7235         char buf[MSG_SIZ], move[MSG_SIZ];
7236         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7237         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7238                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7239         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7240         AddBookMove(buf);
7241         addToBookFlag = FALSE;
7242         ClearHighlights();
7243         return;
7244     }
7245
7246     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7247 }
7248
7249 /* Common tail of UserMoveEvent and DropMenuEvent */
7250 int
7251 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7252 {
7253     char *bookHit = 0;
7254
7255     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7256         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7257         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7258         if(WhiteOnMove(currentMove)) {
7259             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7260         } else {
7261             if(!boards[currentMove][handSize-1-k][1]) return 0;
7262         }
7263     }
7264
7265     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7266        move type in caller when we know the move is a legal promotion */
7267     if(moveType == NormalMove && promoChar)
7268         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7269
7270     /* [HGM] <popupFix> The following if has been moved here from
7271        UserMoveEvent(). Because it seemed to belong here (why not allow
7272        piece drops in training games?), and because it can only be
7273        performed after it is known to what we promote. */
7274     if (gameMode == Training) {
7275       /* compare the move played on the board to the next move in the
7276        * game. If they match, display the move and the opponent's response.
7277        * If they don't match, display an error message.
7278        */
7279       int saveAnimate;
7280       Board testBoard;
7281       CopyBoard(testBoard, boards[currentMove]);
7282       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7283
7284       if (CompareBoards(testBoard, boards[currentMove+1])) {
7285         ForwardInner(currentMove+1);
7286
7287         /* Autoplay the opponent's response.
7288          * if appData.animate was TRUE when Training mode was entered,
7289          * the response will be animated.
7290          */
7291         saveAnimate = appData.animate;
7292         appData.animate = animateTraining;
7293         ForwardInner(currentMove+1);
7294         appData.animate = saveAnimate;
7295
7296         /* check for the end of the game */
7297         if (currentMove >= forwardMostMove) {
7298           gameMode = PlayFromGameFile;
7299           ModeHighlight();
7300           SetTrainingModeOff();
7301           DisplayInformation(_("End of game"));
7302         }
7303       } else {
7304         DisplayError(_("Incorrect move"), 0);
7305       }
7306       return 1;
7307     }
7308
7309   /* Ok, now we know that the move is good, so we can kill
7310      the previous line in Analysis Mode */
7311   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7312                                 && currentMove < forwardMostMove) {
7313     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7314     else forwardMostMove = currentMove;
7315   }
7316
7317   ClearMap();
7318
7319   /* If we need the chess program but it's dead, restart it */
7320   ResurrectChessProgram();
7321
7322   /* A user move restarts a paused game*/
7323   if (pausing)
7324     PauseEvent();
7325
7326   thinkOutput[0] = NULLCHAR;
7327
7328   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7329
7330   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7331     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7332     return 1;
7333   }
7334
7335   if (gameMode == BeginningOfGame) {
7336     if (appData.noChessProgram) {
7337       gameMode = EditGame;
7338       SetGameInfo();
7339     } else {
7340       char buf[MSG_SIZ];
7341       gameMode = MachinePlaysBlack;
7342       StartClocks();
7343       SetGameInfo();
7344       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7345       DisplayTitle(buf);
7346       if (first.sendName) {
7347         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7348         SendToProgram(buf, &first);
7349       }
7350       StartClocks();
7351     }
7352     ModeHighlight();
7353   }
7354
7355   /* Relay move to ICS or chess engine */
7356   if (appData.icsActive) {
7357     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7358         gameMode == IcsExamining) {
7359       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7360         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7361         SendToICS("draw ");
7362         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7363       }
7364       // also send plain move, in case ICS does not understand atomic claims
7365       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7366       ics_user_moved = 1;
7367     }
7368   } else {
7369     if (first.sendTime && (gameMode == BeginningOfGame ||
7370                            gameMode == MachinePlaysWhite ||
7371                            gameMode == MachinePlaysBlack)) {
7372       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7373     }
7374     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7375          // [HGM] book: if program might be playing, let it use book
7376         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7377         first.maybeThinking = TRUE;
7378     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7379         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7380         SendBoard(&first, currentMove+1);
7381         if(second.analyzing) {
7382             if(!second.useSetboard) SendToProgram("undo\n", &second);
7383             SendBoard(&second, currentMove+1);
7384         }
7385     } else {
7386         SendMoveToProgram(forwardMostMove-1, &first);
7387         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7388     }
7389     if (currentMove == cmailOldMove + 1) {
7390       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7391     }
7392   }
7393
7394   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7395
7396   switch (gameMode) {
7397   case EditGame:
7398     if(appData.testLegality)
7399     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7400     case MT_NONE:
7401     case MT_CHECK:
7402       break;
7403     case MT_CHECKMATE:
7404     case MT_STAINMATE:
7405       if (WhiteOnMove(currentMove)) {
7406         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7407       } else {
7408         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7409       }
7410       break;
7411     case MT_STALEMATE:
7412       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7413       break;
7414     }
7415     break;
7416
7417   case MachinePlaysBlack:
7418   case MachinePlaysWhite:
7419     /* disable certain menu options while machine is thinking */
7420     SetMachineThinkingEnables();
7421     break;
7422
7423   default:
7424     break;
7425   }
7426
7427   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7428   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7429
7430   if(bookHit) { // [HGM] book: simulate book reply
7431         static char bookMove[MSG_SIZ]; // a bit generous?
7432
7433         programStats.nodes = programStats.depth = programStats.time =
7434         programStats.score = programStats.got_only_move = 0;
7435         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7436
7437         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7438         strcat(bookMove, bookHit);
7439         HandleMachineMove(bookMove, &first);
7440   }
7441   return 1;
7442 }
7443
7444 void
7445 MarkByFEN(char *fen)
7446 {
7447         int r, f;
7448         if(!appData.markers || !appData.highlightDragging) return;
7449         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7450         r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7451         while(*fen) {
7452             int s = 0;
7453             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7454             if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7455             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7456             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7457             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7458             if(*fen == 'T') marker[r][f++] = 0; else
7459             if(*fen == 'Y') marker[r][f++] = 1; else
7460             if(*fen == 'G') marker[r][f++] = 3; else
7461             if(*fen == 'B') marker[r][f++] = 4; else
7462             if(*fen == 'C') marker[r][f++] = 5; else
7463             if(*fen == 'M') marker[r][f++] = 6; else
7464             if(*fen == 'W') marker[r][f++] = 7; else
7465             if(*fen == 'D') marker[r][f++] = 8; else
7466             if(*fen == 'R') marker[r][f++] = 2; else {
7467                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7468               f += s; fen -= s>0;
7469             }
7470             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7471             if(r < 0) break;
7472             fen++;
7473         }
7474         DrawPosition(TRUE, NULL);
7475 }
7476
7477 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7478
7479 void
7480 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7481 {
7482     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7483     Markers *m = (Markers *) closure;
7484     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7485                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7486         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7487                          || kind == WhiteCapturesEnPassant
7488                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7489     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7490 }
7491
7492 static int hoverSavedValid;
7493
7494 void
7495 MarkTargetSquares (int clear)
7496 {
7497   int x, y, sum=0;
7498   if(clear) { // no reason to ever suppress clearing
7499     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7500     hoverSavedValid = 0;
7501     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7502   } else {
7503     int capt = 0;
7504     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7505        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7506     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7507     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7508       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7509       if(capt)
7510       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = legal[y][x] = 0;
7511     }
7512   }
7513   DrawPosition(FALSE, NULL);
7514 }
7515
7516 int
7517 Explode (Board board, int fromX, int fromY, int toX, int toY)
7518 {
7519     if(gameInfo.variant == VariantAtomic &&
7520        (board[toY][toX] != EmptySquare ||                     // capture?
7521         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7522                          board[fromY][fromX] == BlackPawn   )
7523       )) {
7524         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7525         return TRUE;
7526     }
7527     return FALSE;
7528 }
7529
7530 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7531
7532 int
7533 CanPromote (ChessSquare piece, int y)
7534 {
7535         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7536         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7537         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7538         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7539            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7540           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7541            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7542         return (piece == BlackPawn && y <= zone ||
7543                 piece == WhitePawn && y >= BOARD_HEIGHT-1-deadRanks-zone ||
7544                 piece == BlackLance && y <= zone ||
7545                 piece == WhiteLance && y >= BOARD_HEIGHT-1-deadRanks-zone );
7546 }
7547
7548 void
7549 HoverEvent (int xPix, int yPix, int x, int y)
7550 {
7551         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7552         int r, f;
7553         if(!first.highlight) return;
7554         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7555         if(x == oldX && y == oldY) return; // only do something if we enter new square
7556         oldFromX = fromX; oldFromY = fromY;
7557         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7558           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7559             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7560           hoverSavedValid = 1;
7561         } else if(oldX != x || oldY != y) {
7562           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7563           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7564           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7565             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7566           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7567             char buf[MSG_SIZ];
7568             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7569             SendToProgram(buf, &first);
7570           }
7571           oldX = x; oldY = y;
7572 //        SetHighlights(fromX, fromY, x, y);
7573         }
7574 }
7575
7576 void ReportClick(char *action, int x, int y)
7577 {
7578         char buf[MSG_SIZ]; // Inform engine of what user does
7579         int r, f;
7580         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7581           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7582             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7583         if(!first.highlight || gameMode == EditPosition) return;
7584         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7585         SendToProgram(buf, &first);
7586 }
7587
7588 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7589 Boolean deferChoice;
7590 int createX = -1, createY = -1; // square where we last created a piece in EditPosition mode
7591
7592 void
7593 LeftClick (ClickType clickType, int xPix, int yPix)
7594 {
7595     int x, y;
7596     static Boolean saveAnimate;
7597     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7598     char promoChoice = NULLCHAR;
7599     ChessSquare piece;
7600     static TimeMark lastClickTime, prevClickTime;
7601
7602     if(flashing) return;
7603
7604   if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7605     x = EventToSquare(xPix, BOARD_WIDTH);
7606     y = EventToSquare(yPix, BOARD_HEIGHT);
7607     if (!flipView && y >= 0) {
7608         y = BOARD_HEIGHT - 1 - y;
7609     }
7610     if (flipView && x >= 0) {
7611         x = BOARD_WIDTH - 1 - x;
7612     }
7613
7614     // map clicks in offsetted holdings back to true coords (or switch the offset)
7615     if(x == BOARD_RGHT+1) {
7616         if(handOffsets & 1) {
7617             if(y == 0) { handOffsets &= ~1; DrawPosition(TRUE, boards[currentMove]); return; }
7618             y += handSize - BOARD_HEIGHT;
7619         } else if(y == BOARD_HEIGHT-1) { handOffsets |= 1; DrawPosition(TRUE, boards[currentMove]); return; }
7620     }
7621     if(x == BOARD_LEFT-2) {
7622         if(!(handOffsets & 2)) {
7623             if(y == 0) { handOffsets |= 2; DrawPosition(TRUE, boards[currentMove]); return; }
7624             y += handSize - BOARD_HEIGHT;
7625         } else if(y == BOARD_HEIGHT-1) { handOffsets &= ~2; DrawPosition(TRUE, boards[currentMove]); return; }
7626     }
7627
7628     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && 
7629         (boards[currentMove][y][x] == EmptySquare || x == createX && y == createY) ) {
7630         static int dummy;
7631         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7632         right = TRUE;
7633         return;
7634     }
7635
7636     createX = createY = -1;
7637
7638     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7639
7640     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7641
7642     if (clickType == Press) ErrorPopDown();
7643     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7644
7645     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7646         defaultPromoChoice = promoSweep;
7647         promoSweep = EmptySquare;   // terminate sweep
7648         promoDefaultAltered = TRUE;
7649         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7650     }
7651
7652     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7653         if(clickType == Release) return; // ignore upclick of click-click destination
7654         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7655         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7656         if(gameInfo.holdingsWidth &&
7657                 (WhiteOnMove(currentMove)
7658                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7659                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7660             // click in right holdings, for determining promotion piece
7661             ChessSquare p = boards[currentMove][y][x];
7662             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7663             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7664             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7665                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7666                 fromX = fromY = -1;
7667                 return;
7668             }
7669         }
7670         DrawPosition(FALSE, boards[currentMove]);
7671         return;
7672     }
7673
7674     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7675     if(clickType == Press
7676             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7677               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7678               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7679         return;
7680
7681     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7682         // could be static click on premove from-square: abort premove
7683         gotPremove = 0;
7684         ClearPremoveHighlights();
7685     }
7686
7687     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7688         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7689
7690     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7691         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7692                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7693         defaultPromoChoice = DefaultPromoChoice(side);
7694     }
7695
7696     autoQueen = appData.alwaysPromoteToQueen;
7697
7698     if (fromX == -1) {
7699       int originalY = y;
7700       gatingPiece = EmptySquare;
7701       if (clickType != Press) {
7702         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7703             DragPieceEnd(xPix, yPix); dragging = 0;
7704             DrawPosition(FALSE, NULL);
7705         }
7706         return;
7707       }
7708       doubleClick = FALSE;
7709       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7710         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7711       }
7712       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7713       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7714          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7715          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7716             /* First square */
7717             if (OKToStartUserMove(fromX, fromY)) {
7718                 second = 0;
7719                 ReportClick("lift", x, y);
7720                 MarkTargetSquares(0);
7721                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7722                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7723                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7724                     promoSweep = defaultPromoChoice;
7725                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7726                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7727                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7728                 }
7729                 if (appData.highlightDragging) {
7730                     SetHighlights(fromX, fromY, -1, -1);
7731                 } else {
7732                     ClearHighlights();
7733                 }
7734             } else fromX = fromY = -1;
7735             return;
7736         }
7737     }
7738
7739     /* fromX != -1 */
7740     if (clickType == Press && gameMode != EditPosition) {
7741         ChessSquare fromP;
7742         ChessSquare toP;
7743         int frc;
7744
7745         // ignore off-board to clicks
7746         if(y < 0 || x < 0) return;
7747
7748         /* Check if clicking again on the same color piece */
7749         fromP = boards[currentMove][fromY][fromX];
7750         toP = boards[currentMove][y][x];
7751         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7752         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7753             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7754            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7755              WhitePawn <= toP && toP <= WhiteKing &&
7756              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7757              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7758             (BlackPawn <= fromP && fromP <= BlackKing &&
7759              BlackPawn <= toP && toP <= BlackKing &&
7760              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7761              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7762             /* Clicked again on same color piece -- changed his mind */
7763             second = (x == fromX && y == fromY);
7764             killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7765             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7766                 second = FALSE; // first double-click rather than scond click
7767                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7768             }
7769             promoDefaultAltered = FALSE;
7770            if(!second) MarkTargetSquares(1);
7771            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7772             if (appData.highlightDragging) {
7773                 SetHighlights(x, y, -1, -1);
7774             } else {
7775                 ClearHighlights();
7776             }
7777             if (OKToStartUserMove(x, y)) {
7778                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7779                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7780                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7781                  gatingPiece = boards[currentMove][fromY][fromX];
7782                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7783                 fromX = x;
7784                 fromY = y; dragging = 1;
7785                 if(!second) ReportClick("lift", x, y);
7786                 MarkTargetSquares(0);
7787                 DragPieceBegin(xPix, yPix, FALSE);
7788                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7789                     promoSweep = defaultPromoChoice;
7790                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7791                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7792                 }
7793             }
7794            }
7795            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7796            second = FALSE;
7797         }
7798         // ignore clicks on holdings
7799         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7800     }
7801
7802     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7803         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7804         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7805         return;
7806     }
7807
7808     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7809         DragPieceEnd(xPix, yPix); dragging = 0;
7810         if(clearFlag) {
7811             // a deferred attempt to click-click move an empty square on top of a piece
7812             boards[currentMove][y][x] = EmptySquare;
7813             ClearHighlights();
7814             DrawPosition(FALSE, boards[currentMove]);
7815             fromX = fromY = -1; clearFlag = 0;
7816             return;
7817         }
7818         if (appData.animateDragging) {
7819             /* Undo animation damage if any */
7820             DrawPosition(FALSE, NULL);
7821         }
7822         if (second) {
7823             /* Second up/down in same square; just abort move */
7824             second = 0;
7825             fromX = fromY = -1;
7826             gatingPiece = EmptySquare;
7827             ClearHighlights();
7828             gotPremove = 0;
7829             ClearPremoveHighlights();
7830             MarkTargetSquares(-1);
7831             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7832         } else {
7833             /* First upclick in same square; start click-click mode */
7834             SetHighlights(x, y, -1, -1);
7835         }
7836         return;
7837     }
7838
7839     clearFlag = 0;
7840
7841     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7842        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7843         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7844         DisplayMessage(_("only marked squares are legal"),"");
7845         DrawPosition(TRUE, NULL);
7846         return; // ignore to-click
7847     }
7848
7849     /* we now have a different from- and (possibly off-board) to-square */
7850     /* Completed move */
7851     if(!sweepSelecting) {
7852         toX = x;
7853         toY = y;
7854     }
7855
7856     piece = boards[currentMove][fromY][fromX];
7857
7858     saveAnimate = appData.animate;
7859     if (clickType == Press) {
7860         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7861         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7862             // must be Edit Position mode with empty-square selected
7863             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7864             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7865             return;
7866         }
7867         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7868             return;
7869         }
7870         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7871             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7872         } else
7873         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7874         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7875           if(appData.sweepSelect) {
7876             promoSweep = defaultPromoChoice;
7877             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7878             selectFlag = 0; lastX = xPix; lastY = yPix;
7879             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7880             saveFlash = appData.flashCount; appData.flashCount = 0;
7881             Sweep(0); // Pawn that is going to promote: preview promotion piece
7882             sweepSelecting = 1;
7883             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7884             MarkTargetSquares(1);
7885           }
7886           return; // promo popup appears on up-click
7887         }
7888         /* Finish clickclick move */
7889         if (appData.animate || appData.highlightLastMove) {
7890             SetHighlights(fromX, fromY, toX, toY);
7891         } else {
7892             ClearHighlights();
7893         }
7894         MarkTargetSquares(1);
7895     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7896         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7897         *promoRestrict = 0; appData.flashCount = saveFlash;
7898         if (appData.animate || appData.highlightLastMove) {
7899             SetHighlights(fromX, fromY, toX, toY);
7900         } else {
7901             ClearHighlights();
7902         }
7903         MarkTargetSquares(1);
7904     } else {
7905 #if 0
7906 // [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
7907         /* Finish drag move */
7908         if (appData.highlightLastMove) {
7909             SetHighlights(fromX, fromY, toX, toY);
7910         } else {
7911             ClearHighlights();
7912         }
7913 #endif
7914         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7915           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7916         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7917         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7918           dragging *= 2;            // flag button-less dragging if we are dragging
7919           MarkTargetSquares(1);
7920           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7921           else {
7922             kill2X = killX; kill2Y = killY;
7923             killX = x; killY = y;     // remember this square as intermediate
7924             ReportClick("put", x, y); // and inform engine
7925             ReportClick("lift", x, y);
7926             MarkTargetSquares(0);
7927             return;
7928           }
7929         }
7930         DragPieceEnd(xPix, yPix); dragging = 0;
7931         /* Don't animate move and drag both */
7932         appData.animate = FALSE;
7933         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7934     }
7935
7936     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7937     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7938         ChessSquare piece = boards[currentMove][fromY][fromX];
7939         if(gameMode == EditPosition && piece != EmptySquare &&
7940            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7941             int n;
7942
7943             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7944                 n = PieceToNumber(piece - (int)BlackPawn);
7945                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7946                 boards[currentMove][handSize-1 - n][0] = piece;
7947                 boards[currentMove][handSize-1 - n][1]++;
7948             } else
7949             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7950                 n = PieceToNumber(piece);
7951                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7952                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7953                 boards[currentMove][n][BOARD_WIDTH-2]++;
7954             }
7955             boards[currentMove][fromY][fromX] = EmptySquare;
7956         }
7957         ClearHighlights();
7958         fromX = fromY = -1;
7959         MarkTargetSquares(1);
7960         DrawPosition(TRUE, boards[currentMove]);
7961         return;
7962     }
7963
7964     // off-board moves should not be highlighted
7965     if(x < 0 || y < 0) {
7966         ClearHighlights();
7967         DrawPosition(FALSE, NULL);
7968     } else ReportClick("put", x, y);
7969
7970     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7971  }
7972
7973     if(legal[toY][toX] == 2) { // highlight-induced promotion
7974         if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7975         else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7976     } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7977       if(!*promoRestrict) {           // but has not done that yet
7978         deferChoice = TRUE;           // set up retry for when it does
7979         return;                       // and wait for that
7980       }
7981       promoChoice = ToLower(*promoRestrict); // force engine's choice
7982       deferChoice = FALSE;
7983     }
7984
7985     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7986         SetHighlights(fromX, fromY, toX, toY);
7987         MarkTargetSquares(1);
7988         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7989             // [HGM] super: promotion to captured piece selected from holdings
7990             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7991             promotionChoice = TRUE;
7992             // kludge follows to temporarily execute move on display, without promoting yet
7993             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7994             boards[currentMove][toY][toX] = p;
7995             DrawPosition(FALSE, boards[currentMove]);
7996             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7997             boards[currentMove][toY][toX] = q;
7998             DisplayMessage("Click in holdings to choose piece", "");
7999             return;
8000         }
8001         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
8002         PromotionPopUp(promoChoice);
8003     } else {
8004         int oldMove = currentMove;
8005         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
8006         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
8007         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
8008         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
8009         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
8010            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
8011             DrawPosition(TRUE, boards[currentMove]);
8012         else DrawPosition(FALSE, NULL);
8013         fromX = fromY = -1;
8014         flashing = 0;
8015     }
8016     appData.animate = saveAnimate;
8017     if (appData.animate || appData.animateDragging) {
8018         /* Undo animation damage if needed */
8019 //      DrawPosition(FALSE, NULL);
8020     }
8021 }
8022
8023 int
8024 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
8025 {   // front-end-free part taken out of PieceMenuPopup
8026     int whichMenu; int xSqr, ySqr;
8027
8028     if(seekGraphUp) { // [HGM] seekgraph
8029         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
8030         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
8031         return -2;
8032     }
8033
8034     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
8035          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
8036         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
8037         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
8038         if(action == Press)   {
8039             originalFlip = flipView;
8040             flipView = !flipView; // temporarily flip board to see game from partners perspective
8041             DrawPosition(TRUE, partnerBoard);
8042             DisplayMessage(partnerStatus, "");
8043             partnerUp = TRUE;
8044         } else if(action == Release) {
8045             flipView = originalFlip;
8046             DrawPosition(TRUE, boards[currentMove]);
8047             partnerUp = FALSE;
8048         }
8049         return -2;
8050     }
8051
8052     xSqr = EventToSquare(x, BOARD_WIDTH);
8053     ySqr = EventToSquare(y, BOARD_HEIGHT);
8054     if (action == Release) {
8055         if(pieceSweep != EmptySquare) {
8056             EditPositionMenuEvent(pieceSweep, toX, toY);
8057             pieceSweep = EmptySquare;
8058         } else UnLoadPV(); // [HGM] pv
8059     }
8060     if (action != Press) return -2; // return code to be ignored
8061     switch (gameMode) {
8062       case IcsExamining:
8063         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8064       case EditPosition:
8065         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8066         if (xSqr < 0 || ySqr < 0) return -1;
8067         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8068         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8069         if(xSqr == createX && ySqr == createY && xSqr != BOARD_LEFT-2 && xSqr != BOARD_RGHT+1) {
8070             ChessSquare p = boards[currentMove][ySqr][xSqr];
8071             do { if(++p == EmptySquare) p = WhitePawn; } while(PieceToChar(p) == '.');
8072             boards[currentMove][ySqr][xSqr] = p; DrawPosition(FALSE, boards[currentMove]);
8073             return -2;
8074         }
8075         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
8076         createX = toX = xSqr; createY = toY = ySqr; lastX = x, lastY = y;
8077         NextPiece(0);
8078         return 2; // grab
8079       case IcsObserving:
8080         if(!appData.icsEngineAnalyze) return -1;
8081       case IcsPlayingWhite:
8082       case IcsPlayingBlack:
8083         if(!appData.zippyPlay) goto noZip;
8084       case AnalyzeMode:
8085       case AnalyzeFile:
8086       case MachinePlaysWhite:
8087       case MachinePlaysBlack:
8088       case TwoMachinesPlay: // [HGM] pv: use for showing PV
8089         if (!appData.dropMenu) {
8090           LoadPV(x, y);
8091           return 2; // flag front-end to grab mouse events
8092         }
8093         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8094            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8095       case EditGame:
8096       noZip:
8097         if (xSqr < 0 || ySqr < 0) return -1;
8098         if (!appData.dropMenu || appData.testLegality &&
8099             gameInfo.variant != VariantBughouse &&
8100             gameInfo.variant != VariantCrazyhouse) return -1;
8101         whichMenu = 1; // drop menu
8102         break;
8103       default:
8104         return -1;
8105     }
8106
8107     if (((*fromX = xSqr) < 0) ||
8108         ((*fromY = ySqr) < 0)) {
8109         *fromX = *fromY = -1;
8110         return -1;
8111     }
8112     if (flipView)
8113       *fromX = BOARD_WIDTH - 1 - *fromX;
8114     else
8115       *fromY = BOARD_HEIGHT - 1 - *fromY;
8116
8117     return whichMenu;
8118 }
8119
8120 void
8121 Wheel (int dir, int x, int y)
8122 {
8123     if(gameMode == EditPosition) {
8124         int xSqr = EventToSquare(x, BOARD_WIDTH);
8125         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8126         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8127         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8128         do {
8129             boards[currentMove][ySqr][xSqr] += dir;
8130             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8131             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8132         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8133         DrawPosition(FALSE, boards[currentMove]);
8134     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8135 }
8136
8137 void
8138 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8139 {
8140 //    char * hint = lastHint;
8141     FrontEndProgramStats stats;
8142
8143     stats.which = cps == &first ? 0 : 1;
8144     stats.depth = cpstats->depth;
8145     stats.nodes = cpstats->nodes;
8146     stats.score = cpstats->score;
8147     stats.time = cpstats->time;
8148     stats.pv = cpstats->movelist;
8149     stats.hint = lastHint;
8150     stats.an_move_index = 0;
8151     stats.an_move_count = 0;
8152
8153     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8154         stats.hint = cpstats->move_name;
8155         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8156         stats.an_move_count = cpstats->nr_moves;
8157     }
8158
8159     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
8160
8161     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8162         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8163
8164     SetProgramStats( &stats );
8165 }
8166
8167 void
8168 ClearEngineOutputPane (int which)
8169 {
8170     static FrontEndProgramStats dummyStats;
8171     dummyStats.which = which;
8172     dummyStats.pv = "#";
8173     SetProgramStats( &dummyStats );
8174 }
8175
8176 #define MAXPLAYERS 500
8177
8178 char *
8179 TourneyStandings (int display)
8180 {
8181     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8182     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8183     char result, *p, *names[MAXPLAYERS];
8184
8185     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8186         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8187     names[0] = p = strdup(appData.participants);
8188     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8189
8190     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8191
8192     while(result = appData.results[nr]) {
8193         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8194         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8195         wScore = bScore = 0;
8196         switch(result) {
8197           case '+': wScore = 2; break;
8198           case '-': bScore = 2; break;
8199           case '=': wScore = bScore = 1; break;
8200           case ' ':
8201           case '*': return strdup("busy"); // tourney not finished
8202         }
8203         score[w] += wScore;
8204         score[b] += bScore;
8205         games[w]++;
8206         games[b]++;
8207         nr++;
8208     }
8209     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8210     for(w=0; w<nPlayers; w++) {
8211         bScore = -1;
8212         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8213         ranking[w] = b; points[w] = bScore; score[b] = -2;
8214     }
8215     p = malloc(nPlayers*34+1);
8216     for(w=0; w<nPlayers && w<display; w++)
8217         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8218     free(names[0]);
8219     return p;
8220 }
8221
8222 void
8223 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8224 {       // count all piece types
8225         int p, f, r;
8226         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8227         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8228         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8229                 p = board[r][f];
8230                 pCnt[p]++;
8231                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8232                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8233                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8234                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8235                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8236                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8237         }
8238 }
8239
8240 int
8241 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8242 {
8243         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8244         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8245
8246         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8247         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8248         if(myPawns == 2 && nMine == 3) // KPP
8249             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8250         if(myPawns == 1 && nMine == 2) // KP
8251             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8252         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8253             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8254         if(myPawns) return FALSE;
8255         if(pCnt[WhiteRook+side])
8256             return pCnt[BlackRook-side] ||
8257                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8258                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8259                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8260         if(pCnt[WhiteCannon+side]) {
8261             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8262             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8263         }
8264         if(pCnt[WhiteKnight+side])
8265             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8266         return FALSE;
8267 }
8268
8269 int
8270 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8271 {
8272         VariantClass v = gameInfo.variant;
8273
8274         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8275         if(v == VariantShatranj) return TRUE; // always winnable through baring
8276         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8277         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8278
8279         if(v == VariantXiangqi) {
8280                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8281
8282                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8283                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8284                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8285                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8286                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8287                 if(stale) // we have at least one last-rank P plus perhaps C
8288                     return majors // KPKX
8289                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8290                 else // KCA*E*
8291                     return pCnt[WhiteFerz+side] // KCAK
8292                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8293                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8294                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8295
8296         } else if(v == VariantKnightmate) {
8297                 if(nMine == 1) return FALSE;
8298                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8299         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8300                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8301
8302                 if(nMine == 1) return FALSE; // bare King
8303                 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
8304                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8305                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8306                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8307                 if(pCnt[WhiteKnight+side])
8308                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8309                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8310                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8311                 if(nBishops)
8312                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8313                 if(pCnt[WhiteAlfil+side])
8314                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8315                 if(pCnt[WhiteWazir+side])
8316                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8317         }
8318
8319         return TRUE;
8320 }
8321
8322 int
8323 CompareWithRights (Board b1, Board b2)
8324 {
8325     int rights = 0;
8326     if(!CompareBoards(b1, b2)) return FALSE;
8327     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8328     /* compare castling rights */
8329     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8330            rights++; /* King lost rights, while rook still had them */
8331     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8332         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8333            rights++; /* but at least one rook lost them */
8334     }
8335     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8336            rights++;
8337     if( b1[CASTLING][5] != NoRights ) {
8338         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8339            rights++;
8340     }
8341     return rights == 0;
8342 }
8343
8344 int
8345 Adjudicate (ChessProgramState *cps)
8346 {       // [HGM] some adjudications useful with buggy engines
8347         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8348         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8349         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8350         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8351         int k, drop, count = 0; static int bare = 1;
8352         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8353         Boolean canAdjudicate = !appData.icsActive;
8354
8355         // most tests only when we understand the game, i.e. legality-checking on
8356             if( appData.testLegality )
8357             {   /* [HGM] Some more adjudications for obstinate engines */
8358                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8359                 static int moveCount = 6;
8360                 ChessMove result;
8361                 char *reason = NULL;
8362
8363                 /* Count what is on board. */
8364                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8365
8366                 /* Some material-based adjudications that have to be made before stalemate test */
8367                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8368                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8369                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8370                      if(canAdjudicate && appData.checkMates) {
8371                          if(engineOpponent)
8372                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8373                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8374                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8375                          return 1;
8376                      }
8377                 }
8378
8379                 /* Bare King in Shatranj (loses) or Losers (wins) */
8380                 if( nrW == 1 || nrB == 1) {
8381                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8382                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8383                      if(canAdjudicate && appData.checkMates) {
8384                          if(engineOpponent)
8385                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8386                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8387                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8388                          return 1;
8389                      }
8390                   } else
8391                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8392                   {    /* bare King */
8393                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8394                         if(canAdjudicate && appData.checkMates) {
8395                             /* but only adjudicate if adjudication enabled */
8396                             if(engineOpponent)
8397                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8398                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8399                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8400                             return 1;
8401                         }
8402                   }
8403                 } else bare = 1;
8404
8405
8406             // don't wait for engine to announce game end if we can judge ourselves
8407             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8408               case MT_CHECK:
8409                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8410                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8411                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8412                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8413                             checkCnt++;
8414                         if(checkCnt >= 2) {
8415                             reason = "Xboard adjudication: 3rd check";
8416                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8417                             break;
8418                         }
8419                     }
8420                 }
8421               case MT_NONE:
8422               default:
8423                 break;
8424               case MT_STEALMATE:
8425               case MT_STALEMATE:
8426               case MT_STAINMATE:
8427                 reason = "Xboard adjudication: Stalemate";
8428                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8429                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8430                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8431                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8432                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8433                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8434                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8435                                                                         EP_CHECKMATE : EP_WINS);
8436                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8437                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8438                 }
8439                 break;
8440               case MT_CHECKMATE:
8441                 reason = "Xboard adjudication: Checkmate";
8442                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8443                 if(gameInfo.variant == VariantShogi) {
8444                     if(forwardMostMove > backwardMostMove
8445                        && moveList[forwardMostMove-1][1] == '@'
8446                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8447                         reason = "XBoard adjudication: pawn-drop mate";
8448                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8449                     }
8450                 }
8451                 break;
8452             }
8453
8454                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8455                     case EP_STALEMATE:
8456                         result = GameIsDrawn; break;
8457                     case EP_CHECKMATE:
8458                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8459                     case EP_WINS:
8460                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8461                     default:
8462                         result = EndOfFile;
8463                 }
8464                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8465                     if(engineOpponent)
8466                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8467                     GameEnds( result, reason, GE_XBOARD );
8468                     return 1;
8469                 }
8470
8471                 /* Next absolutely insufficient mating material. */
8472                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8473                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8474                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8475
8476                      /* always flag draws, for judging claims */
8477                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8478
8479                      if(canAdjudicate && appData.materialDraws) {
8480                          /* but only adjudicate them if adjudication enabled */
8481                          if(engineOpponent) {
8482                            SendToProgram("force\n", engineOpponent); // suppress reply
8483                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8484                          }
8485                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8486                          return 1;
8487                      }
8488                 }
8489
8490                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8491                 if(gameInfo.variant == VariantXiangqi ?
8492                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8493                  : nrW + nrB == 4 &&
8494                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8495                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8496                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8497                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8498                    ) ) {
8499                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8500                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8501                           if(engineOpponent) {
8502                             SendToProgram("force\n", engineOpponent); // suppress reply
8503                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8504                           }
8505                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8506                           return 1;
8507                      }
8508                 } else moveCount = 6;
8509             }
8510
8511         // Repetition draws and 50-move rule can be applied independently of legality testing
8512
8513                 /* Check for rep-draws */
8514                 count = 0;
8515                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8516                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8517                 for(k = forwardMostMove-2;
8518                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8519                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8520                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8521                     k-=2)
8522                 {   int rights=0;
8523                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8524                         /* compare castling rights */
8525                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8526                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8527                                 rights++; /* King lost rights, while rook still had them */
8528                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8529                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8530                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8531                                    rights++; /* but at least one rook lost them */
8532                         }
8533                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8534                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8535                                 rights++;
8536                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8537                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8538                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8539                                    rights++;
8540                         }
8541                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8542                             && appData.drawRepeats > 1) {
8543                              /* adjudicate after user-specified nr of repeats */
8544                              int result = GameIsDrawn;
8545                              char *details = "XBoard adjudication: repetition draw";
8546                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8547                                 // [HGM] xiangqi: check for forbidden perpetuals
8548                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8549                                 for(m=forwardMostMove; m>k; m-=2) {
8550                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8551                                         ourPerpetual = 0; // the current mover did not always check
8552                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8553                                         hisPerpetual = 0; // the opponent did not always check
8554                                 }
8555                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8556                                                                         ourPerpetual, hisPerpetual);
8557                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8558                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8559                                     details = "Xboard adjudication: perpetual checking";
8560                                 } else
8561                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8562                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8563                                 } else
8564                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8565                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8566                                         result = BlackWins;
8567                                         details = "Xboard adjudication: repetition";
8568                                     }
8569                                 } else // it must be XQ
8570                                 // Now check for perpetual chases
8571                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8572                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8573                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8574                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8575                                         static char resdet[MSG_SIZ];
8576                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8577                                         details = resdet;
8578                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8579                                     } else
8580                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8581                                         break; // Abort repetition-checking loop.
8582                                 }
8583                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8584                              }
8585                              if(engineOpponent) {
8586                                SendToProgram("force\n", engineOpponent); // suppress reply
8587                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8588                              }
8589                              GameEnds( result, details, GE_XBOARD );
8590                              return 1;
8591                         }
8592                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8593                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8594                     }
8595                 }
8596
8597                 /* Now we test for 50-move draws. Determine ply count */
8598                 count = forwardMostMove;
8599                 /* look for last irreversble move */
8600                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8601                     count--;
8602                 /* if we hit starting position, add initial plies */
8603                 if( count == backwardMostMove )
8604                     count -= initialRulePlies;
8605                 count = forwardMostMove - count;
8606                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8607                         // adjust reversible move counter for checks in Xiangqi
8608                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8609                         if(i < backwardMostMove) i = backwardMostMove;
8610                         while(i <= forwardMostMove) {
8611                                 lastCheck = inCheck; // check evasion does not count
8612                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8613                                 if(inCheck || lastCheck) count--; // check does not count
8614                                 i++;
8615                         }
8616                 }
8617                 if( count >= 100)
8618                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8619                          /* this is used to judge if draw claims are legal */
8620                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8621                          if(engineOpponent) {
8622                            SendToProgram("force\n", engineOpponent); // suppress reply
8623                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8624                          }
8625                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8626                          return 1;
8627                 }
8628
8629                 /* if draw offer is pending, treat it as a draw claim
8630                  * when draw condition present, to allow engines a way to
8631                  * claim draws before making their move to avoid a race
8632                  * condition occurring after their move
8633                  */
8634                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8635                          char *p = NULL;
8636                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8637                              p = "Draw claim: 50-move rule";
8638                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8639                              p = "Draw claim: 3-fold repetition";
8640                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8641                              p = "Draw claim: insufficient mating material";
8642                          if( p != NULL && canAdjudicate) {
8643                              if(engineOpponent) {
8644                                SendToProgram("force\n", engineOpponent); // suppress reply
8645                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8646                              }
8647                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8648                              return 1;
8649                          }
8650                 }
8651
8652                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8653                     if(engineOpponent) {
8654                       SendToProgram("force\n", engineOpponent); // suppress reply
8655                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8656                     }
8657                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8658                     return 1;
8659                 }
8660         return 0;
8661 }
8662
8663 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8664 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8665 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8666
8667 static int
8668 BitbaseProbe ()
8669 {
8670     int pieces[10], squares[10], cnt=0, r, f, res;
8671     static int loaded;
8672     static PPROBE_EGBB probeBB;
8673     if(!appData.testLegality) return 10;
8674     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8675     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8676     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8677     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8678         ChessSquare piece = boards[forwardMostMove][r][f];
8679         int black = (piece >= BlackPawn);
8680         int type = piece - black*BlackPawn;
8681         if(piece == EmptySquare) continue;
8682         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8683         if(type == WhiteKing) type = WhiteQueen + 1;
8684         type = egbbCode[type];
8685         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8686         pieces[cnt] = type + black*6;
8687         if(++cnt > 5) return 11;
8688     }
8689     pieces[cnt] = squares[cnt] = 0;
8690     // probe EGBB
8691     if(loaded == 2) return 13; // loading failed before
8692     if(loaded == 0) {
8693         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8694         HMODULE lib;
8695         PLOAD_EGBB loadBB;
8696         loaded = 2; // prepare for failure
8697         if(!path) return 13; // no egbb installed
8698         strncpy(buf, path + 8, MSG_SIZ);
8699         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8700         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8701         lib = LoadLibrary(buf);
8702         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8703         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8704         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8705         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8706         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8707         loaded = 1; // success!
8708     }
8709     res = probeBB(forwardMostMove & 1, pieces, squares);
8710     return res > 0 ? 1 : res < 0 ? -1 : 0;
8711 }
8712
8713 char *
8714 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8715 {   // [HGM] book: this routine intercepts moves to simulate book replies
8716     char *bookHit = NULL;
8717
8718     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8719         char buf[MSG_SIZ];
8720         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8721         SendToProgram(buf, cps);
8722     }
8723     //first determine if the incoming move brings opponent into his book
8724     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8725         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8726     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8727     if(bookHit != NULL && !cps->bookSuspend) {
8728         // make sure opponent is not going to reply after receiving move to book position
8729         SendToProgram("force\n", cps);
8730         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8731     }
8732     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8733     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8734     // now arrange restart after book miss
8735     if(bookHit) {
8736         // after a book hit we never send 'go', and the code after the call to this routine
8737         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8738         char buf[MSG_SIZ], *move = bookHit;
8739         if(cps->useSAN) {
8740             int fromX, fromY, toX, toY;
8741             char promoChar;
8742             ChessMove moveType;
8743             move = buf + 30;
8744             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8745                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8746                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8747                                     PosFlags(forwardMostMove),
8748                                     fromY, fromX, toY, toX, promoChar, move);
8749             } else {
8750                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8751                 bookHit = NULL;
8752             }
8753         }
8754         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8755         SendToProgram(buf, cps);
8756         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8757     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8758         SendToProgram("go\n", cps);
8759         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8760     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8761         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8762             SendToProgram("go\n", cps);
8763         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8764     }
8765     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8766 }
8767
8768 int
8769 LoadError (char *errmess, ChessProgramState *cps)
8770 {   // unloads engine and switches back to -ncp mode if it was first
8771     if(cps->initDone) return FALSE;
8772     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8773     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8774     cps->pr = NoProc;
8775     if(cps == &first) {
8776         appData.noChessProgram = TRUE;
8777         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8778         gameMode = BeginningOfGame; ModeHighlight();
8779         SetNCPMode();
8780     }
8781     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8782     DisplayMessage("", ""); // erase waiting message
8783     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8784     return TRUE;
8785 }
8786
8787 char *savedMessage;
8788 ChessProgramState *savedState;
8789 void
8790 DeferredBookMove (void)
8791 {
8792         if(savedState->lastPing != savedState->lastPong)
8793                     ScheduleDelayedEvent(DeferredBookMove, 10);
8794         else
8795         HandleMachineMove(savedMessage, savedState);
8796 }
8797
8798 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8799 static ChessProgramState *stalledEngine;
8800 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8801
8802 void
8803 HandleMachineMove (char *message, ChessProgramState *cps)
8804 {
8805     static char firstLeg[20], legs;
8806     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8807     char realname[MSG_SIZ];
8808     int fromX, fromY, toX, toY;
8809     ChessMove moveType;
8810     char promoChar, roar;
8811     char *p, *pv=buf1;
8812     int oldError;
8813     char *bookHit;
8814
8815     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8816         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8817         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8818             DisplayError(_("Invalid pairing from pairing engine"), 0);
8819             return;
8820         }
8821         pairingReceived = 1;
8822         NextMatchGame();
8823         return; // Skim the pairing messages here.
8824     }
8825
8826     oldError = cps->userError; cps->userError = 0;
8827
8828 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8829     /*
8830      * Kludge to ignore BEL characters
8831      */
8832     while (*message == '\007') message++;
8833
8834     /*
8835      * [HGM] engine debug message: ignore lines starting with '#' character
8836      */
8837     if(cps->debug && *message == '#') return;
8838
8839     /*
8840      * Look for book output
8841      */
8842     if (cps == &first && bookRequested) {
8843         if (message[0] == '\t' || message[0] == ' ') {
8844             /* Part of the book output is here; append it */
8845             strcat(bookOutput, message);
8846             strcat(bookOutput, "  \n");
8847             return;
8848         } else if (bookOutput[0] != NULLCHAR) {
8849             /* All of book output has arrived; display it */
8850             char *p = bookOutput;
8851             while (*p != NULLCHAR) {
8852                 if (*p == '\t') *p = ' ';
8853                 p++;
8854             }
8855             DisplayInformation(bookOutput);
8856             bookRequested = FALSE;
8857             /* Fall through to parse the current output */
8858         }
8859     }
8860
8861     /*
8862      * Look for machine move.
8863      */
8864     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8865         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8866     {
8867         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8868             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8869             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8870             stalledEngine = cps;
8871             if(appData.ponderNextMove) { // bring opponent out of ponder
8872                 if(gameMode == TwoMachinesPlay) {
8873                     if(cps->other->pause)
8874                         PauseEngine(cps->other);
8875                     else
8876                         SendToProgram("easy\n", cps->other);
8877                 }
8878             }
8879             StopClocks();
8880             return;
8881         }
8882
8883       if(cps->usePing) {
8884
8885         /* This method is only useful on engines that support ping */
8886         if(abortEngineThink) {
8887             if (appData.debugMode) {
8888                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8889             }
8890             SendToProgram("undo\n", cps);
8891             return;
8892         }
8893
8894         if (cps->lastPing != cps->lastPong) {
8895             /* Extra move from before last new; ignore */
8896             if (appData.debugMode) {
8897                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8898             }
8899           return;
8900         }
8901
8902       } else {
8903
8904         int machineWhite = FALSE;
8905
8906         switch (gameMode) {
8907           case BeginningOfGame:
8908             /* Extra move from before last reset; ignore */
8909             if (appData.debugMode) {
8910                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8911             }
8912             return;
8913
8914           case EndOfGame:
8915           case IcsIdle:
8916           default:
8917             /* Extra move after we tried to stop.  The mode test is
8918                not a reliable way of detecting this problem, but it's
8919                the best we can do on engines that don't support ping.
8920             */
8921             if (appData.debugMode) {
8922                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8923                         cps->which, gameMode);
8924             }
8925             SendToProgram("undo\n", cps);
8926             return;
8927
8928           case MachinePlaysWhite:
8929           case IcsPlayingWhite:
8930             machineWhite = TRUE;
8931             break;
8932
8933           case MachinePlaysBlack:
8934           case IcsPlayingBlack:
8935             machineWhite = FALSE;
8936             break;
8937
8938           case TwoMachinesPlay:
8939             machineWhite = (cps->twoMachinesColor[0] == 'w');
8940             break;
8941         }
8942         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8943             if (appData.debugMode) {
8944                 fprintf(debugFP,
8945                         "Ignoring move out of turn by %s, gameMode %d"
8946                         ", forwardMost %d\n",
8947                         cps->which, gameMode, forwardMostMove);
8948             }
8949             return;
8950         }
8951       }
8952
8953         if(cps->alphaRank) AlphaRank(machineMove, 4);
8954
8955         // [HGM] lion: (some very limited) support for Alien protocol
8956         killX = killY = kill2X = kill2Y = -1;
8957         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8958             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8959             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8960             return;
8961         }
8962         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8963             char *q = strchr(p+1, ',');            // second comma?
8964             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8965             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8966             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8967         }
8968         if(firstLeg[0]) { // there was a previous leg;
8969             // only support case where same piece makes two step
8970             char buf[20], *p = machineMove+1, *q = buf+1, f;
8971             safeStrCpy(buf, machineMove, 20);
8972             while(isdigit(*q)) q++; // find start of to-square
8973             safeStrCpy(machineMove, firstLeg, 20);
8974             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8975             if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg
8976             else if(*p == *buf)   // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling)
8977             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8978             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8979             firstLeg[0] = NULLCHAR; legs = 0;
8980         }
8981
8982         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8983                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8984             /* Machine move could not be parsed; ignore it. */
8985           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8986                     machineMove, _(cps->which));
8987             DisplayMoveError(buf1);
8988             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8989                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8990             if (gameMode == TwoMachinesPlay) {
8991               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8992                        buf1, GE_XBOARD);
8993             }
8994             return;
8995         }
8996
8997         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8998         /* So we have to redo legality test with true e.p. status here,  */
8999         /* to make sure an illegal e.p. capture does not slip through,   */
9000         /* to cause a forfeit on a justified illegal-move complaint      */
9001         /* of the opponent.                                              */
9002         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
9003            ChessMove moveType;
9004            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
9005                              fromY, fromX, toY, toX, promoChar);
9006             if(moveType == IllegalMove) {
9007               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
9008                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
9009                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9010                            buf1, GE_XBOARD);
9011                 return;
9012            } else if(!appData.fischerCastling)
9013            /* [HGM] Kludge to handle engines that send FRC-style castling
9014               when they shouldn't (like TSCP-Gothic) */
9015            switch(moveType) {
9016              case WhiteASideCastleFR:
9017              case BlackASideCastleFR:
9018                toX+=2;
9019                currentMoveString[2]++;
9020                break;
9021              case WhiteHSideCastleFR:
9022              case BlackHSideCastleFR:
9023                toX--;
9024                currentMoveString[2]--;
9025                break;
9026              default: ; // nothing to do, but suppresses warning of pedantic compilers
9027            }
9028         }
9029         hintRequested = FALSE;
9030         lastHint[0] = NULLCHAR;
9031         bookRequested = FALSE;
9032         /* Program may be pondering now */
9033         cps->maybeThinking = TRUE;
9034         if (cps->sendTime == 2) cps->sendTime = 1;
9035         if (cps->offeredDraw) cps->offeredDraw--;
9036
9037         /* [AS] Save move info*/
9038         pvInfoList[ forwardMostMove ].score = programStats.score;
9039         pvInfoList[ forwardMostMove ].depth = programStats.depth;
9040         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
9041
9042         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
9043
9044         /* Test suites abort the 'game' after one move */
9045         if(*appData.finger) {
9046            static FILE *f;
9047            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
9048            if(!f) f = fopen(appData.finger, "w");
9049            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
9050            else { DisplayFatalError("Bad output file", errno, 0); return; }
9051            free(fen);
9052            GameEnds(GameUnfinished, NULL, GE_XBOARD);
9053         }
9054         if(appData.epd) {
9055            if(solvingTime >= 0) {
9056               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
9057               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
9058            } else {
9059               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
9060               if(solvingTime == -2) second.matchWins++;
9061            }
9062            OutputKibitz(2, buf1);
9063            GameEnds(GameUnfinished, NULL, GE_XBOARD);
9064         }
9065
9066         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
9067         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9068             int count = 0;
9069
9070             while( count < adjudicateLossPlies ) {
9071                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9072
9073                 if( count & 1 ) {
9074                     score = -score; /* Flip score for winning side */
9075                 }
9076
9077                 if( score > appData.adjudicateLossThreshold ) {
9078                     break;
9079                 }
9080
9081                 count++;
9082             }
9083
9084             if( count >= adjudicateLossPlies ) {
9085                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9086
9087                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9088                     "Xboard adjudication",
9089                     GE_XBOARD );
9090
9091                 return;
9092             }
9093         }
9094
9095         if(Adjudicate(cps)) {
9096             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9097             return; // [HGM] adjudicate: for all automatic game ends
9098         }
9099
9100 #if ZIPPY
9101         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9102             first.initDone) {
9103           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9104                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9105                 SendToICS("draw ");
9106                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9107           }
9108           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9109           ics_user_moved = 1;
9110           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9111                 char buf[3*MSG_SIZ];
9112
9113                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9114                         programStats.score / 100.,
9115                         programStats.depth,
9116                         programStats.time / 100.,
9117                         (unsigned int)programStats.nodes,
9118                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9119                         programStats.movelist);
9120                 SendToICS(buf);
9121           }
9122         }
9123 #endif
9124
9125         /* [AS] Clear stats for next move */
9126         ClearProgramStats();
9127         thinkOutput[0] = NULLCHAR;
9128         hiddenThinkOutputState = 0;
9129
9130         bookHit = NULL;
9131         if (gameMode == TwoMachinesPlay) {
9132             /* [HGM] relaying draw offers moved to after reception of move */
9133             /* and interpreting offer as claim if it brings draw condition */
9134             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9135                 SendToProgram("draw\n", cps->other);
9136             }
9137             if (cps->other->sendTime) {
9138                 SendTimeRemaining(cps->other,
9139                                   cps->other->twoMachinesColor[0] == 'w');
9140             }
9141             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9142             if (firstMove && !bookHit) {
9143                 firstMove = FALSE;
9144                 if (cps->other->useColors) {
9145                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9146                 }
9147                 SendToProgram("go\n", cps->other);
9148             }
9149             cps->other->maybeThinking = TRUE;
9150         }
9151
9152         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9153
9154         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9155
9156         if (!pausing && appData.ringBellAfterMoves) {
9157             if(!roar) RingBell();
9158         }
9159
9160         /*
9161          * Reenable menu items that were disabled while
9162          * machine was thinking
9163          */
9164         if (gameMode != TwoMachinesPlay)
9165             SetUserThinkingEnables();
9166
9167         // [HGM] book: after book hit opponent has received move and is now in force mode
9168         // force the book reply into it, and then fake that it outputted this move by jumping
9169         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9170         if(bookHit) {
9171                 static char bookMove[MSG_SIZ]; // a bit generous?
9172
9173                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9174                 strcat(bookMove, bookHit);
9175                 message = bookMove;
9176                 cps = cps->other;
9177                 programStats.nodes = programStats.depth = programStats.time =
9178                 programStats.score = programStats.got_only_move = 0;
9179                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9180
9181                 if(cps->lastPing != cps->lastPong) {
9182                     savedMessage = message; // args for deferred call
9183                     savedState = cps;
9184                     ScheduleDelayedEvent(DeferredBookMove, 10);
9185                     return;
9186                 }
9187                 goto FakeBookMove;
9188         }
9189
9190         return;
9191     }
9192
9193     /* Set special modes for chess engines.  Later something general
9194      *  could be added here; for now there is just one kludge feature,
9195      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9196      *  when "xboard" is given as an interactive command.
9197      */
9198     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9199         cps->useSigint = FALSE;
9200         cps->useSigterm = FALSE;
9201     }
9202     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9203       ParseFeatures(message+8, cps);
9204       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9205     }
9206
9207     if (!strncmp(message, "setup ", 6) && 
9208         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9209           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9210                                         ) { // [HGM] allow first engine to define opening position
9211       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9212       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9213       *buf = NULLCHAR;
9214       if(sscanf(message, "setup (%s", buf) == 1) {
9215         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9216         ASSIGN(appData.pieceToCharTable, buf);
9217       }
9218       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9219       if(dummy >= 3) {
9220         while(message[s] && message[s++] != ' ');
9221         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9222            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9223 //          if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9224             if(hand > h) handSize = hand; else handSize = h;
9225             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9226             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9227           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9228           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9229           startedFromSetupPosition = FALSE;
9230         }
9231       }
9232       if(startedFromSetupPosition) return;
9233       ParseFEN(boards[0], &dummy, message+s, FALSE);
9234       DrawPosition(TRUE, boards[0]);
9235       CopyBoard(initialPosition, boards[0]);
9236       startedFromSetupPosition = TRUE;
9237       return;
9238     }
9239     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9240       ChessSquare piece = WhitePawn;
9241       char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9242       if(*p == '+') promoted++, ID = *++p;
9243       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9244       piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9245       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9246       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9247       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9248       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9249       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9250       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9251                                                && gameInfo.variant != VariantGreat
9252                                                && gameInfo.variant != VariantFairy    ) return;
9253       if(piece < EmptySquare) {
9254         pieceDefs = TRUE;
9255         ASSIGN(pieceDesc[piece], buf1);
9256         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9257       }
9258       return;
9259     }
9260     if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9261       if(deferChoice) {
9262         LeftClick(Press, 0, 0); // finish the click that was interrupted
9263       } else if(promoSweep != EmptySquare) {
9264         promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9265         if(strlen(promoRestrict) > 1) Sweep(0);
9266       }
9267       return;
9268     }
9269     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9270      * want this, I was asked to put it in, and obliged.
9271      */
9272     if (!strncmp(message, "setboard ", 9)) {
9273         Board initial_position;
9274
9275         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9276
9277         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9278             DisplayError(_("Bad FEN received from engine"), 0);
9279             return ;
9280         } else {
9281            Reset(TRUE, FALSE);
9282            CopyBoard(boards[0], initial_position);
9283            initialRulePlies = FENrulePlies;
9284            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9285            else gameMode = MachinePlaysBlack;
9286            DrawPosition(FALSE, boards[currentMove]);
9287         }
9288         return;
9289     }
9290
9291     /*
9292      * Look for communication commands
9293      */
9294     if (!strncmp(message, "telluser ", 9)) {
9295         if(message[9] == '\\' && message[10] == '\\')
9296             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9297         PlayTellSound();
9298         DisplayNote(message + 9);
9299         return;
9300     }
9301     if (!strncmp(message, "tellusererror ", 14)) {
9302         cps->userError = 1;
9303         if(message[14] == '\\' && message[15] == '\\')
9304             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9305         PlayTellSound();
9306         DisplayError(message + 14, 0);
9307         return;
9308     }
9309     if (!strncmp(message, "tellopponent ", 13)) {
9310       if (appData.icsActive) {
9311         if (loggedOn) {
9312           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9313           SendToICS(buf1);
9314         }
9315       } else {
9316         DisplayNote(message + 13);
9317       }
9318       return;
9319     }
9320     if (!strncmp(message, "tellothers ", 11)) {
9321       if (appData.icsActive) {
9322         if (loggedOn) {
9323           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9324           SendToICS(buf1);
9325         }
9326       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9327       return;
9328     }
9329     if (!strncmp(message, "tellall ", 8)) {
9330       if (appData.icsActive) {
9331         if (loggedOn) {
9332           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9333           SendToICS(buf1);
9334         }
9335       } else {
9336         DisplayNote(message + 8);
9337       }
9338       return;
9339     }
9340     if (strncmp(message, "warning", 7) == 0) {
9341         /* Undocumented feature, use tellusererror in new code */
9342         DisplayError(message, 0);
9343         return;
9344     }
9345     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9346         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9347         strcat(realname, " query");
9348         AskQuestion(realname, buf2, buf1, cps->pr);
9349         return;
9350     }
9351     /* Commands from the engine directly to ICS.  We don't allow these to be
9352      *  sent until we are logged on. Crafty kibitzes have been known to
9353      *  interfere with the login process.
9354      */
9355     if (loggedOn) {
9356         if (!strncmp(message, "tellics ", 8)) {
9357             SendToICS(message + 8);
9358             SendToICS("\n");
9359             return;
9360         }
9361         if (!strncmp(message, "tellicsnoalias ", 15)) {
9362             SendToICS(ics_prefix);
9363             SendToICS(message + 15);
9364             SendToICS("\n");
9365             return;
9366         }
9367         /* The following are for backward compatibility only */
9368         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9369             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9370             SendToICS(ics_prefix);
9371             SendToICS(message);
9372             SendToICS("\n");
9373             return;
9374         }
9375     }
9376     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9377         if(initPing == cps->lastPong) {
9378             if(gameInfo.variant == VariantUnknown) {
9379                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9380                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9381                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9382             }
9383             initPing = -1;
9384         }
9385         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9386             abortEngineThink = FALSE;
9387             DisplayMessage("", "");
9388             ThawUI();
9389         }
9390         return;
9391     }
9392     if(!strncmp(message, "highlight ", 10)) {
9393         if(appData.testLegality && !*engineVariant && appData.markers) return;
9394         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9395         return;
9396     }
9397     if(!strncmp(message, "click ", 6)) {
9398         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9399         if(appData.testLegality || !appData.oneClick) return;
9400         sscanf(message+6, "%c%d%c", &f, &y, &c);
9401         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9402         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9403         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9404         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9405         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9406         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9407             LeftClick(Release, lastLeftX, lastLeftY);
9408         controlKey  = (c == ',');
9409         LeftClick(Press, x, y);
9410         LeftClick(Release, x, y);
9411         first.highlight = f;
9412         return;
9413     }
9414     /*
9415      * If the move is illegal, cancel it and redraw the board.
9416      * Also deal with other error cases.  Matching is rather loose
9417      * here to accommodate engines written before the spec.
9418      */
9419     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9420         strncmp(message, "Error", 5) == 0) {
9421         if (StrStr(message, "name") ||
9422             StrStr(message, "rating") || StrStr(message, "?") ||
9423             StrStr(message, "result") || StrStr(message, "board") ||
9424             StrStr(message, "bk") || StrStr(message, "computer") ||
9425             StrStr(message, "variant") || StrStr(message, "hint") ||
9426             StrStr(message, "random") || StrStr(message, "depth") ||
9427             StrStr(message, "accepted")) {
9428             return;
9429         }
9430         if (StrStr(message, "protover")) {
9431           /* Program is responding to input, so it's apparently done
9432              initializing, and this error message indicates it is
9433              protocol version 1.  So we don't need to wait any longer
9434              for it to initialize and send feature commands. */
9435           FeatureDone(cps, 1);
9436           cps->protocolVersion = 1;
9437           return;
9438         }
9439         cps->maybeThinking = FALSE;
9440
9441         if (StrStr(message, "draw")) {
9442             /* Program doesn't have "draw" command */
9443             cps->sendDrawOffers = 0;
9444             return;
9445         }
9446         if (cps->sendTime != 1 &&
9447             (StrStr(message, "time") || StrStr(message, "otim"))) {
9448           /* Program apparently doesn't have "time" or "otim" command */
9449           cps->sendTime = 0;
9450           return;
9451         }
9452         if (StrStr(message, "analyze")) {
9453             cps->analysisSupport = FALSE;
9454             cps->analyzing = FALSE;
9455 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9456             EditGameEvent(); // [HGM] try to preserve loaded game
9457             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9458             DisplayError(buf2, 0);
9459             return;
9460         }
9461         if (StrStr(message, "(no matching move)st")) {
9462           /* Special kludge for GNU Chess 4 only */
9463           cps->stKludge = TRUE;
9464           SendTimeControl(cps, movesPerSession, timeControl,
9465                           timeIncrement, appData.searchDepth,
9466                           searchTime);
9467           return;
9468         }
9469         if (StrStr(message, "(no matching move)sd")) {
9470           /* Special kludge for GNU Chess 4 only */
9471           cps->sdKludge = TRUE;
9472           SendTimeControl(cps, movesPerSession, timeControl,
9473                           timeIncrement, appData.searchDepth,
9474                           searchTime);
9475           return;
9476         }
9477         if (!StrStr(message, "llegal")) {
9478             return;
9479         }
9480         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9481             gameMode == IcsIdle) return;
9482         if (forwardMostMove <= backwardMostMove) return;
9483         if (pausing) PauseEvent();
9484       if(appData.forceIllegal) {
9485             // [HGM] illegal: machine refused move; force position after move into it
9486           SendToProgram("force\n", cps);
9487           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9488                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9489                 // when black is to move, while there might be nothing on a2 or black
9490                 // might already have the move. So send the board as if white has the move.
9491                 // But first we must change the stm of the engine, as it refused the last move
9492                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9493                 if(WhiteOnMove(forwardMostMove)) {
9494                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9495                     SendBoard(cps, forwardMostMove); // kludgeless board
9496                 } else {
9497                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9498                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9499                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9500                 }
9501           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9502             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9503                  gameMode == TwoMachinesPlay)
9504               SendToProgram("go\n", cps);
9505             return;
9506       } else
9507         if (gameMode == PlayFromGameFile) {
9508             /* Stop reading this game file */
9509             gameMode = EditGame;
9510             ModeHighlight();
9511         }
9512         /* [HGM] illegal-move claim should forfeit game when Xboard */
9513         /* only passes fully legal moves                            */
9514         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9515             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9516                                 "False illegal-move claim", GE_XBOARD );
9517             return; // do not take back move we tested as valid
9518         }
9519         currentMove = forwardMostMove-1;
9520         DisplayMove(currentMove-1); /* before DisplayMoveError */
9521         SwitchClocks(forwardMostMove-1); // [HGM] race
9522         DisplayBothClocks();
9523         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9524                 parseList[currentMove], _(cps->which));
9525         DisplayMoveError(buf1);
9526         DrawPosition(FALSE, boards[currentMove]);
9527
9528         SetUserThinkingEnables();
9529         return;
9530     }
9531     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9532         /* Program has a broken "time" command that
9533            outputs a string not ending in newline.
9534            Don't use it. */
9535         cps->sendTime = 0;
9536     }
9537     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9538         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9539             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9540     }
9541
9542     /*
9543      * If chess program startup fails, exit with an error message.
9544      * Attempts to recover here are futile. [HGM] Well, we try anyway
9545      */
9546     if ((StrStr(message, "unknown host") != NULL)
9547         || (StrStr(message, "No remote directory") != NULL)
9548         || (StrStr(message, "not found") != NULL)
9549         || (StrStr(message, "No such file") != NULL)
9550         || (StrStr(message, "can't alloc") != NULL)
9551         || (StrStr(message, "Permission denied") != NULL)) {
9552
9553         cps->maybeThinking = FALSE;
9554         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9555                 _(cps->which), cps->program, cps->host, message);
9556         RemoveInputSource(cps->isr);
9557         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9558             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9559             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9560         }
9561         return;
9562     }
9563
9564     /*
9565      * Look for hint output
9566      */
9567     if (sscanf(message, "Hint: %s", buf1) == 1) {
9568         if (cps == &first && hintRequested) {
9569             hintRequested = FALSE;
9570             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9571                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9572                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9573                                     PosFlags(forwardMostMove),
9574                                     fromY, fromX, toY, toX, promoChar, buf1);
9575                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9576                 DisplayInformation(buf2);
9577             } else {
9578                 /* Hint move could not be parsed!? */
9579               snprintf(buf2, sizeof(buf2),
9580                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9581                         buf1, _(cps->which));
9582                 DisplayError(buf2, 0);
9583             }
9584         } else {
9585           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9586         }
9587         return;
9588     }
9589
9590     /*
9591      * Ignore other messages if game is not in progress
9592      */
9593     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9594         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9595
9596     /*
9597      * look for win, lose, draw, or draw offer
9598      */
9599     if (strncmp(message, "1-0", 3) == 0) {
9600         char *p, *q, *r = "";
9601         p = strchr(message, '{');
9602         if (p) {
9603             q = strchr(p, '}');
9604             if (q) {
9605                 *q = NULLCHAR;
9606                 r = p + 1;
9607             }
9608         }
9609         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9610         return;
9611     } else if (strncmp(message, "0-1", 3) == 0) {
9612         char *p, *q, *r = "";
9613         p = strchr(message, '{');
9614         if (p) {
9615             q = strchr(p, '}');
9616             if (q) {
9617                 *q = NULLCHAR;
9618                 r = p + 1;
9619             }
9620         }
9621         /* Kludge for Arasan 4.1 bug */
9622         if (strcmp(r, "Black resigns") == 0) {
9623             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9624             return;
9625         }
9626         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9627         return;
9628     } else if (strncmp(message, "1/2", 3) == 0) {
9629         char *p, *q, *r = "";
9630         p = strchr(message, '{');
9631         if (p) {
9632             q = strchr(p, '}');
9633             if (q) {
9634                 *q = NULLCHAR;
9635                 r = p + 1;
9636             }
9637         }
9638
9639         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9640         return;
9641
9642     } else if (strncmp(message, "White resign", 12) == 0) {
9643         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9644         return;
9645     } else if (strncmp(message, "Black resign", 12) == 0) {
9646         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9647         return;
9648     } else if (strncmp(message, "White matches", 13) == 0 ||
9649                strncmp(message, "Black matches", 13) == 0   ) {
9650         /* [HGM] ignore GNUShogi noises */
9651         return;
9652     } else if (strncmp(message, "White", 5) == 0 &&
9653                message[5] != '(' &&
9654                StrStr(message, "Black") == NULL) {
9655         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9656         return;
9657     } else if (strncmp(message, "Black", 5) == 0 &&
9658                message[5] != '(') {
9659         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9660         return;
9661     } else if (strcmp(message, "resign") == 0 ||
9662                strcmp(message, "computer resigns") == 0) {
9663         switch (gameMode) {
9664           case MachinePlaysBlack:
9665           case IcsPlayingBlack:
9666             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9667             break;
9668           case MachinePlaysWhite:
9669           case IcsPlayingWhite:
9670             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9671             break;
9672           case TwoMachinesPlay:
9673             if (cps->twoMachinesColor[0] == 'w')
9674               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9675             else
9676               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9677             break;
9678           default:
9679             /* can't happen */
9680             break;
9681         }
9682         return;
9683     } else if (strncmp(message, "opponent mates", 14) == 0) {
9684         switch (gameMode) {
9685           case MachinePlaysBlack:
9686           case IcsPlayingBlack:
9687             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9688             break;
9689           case MachinePlaysWhite:
9690           case IcsPlayingWhite:
9691             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9692             break;
9693           case TwoMachinesPlay:
9694             if (cps->twoMachinesColor[0] == 'w')
9695               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9696             else
9697               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9698             break;
9699           default:
9700             /* can't happen */
9701             break;
9702         }
9703         return;
9704     } else if (strncmp(message, "computer mates", 14) == 0) {
9705         switch (gameMode) {
9706           case MachinePlaysBlack:
9707           case IcsPlayingBlack:
9708             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9709             break;
9710           case MachinePlaysWhite:
9711           case IcsPlayingWhite:
9712             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9713             break;
9714           case TwoMachinesPlay:
9715             if (cps->twoMachinesColor[0] == 'w')
9716               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9717             else
9718               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9719             break;
9720           default:
9721             /* can't happen */
9722             break;
9723         }
9724         return;
9725     } else if (strncmp(message, "checkmate", 9) == 0) {
9726         if (WhiteOnMove(forwardMostMove)) {
9727             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9728         } else {
9729             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9730         }
9731         return;
9732     } else if (strstr(message, "Draw") != NULL ||
9733                strstr(message, "game is a draw") != NULL) {
9734         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9735         return;
9736     } else if (strstr(message, "offer") != NULL &&
9737                strstr(message, "draw") != NULL) {
9738 #if ZIPPY
9739         if (appData.zippyPlay && first.initDone) {
9740             /* Relay offer to ICS */
9741             SendToICS(ics_prefix);
9742             SendToICS("draw\n");
9743         }
9744 #endif
9745         cps->offeredDraw = 2; /* valid until this engine moves twice */
9746         if (gameMode == TwoMachinesPlay) {
9747             if (cps->other->offeredDraw) {
9748                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9749             /* [HGM] in two-machine mode we delay relaying draw offer      */
9750             /* until after we also have move, to see if it is really claim */
9751             }
9752         } else if (gameMode == MachinePlaysWhite ||
9753                    gameMode == MachinePlaysBlack) {
9754           if (userOfferedDraw) {
9755             DisplayInformation(_("Machine accepts your draw offer"));
9756             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9757           } else {
9758             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9759           }
9760         }
9761     }
9762
9763
9764     /*
9765      * Look for thinking output
9766      */
9767     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9768           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9769                                 ) {
9770         int plylev, mvleft, mvtot, curscore, time;
9771         char mvname[MOVE_LEN];
9772         u64 nodes; // [DM]
9773         char plyext;
9774         int ignore = FALSE;
9775         int prefixHint = FALSE;
9776         mvname[0] = NULLCHAR;
9777
9778         switch (gameMode) {
9779           case MachinePlaysBlack:
9780           case IcsPlayingBlack:
9781             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9782             break;
9783           case MachinePlaysWhite:
9784           case IcsPlayingWhite:
9785             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9786             break;
9787           case AnalyzeMode:
9788           case AnalyzeFile:
9789             break;
9790           case IcsObserving: /* [DM] icsEngineAnalyze */
9791             if (!appData.icsEngineAnalyze) ignore = TRUE;
9792             break;
9793           case TwoMachinesPlay:
9794             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9795                 ignore = TRUE;
9796             }
9797             break;
9798           default:
9799             ignore = TRUE;
9800             break;
9801         }
9802
9803         if (!ignore) {
9804             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9805             int solved = 0;
9806             buf1[0] = NULLCHAR;
9807             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9808                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9809                 char score_buf[MSG_SIZ];
9810
9811                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9812                     nodes += u64Const(0x100000000);
9813
9814                 if (plyext != ' ' && plyext != '\t') {
9815                     time *= 100;
9816                 }
9817
9818                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9819                 if( cps->scoreIsAbsolute &&
9820                     ( gameMode == MachinePlaysBlack ||
9821                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9822                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9823                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9824                      !WhiteOnMove(currentMove)
9825                     ) )
9826                 {
9827                     curscore = -curscore;
9828                 }
9829
9830                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9831
9832                 if(*bestMove) { // rememer time best EPD move was first found
9833                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9834                     ChessMove mt; char *p = bestMove;
9835                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9836                     solved = 0;
9837                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9838                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9839                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9840                             solved = 1;
9841                             break;
9842                         }
9843                         while(*p && *p != ' ') p++;
9844                         while(*p == ' ') p++;
9845                     }
9846                     if(!solved) solvingTime = -1;
9847                 }
9848                 if(*avoidMove && !solved) {
9849                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9850                     ChessMove mt; char *p = avoidMove, solved = 1;
9851                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9852                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9853                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9854                             solved = 0; solvingTime = -2;
9855                             break;
9856                         }
9857                         while(*p && *p != ' ') p++;
9858                         while(*p == ' ') p++;
9859                     }
9860                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9861                 }
9862
9863                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9864                         char buf[MSG_SIZ];
9865                         FILE *f;
9866                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9867                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9868                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9869                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9870                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9871                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9872                                 fclose(f);
9873                         }
9874                         else
9875                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9876                           DisplayError(_("failed writing PV"), 0);
9877                 }
9878
9879                 tempStats.depth = plylev;
9880                 tempStats.nodes = nodes;
9881                 tempStats.time = time;
9882                 tempStats.score = curscore;
9883                 tempStats.got_only_move = 0;
9884
9885                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9886                         int ticklen;
9887
9888                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9889                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9890                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9891                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9892                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9893                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9894                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9895                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9896                 }
9897
9898                 /* Buffer overflow protection */
9899                 if (pv[0] != NULLCHAR) {
9900                     if (strlen(pv) >= sizeof(tempStats.movelist)
9901                         && appData.debugMode) {
9902                         fprintf(debugFP,
9903                                 "PV is too long; using the first %u bytes.\n",
9904                                 (unsigned) sizeof(tempStats.movelist) - 1);
9905                     }
9906
9907                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9908                 } else {
9909                     sprintf(tempStats.movelist, " no PV\n");
9910                 }
9911
9912                 if (tempStats.seen_stat) {
9913                     tempStats.ok_to_send = 1;
9914                 }
9915
9916                 if (strchr(tempStats.movelist, '(') != NULL) {
9917                     tempStats.line_is_book = 1;
9918                     tempStats.nr_moves = 0;
9919                     tempStats.moves_left = 0;
9920                 } else {
9921                     tempStats.line_is_book = 0;
9922                 }
9923
9924                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9925                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9926
9927                 SendProgramStatsToFrontend( cps, &tempStats );
9928
9929                 /*
9930                     [AS] Protect the thinkOutput buffer from overflow... this
9931                     is only useful if buf1 hasn't overflowed first!
9932                 */
9933                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9934                 if(curscore >= MATE_SCORE) 
9935                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9936                 else if(curscore <= -MATE_SCORE) 
9937                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9938                 else
9939                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9940                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9941                          plylev,
9942                          (gameMode == TwoMachinesPlay ?
9943                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9944                          score_buf,
9945                          prefixHint ? lastHint : "",
9946                          prefixHint ? " " : "" );
9947
9948                 if( buf1[0] != NULLCHAR ) {
9949                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9950
9951                     if( strlen(pv) > max_len ) {
9952                         if( appData.debugMode) {
9953                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9954                         }
9955                         pv[max_len+1] = '\0';
9956                     }
9957
9958                     strcat( thinkOutput, pv);
9959                 }
9960
9961                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9962                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9963                     DisplayMove(currentMove - 1);
9964                 }
9965                 return;
9966
9967             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9968                 /* crafty (9.25+) says "(only move) <move>"
9969                  * if there is only 1 legal move
9970                  */
9971                 sscanf(p, "(only move) %s", buf1);
9972                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9973                 sprintf(programStats.movelist, "%s (only move)", buf1);
9974                 programStats.depth = 1;
9975                 programStats.nr_moves = 1;
9976                 programStats.moves_left = 1;
9977                 programStats.nodes = 1;
9978                 programStats.time = 1;
9979                 programStats.got_only_move = 1;
9980
9981                 /* Not really, but we also use this member to
9982                    mean "line isn't going to change" (Crafty
9983                    isn't searching, so stats won't change) */
9984                 programStats.line_is_book = 1;
9985
9986                 SendProgramStatsToFrontend( cps, &programStats );
9987
9988                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9989                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9990                     DisplayMove(currentMove - 1);
9991                 }
9992                 return;
9993             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9994                               &time, &nodes, &plylev, &mvleft,
9995                               &mvtot, mvname) >= 5) {
9996                 /* The stat01: line is from Crafty (9.29+) in response
9997                    to the "." command */
9998                 programStats.seen_stat = 1;
9999                 cps->maybeThinking = TRUE;
10000
10001                 if (programStats.got_only_move || !appData.periodicUpdates)
10002                   return;
10003
10004                 programStats.depth = plylev;
10005                 programStats.time = time;
10006                 programStats.nodes = nodes;
10007                 programStats.moves_left = mvleft;
10008                 programStats.nr_moves = mvtot;
10009                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
10010                 programStats.ok_to_send = 1;
10011                 programStats.movelist[0] = '\0';
10012
10013                 SendProgramStatsToFrontend( cps, &programStats );
10014
10015                 return;
10016
10017             } else if (strncmp(message,"++",2) == 0) {
10018                 /* Crafty 9.29+ outputs this */
10019                 programStats.got_fail = 2;
10020                 return;
10021
10022             } else if (strncmp(message,"--",2) == 0) {
10023                 /* Crafty 9.29+ outputs this */
10024                 programStats.got_fail = 1;
10025                 return;
10026
10027             } else if (thinkOutput[0] != NULLCHAR &&
10028                        strncmp(message, "    ", 4) == 0) {
10029                 unsigned message_len;
10030
10031                 p = message;
10032                 while (*p && *p == ' ') p++;
10033
10034                 message_len = strlen( p );
10035
10036                 /* [AS] Avoid buffer overflow */
10037                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
10038                     strcat(thinkOutput, " ");
10039                     strcat(thinkOutput, p);
10040                 }
10041
10042                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
10043                     strcat(programStats.movelist, " ");
10044                     strcat(programStats.movelist, p);
10045                 }
10046
10047                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10048                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10049                     DisplayMove(currentMove - 1);
10050                 }
10051                 return;
10052             }
10053         }
10054         else {
10055             buf1[0] = NULLCHAR;
10056
10057             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
10058                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
10059             {
10060                 ChessProgramStats cpstats;
10061
10062                 if (plyext != ' ' && plyext != '\t') {
10063                     time *= 100;
10064                 }
10065
10066                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
10067                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
10068                     curscore = -curscore;
10069                 }
10070
10071                 cpstats.depth = plylev;
10072                 cpstats.nodes = nodes;
10073                 cpstats.time = time;
10074                 cpstats.score = curscore;
10075                 cpstats.got_only_move = 0;
10076                 cpstats.movelist[0] = '\0';
10077
10078                 if (buf1[0] != NULLCHAR) {
10079                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10080                 }
10081
10082                 cpstats.ok_to_send = 0;
10083                 cpstats.line_is_book = 0;
10084                 cpstats.nr_moves = 0;
10085                 cpstats.moves_left = 0;
10086
10087                 SendProgramStatsToFrontend( cps, &cpstats );
10088             }
10089         }
10090     }
10091 }
10092
10093
10094 /* Parse a game score from the character string "game", and
10095    record it as the history of the current game.  The game
10096    score is NOT assumed to start from the standard position.
10097    The display is not updated in any way.
10098    */
10099 void
10100 ParseGameHistory (char *game)
10101 {
10102     ChessMove moveType;
10103     int fromX, fromY, toX, toY, boardIndex, mask;
10104     char promoChar;
10105     char *p, *q;
10106     char buf[MSG_SIZ];
10107
10108     if (appData.debugMode)
10109       fprintf(debugFP, "Parsing game history: %s\n", game);
10110
10111     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10112     gameInfo.site = StrSave(appData.icsHost);
10113     gameInfo.date = PGNDate();
10114     gameInfo.round = StrSave("-");
10115
10116     /* Parse out names of players */
10117     while (*game == ' ') game++;
10118     p = buf;
10119     while (*game != ' ') *p++ = *game++;
10120     *p = NULLCHAR;
10121     gameInfo.white = StrSave(buf);
10122     while (*game == ' ') game++;
10123     p = buf;
10124     while (*game != ' ' && *game != '\n') *p++ = *game++;
10125     *p = NULLCHAR;
10126     gameInfo.black = StrSave(buf);
10127
10128     /* Parse moves */
10129     boardIndex = blackPlaysFirst ? 1 : 0;
10130     yynewstr(game);
10131     for (;;) {
10132         yyboardindex = boardIndex;
10133         moveType = (ChessMove) Myylex();
10134         switch (moveType) {
10135           case IllegalMove:             /* maybe suicide chess, etc. */
10136   if (appData.debugMode) {
10137     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10138     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10139     setbuf(debugFP, NULL);
10140   }
10141           case WhitePromotion:
10142           case BlackPromotion:
10143           case WhiteNonPromotion:
10144           case BlackNonPromotion:
10145           case NormalMove:
10146           case FirstLeg:
10147           case WhiteCapturesEnPassant:
10148           case BlackCapturesEnPassant:
10149           case WhiteKingSideCastle:
10150           case WhiteQueenSideCastle:
10151           case BlackKingSideCastle:
10152           case BlackQueenSideCastle:
10153           case WhiteKingSideCastleWild:
10154           case WhiteQueenSideCastleWild:
10155           case BlackKingSideCastleWild:
10156           case BlackQueenSideCastleWild:
10157           /* PUSH Fabien */
10158           case WhiteHSideCastleFR:
10159           case WhiteASideCastleFR:
10160           case BlackHSideCastleFR:
10161           case BlackASideCastleFR:
10162           /* POP Fabien */
10163             fromX = currentMoveString[0] - AAA;
10164             fromY = currentMoveString[1] - ONE;
10165             toX = currentMoveString[2] - AAA;
10166             toY = currentMoveString[3] - ONE;
10167             promoChar = currentMoveString[4];
10168             break;
10169           case WhiteDrop:
10170           case BlackDrop:
10171             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10172             fromX = moveType == WhiteDrop ?
10173               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10174             (int) CharToPiece(ToLower(currentMoveString[0]));
10175             fromY = DROP_RANK;
10176             toX = currentMoveString[2] - AAA;
10177             toY = currentMoveString[3] - ONE;
10178             promoChar = NULLCHAR;
10179             break;
10180           case AmbiguousMove:
10181             /* bug? */
10182             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10183   if (appData.debugMode) {
10184     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10185     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10186     setbuf(debugFP, NULL);
10187   }
10188             DisplayError(buf, 0);
10189             return;
10190           case ImpossibleMove:
10191             /* bug? */
10192             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10193   if (appData.debugMode) {
10194     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10195     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10196     setbuf(debugFP, NULL);
10197   }
10198             DisplayError(buf, 0);
10199             return;
10200           case EndOfFile:
10201             if (boardIndex < backwardMostMove) {
10202                 /* Oops, gap.  How did that happen? */
10203                 DisplayError(_("Gap in move list"), 0);
10204                 return;
10205             }
10206             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10207             if (boardIndex > forwardMostMove) {
10208                 forwardMostMove = boardIndex;
10209             }
10210             return;
10211           case ElapsedTime:
10212             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10213                 strcat(parseList[boardIndex-1], " ");
10214                 strcat(parseList[boardIndex-1], yy_text);
10215             }
10216             continue;
10217           case Comment:
10218           case PGNTag:
10219           case NAG:
10220           default:
10221             /* ignore */
10222             continue;
10223           case WhiteWins:
10224           case BlackWins:
10225           case GameIsDrawn:
10226           case GameUnfinished:
10227             if (gameMode == IcsExamining) {
10228                 if (boardIndex < backwardMostMove) {
10229                     /* Oops, gap.  How did that happen? */
10230                     return;
10231                 }
10232                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10233                 return;
10234             }
10235             gameInfo.result = moveType;
10236             p = strchr(yy_text, '{');
10237             if (p == NULL) p = strchr(yy_text, '(');
10238             if (p == NULL) {
10239                 p = yy_text;
10240                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10241             } else {
10242                 q = strchr(p, *p == '{' ? '}' : ')');
10243                 if (q != NULL) *q = NULLCHAR;
10244                 p++;
10245             }
10246             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10247             gameInfo.resultDetails = StrSave(p);
10248             continue;
10249         }
10250         if (boardIndex >= forwardMostMove &&
10251             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10252             backwardMostMove = blackPlaysFirst ? 1 : 0;
10253             return;
10254         }
10255         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10256                                  fromY, fromX, toY, toX, promoChar,
10257                                  parseList[boardIndex]);
10258         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10259         /* currentMoveString is set as a side-effect of yylex */
10260         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10261         strcat(moveList[boardIndex], "\n");
10262         boardIndex++;
10263         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10264         mask = (WhiteOnMove(boardIndex) ? 0xFF : 0xFF00);
10265         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10266           case MT_NONE:
10267           case MT_STALEMATE:
10268           default:
10269             break;
10270           case MT_CHECK:
10271             if(boards[boardIndex][CHECK_COUNT]) boards[boardIndex][CHECK_COUNT] -= mask & 0x101;
10272             if(!boards[boardIndex][CHECK_COUNT] || boards[boardIndex][CHECK_COUNT] & mask) {
10273                 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+");
10274                 break;
10275             }
10276           case MT_CHECKMATE:
10277           case MT_STAINMATE:
10278             strcat(parseList[boardIndex - 1], "#");
10279             break;
10280         }
10281     }
10282 }
10283
10284
10285 /* Apply a move to the given board  */
10286 void
10287 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10288 {
10289   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10290   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10291
10292     /* [HGM] compute & store e.p. status and castling rights for new position */
10293     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10294
10295       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10296       oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8;
10297       board[EP_STATUS] = EP_NONE;
10298       board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
10299
10300   if (fromY == DROP_RANK) {
10301         /* must be first */
10302         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10303             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10304             return;
10305         }
10306         piece = board[toY][toX] = (ChessSquare) fromX;
10307   } else {
10308 //      ChessSquare victim;
10309       int i;
10310
10311       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10312 //           victim = board[killY][killX],
10313            killed = board[killY][killX],
10314            board[killY][killX] = EmptySquare,
10315            board[EP_STATUS] = EP_CAPTURE;
10316            if( kill2X >= 0 && kill2Y >= 0)
10317              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10318       }
10319
10320       if( board[toY][toX] != EmptySquare ) {
10321            board[EP_STATUS] = EP_CAPTURE;
10322            if( (fromX != toX || fromY != toY) && // not igui!
10323                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10324                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10325                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10326            }
10327       }
10328
10329       pawn = board[fromY][fromX];
10330       if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10331         if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10332             captured = board[lastRank][lastFile]; // remove victim
10333             board[lastRank][lastFile] = EmptySquare;
10334             pawn = EmptySquare; // kludge to suppress old e.p. code
10335         }
10336       }
10337       if( pawn == WhiteLance || pawn == BlackLance ) {
10338            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10339                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10340                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10341            }
10342       }
10343       if( pawn == WhitePawn ) {
10344            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10345                board[EP_STATUS] = EP_PAWN_MOVE;
10346            if( toY-fromY>=2) {
10347                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10348                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10349                         gameInfo.variant != VariantBerolina || toX < fromX)
10350                       board[EP_STATUS] = toX | berolina;
10351                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10352                         gameInfo.variant != VariantBerolina || toX > fromX)
10353                       board[EP_STATUS] = toX;
10354                board[LAST_TO] = toX + 256*toY;
10355            }
10356       } else
10357       if( pawn == BlackPawn ) {
10358            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10359                board[EP_STATUS] = EP_PAWN_MOVE;
10360            if( toY-fromY<= -2) {
10361                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10362                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10363                         gameInfo.variant != VariantBerolina || toX < fromX)
10364                       board[EP_STATUS] = toX | berolina;
10365                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10366                         gameInfo.variant != VariantBerolina || toX > fromX)
10367                       board[EP_STATUS] = toX;
10368                board[LAST_TO] = toX + 256*toY;
10369            }
10370        }
10371
10372        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10373        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10374        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10375        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10376
10377        for(i=0; i<nrCastlingRights; i++) {
10378            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10379               board[CASTLING][i] == toX   && castlingRank[i] == toY
10380              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10381        }
10382
10383        if(gameInfo.variant == VariantSChess) { // update virginity
10384            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10385            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10386            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10387            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10388        }
10389
10390      if (fromX == toX && fromY == toY && killX < 0) return;
10391
10392      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10393      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10394      if(gameInfo.variant == VariantKnightmate)
10395          king += (int) WhiteUnicorn - (int) WhiteKing;
10396
10397     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10398        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10399         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10400         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10401         board[EP_STATUS] = EP_NONE; // capture was fake!
10402     } else
10403     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10404         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10405         board[toY][toX] = piece;
10406         board[EP_STATUS] = EP_NONE; // capture was fake!
10407     } else
10408     /* Code added by Tord: */
10409     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10410     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10411         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10412       board[EP_STATUS] = EP_NONE; // capture was fake!
10413       board[fromY][fromX] = EmptySquare;
10414       board[toY][toX] = EmptySquare;
10415       if((toX > fromX) != (piece == WhiteRook)) {
10416         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10417       } else {
10418         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10419       }
10420     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10421                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10422       board[EP_STATUS] = EP_NONE;
10423       board[fromY][fromX] = EmptySquare;
10424       board[toY][toX] = EmptySquare;
10425       if((toX > fromX) != (piece == BlackRook)) {
10426         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10427       } else {
10428         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10429       }
10430     /* End of code added by Tord */
10431
10432     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10433         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10434         board[toY][toX] = piece;
10435     } else if (board[fromY][fromX] == king
10436         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10437         && toY == fromY && toX > fromX+1) {
10438         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10439                                                                                              ; // castle with nearest piece
10440         board[fromY][toX-1] = board[fromY][rookX];
10441         board[fromY][rookX] = EmptySquare;
10442         board[fromY][fromX] = EmptySquare;
10443         board[toY][toX] = king;
10444     } else if (board[fromY][fromX] == king
10445         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10446                && toY == fromY && toX < fromX-1) {
10447         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10448                                                                                   ; // castle with nearest piece
10449         board[fromY][toX+1] = board[fromY][rookX];
10450         board[fromY][rookX] = EmptySquare;
10451         board[fromY][fromX] = EmptySquare;
10452         board[toY][toX] = king;
10453     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10454                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10455                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10456                ) {
10457         /* white pawn promotion */
10458         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10459         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10460             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10461         board[fromY][fromX] = EmptySquare;
10462     } else if ((fromY >= BOARD_HEIGHT>>1)
10463                && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10464                && (toX != fromX)
10465                && gameInfo.variant != VariantXiangqi
10466                && gameInfo.variant != VariantBerolina
10467                && (pawn == WhitePawn)
10468                && (board[toY][toX] == EmptySquare)) {
10469         if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10470         board[fromY][fromX] = EmptySquare;
10471         board[toY][toX] = piece;
10472         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10473     } else if ((fromY == BOARD_HEIGHT-4)
10474                && (toX == fromX)
10475                && gameInfo.variant == VariantBerolina
10476                && (board[fromY][fromX] == WhitePawn)
10477                && (board[toY][toX] == EmptySquare)) {
10478         board[fromY][fromX] = EmptySquare;
10479         board[toY][toX] = WhitePawn;
10480         if(oldEP & EP_BEROLIN_A) {
10481                 captured = board[fromY][fromX-1];
10482                 board[fromY][fromX-1] = EmptySquare;
10483         }else{  captured = board[fromY][fromX+1];
10484                 board[fromY][fromX+1] = EmptySquare;
10485         }
10486     } else if (board[fromY][fromX] == king
10487         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10488                && toY == fromY && toX > fromX+1) {
10489         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10490                                                                                              ;
10491         board[fromY][toX-1] = board[fromY][rookX];
10492         board[fromY][rookX] = EmptySquare;
10493         board[fromY][fromX] = EmptySquare;
10494         board[toY][toX] = king;
10495     } else if (board[fromY][fromX] == king
10496         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10497                && toY == fromY && toX < fromX-1) {
10498         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10499                                                                                 ;
10500         board[fromY][toX+1] = board[fromY][rookX];
10501         board[fromY][rookX] = EmptySquare;
10502         board[fromY][fromX] = EmptySquare;
10503         board[toY][toX] = king;
10504     } else if (fromY == 7 && fromX == 3
10505                && board[fromY][fromX] == BlackKing
10506                && toY == 7 && toX == 5) {
10507         board[fromY][fromX] = EmptySquare;
10508         board[toY][toX] = BlackKing;
10509         board[fromY][7] = EmptySquare;
10510         board[toY][4] = BlackRook;
10511     } else if (fromY == 7 && fromX == 3
10512                && board[fromY][fromX] == BlackKing
10513                && toY == 7 && toX == 1) {
10514         board[fromY][fromX] = EmptySquare;
10515         board[toY][toX] = BlackKing;
10516         board[fromY][0] = EmptySquare;
10517         board[toY][2] = BlackRook;
10518     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10519                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10520                && toY < promoRank && promoChar
10521                ) {
10522         /* black pawn promotion */
10523         board[toY][toX] = CharToPiece(ToLower(promoChar));
10524         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10525             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10526         board[fromY][fromX] = EmptySquare;
10527     } else if ((fromY < BOARD_HEIGHT>>1)
10528                && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10529                && (toX != fromX)
10530                && gameInfo.variant != VariantXiangqi
10531                && gameInfo.variant != VariantBerolina
10532                && (pawn == BlackPawn)
10533                && (board[toY][toX] == EmptySquare)) {
10534         if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10535         board[fromY][fromX] = EmptySquare;
10536         board[toY][toX] = piece;
10537         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10538     } else if ((fromY == 3)
10539                && (toX == fromX)
10540                && gameInfo.variant == VariantBerolina
10541                && (board[fromY][fromX] == BlackPawn)
10542                && (board[toY][toX] == EmptySquare)) {
10543         board[fromY][fromX] = EmptySquare;
10544         board[toY][toX] = BlackPawn;
10545         if(oldEP & EP_BEROLIN_A) {
10546                 captured = board[fromY][fromX-1];
10547                 board[fromY][fromX-1] = EmptySquare;
10548         }else{  captured = board[fromY][fromX+1];
10549                 board[fromY][fromX+1] = EmptySquare;
10550         }
10551     } else {
10552         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10553         board[fromY][fromX] = EmptySquare;
10554         board[toY][toX] = piece;
10555     }
10556   }
10557
10558     if (gameInfo.holdingsWidth != 0) {
10559
10560       /* !!A lot more code needs to be written to support holdings  */
10561       /* [HGM] OK, so I have written it. Holdings are stored in the */
10562       /* penultimate board files, so they are automaticlly stored   */
10563       /* in the game history.                                       */
10564       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10565                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10566         /* Delete from holdings, by decreasing count */
10567         /* and erasing image if necessary            */
10568         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10569         if(p < (int) BlackPawn) { /* white drop */
10570              p -= (int)WhitePawn;
10571                  p = PieceToNumber((ChessSquare)p);
10572              if(p >= gameInfo.holdingsSize) p = 0;
10573              if(--board[p][BOARD_WIDTH-2] <= 0)
10574                   board[p][BOARD_WIDTH-1] = EmptySquare;
10575              if((int)board[p][BOARD_WIDTH-2] < 0)
10576                         board[p][BOARD_WIDTH-2] = 0;
10577         } else {                  /* black drop */
10578              p -= (int)BlackPawn;
10579                  p = PieceToNumber((ChessSquare)p);
10580              if(p >= gameInfo.holdingsSize) p = 0;
10581              if(--board[handSize-1-p][1] <= 0)
10582                   board[handSize-1-p][0] = EmptySquare;
10583              if((int)board[handSize-1-p][1] < 0)
10584                         board[handSize-1-p][1] = 0;
10585         }
10586       }
10587       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10588           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10589         /* [HGM] holdings: Add to holdings, if holdings exist */
10590         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10591                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10592                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10593         }
10594         p = (int) captured;
10595         if (p >= (int) BlackPawn) {
10596           p -= (int)BlackPawn;
10597           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10598                   /* Restore shogi-promoted piece to its original  first */
10599                   captured = (ChessSquare) (DEMOTED(captured));
10600                   p = DEMOTED(p);
10601           }
10602           p = PieceToNumber((ChessSquare)p);
10603           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10604           board[p][BOARD_WIDTH-2]++;
10605           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10606         } else {
10607           p -= (int)WhitePawn;
10608           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10609                   captured = (ChessSquare) (DEMOTED(captured));
10610                   p = DEMOTED(p);
10611           }
10612           p = PieceToNumber((ChessSquare)p);
10613           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10614           board[handSize-1-p][1]++;
10615           board[handSize-1-p][0] = WHITE_TO_BLACK captured;
10616         }
10617       }
10618     } else if (gameInfo.variant == VariantAtomic) {
10619       if (captured != EmptySquare) {
10620         int y, x;
10621         for (y = toY-1; y <= toY+1; y++) {
10622           for (x = toX-1; x <= toX+1; x++) {
10623             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10624                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10625               board[y][x] = EmptySquare;
10626             }
10627           }
10628         }
10629         board[toY][toX] = EmptySquare;
10630       }
10631     }
10632
10633     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10634         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10635     } else
10636     if(promoChar == '+') {
10637         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10638         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10639         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10640           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10641     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10642         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10643         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10644            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10645         board[toY][toX] = newPiece;
10646     }
10647     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10648                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10649         // [HGM] superchess: take promotion piece out of holdings
10650         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10651         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10652             if(!--board[k][BOARD_WIDTH-2])
10653                 board[k][BOARD_WIDTH-1] = EmptySquare;
10654         } else {
10655             if(!--board[handSize-1-k][1])
10656                 board[handSize-1-k][0] = EmptySquare;
10657         }
10658     }
10659 }
10660
10661 /* Updates forwardMostMove */
10662 void
10663 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10664 {
10665     int x = toX, y = toY, mask;
10666     char *s = parseList[forwardMostMove];
10667     ChessSquare p = boards[forwardMostMove][toY][toX];
10668 //    forwardMostMove++; // [HGM] bare: moved downstream
10669
10670     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10671     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10672     (void) CoordsToAlgebraic(boards[forwardMostMove],
10673                              PosFlags(forwardMostMove),
10674                              fromY, fromX, y, x, (killX < 0)*promoChar,
10675                              s);
10676     if(kill2X >= 0 && kill2Y >= 0)
10677         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10678     if(killX >= 0 && killY >= 0)
10679         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10680                                            toX + AAA, toY + ONE - '0', promoChar);
10681
10682     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10683         int timeLeft; static int lastLoadFlag=0; int king, piece;
10684         piece = boards[forwardMostMove][fromY][fromX];
10685         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10686         if(gameInfo.variant == VariantKnightmate)
10687             king += (int) WhiteUnicorn - (int) WhiteKing;
10688         if(forwardMostMove == 0) {
10689             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10690                 fprintf(serverMoves, "%s;", UserName());
10691             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10692                 fprintf(serverMoves, "%s;", second.tidy);
10693             fprintf(serverMoves, "%s;", first.tidy);
10694             if(gameMode == MachinePlaysWhite)
10695                 fprintf(serverMoves, "%s;", UserName());
10696             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10697                 fprintf(serverMoves, "%s;", second.tidy);
10698         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10699         lastLoadFlag = loadFlag;
10700         // print base move
10701         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10702         // print castling suffix
10703         if( toY == fromY && piece == king ) {
10704             if(toX-fromX > 1)
10705                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10706             if(fromX-toX >1)
10707                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10708         }
10709         // e.p. suffix
10710         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10711              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10712              boards[forwardMostMove][toY][toX] == EmptySquare
10713              && fromX != toX && fromY != toY)
10714                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10715         // promotion suffix
10716         if(promoChar != NULLCHAR) {
10717             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10718                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10719                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10720             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10721         }
10722         if(!loadFlag) {
10723                 char buf[MOVE_LEN*2], *p; int len;
10724             fprintf(serverMoves, "/%d/%d",
10725                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10726             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10727             else                      timeLeft = blackTimeRemaining/1000;
10728             fprintf(serverMoves, "/%d", timeLeft);
10729                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10730                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10731                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10732                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10733             fprintf(serverMoves, "/%s", buf);
10734         }
10735         fflush(serverMoves);
10736     }
10737
10738     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10739         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10740       return;
10741     }
10742     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10743     if (commentList[forwardMostMove+1] != NULL) {
10744         free(commentList[forwardMostMove+1]);
10745         commentList[forwardMostMove+1] = NULL;
10746     }
10747     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10748     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10749     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10750     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10751     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10752     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10753     adjustedClock = FALSE;
10754     gameInfo.result = GameUnfinished;
10755     if (gameInfo.resultDetails != NULL) {
10756         free(gameInfo.resultDetails);
10757         gameInfo.resultDetails = NULL;
10758     }
10759     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10760                               moveList[forwardMostMove - 1]);
10761     mask = (WhiteOnMove(forwardMostMove) ? 0xFF : 0xFF00);
10762     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10763       case MT_NONE:
10764       case MT_STALEMATE:
10765       default:
10766         break;
10767       case MT_CHECK:
10768         if(boards[forwardMostMove][CHECK_COUNT]) boards[forwardMostMove][CHECK_COUNT] -= mask & 0x101;
10769         if(!boards[forwardMostMove][CHECK_COUNT] || boards[forwardMostMove][CHECK_COUNT] & mask) {
10770             if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+");
10771             break;
10772         }
10773       case MT_CHECKMATE:
10774       case MT_STAINMATE:
10775         strcat(parseList[forwardMostMove - 1], "#");
10776         break;
10777     }
10778 }
10779
10780 /* Updates currentMove if not pausing */
10781 void
10782 ShowMove (int fromX, int fromY, int toX, int toY)
10783 {
10784     int instant = (gameMode == PlayFromGameFile) ?
10785         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10786     if(appData.noGUI) return;
10787     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10788         if (!instant) {
10789             if (forwardMostMove == currentMove + 1) {
10790                 AnimateMove(boards[forwardMostMove - 1],
10791                             fromX, fromY, toX, toY);
10792             }
10793         }
10794         currentMove = forwardMostMove;
10795     }
10796
10797     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10798
10799     if (instant) return;
10800
10801     DisplayMove(currentMove - 1);
10802     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10803             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10804                 SetHighlights(fromX, fromY, toX, toY);
10805             }
10806     }
10807     DrawPosition(FALSE, boards[currentMove]);
10808     DisplayBothClocks();
10809     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10810 }
10811
10812 void
10813 SendEgtPath (ChessProgramState *cps)
10814 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10815         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10816
10817         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10818
10819         while(*p) {
10820             char c, *q = name+1, *r, *s;
10821
10822             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10823             while(*p && *p != ',') *q++ = *p++;
10824             *q++ = ':'; *q = 0;
10825             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10826                 strcmp(name, ",nalimov:") == 0 ) {
10827                 // take nalimov path from the menu-changeable option first, if it is defined
10828               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10829                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10830             } else
10831             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10832                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10833                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10834                 s = r = StrStr(s, ":") + 1; // beginning of path info
10835                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10836                 c = *r; *r = 0;             // temporarily null-terminate path info
10837                     *--q = 0;               // strip of trailig ':' from name
10838                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10839                 *r = c;
10840                 SendToProgram(buf,cps);     // send egtbpath command for this format
10841             }
10842             if(*p == ',') p++; // read away comma to position for next format name
10843         }
10844 }
10845
10846 static int
10847 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10848 {
10849       int width = 8, height = 8, holdings = 0;             // most common sizes
10850       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10851       // correct the deviations default for each variant
10852       if( v == VariantXiangqi ) width = 9,  height = 10;
10853       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10854       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10855       if( v == VariantCapablanca || v == VariantCapaRandom ||
10856           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10857                                 width = 10;
10858       if( v == VariantCourier ) width = 12;
10859       if( v == VariantSuper )                            holdings = 8;
10860       if( v == VariantGreat )   width = 10,              holdings = 8;
10861       if( v == VariantSChess )                           holdings = 7;
10862       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10863       if( v == VariantChuChess) width = 10, height = 10;
10864       if( v == VariantChu )     width = 12, height = 12;
10865       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10866              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10867              holdingsSize >= 0 && holdingsSize != holdings;
10868 }
10869
10870 char variantError[MSG_SIZ];
10871
10872 char *
10873 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10874 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10875       char *p, *variant = VariantName(v);
10876       static char b[MSG_SIZ];
10877       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10878            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10879                                                holdingsSize, variant); // cook up sized variant name
10880            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10881            if(StrStr(list, b) == NULL) {
10882                // specific sized variant not known, check if general sizing allowed
10883                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10884                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10885                             boardWidth, boardHeight, holdingsSize, engine);
10886                    return NULL;
10887                }
10888                /* [HGM] here we really should compare with the maximum supported board size */
10889            }
10890       } else snprintf(b, MSG_SIZ,"%s", variant);
10891       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10892       p = StrStr(list, b);
10893       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10894       if(p == NULL) {
10895           // occurs not at all in list, or only as sub-string
10896           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10897           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10898               int l = strlen(variantError);
10899               char *q;
10900               while(p != list && p[-1] != ',') p--;
10901               q = strchr(p, ',');
10902               if(q) *q = NULLCHAR;
10903               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10904               if(q) *q= ',';
10905           }
10906           return NULL;
10907       }
10908       return b;
10909 }
10910
10911 void
10912 InitChessProgram (ChessProgramState *cps, int setup)
10913 /* setup needed to setup FRC opening position */
10914 {
10915     char buf[MSG_SIZ], *b;
10916     if (appData.noChessProgram) return;
10917     hintRequested = FALSE;
10918     bookRequested = FALSE;
10919
10920     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10921     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10922     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10923     if(cps->memSize) { /* [HGM] memory */
10924       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10925         SendToProgram(buf, cps);
10926     }
10927     SendEgtPath(cps); /* [HGM] EGT */
10928     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10929       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10930         SendToProgram(buf, cps);
10931     }
10932
10933     setboardSpoiledMachineBlack = FALSE;
10934     SendToProgram(cps->initString, cps);
10935     if (gameInfo.variant != VariantNormal &&
10936         gameInfo.variant != VariantLoadable
10937         /* [HGM] also send variant if board size non-standard */
10938         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10939
10940       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10941                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10942
10943       if (b == NULL) {
10944         VariantClass v;
10945         char c, *q = cps->variants, *p = strchr(q, ',');
10946         if(p) *p = NULLCHAR;
10947         v = StringToVariant(q);
10948         DisplayError(variantError, 0);
10949         if(v != VariantUnknown && cps == &first) {
10950             int w, h, s;
10951             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10952                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10953             ASSIGN(appData.variant, q);
10954             Reset(TRUE, FALSE);
10955         }
10956         if(p) *p = ',';
10957         return;
10958       }
10959
10960       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10961       SendToProgram(buf, cps);
10962     }
10963     currentlyInitializedVariant = gameInfo.variant;
10964
10965     /* [HGM] send opening position in FRC to first engine */
10966     if(setup) {
10967           SendToProgram("force\n", cps);
10968           SendBoard(cps, 0);
10969           /* engine is now in force mode! Set flag to wake it up after first move. */
10970           setboardSpoiledMachineBlack = 1;
10971     }
10972
10973     if (cps->sendICS) {
10974       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10975       SendToProgram(buf, cps);
10976     }
10977     cps->maybeThinking = FALSE;
10978     cps->offeredDraw = 0;
10979     if (!appData.icsActive) {
10980         SendTimeControl(cps, movesPerSession, timeControl,
10981                         timeIncrement, appData.searchDepth,
10982                         searchTime);
10983     }
10984     if (appData.showThinking
10985         // [HGM] thinking: four options require thinking output to be sent
10986         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10987                                 ) {
10988         SendToProgram("post\n", cps);
10989     }
10990     SendToProgram("hard\n", cps);
10991     if (!appData.ponderNextMove) {
10992         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10993            it without being sure what state we are in first.  "hard"
10994            is not a toggle, so that one is OK.
10995          */
10996         SendToProgram("easy\n", cps);
10997     }
10998     if (cps->usePing) {
10999       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
11000       SendToProgram(buf, cps);
11001     }
11002     cps->initDone = TRUE;
11003     ClearEngineOutputPane(cps == &second);
11004 }
11005
11006
11007 char *
11008 ResendOptions (ChessProgramState *cps, int toEngine)
11009 { // send the stored value of the options
11010   int i;
11011   static char buf2[MSG_SIZ*10];
11012   char buf[MSG_SIZ], *p = buf2;
11013   Option *opt = cps->option;
11014   *p = NULLCHAR;
11015   for(i=0; i<cps->nrOptions; i++, opt++) {
11016       *buf = NULLCHAR;
11017       switch(opt->type) {
11018         case Spin:
11019         case Slider:
11020         case CheckBox:
11021             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11022             snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
11023           break;
11024         case ComboBox:
11025             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11026             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
11027           break;
11028         default:
11029             if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
11030             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
11031           break;
11032         case Button:
11033         case SaveButton:
11034           continue;
11035       }
11036       if(*buf) {
11037         if(toEngine) {
11038           snprintf(buf2, MSG_SIZ, "option %s\n", buf);
11039           SendToProgram(buf2, cps);
11040         } else {
11041           if(p != buf2) *p++ = ',';
11042           strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
11043           while(*p) p++;
11044         }
11045       }
11046   }
11047   return buf2;
11048 }
11049
11050 void
11051 StartChessProgram (ChessProgramState *cps)
11052 {
11053     char buf[MSG_SIZ];
11054     int err;
11055
11056     if (appData.noChessProgram) return;
11057     cps->initDone = FALSE;
11058
11059     if (strcmp(cps->host, "localhost") == 0) {
11060         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
11061     } else if (*appData.remoteShell == NULLCHAR) {
11062         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
11063     } else {
11064         if (*appData.remoteUser == NULLCHAR) {
11065           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
11066                     cps->program);
11067         } else {
11068           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
11069                     cps->host, appData.remoteUser, cps->program);
11070         }
11071         err = StartChildProcess(buf, "", &cps->pr);
11072     }
11073
11074     if (err != 0) {
11075       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11076         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11077         if(cps != &first) return;
11078         appData.noChessProgram = TRUE;
11079         ThawUI();
11080         SetNCPMode();
11081 //      DisplayFatalError(buf, err, 1);
11082 //      cps->pr = NoProc;
11083 //      cps->isr = NULL;
11084         return;
11085     }
11086
11087     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11088     if (cps->protocolVersion > 1) {
11089       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11090       if(!cps->reload) { // do not clear options when reloading because of -xreuse
11091         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11092         cps->comboCnt = 0;  //                and values of combo boxes
11093       }
11094       SendToProgram(buf, cps);
11095       if(cps->reload) ResendOptions(cps, TRUE);
11096     } else {
11097       SendToProgram("xboard\n", cps);
11098     }
11099 }
11100
11101 void
11102 TwoMachinesEventIfReady P((void))
11103 {
11104   static int curMess = 0;
11105   if (first.lastPing != first.lastPong) {
11106     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11107     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11108     return;
11109   }
11110   if (second.lastPing != second.lastPong) {
11111     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11112     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11113     return;
11114   }
11115   DisplayMessage("", ""); curMess = 0;
11116   TwoMachinesEvent();
11117 }
11118
11119 char *
11120 MakeName (char *template)
11121 {
11122     time_t clock;
11123     struct tm *tm;
11124     static char buf[MSG_SIZ];
11125     char *p = buf;
11126     int i;
11127
11128     clock = time((time_t *)NULL);
11129     tm = localtime(&clock);
11130
11131     while(*p++ = *template++) if(p[-1] == '%') {
11132         switch(*template++) {
11133           case 0:   *p = 0; return buf;
11134           case 'Y': i = tm->tm_year+1900; break;
11135           case 'y': i = tm->tm_year-100; break;
11136           case 'M': i = tm->tm_mon+1; break;
11137           case 'd': i = tm->tm_mday; break;
11138           case 'h': i = tm->tm_hour; break;
11139           case 'm': i = tm->tm_min; break;
11140           case 's': i = tm->tm_sec; break;
11141           default:  i = 0;
11142         }
11143         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11144     }
11145     return buf;
11146 }
11147
11148 int
11149 CountPlayers (char *p)
11150 {
11151     int n = 0;
11152     while(p = strchr(p, '\n')) p++, n++; // count participants
11153     return n;
11154 }
11155
11156 FILE *
11157 WriteTourneyFile (char *results, FILE *f)
11158 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11159     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11160     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11161         // create a file with tournament description
11162         fprintf(f, "-participants {%s}\n", appData.participants);
11163         fprintf(f, "-seedBase %d\n", appData.seedBase);
11164         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11165         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11166         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11167         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11168         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11169         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11170         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11171         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11172         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11173         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11174         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11175         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11176         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11177         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11178         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11179         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11180         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11181         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11182         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11183         fprintf(f, "-smpCores %d\n", appData.smpCores);
11184         if(searchTime > 0)
11185                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11186         else {
11187                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11188                 fprintf(f, "-tc %s\n", appData.timeControl);
11189                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11190         }
11191         fprintf(f, "-results \"%s\"\n", results);
11192     }
11193     return f;
11194 }
11195
11196 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11197
11198 void
11199 Substitute (char *participants, int expunge)
11200 {
11201     int i, changed, changes=0, nPlayers=0;
11202     char *p, *q, *r, buf[MSG_SIZ];
11203     if(participants == NULL) return;
11204     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11205     r = p = participants; q = appData.participants;
11206     while(*p && *p == *q) {
11207         if(*p == '\n') r = p+1, nPlayers++;
11208         p++; q++;
11209     }
11210     if(*p) { // difference
11211         while(*p && *p++ != '\n')
11212                                  ;
11213         while(*q && *q++ != '\n')
11214                                  ;
11215       changed = nPlayers;
11216         changes = 1 + (strcmp(p, q) != 0);
11217     }
11218     if(changes == 1) { // a single engine mnemonic was changed
11219         q = r; while(*q) nPlayers += (*q++ == '\n');
11220         p = buf; while(*r && (*p = *r++) != '\n') p++;
11221         *p = NULLCHAR;
11222         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11223         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11224         if(mnemonic[i]) { // The substitute is valid
11225             FILE *f;
11226             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11227                 flock(fileno(f), LOCK_EX);
11228                 ParseArgsFromFile(f);
11229                 fseek(f, 0, SEEK_SET);
11230                 FREE(appData.participants); appData.participants = participants;
11231                 if(expunge) { // erase results of replaced engine
11232                     int len = strlen(appData.results), w, b, dummy;
11233                     for(i=0; i<len; i++) {
11234                         Pairing(i, nPlayers, &w, &b, &dummy);
11235                         if((w == changed || b == changed) && appData.results[i] == '*') {
11236                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11237                             fclose(f);
11238                             return;
11239                         }
11240                     }
11241                     for(i=0; i<len; i++) {
11242                         Pairing(i, nPlayers, &w, &b, &dummy);
11243                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11244                     }
11245                 }
11246                 WriteTourneyFile(appData.results, f);
11247                 fclose(f); // release lock
11248                 return;
11249             }
11250         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11251     }
11252     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11253     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11254     free(participants);
11255     return;
11256 }
11257
11258 int
11259 CheckPlayers (char *participants)
11260 {
11261         int i;
11262         char buf[MSG_SIZ], *p;
11263         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11264         while(p = strchr(participants, '\n')) {
11265             *p = NULLCHAR;
11266             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11267             if(!mnemonic[i]) {
11268                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11269                 *p = '\n';
11270                 DisplayError(buf, 0);
11271                 return 1;
11272             }
11273             *p = '\n';
11274             participants = p + 1;
11275         }
11276         return 0;
11277 }
11278
11279 int
11280 CreateTourney (char *name)
11281 {
11282         FILE *f;
11283         if(matchMode && strcmp(name, appData.tourneyFile)) {
11284              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11285         }
11286         if(name[0] == NULLCHAR) {
11287             if(appData.participants[0])
11288                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11289             return 0;
11290         }
11291         f = fopen(name, "r");
11292         if(f) { // file exists
11293             ASSIGN(appData.tourneyFile, name);
11294             ParseArgsFromFile(f); // parse it
11295         } else {
11296             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11297             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11298                 DisplayError(_("Not enough participants"), 0);
11299                 return 0;
11300             }
11301             if(CheckPlayers(appData.participants)) return 0;
11302             ASSIGN(appData.tourneyFile, name);
11303             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11304             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11305         }
11306         fclose(f);
11307         appData.noChessProgram = FALSE;
11308         appData.clockMode = TRUE;
11309         SetGNUMode();
11310         return 1;
11311 }
11312
11313 int
11314 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11315 {
11316     char buf[2*MSG_SIZ], *p, *q;
11317     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11318     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11319     skip = !all && group[0]; // if group requested, we start in skip mode
11320     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11321         p = names; q = buf; header = 0;
11322         while(*p && *p != '\n') *q++ = *p++;
11323         *q = 0;
11324         if(*p == '\n') p++;
11325         if(buf[0] == '#') {
11326             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11327             depth++; // we must be entering a new group
11328             if(all) continue; // suppress printing group headers when complete list requested
11329             header = 1;
11330             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11331         }
11332         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11333         if(engineList[i]) free(engineList[i]);
11334         engineList[i] = strdup(buf);
11335         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11336         if(engineMnemonic[i]) free(engineMnemonic[i]);
11337         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11338             strcat(buf, " (");
11339             sscanf(q + 8, "%s", buf + strlen(buf));
11340             strcat(buf, ")");
11341         }
11342         engineMnemonic[i] = strdup(buf);
11343         i++;
11344     }
11345     engineList[i] = engineMnemonic[i] = NULL;
11346     return i;
11347 }
11348
11349 void
11350 SaveEngineSettings (int n)
11351 {
11352     int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11353     if(!currentEngine[n] || !currentEngine[n][0]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
11354     p = strstr(firstChessProgramNames, currentEngine[n]);
11355     if(!p) { DisplayMessage("saving failed: engine not found in list", ""); return; } // sanity check; engine could be deleted from list after loading
11356     optionSettings = ResendOptions(n ? &second : &first, FALSE);
11357     len = strlen(currentEngine[n]);
11358     q = p + len; *p = 0; // cut list into head and tail piece
11359     s = strstr(currentEngine[n], "firstOptions");
11360     if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11361         char *r = s + 14;
11362         while(*r && *r != s[13]) r++;
11363         s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11364         snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11365     } else if(*optionSettings) {
11366         snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11367     }
11368     ASSIGN(currentEngine[n], buf); // updated engine line
11369     len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11370     s = malloc(len);
11371     snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11372     FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11373 }
11374
11375 // following implemented as macro to avoid type limitations
11376 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11377
11378 void
11379 SwapEngines (int n)
11380 {   // swap settings for first engine and other engine (so far only some selected options)
11381     int h;
11382     char *p;
11383     if(n == 0) return;
11384     SWAP(directory, p)
11385     SWAP(chessProgram, p)
11386     SWAP(isUCI, h)
11387     SWAP(hasOwnBookUCI, h)
11388     SWAP(protocolVersion, h)
11389     SWAP(reuse, h)
11390     SWAP(scoreIsAbsolute, h)
11391     SWAP(timeOdds, h)
11392     SWAP(logo, p)
11393     SWAP(pgnName, p)
11394     SWAP(pvSAN, h)
11395     SWAP(engOptions, p)
11396     SWAP(engInitString, p)
11397     SWAP(computerString, p)
11398     SWAP(features, p)
11399     SWAP(fenOverride, p)
11400     SWAP(NPS, h)
11401     SWAP(accumulateTC, h)
11402     SWAP(drawDepth, h)
11403     SWAP(host, p)
11404     SWAP(pseudo, h)
11405 }
11406
11407 int
11408 GetEngineLine (char *s, int n)
11409 {
11410     int i;
11411     char buf[MSG_SIZ];
11412     extern char *icsNames;
11413     if(!s || !*s) return 0;
11414     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11415     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11416     if(!mnemonic[i]) return 0;
11417     if(n == 11) return 1; // just testing if there was a match
11418     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11419     if(n == 1) SwapEngines(n);
11420     ParseArgsFromString(buf);
11421     if(n == 1) SwapEngines(n);
11422     if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
11423     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11424         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11425         ParseArgsFromString(buf);
11426     }
11427     return 1;
11428 }
11429
11430 int
11431 SetPlayer (int player, char *p)
11432 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11433     int i;
11434     char buf[MSG_SIZ], *engineName;
11435     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11436     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11437     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11438     if(mnemonic[i]) {
11439         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11440         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11441         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11442         ParseArgsFromString(buf);
11443     } else { // no engine with this nickname is installed!
11444         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11445         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11446         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11447         ModeHighlight();
11448         DisplayError(buf, 0);
11449         return 0;
11450     }
11451     free(engineName);
11452     return i;
11453 }
11454
11455 char *recentEngines;
11456
11457 void
11458 RecentEngineEvent (int nr)
11459 {
11460     int n;
11461 //    SwapEngines(1); // bump first to second
11462 //    ReplaceEngine(&second, 1); // and load it there
11463     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11464     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11465     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11466         ReplaceEngine(&first, 0);
11467         FloatToFront(&appData.recentEngineList, command[n]);
11468         ASSIGN(currentEngine[0], command[n]);
11469     }
11470 }
11471
11472 int
11473 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11474 {   // determine players from game number
11475     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11476
11477     if(appData.tourneyType == 0) {
11478         roundsPerCycle = (nPlayers - 1) | 1;
11479         pairingsPerRound = nPlayers / 2;
11480     } else if(appData.tourneyType > 0) {
11481         roundsPerCycle = nPlayers - appData.tourneyType;
11482         pairingsPerRound = appData.tourneyType;
11483     }
11484     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11485     gamesPerCycle = gamesPerRound * roundsPerCycle;
11486     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11487     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11488     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11489     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11490     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11491     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11492
11493     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11494     if(appData.roundSync) *syncInterval = gamesPerRound;
11495
11496     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11497
11498     if(appData.tourneyType == 0) {
11499         if(curPairing == (nPlayers-1)/2 ) {
11500             *whitePlayer = curRound;
11501             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11502         } else {
11503             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11504             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11505             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11506             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11507         }
11508     } else if(appData.tourneyType > 1) {
11509         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11510         *whitePlayer = curRound + appData.tourneyType;
11511     } else if(appData.tourneyType > 0) {
11512         *whitePlayer = curPairing;
11513         *blackPlayer = curRound + appData.tourneyType;
11514     }
11515
11516     // take care of white/black alternation per round.
11517     // For cycles and games this is already taken care of by default, derived from matchGame!
11518     return curRound & 1;
11519 }
11520
11521 int
11522 NextTourneyGame (int nr, int *swapColors)
11523 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11524     char *p, *q;
11525     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11526     FILE *tf;
11527     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11528     tf = fopen(appData.tourneyFile, "r");
11529     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11530     ParseArgsFromFile(tf); fclose(tf);
11531     InitTimeControls(); // TC might be altered from tourney file
11532
11533     nPlayers = CountPlayers(appData.participants); // count participants
11534     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11535     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11536
11537     if(syncInterval) {
11538         p = q = appData.results;
11539         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11540         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11541             DisplayMessage(_("Waiting for other game(s)"),"");
11542             waitingForGame = TRUE;
11543             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11544             return 0;
11545         }
11546         waitingForGame = FALSE;
11547     }
11548
11549     if(appData.tourneyType < 0) {
11550         if(nr>=0 && !pairingReceived) {
11551             char buf[1<<16];
11552             if(pairing.pr == NoProc) {
11553                 if(!appData.pairingEngine[0]) {
11554                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11555                     return 0;
11556                 }
11557                 StartChessProgram(&pairing); // starts the pairing engine
11558             }
11559             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11560             SendToProgram(buf, &pairing);
11561             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11562             SendToProgram(buf, &pairing);
11563             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11564         }
11565         pairingReceived = 0;                              // ... so we continue here
11566         *swapColors = 0;
11567         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11568         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11569         matchGame = 1; roundNr = nr / syncInterval + 1;
11570     }
11571
11572     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11573
11574     // redefine engines, engine dir, etc.
11575     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11576     if(first.pr == NoProc) {
11577       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11578       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11579     }
11580     if(second.pr == NoProc) {
11581       SwapEngines(1);
11582       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11583       SwapEngines(1);         // and make that valid for second engine by swapping
11584       InitEngine(&second, 1);
11585     }
11586     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11587     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11588     return OK;
11589 }
11590
11591 void
11592 NextMatchGame ()
11593 {   // performs game initialization that does not invoke engines, and then tries to start the game
11594     int res, firstWhite, swapColors = 0;
11595     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11596     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
11597         char buf[MSG_SIZ];
11598         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11599         if(strcmp(buf, currentDebugFile)) { // name has changed
11600             FILE *f = fopen(buf, "w");
11601             if(f) { // if opening the new file failed, just keep using the old one
11602                 ASSIGN(currentDebugFile, buf);
11603                 fclose(debugFP);
11604                 debugFP = f;
11605             }
11606             if(appData.serverFileName) {
11607                 if(serverFP) fclose(serverFP);
11608                 serverFP = fopen(appData.serverFileName, "w");
11609                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11610                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11611             }
11612         }
11613     }
11614     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11615     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11616     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11617     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11618     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11619     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11620     Reset(FALSE, first.pr != NoProc);
11621     res = LoadGameOrPosition(matchGame); // setup game
11622     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11623     if(!res) return; // abort when bad game/pos file
11624     if(appData.epd) {// in EPD mode we make sure first engine is to move
11625         firstWhite = !(forwardMostMove & 1);
11626         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11627         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11628     }
11629     TwoMachinesEvent();
11630 }
11631
11632 void
11633 UserAdjudicationEvent (int result)
11634 {
11635     ChessMove gameResult = GameIsDrawn;
11636
11637     if( result > 0 ) {
11638         gameResult = WhiteWins;
11639     }
11640     else if( result < 0 ) {
11641         gameResult = BlackWins;
11642     }
11643
11644     if( gameMode == TwoMachinesPlay ) {
11645         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11646     }
11647 }
11648
11649
11650 // [HGM] save: calculate checksum of game to make games easily identifiable
11651 int
11652 StringCheckSum (char *s)
11653 {
11654         int i = 0;
11655         if(s==NULL) return 0;
11656         while(*s) i = i*259 + *s++;
11657         return i;
11658 }
11659
11660 int
11661 GameCheckSum ()
11662 {
11663         int i, sum=0;
11664         for(i=backwardMostMove; i<forwardMostMove; i++) {
11665                 sum += pvInfoList[i].depth;
11666                 sum += StringCheckSum(parseList[i]);
11667                 sum += StringCheckSum(commentList[i]);
11668                 sum *= 261;
11669         }
11670         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11671         return sum + StringCheckSum(commentList[i]);
11672 } // end of save patch
11673
11674 void
11675 GameEnds (ChessMove result, char *resultDetails, int whosays)
11676 {
11677     GameMode nextGameMode;
11678     int isIcsGame;
11679     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11680
11681     if(endingGame) return; /* [HGM] crash: forbid recursion */
11682     endingGame = 1;
11683     if(twoBoards) { // [HGM] dual: switch back to one board
11684         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11685         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11686     }
11687     if (appData.debugMode) {
11688       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11689               result, resultDetails ? resultDetails : "(null)", whosays);
11690     }
11691
11692     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11693
11694     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11695
11696     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11697         /* If we are playing on ICS, the server decides when the
11698            game is over, but the engine can offer to draw, claim
11699            a draw, or resign.
11700          */
11701 #if ZIPPY
11702         if (appData.zippyPlay && first.initDone) {
11703             if (result == GameIsDrawn) {
11704                 /* In case draw still needs to be claimed */
11705                 SendToICS(ics_prefix);
11706                 SendToICS("draw\n");
11707             } else if (StrCaseStr(resultDetails, "resign")) {
11708                 SendToICS(ics_prefix);
11709                 SendToICS("resign\n");
11710             }
11711         }
11712 #endif
11713         endingGame = 0; /* [HGM] crash */
11714         return;
11715     }
11716
11717     /* If we're loading the game from a file, stop */
11718     if (whosays == GE_FILE) {
11719       (void) StopLoadGameTimer();
11720       gameFileFP = NULL;
11721     }
11722
11723     /* Cancel draw offers */
11724     first.offeredDraw = second.offeredDraw = 0;
11725
11726     /* If this is an ICS game, only ICS can really say it's done;
11727        if not, anyone can. */
11728     isIcsGame = (gameMode == IcsPlayingWhite ||
11729                  gameMode == IcsPlayingBlack ||
11730                  gameMode == IcsObserving    ||
11731                  gameMode == IcsExamining);
11732
11733     if (!isIcsGame || whosays == GE_ICS) {
11734         /* OK -- not an ICS game, or ICS said it was done */
11735         StopClocks();
11736         if (!isIcsGame && !appData.noChessProgram)
11737           SetUserThinkingEnables();
11738
11739         /* [HGM] if a machine claims the game end we verify this claim */
11740         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11741             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11742                 char claimer;
11743                 ChessMove trueResult = (ChessMove) -1;
11744
11745                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11746                                             first.twoMachinesColor[0] :
11747                                             second.twoMachinesColor[0] ;
11748
11749                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11750                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11751                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11752                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11753                 } else
11754                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11755                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11756                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11757                 } else
11758                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11759                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11760                 }
11761
11762                 // now verify win claims, but not in drop games, as we don't understand those yet
11763                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11764                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11765                     (result == WhiteWins && claimer == 'w' ||
11766                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11767                       if (appData.debugMode) {
11768                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11769                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11770                       }
11771                       if(result != trueResult) {
11772                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11773                               result = claimer == 'w' ? BlackWins : WhiteWins;
11774                               resultDetails = buf;
11775                       }
11776                 } else
11777                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11778                     && (forwardMostMove <= backwardMostMove ||
11779                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11780                         (claimer=='b')==(forwardMostMove&1))
11781                                                                                   ) {
11782                       /* [HGM] verify: draws that were not flagged are false claims */
11783                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11784                       result = claimer == 'w' ? BlackWins : WhiteWins;
11785                       resultDetails = buf;
11786                 }
11787                 /* (Claiming a loss is accepted no questions asked!) */
11788             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11789                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11790                 result = GameUnfinished;
11791                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11792             }
11793             /* [HGM] bare: don't allow bare King to win */
11794             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11795                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11796                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11797                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11798                && result != GameIsDrawn)
11799             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11800                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11801                         int p = (int)boards[forwardMostMove][i][j] - color;
11802                         if(p >= 0 && p <= (int)WhiteKing) k++;
11803                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11804                 }
11805                 if (appData.debugMode) {
11806                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11807                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11808                 }
11809                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11810                         result = GameIsDrawn;
11811                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11812                         resultDetails = buf;
11813                 }
11814             }
11815         }
11816
11817
11818         if(serverMoves != NULL && !loadFlag) { char c = '=';
11819             if(result==WhiteWins) c = '+';
11820             if(result==BlackWins) c = '-';
11821             if(resultDetails != NULL)
11822                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11823         }
11824         if (resultDetails != NULL) {
11825             gameInfo.result = result;
11826             gameInfo.resultDetails = StrSave(resultDetails);
11827
11828             /* display last move only if game was not loaded from file */
11829             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11830                 DisplayMove(currentMove - 1);
11831
11832             if (forwardMostMove != 0) {
11833                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11834                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11835                                                                 ) {
11836                     if (*appData.saveGameFile != NULLCHAR) {
11837                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11838                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11839                         else
11840                         SaveGameToFile(appData.saveGameFile, TRUE);
11841                     } else if (appData.autoSaveGames) {
11842                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11843                     }
11844                     if (*appData.savePositionFile != NULLCHAR) {
11845                         SavePositionToFile(appData.savePositionFile);
11846                     }
11847                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11848                 }
11849             }
11850
11851             /* Tell program how game ended in case it is learning */
11852             /* [HGM] Moved this to after saving the PGN, just in case */
11853             /* engine died and we got here through time loss. In that */
11854             /* case we will get a fatal error writing the pipe, which */
11855             /* would otherwise lose us the PGN.                       */
11856             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11857             /* output during GameEnds should never be fatal anymore   */
11858             if (gameMode == MachinePlaysWhite ||
11859                 gameMode == MachinePlaysBlack ||
11860                 gameMode == TwoMachinesPlay ||
11861                 gameMode == IcsPlayingWhite ||
11862                 gameMode == IcsPlayingBlack ||
11863                 gameMode == BeginningOfGame) {
11864                 char buf[MSG_SIZ];
11865                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11866                         resultDetails);
11867                 if (first.pr != NoProc) {
11868                     SendToProgram(buf, &first);
11869                 }
11870                 if (second.pr != NoProc &&
11871                     gameMode == TwoMachinesPlay) {
11872                     SendToProgram(buf, &second);
11873                 }
11874             }
11875         }
11876
11877         if (appData.icsActive) {
11878             if (appData.quietPlay &&
11879                 (gameMode == IcsPlayingWhite ||
11880                  gameMode == IcsPlayingBlack)) {
11881                 SendToICS(ics_prefix);
11882                 SendToICS("set shout 1\n");
11883             }
11884             nextGameMode = IcsIdle;
11885             ics_user_moved = FALSE;
11886             /* clean up premove.  It's ugly when the game has ended and the
11887              * premove highlights are still on the board.
11888              */
11889             if (gotPremove) {
11890               gotPremove = FALSE;
11891               ClearPremoveHighlights();
11892               DrawPosition(FALSE, boards[currentMove]);
11893             }
11894             if (whosays == GE_ICS) {
11895                 switch (result) {
11896                 case WhiteWins:
11897                     if (gameMode == IcsPlayingWhite)
11898                         PlayIcsWinSound();
11899                     else if(gameMode == IcsPlayingBlack)
11900                         PlayIcsLossSound();
11901                     break;
11902                 case BlackWins:
11903                     if (gameMode == IcsPlayingBlack)
11904                         PlayIcsWinSound();
11905                     else if(gameMode == IcsPlayingWhite)
11906                         PlayIcsLossSound();
11907                     break;
11908                 case GameIsDrawn:
11909                     PlayIcsDrawSound();
11910                     break;
11911                 default:
11912                     PlayIcsUnfinishedSound();
11913                 }
11914             }
11915             if(appData.quitNext) { ExitEvent(0); return; }
11916         } else if (gameMode == EditGame ||
11917                    gameMode == PlayFromGameFile ||
11918                    gameMode == AnalyzeMode ||
11919                    gameMode == AnalyzeFile) {
11920             nextGameMode = gameMode;
11921         } else {
11922             nextGameMode = EndOfGame;
11923         }
11924         pausing = FALSE;
11925         ModeHighlight();
11926     } else {
11927         nextGameMode = gameMode;
11928     }
11929
11930     if (appData.noChessProgram) {
11931         gameMode = nextGameMode;
11932         ModeHighlight();
11933         endingGame = 0; /* [HGM] crash */
11934         return;
11935     }
11936
11937     if (first.reuse) {
11938         /* Put first chess program into idle state */
11939         if (first.pr != NoProc &&
11940             (gameMode == MachinePlaysWhite ||
11941              gameMode == MachinePlaysBlack ||
11942              gameMode == TwoMachinesPlay ||
11943              gameMode == IcsPlayingWhite ||
11944              gameMode == IcsPlayingBlack ||
11945              gameMode == BeginningOfGame)) {
11946             SendToProgram("force\n", &first);
11947             if (first.usePing) {
11948               char buf[MSG_SIZ];
11949               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11950               SendToProgram(buf, &first);
11951             }
11952         }
11953     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11954         /* Kill off first chess program */
11955         if (first.isr != NULL)
11956           RemoveInputSource(first.isr);
11957         first.isr = NULL;
11958
11959         if (first.pr != NoProc) {
11960             ExitAnalyzeMode();
11961             DoSleep( appData.delayBeforeQuit );
11962             SendToProgram("quit\n", &first);
11963             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11964             first.reload = TRUE;
11965         }
11966         first.pr = NoProc;
11967     }
11968     if (second.reuse) {
11969         /* Put second chess program into idle state */
11970         if (second.pr != NoProc &&
11971             gameMode == TwoMachinesPlay) {
11972             SendToProgram("force\n", &second);
11973             if (second.usePing) {
11974               char buf[MSG_SIZ];
11975               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11976               SendToProgram(buf, &second);
11977             }
11978         }
11979     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11980         /* Kill off second chess program */
11981         if (second.isr != NULL)
11982           RemoveInputSource(second.isr);
11983         second.isr = NULL;
11984
11985         if (second.pr != NoProc) {
11986             DoSleep( appData.delayBeforeQuit );
11987             SendToProgram("quit\n", &second);
11988             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11989             second.reload = TRUE;
11990         }
11991         second.pr = NoProc;
11992     }
11993
11994     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11995         char resChar = '=';
11996         switch (result) {
11997         case WhiteWins:
11998           resChar = '+';
11999           if (first.twoMachinesColor[0] == 'w') {
12000             first.matchWins++;
12001           } else {
12002             second.matchWins++;
12003           }
12004           break;
12005         case BlackWins:
12006           resChar = '-';
12007           if (first.twoMachinesColor[0] == 'b') {
12008             first.matchWins++;
12009           } else {
12010             second.matchWins++;
12011           }
12012           break;
12013         case GameUnfinished:
12014           resChar = ' ';
12015         default:
12016           break;
12017         }
12018
12019         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
12020         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
12021             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
12022             ReserveGame(nextGame, resChar); // sets nextGame
12023             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
12024             else ranking = strdup("busy"); //suppress popup when aborted but not finished
12025         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
12026
12027         if (nextGame <= appData.matchGames && !abortMatch) {
12028             gameMode = nextGameMode;
12029             matchGame = nextGame; // this will be overruled in tourney mode!
12030             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
12031             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
12032             endingGame = 0; /* [HGM] crash */
12033             return;
12034         } else {
12035             gameMode = nextGameMode;
12036             if(appData.epd) {
12037                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
12038                 OutputKibitz(2, buf);
12039                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
12040                 OutputKibitz(2, buf);
12041                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
12042                 if(second.matchWins) OutputKibitz(2, buf);
12043                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
12044                 OutputKibitz(2, buf);
12045             }
12046             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
12047                      first.tidy, second.tidy,
12048                      first.matchWins, second.matchWins,
12049                      appData.matchGames - (first.matchWins + second.matchWins));
12050             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
12051             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
12052             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
12053             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
12054                 first.twoMachinesColor = "black\n";
12055                 second.twoMachinesColor = "white\n";
12056             } else {
12057                 first.twoMachinesColor = "white\n";
12058                 second.twoMachinesColor = "black\n";
12059             }
12060         }
12061     }
12062     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
12063         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
12064       ExitAnalyzeMode();
12065     gameMode = nextGameMode;
12066     ModeHighlight();
12067     endingGame = 0;  /* [HGM] crash */
12068     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
12069         if(matchMode == TRUE) { // match through command line: exit with or without popup
12070             if(ranking) {
12071                 ToNrEvent(forwardMostMove);
12072                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
12073                 else ExitEvent(0);
12074             } else DisplayFatalError(buf, 0, 0);
12075         } else { // match through menu; just stop, with or without popup
12076             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12077             ModeHighlight();
12078             if(ranking){
12079                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12080             } else DisplayNote(buf);
12081       }
12082       if(ranking) free(ranking);
12083     }
12084 }
12085
12086 /* Assumes program was just initialized (initString sent).
12087    Leaves program in force mode. */
12088 void
12089 FeedMovesToProgram (ChessProgramState *cps, int upto)
12090 {
12091     int i;
12092
12093     if (appData.debugMode)
12094       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12095               startedFromSetupPosition ? "position and " : "",
12096               backwardMostMove, upto, cps->which);
12097     if(currentlyInitializedVariant != gameInfo.variant) {
12098       char buf[MSG_SIZ];
12099         // [HGM] variantswitch: make engine aware of new variant
12100         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12101                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12102                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12103         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12104         SendToProgram(buf, cps);
12105         currentlyInitializedVariant = gameInfo.variant;
12106     }
12107     SendToProgram("force\n", cps);
12108     if (startedFromSetupPosition) {
12109         SendBoard(cps, backwardMostMove);
12110     if (appData.debugMode) {
12111         fprintf(debugFP, "feedMoves\n");
12112     }
12113     }
12114     for (i = backwardMostMove; i < upto; i++) {
12115         SendMoveToProgram(i, cps);
12116     }
12117 }
12118
12119
12120 int
12121 ResurrectChessProgram ()
12122 {
12123      /* The chess program may have exited.
12124         If so, restart it and feed it all the moves made so far. */
12125     static int doInit = 0;
12126
12127     if (appData.noChessProgram) return 1;
12128
12129     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12130         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12131         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12132         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12133     } else {
12134         if (first.pr != NoProc) return 1;
12135         StartChessProgram(&first);
12136     }
12137     InitChessProgram(&first, FALSE);
12138     FeedMovesToProgram(&first, currentMove);
12139
12140     if (!first.sendTime) {
12141         /* can't tell gnuchess what its clock should read,
12142            so we bow to its notion. */
12143         ResetClocks();
12144         timeRemaining[0][currentMove] = whiteTimeRemaining;
12145         timeRemaining[1][currentMove] = blackTimeRemaining;
12146     }
12147
12148     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12149                 appData.icsEngineAnalyze) && first.analysisSupport) {
12150       SendToProgram("analyze\n", &first);
12151       first.analyzing = TRUE;
12152     }
12153     return 1;
12154 }
12155
12156 /*
12157  * Button procedures
12158  */
12159 void
12160 Reset (int redraw, int init)
12161 {
12162     int i;
12163
12164     if (appData.debugMode) {
12165         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12166                 redraw, init, gameMode);
12167     }
12168     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12169     deadRanks = 0; // assume entire board is used
12170     handSize = 0;
12171     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12172     CleanupTail(); // [HGM] vari: delete any stored variations
12173     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12174     pausing = pauseExamInvalid = FALSE;
12175     startedFromSetupPosition = blackPlaysFirst = FALSE;
12176     firstMove = TRUE;
12177     whiteFlag = blackFlag = FALSE;
12178     userOfferedDraw = FALSE;
12179     hintRequested = bookRequested = FALSE;
12180     first.maybeThinking = FALSE;
12181     second.maybeThinking = FALSE;
12182     first.bookSuspend = FALSE; // [HGM] book
12183     second.bookSuspend = FALSE;
12184     thinkOutput[0] = NULLCHAR;
12185     lastHint[0] = NULLCHAR;
12186     ClearGameInfo(&gameInfo);
12187     gameInfo.variant = StringToVariant(appData.variant);
12188     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12189         gameInfo.variant = VariantUnknown;
12190         strncpy(engineVariant, appData.variant, MSG_SIZ);
12191     }
12192     ics_user_moved = ics_clock_paused = FALSE;
12193     ics_getting_history = H_FALSE;
12194     ics_gamenum = -1;
12195     white_holding[0] = black_holding[0] = NULLCHAR;
12196     ClearProgramStats();
12197     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12198
12199     ResetFrontEnd();
12200     ClearHighlights();
12201     flipView = appData.flipView;
12202     ClearPremoveHighlights();
12203     gotPremove = FALSE;
12204     alarmSounded = FALSE;
12205     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12206
12207     GameEnds(EndOfFile, NULL, GE_PLAYER);
12208     if(appData.serverMovesName != NULL) {
12209         /* [HGM] prepare to make moves file for broadcasting */
12210         clock_t t = clock();
12211         if(serverMoves != NULL) fclose(serverMoves);
12212         serverMoves = fopen(appData.serverMovesName, "r");
12213         if(serverMoves != NULL) {
12214             fclose(serverMoves);
12215             /* delay 15 sec before overwriting, so all clients can see end */
12216             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12217         }
12218         serverMoves = fopen(appData.serverMovesName, "w");
12219     }
12220
12221     ExitAnalyzeMode();
12222     gameMode = BeginningOfGame;
12223     ModeHighlight();
12224     if(appData.icsActive) gameInfo.variant = VariantNormal;
12225     currentMove = forwardMostMove = backwardMostMove = 0;
12226     MarkTargetSquares(1);
12227     InitPosition(redraw);
12228     for (i = 0; i < MAX_MOVES; i++) {
12229         if (commentList[i] != NULL) {
12230             free(commentList[i]);
12231             commentList[i] = NULL;
12232         }
12233     }
12234     ResetClocks();
12235     timeRemaining[0][0] = whiteTimeRemaining;
12236     timeRemaining[1][0] = blackTimeRemaining;
12237
12238     if (first.pr == NoProc) {
12239         StartChessProgram(&first);
12240     }
12241     if (init) {
12242             InitChessProgram(&first, startedFromSetupPosition);
12243     }
12244     DisplayTitle("");
12245     DisplayMessage("", "");
12246     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12247     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12248     ClearMap();        // [HGM] exclude: invalidate map
12249 }
12250
12251 void
12252 AutoPlayGameLoop ()
12253 {
12254     for (;;) {
12255         if (!AutoPlayOneMove())
12256           return;
12257         if (matchMode || appData.timeDelay == 0)
12258           continue;
12259         if (appData.timeDelay < 0)
12260           return;
12261         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12262         break;
12263     }
12264 }
12265
12266 void
12267 AnalyzeNextGame()
12268 {
12269     ReloadGame(1); // next game
12270 }
12271
12272 int
12273 AutoPlayOneMove ()
12274 {
12275     int fromX, fromY, toX, toY;
12276
12277     if (appData.debugMode) {
12278       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12279     }
12280
12281     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12282       return FALSE;
12283
12284     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12285       pvInfoList[currentMove].depth = programStats.depth;
12286       pvInfoList[currentMove].score = programStats.score;
12287       pvInfoList[currentMove].time  = 0;
12288       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12289       else { // append analysis of final position as comment
12290         char buf[MSG_SIZ];
12291         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12292         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12293       }
12294       programStats.depth = 0;
12295     }
12296
12297     if (currentMove >= forwardMostMove) {
12298       if(gameMode == AnalyzeFile) {
12299           if(appData.loadGameIndex == -1) {
12300             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12301           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12302           } else {
12303           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12304         }
12305       }
12306 //      gameMode = EndOfGame;
12307 //      ModeHighlight();
12308
12309       /* [AS] Clear current move marker at the end of a game */
12310       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12311
12312       return FALSE;
12313     }
12314
12315     toX = moveList[currentMove][2] - AAA;
12316     toY = moveList[currentMove][3] - ONE;
12317
12318     if (moveList[currentMove][1] == '@') {
12319         if (appData.highlightLastMove) {
12320             SetHighlights(-1, -1, toX, toY);
12321         }
12322     } else {
12323         fromX = moveList[currentMove][0] - AAA;
12324         fromY = moveList[currentMove][1] - ONE;
12325
12326         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12327
12328         if(moveList[currentMove][4] == ';') { // multi-leg
12329             killX = moveList[currentMove][5] - AAA;
12330             killY = moveList[currentMove][6] - ONE;
12331         }
12332         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12333         killX = killY = -1;
12334
12335         if (appData.highlightLastMove) {
12336             SetHighlights(fromX, fromY, toX, toY);
12337         }
12338     }
12339     DisplayMove(currentMove);
12340     SendMoveToProgram(currentMove++, &first);
12341     DisplayBothClocks();
12342     DrawPosition(FALSE, boards[currentMove]);
12343     // [HGM] PV info: always display, routine tests if empty
12344     DisplayComment(currentMove - 1, commentList[currentMove]);
12345     return TRUE;
12346 }
12347
12348
12349 int
12350 LoadGameOneMove (ChessMove readAhead)
12351 {
12352     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12353     char promoChar = NULLCHAR;
12354     ChessMove moveType;
12355     char move[MSG_SIZ];
12356     char *p, *q;
12357
12358     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12359         gameMode != AnalyzeMode && gameMode != Training) {
12360         gameFileFP = NULL;
12361         return FALSE;
12362     }
12363
12364     yyboardindex = forwardMostMove;
12365     if (readAhead != EndOfFile) {
12366       moveType = readAhead;
12367     } else {
12368       if (gameFileFP == NULL)
12369           return FALSE;
12370       moveType = (ChessMove) Myylex();
12371     }
12372
12373     done = FALSE;
12374     switch (moveType) {
12375       case Comment:
12376         if (appData.debugMode)
12377           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12378         p = yy_text;
12379
12380         /* append the comment but don't display it */
12381         AppendComment(currentMove, p, FALSE);
12382         return TRUE;
12383
12384       case WhiteCapturesEnPassant:
12385       case BlackCapturesEnPassant:
12386       case WhitePromotion:
12387       case BlackPromotion:
12388       case WhiteNonPromotion:
12389       case BlackNonPromotion:
12390       case NormalMove:
12391       case FirstLeg:
12392       case WhiteKingSideCastle:
12393       case WhiteQueenSideCastle:
12394       case BlackKingSideCastle:
12395       case BlackQueenSideCastle:
12396       case WhiteKingSideCastleWild:
12397       case WhiteQueenSideCastleWild:
12398       case BlackKingSideCastleWild:
12399       case BlackQueenSideCastleWild:
12400       /* PUSH Fabien */
12401       case WhiteHSideCastleFR:
12402       case WhiteASideCastleFR:
12403       case BlackHSideCastleFR:
12404       case BlackASideCastleFR:
12405       /* POP Fabien */
12406         if (appData.debugMode)
12407           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12408         fromX = currentMoveString[0] - AAA;
12409         fromY = currentMoveString[1] - ONE;
12410         toX = currentMoveString[2] - AAA;
12411         toY = currentMoveString[3] - ONE;
12412         promoChar = currentMoveString[4];
12413         if(promoChar == ';') promoChar = currentMoveString[7];
12414         break;
12415
12416       case WhiteDrop:
12417       case BlackDrop:
12418         if (appData.debugMode)
12419           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12420         fromX = moveType == WhiteDrop ?
12421           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12422         (int) CharToPiece(ToLower(currentMoveString[0]));
12423         fromY = DROP_RANK;
12424         toX = currentMoveString[2] - AAA;
12425         toY = currentMoveString[3] - ONE;
12426         break;
12427
12428       case WhiteWins:
12429       case BlackWins:
12430       case GameIsDrawn:
12431       case GameUnfinished:
12432         if (appData.debugMode)
12433           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12434         p = strchr(yy_text, '{');
12435         if (p == NULL) p = strchr(yy_text, '(');
12436         if (p == NULL) {
12437             p = yy_text;
12438             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12439         } else {
12440             q = strchr(p, *p == '{' ? '}' : ')');
12441             if (q != NULL) *q = NULLCHAR;
12442             p++;
12443         }
12444         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12445         GameEnds(moveType, p, GE_FILE);
12446         done = TRUE;
12447         if (cmailMsgLoaded) {
12448             ClearHighlights();
12449             flipView = WhiteOnMove(currentMove);
12450             if (moveType == GameUnfinished) flipView = !flipView;
12451             if (appData.debugMode)
12452               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12453         }
12454         break;
12455
12456       case EndOfFile:
12457         if (appData.debugMode)
12458           fprintf(debugFP, "Parser hit end of file\n");
12459         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12460           case MT_NONE:
12461           case MT_CHECK:
12462             break;
12463           case MT_CHECKMATE:
12464           case MT_STAINMATE:
12465             if (WhiteOnMove(currentMove)) {
12466                 GameEnds(BlackWins, "Black mates", GE_FILE);
12467             } else {
12468                 GameEnds(WhiteWins, "White mates", GE_FILE);
12469             }
12470             break;
12471           case MT_STALEMATE:
12472             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12473             break;
12474         }
12475         done = TRUE;
12476         break;
12477
12478       case MoveNumberOne:
12479         if (lastLoadGameStart == GNUChessGame) {
12480             /* GNUChessGames have numbers, but they aren't move numbers */
12481             if (appData.debugMode)
12482               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12483                       yy_text, (int) moveType);
12484             return LoadGameOneMove(EndOfFile); /* tail recursion */
12485         }
12486         /* else fall thru */
12487
12488       case XBoardGame:
12489       case GNUChessGame:
12490       case PGNTag:
12491         /* Reached start of next game in file */
12492         if (appData.debugMode)
12493           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12494         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12495           case MT_NONE:
12496           case MT_CHECK:
12497             break;
12498           case MT_CHECKMATE:
12499           case MT_STAINMATE:
12500             if (WhiteOnMove(currentMove)) {
12501                 GameEnds(BlackWins, "Black mates", GE_FILE);
12502             } else {
12503                 GameEnds(WhiteWins, "White mates", GE_FILE);
12504             }
12505             break;
12506           case MT_STALEMATE:
12507             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12508             break;
12509         }
12510         done = TRUE;
12511         break;
12512
12513       case PositionDiagram:     /* should not happen; ignore */
12514       case ElapsedTime:         /* ignore */
12515       case NAG:                 /* ignore */
12516         if (appData.debugMode)
12517           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12518                   yy_text, (int) moveType);
12519         return LoadGameOneMove(EndOfFile); /* tail recursion */
12520
12521       case IllegalMove:
12522         if (appData.testLegality) {
12523             if (appData.debugMode)
12524               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12525             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12526                     (forwardMostMove / 2) + 1,
12527                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12528             DisplayError(move, 0);
12529             done = TRUE;
12530         } else {
12531             if (appData.debugMode)
12532               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12533                       yy_text, currentMoveString);
12534             if(currentMoveString[1] == '@') {
12535                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12536                 fromY = DROP_RANK;
12537             } else {
12538                 fromX = currentMoveString[0] - AAA;
12539                 fromY = currentMoveString[1] - ONE;
12540             }
12541             toX = currentMoveString[2] - AAA;
12542             toY = currentMoveString[3] - ONE;
12543             promoChar = currentMoveString[4];
12544         }
12545         break;
12546
12547       case AmbiguousMove:
12548         if (appData.debugMode)
12549           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12550         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12551                 (forwardMostMove / 2) + 1,
12552                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12553         DisplayError(move, 0);
12554         done = TRUE;
12555         break;
12556
12557       default:
12558       case ImpossibleMove:
12559         if (appData.debugMode)
12560           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12561         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12562                 (forwardMostMove / 2) + 1,
12563                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12564         DisplayError(move, 0);
12565         done = TRUE;
12566         break;
12567     }
12568
12569     if (done) {
12570         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12571             DrawPosition(FALSE, boards[currentMove]);
12572             DisplayBothClocks();
12573             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12574               DisplayComment(currentMove - 1, commentList[currentMove]);
12575         }
12576         (void) StopLoadGameTimer();
12577         gameFileFP = NULL;
12578         cmailOldMove = forwardMostMove;
12579         return FALSE;
12580     } else {
12581         /* currentMoveString is set as a side-effect of yylex */
12582
12583         thinkOutput[0] = NULLCHAR;
12584         MakeMove(fromX, fromY, toX, toY, promoChar);
12585         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12586         currentMove = forwardMostMove;
12587         return TRUE;
12588     }
12589 }
12590
12591 /* Load the nth game from the given file */
12592 int
12593 LoadGameFromFile (char *filename, int n, char *title, int useList)
12594 {
12595     FILE *f;
12596     char buf[MSG_SIZ];
12597
12598     if (strcmp(filename, "-") == 0) {
12599         f = stdin;
12600         title = "stdin";
12601     } else {
12602         f = fopen(filename, "rb");
12603         if (f == NULL) {
12604           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12605             DisplayError(buf, errno);
12606             return FALSE;
12607         }
12608     }
12609     if (fseek(f, 0, 0) == -1) {
12610         /* f is not seekable; probably a pipe */
12611         useList = FALSE;
12612     }
12613     if (useList && n == 0) {
12614         int error = GameListBuild(f);
12615         if (error) {
12616             DisplayError(_("Cannot build game list"), error);
12617         } else if (!ListEmpty(&gameList) &&
12618                    ((ListGame *) gameList.tailPred)->number > 1) {
12619             GameListPopUp(f, title);
12620             return TRUE;
12621         }
12622         GameListDestroy();
12623         n = 1;
12624     }
12625     if (n == 0) n = 1;
12626     return LoadGame(f, n, title, FALSE);
12627 }
12628
12629
12630 void
12631 MakeRegisteredMove ()
12632 {
12633     int fromX, fromY, toX, toY;
12634     char promoChar;
12635     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12636         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12637           case CMAIL_MOVE:
12638           case CMAIL_DRAW:
12639             if (appData.debugMode)
12640               fprintf(debugFP, "Restoring %s for game %d\n",
12641                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12642
12643             thinkOutput[0] = NULLCHAR;
12644             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12645             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12646             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12647             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12648             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12649             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12650             MakeMove(fromX, fromY, toX, toY, promoChar);
12651             ShowMove(fromX, fromY, toX, toY);
12652
12653             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12654               case MT_NONE:
12655               case MT_CHECK:
12656                 break;
12657
12658               case MT_CHECKMATE:
12659               case MT_STAINMATE:
12660                 if (WhiteOnMove(currentMove)) {
12661                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12662                 } else {
12663                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12664                 }
12665                 break;
12666
12667               case MT_STALEMATE:
12668                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12669                 break;
12670             }
12671
12672             break;
12673
12674           case CMAIL_RESIGN:
12675             if (WhiteOnMove(currentMove)) {
12676                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12677             } else {
12678                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12679             }
12680             break;
12681
12682           case CMAIL_ACCEPT:
12683             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12684             break;
12685
12686           default:
12687             break;
12688         }
12689     }
12690
12691     return;
12692 }
12693
12694 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12695 int
12696 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12697 {
12698     int retVal;
12699
12700     if (gameNumber > nCmailGames) {
12701         DisplayError(_("No more games in this message"), 0);
12702         return FALSE;
12703     }
12704     if (f == lastLoadGameFP) {
12705         int offset = gameNumber - lastLoadGameNumber;
12706         if (offset == 0) {
12707             cmailMsg[0] = NULLCHAR;
12708             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12709                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12710                 nCmailMovesRegistered--;
12711             }
12712             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12713             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12714                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12715             }
12716         } else {
12717             if (! RegisterMove()) return FALSE;
12718         }
12719     }
12720
12721     retVal = LoadGame(f, gameNumber, title, useList);
12722
12723     /* Make move registered during previous look at this game, if any */
12724     MakeRegisteredMove();
12725
12726     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12727         commentList[currentMove]
12728           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12729         DisplayComment(currentMove - 1, commentList[currentMove]);
12730     }
12731
12732     return retVal;
12733 }
12734
12735 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12736 int
12737 ReloadGame (int offset)
12738 {
12739     int gameNumber = lastLoadGameNumber + offset;
12740     if (lastLoadGameFP == NULL) {
12741         DisplayError(_("No game has been loaded yet"), 0);
12742         return FALSE;
12743     }
12744     if (gameNumber <= 0) {
12745         DisplayError(_("Can't back up any further"), 0);
12746         return FALSE;
12747     }
12748     if (cmailMsgLoaded) {
12749         return CmailLoadGame(lastLoadGameFP, gameNumber,
12750                              lastLoadGameTitle, lastLoadGameUseList);
12751     } else {
12752         return LoadGame(lastLoadGameFP, gameNumber,
12753                         lastLoadGameTitle, lastLoadGameUseList);
12754     }
12755 }
12756
12757 int keys[EmptySquare+1];
12758
12759 int
12760 PositionMatches (Board b1, Board b2)
12761 {
12762     int r, f, sum=0;
12763     switch(appData.searchMode) {
12764         case 1: return CompareWithRights(b1, b2);
12765         case 2:
12766             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12767                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12768             }
12769             return TRUE;
12770         case 3:
12771             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12772               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12773                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12774             }
12775             return sum==0;
12776         case 4:
12777             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12778                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12779             }
12780             return sum==0;
12781     }
12782     return TRUE;
12783 }
12784
12785 #define Q_PROMO  4
12786 #define Q_EP     3
12787 #define Q_BCASTL 2
12788 #define Q_WCASTL 1
12789
12790 int pieceList[256], quickBoard[256];
12791 ChessSquare pieceType[256] = { EmptySquare };
12792 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12793 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12794 int soughtTotal, turn;
12795 Boolean epOK, flipSearch;
12796
12797 typedef struct {
12798     unsigned char piece, to;
12799 } Move;
12800
12801 #define DSIZE (250000)
12802
12803 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12804 Move *moveDatabase = initialSpace;
12805 unsigned int movePtr, dataSize = DSIZE;
12806
12807 int
12808 MakePieceList (Board board, int *counts)
12809 {
12810     int r, f, n=Q_PROMO, total=0;
12811     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12812     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12813         int sq = f + (r<<4);
12814         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12815             quickBoard[sq] = ++n;
12816             pieceList[n] = sq;
12817             pieceType[n] = board[r][f];
12818             counts[board[r][f]]++;
12819             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12820             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12821             total++;
12822         }
12823     }
12824     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12825     return total;
12826 }
12827
12828 void
12829 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12830 {
12831     int sq = fromX + (fromY<<4);
12832     int piece = quickBoard[sq], rook;
12833     quickBoard[sq] = 0;
12834     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12835     if(piece == pieceList[1] && fromY == toY) {
12836       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12837         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12838         moveDatabase[movePtr++].piece = Q_WCASTL;
12839         quickBoard[sq] = piece;
12840         piece = quickBoard[from]; quickBoard[from] = 0;
12841         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12842       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12843         quickBoard[sq] = 0; // remove Rook
12844         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12845         moveDatabase[movePtr++].piece = Q_WCASTL;
12846         quickBoard[sq] = pieceList[1]; // put King
12847         piece = rook;
12848         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12849       }
12850     } else
12851     if(piece == pieceList[2] && fromY == toY) {
12852       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12853         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12854         moveDatabase[movePtr++].piece = Q_BCASTL;
12855         quickBoard[sq] = piece;
12856         piece = quickBoard[from]; quickBoard[from] = 0;
12857         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12858       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12859         quickBoard[sq] = 0; // remove Rook
12860         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12861         moveDatabase[movePtr++].piece = Q_BCASTL;
12862         quickBoard[sq] = pieceList[2]; // put King
12863         piece = rook;
12864         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12865       }
12866     } else
12867     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12868         quickBoard[(fromY<<4)+toX] = 0;
12869         moveDatabase[movePtr].piece = Q_EP;
12870         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12871         moveDatabase[movePtr].to = sq;
12872     } else
12873     if(promoPiece != pieceType[piece]) {
12874         moveDatabase[movePtr++].piece = Q_PROMO;
12875         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12876     }
12877     moveDatabase[movePtr].piece = piece;
12878     quickBoard[sq] = piece;
12879     movePtr++;
12880 }
12881
12882 int
12883 PackGame (Board board)
12884 {
12885     Move *newSpace = NULL;
12886     moveDatabase[movePtr].piece = 0; // terminate previous game
12887     if(movePtr > dataSize) {
12888         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12889         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12890         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12891         if(newSpace) {
12892             int i;
12893             Move *p = moveDatabase, *q = newSpace;
12894             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12895             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12896             moveDatabase = newSpace;
12897         } else { // calloc failed, we must be out of memory. Too bad...
12898             dataSize = 0; // prevent calloc events for all subsequent games
12899             return 0;     // and signal this one isn't cached
12900         }
12901     }
12902     movePtr++;
12903     MakePieceList(board, counts);
12904     return movePtr;
12905 }
12906
12907 int
12908 QuickCompare (Board board, int *minCounts, int *maxCounts)
12909 {   // compare according to search mode
12910     int r, f;
12911     switch(appData.searchMode)
12912     {
12913       case 1: // exact position match
12914         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12915         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12916             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12917         }
12918         break;
12919       case 2: // can have extra material on empty squares
12920         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12921             if(board[r][f] == EmptySquare) continue;
12922             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12923         }
12924         break;
12925       case 3: // material with exact Pawn structure
12926         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12927             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12928             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12929         } // fall through to material comparison
12930       case 4: // exact material
12931         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12932         break;
12933       case 6: // material range with given imbalance
12934         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12935         // fall through to range comparison
12936       case 5: // material range
12937         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12938     }
12939     return TRUE;
12940 }
12941
12942 int
12943 QuickScan (Board board, Move *move)
12944 {   // reconstruct game,and compare all positions in it
12945     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12946     do {
12947         int piece = move->piece;
12948         int to = move->to, from = pieceList[piece];
12949         if(found < 0) { // if already found just scan to game end for final piece count
12950           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12951            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12952            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12953                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12954             ) {
12955             static int lastCounts[EmptySquare+1];
12956             int i;
12957             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12958             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12959           } else stretch = 0;
12960           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12961           if(found >= 0 && !appData.minPieces) return found;
12962         }
12963         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12964           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12965           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12966             piece = (++move)->piece;
12967             from = pieceList[piece];
12968             counts[pieceType[piece]]--;
12969             pieceType[piece] = (ChessSquare) move->to;
12970             counts[move->to]++;
12971           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12972             counts[pieceType[quickBoard[to]]]--;
12973             quickBoard[to] = 0; total--;
12974             move++;
12975             continue;
12976           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12977             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12978             from  = pieceList[piece]; // so this must be King
12979             quickBoard[from] = 0;
12980             pieceList[piece] = to;
12981             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12982             quickBoard[from] = 0; // rook
12983             quickBoard[to] = piece;
12984             to = move->to; piece = move->piece;
12985             goto aftercastle;
12986           }
12987         }
12988         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12989         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12990         quickBoard[from] = 0;
12991       aftercastle:
12992         quickBoard[to] = piece;
12993         pieceList[piece] = to;
12994         cnt++; turn ^= 3;
12995         move++;
12996     } while(1);
12997 }
12998
12999 void
13000 InitSearch ()
13001 {
13002     int r, f;
13003     flipSearch = FALSE;
13004     CopyBoard(soughtBoard, boards[currentMove]);
13005     soughtTotal = MakePieceList(soughtBoard, maxSought);
13006     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
13007     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
13008     CopyBoard(reverseBoard, boards[currentMove]);
13009     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13010         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
13011         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
13012         reverseBoard[r][f] = piece;
13013     }
13014     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
13015     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
13016     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
13017                  || (boards[currentMove][CASTLING][2] == NoRights ||
13018                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
13019                  && (boards[currentMove][CASTLING][5] == NoRights ||
13020                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
13021       ) {
13022         flipSearch = TRUE;
13023         CopyBoard(flipBoard, soughtBoard);
13024         CopyBoard(rotateBoard, reverseBoard);
13025         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13026             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
13027             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
13028         }
13029     }
13030     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
13031     if(appData.searchMode >= 5) {
13032         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
13033         MakePieceList(soughtBoard, minSought);
13034         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
13035     }
13036     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
13037         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
13038 }
13039
13040 GameInfo dummyInfo;
13041 static int creatingBook;
13042
13043 int
13044 GameContainsPosition (FILE *f, ListGame *lg)
13045 {
13046     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
13047     int fromX, fromY, toX, toY;
13048     char promoChar;
13049     static int initDone=FALSE;
13050
13051     // weed out games based on numerical tag comparison
13052     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
13053     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
13054     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
13055     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
13056     if(!initDone) {
13057         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
13058         initDone = TRUE;
13059     }
13060     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
13061     else CopyBoard(boards[scratch], initialPosition); // default start position
13062     if(lg->moves) {
13063         turn = btm + 1;
13064         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
13065         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
13066     }
13067     if(btm) plyNr++;
13068     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13069     fseek(f, lg->offset, 0);
13070     yynewfile(f);
13071     while(1) {
13072         yyboardindex = scratch;
13073         quickFlag = plyNr+1;
13074         next = Myylex();
13075         quickFlag = 0;
13076         switch(next) {
13077             case PGNTag:
13078                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13079             default:
13080                 continue;
13081
13082             case XBoardGame:
13083             case GNUChessGame:
13084                 if(plyNr) return -1; // after we have seen moves, this is for new game
13085               continue;
13086
13087             case AmbiguousMove: // we cannot reconstruct the game beyond these two
13088             case ImpossibleMove:
13089             case WhiteWins: // game ends here with these four
13090             case BlackWins:
13091             case GameIsDrawn:
13092             case GameUnfinished:
13093                 return -1;
13094
13095             case IllegalMove:
13096                 if(appData.testLegality) return -1;
13097             case WhiteCapturesEnPassant:
13098             case BlackCapturesEnPassant:
13099             case WhitePromotion:
13100             case BlackPromotion:
13101             case WhiteNonPromotion:
13102             case BlackNonPromotion:
13103             case NormalMove:
13104             case FirstLeg:
13105             case WhiteKingSideCastle:
13106             case WhiteQueenSideCastle:
13107             case BlackKingSideCastle:
13108             case BlackQueenSideCastle:
13109             case WhiteKingSideCastleWild:
13110             case WhiteQueenSideCastleWild:
13111             case BlackKingSideCastleWild:
13112             case BlackQueenSideCastleWild:
13113             case WhiteHSideCastleFR:
13114             case WhiteASideCastleFR:
13115             case BlackHSideCastleFR:
13116             case BlackASideCastleFR:
13117                 fromX = currentMoveString[0] - AAA;
13118                 fromY = currentMoveString[1] - ONE;
13119                 toX = currentMoveString[2] - AAA;
13120                 toY = currentMoveString[3] - ONE;
13121                 promoChar = currentMoveString[4];
13122                 break;
13123             case WhiteDrop:
13124             case BlackDrop:
13125                 fromX = next == WhiteDrop ?
13126                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
13127                   (int) CharToPiece(ToLower(currentMoveString[0]));
13128                 fromY = DROP_RANK;
13129                 toX = currentMoveString[2] - AAA;
13130                 toY = currentMoveString[3] - ONE;
13131                 promoChar = 0;
13132                 break;
13133         }
13134         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13135         plyNr++;
13136         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13137         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13138         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13139         if(appData.findMirror) {
13140             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13141             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13142         }
13143     }
13144 }
13145
13146 /* Load the nth game from open file f */
13147 int
13148 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13149 {
13150     ChessMove cm;
13151     char buf[MSG_SIZ];
13152     int gn = gameNumber;
13153     ListGame *lg = NULL;
13154     int numPGNTags = 0, i;
13155     int err, pos = -1;
13156     GameMode oldGameMode;
13157     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13158     char oldName[MSG_SIZ];
13159
13160     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13161
13162     if (appData.debugMode)
13163         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13164
13165     if (gameMode == Training )
13166         SetTrainingModeOff();
13167
13168     oldGameMode = gameMode;
13169     if (gameMode != BeginningOfGame) {
13170       Reset(FALSE, TRUE);
13171     }
13172     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13173
13174     gameFileFP = f;
13175     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13176         fclose(lastLoadGameFP);
13177     }
13178
13179     if (useList) {
13180         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13181
13182         if (lg) {
13183             fseek(f, lg->offset, 0);
13184             GameListHighlight(gameNumber);
13185             pos = lg->position;
13186             gn = 1;
13187         }
13188         else {
13189             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13190               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13191             else
13192             DisplayError(_("Game number out of range"), 0);
13193             return FALSE;
13194         }
13195     } else {
13196         GameListDestroy();
13197         if (fseek(f, 0, 0) == -1) {
13198             if (f == lastLoadGameFP ?
13199                 gameNumber == lastLoadGameNumber + 1 :
13200                 gameNumber == 1) {
13201                 gn = 1;
13202             } else {
13203                 DisplayError(_("Can't seek on game file"), 0);
13204                 return FALSE;
13205             }
13206         }
13207     }
13208     lastLoadGameFP = f;
13209     lastLoadGameNumber = gameNumber;
13210     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13211     lastLoadGameUseList = useList;
13212
13213     yynewfile(f);
13214
13215     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13216       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13217                 lg->gameInfo.black);
13218             DisplayTitle(buf);
13219     } else if (*title != NULLCHAR) {
13220         if (gameNumber > 1) {
13221           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13222             DisplayTitle(buf);
13223         } else {
13224             DisplayTitle(title);
13225         }
13226     }
13227
13228     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13229         gameMode = PlayFromGameFile;
13230         ModeHighlight();
13231     }
13232
13233     currentMove = forwardMostMove = backwardMostMove = 0;
13234     CopyBoard(boards[0], initialPosition);
13235     StopClocks();
13236
13237     /*
13238      * Skip the first gn-1 games in the file.
13239      * Also skip over anything that precedes an identifiable
13240      * start of game marker, to avoid being confused by
13241      * garbage at the start of the file.  Currently
13242      * recognized start of game markers are the move number "1",
13243      * the pattern "gnuchess .* game", the pattern
13244      * "^[#;%] [^ ]* game file", and a PGN tag block.
13245      * A game that starts with one of the latter two patterns
13246      * will also have a move number 1, possibly
13247      * following a position diagram.
13248      * 5-4-02: Let's try being more lenient and allowing a game to
13249      * start with an unnumbered move.  Does that break anything?
13250      */
13251     cm = lastLoadGameStart = EndOfFile;
13252     while (gn > 0) {
13253         yyboardindex = forwardMostMove;
13254         cm = (ChessMove) Myylex();
13255         switch (cm) {
13256           case EndOfFile:
13257             if (cmailMsgLoaded) {
13258                 nCmailGames = CMAIL_MAX_GAMES - gn;
13259             } else {
13260                 Reset(TRUE, TRUE);
13261                 DisplayError(_("Game not found in file"), 0);
13262             }
13263             return FALSE;
13264
13265           case GNUChessGame:
13266           case XBoardGame:
13267             gn--;
13268             lastLoadGameStart = cm;
13269             break;
13270
13271           case MoveNumberOne:
13272             switch (lastLoadGameStart) {
13273               case GNUChessGame:
13274               case XBoardGame:
13275               case PGNTag:
13276                 break;
13277               case MoveNumberOne:
13278               case EndOfFile:
13279                 gn--;           /* count this game */
13280                 lastLoadGameStart = cm;
13281                 break;
13282               default:
13283                 /* impossible */
13284                 break;
13285             }
13286             break;
13287
13288           case PGNTag:
13289             switch (lastLoadGameStart) {
13290               case GNUChessGame:
13291               case PGNTag:
13292               case MoveNumberOne:
13293               case EndOfFile:
13294                 gn--;           /* count this game */
13295                 lastLoadGameStart = cm;
13296                 break;
13297               case XBoardGame:
13298                 lastLoadGameStart = cm; /* game counted already */
13299                 break;
13300               default:
13301                 /* impossible */
13302                 break;
13303             }
13304             if (gn > 0) {
13305                 do {
13306                     yyboardindex = forwardMostMove;
13307                     cm = (ChessMove) Myylex();
13308                 } while (cm == PGNTag || cm == Comment);
13309             }
13310             break;
13311
13312           case WhiteWins:
13313           case BlackWins:
13314           case GameIsDrawn:
13315             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13316                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13317                     != CMAIL_OLD_RESULT) {
13318                     nCmailResults ++ ;
13319                     cmailResult[  CMAIL_MAX_GAMES
13320                                 - gn - 1] = CMAIL_OLD_RESULT;
13321                 }
13322             }
13323             break;
13324
13325           case NormalMove:
13326           case FirstLeg:
13327             /* Only a NormalMove can be at the start of a game
13328              * without a position diagram. */
13329             if (lastLoadGameStart == EndOfFile ) {
13330               gn--;
13331               lastLoadGameStart = MoveNumberOne;
13332             }
13333             break;
13334
13335           default:
13336             break;
13337         }
13338     }
13339
13340     if (appData.debugMode)
13341       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13342
13343     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13344
13345     if (cm == XBoardGame) {
13346         /* Skip any header junk before position diagram and/or move 1 */
13347         for (;;) {
13348             yyboardindex = forwardMostMove;
13349             cm = (ChessMove) Myylex();
13350
13351             if (cm == EndOfFile ||
13352                 cm == GNUChessGame || cm == XBoardGame) {
13353                 /* Empty game; pretend end-of-file and handle later */
13354                 cm = EndOfFile;
13355                 break;
13356             }
13357
13358             if (cm == MoveNumberOne || cm == PositionDiagram ||
13359                 cm == PGNTag || cm == Comment)
13360               break;
13361         }
13362     } else if (cm == GNUChessGame) {
13363         if (gameInfo.event != NULL) {
13364             free(gameInfo.event);
13365         }
13366         gameInfo.event = StrSave(yy_text);
13367     }
13368
13369     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13370     while (cm == PGNTag) {
13371         if (appData.debugMode)
13372           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13373         err = ParsePGNTag(yy_text, &gameInfo);
13374         if (!err) numPGNTags++;
13375
13376         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13377         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13378             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13379             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13380             InitPosition(TRUE);
13381             oldVariant = gameInfo.variant;
13382             if (appData.debugMode)
13383               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13384         }
13385
13386
13387         if (gameInfo.fen != NULL) {
13388           Board initial_position;
13389           startedFromSetupPosition = TRUE;
13390           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13391             Reset(TRUE, TRUE);
13392             DisplayError(_("Bad FEN position in file"), 0);
13393             return FALSE;
13394           }
13395           CopyBoard(boards[0], initial_position);
13396           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13397             CopyBoard(initialPosition, initial_position);
13398           if (blackPlaysFirst) {
13399             currentMove = forwardMostMove = backwardMostMove = 1;
13400             CopyBoard(boards[1], initial_position);
13401             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13402             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13403             timeRemaining[0][1] = whiteTimeRemaining;
13404             timeRemaining[1][1] = blackTimeRemaining;
13405             if (commentList[0] != NULL) {
13406               commentList[1] = commentList[0];
13407               commentList[0] = NULL;
13408             }
13409           } else {
13410             currentMove = forwardMostMove = backwardMostMove = 0;
13411           }
13412           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13413           {   int i;
13414               initialRulePlies = FENrulePlies;
13415               for( i=0; i< nrCastlingRights; i++ )
13416                   initialRights[i] = initial_position[CASTLING][i];
13417           }
13418           yyboardindex = forwardMostMove;
13419           free(gameInfo.fen);
13420           gameInfo.fen = NULL;
13421         }
13422
13423         yyboardindex = forwardMostMove;
13424         cm = (ChessMove) Myylex();
13425
13426         /* Handle comments interspersed among the tags */
13427         while (cm == Comment) {
13428             char *p;
13429             if (appData.debugMode)
13430               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13431             p = yy_text;
13432             AppendComment(currentMove, p, FALSE);
13433             yyboardindex = forwardMostMove;
13434             cm = (ChessMove) Myylex();
13435         }
13436     }
13437
13438     /* don't rely on existence of Event tag since if game was
13439      * pasted from clipboard the Event tag may not exist
13440      */
13441     if (numPGNTags > 0){
13442         char *tags;
13443         if (gameInfo.variant == VariantNormal) {
13444           VariantClass v = StringToVariant(gameInfo.event);
13445           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13446           if(v < VariantShogi) gameInfo.variant = v;
13447         }
13448         if (!matchMode) {
13449           if( appData.autoDisplayTags ) {
13450             tags = PGNTags(&gameInfo);
13451             TagsPopUp(tags, CmailMsg());
13452             free(tags);
13453           }
13454         }
13455     } else {
13456         /* Make something up, but don't display it now */
13457         SetGameInfo();
13458         TagsPopDown();
13459     }
13460
13461     if (cm == PositionDiagram) {
13462         int i, j;
13463         char *p;
13464         Board initial_position;
13465
13466         if (appData.debugMode)
13467           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13468
13469         if (!startedFromSetupPosition) {
13470             p = yy_text;
13471             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13472               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13473                 switch (*p) {
13474                   case '{':
13475                   case '[':
13476                   case '-':
13477                   case ' ':
13478                   case '\t':
13479                   case '\n':
13480                   case '\r':
13481                     break;
13482                   default:
13483                     initial_position[i][j++] = CharToPiece(*p);
13484                     break;
13485                 }
13486             while (*p == ' ' || *p == '\t' ||
13487                    *p == '\n' || *p == '\r') p++;
13488
13489             if (strncmp(p, "black", strlen("black"))==0)
13490               blackPlaysFirst = TRUE;
13491             else
13492               blackPlaysFirst = FALSE;
13493             startedFromSetupPosition = TRUE;
13494
13495             CopyBoard(boards[0], initial_position);
13496             if (blackPlaysFirst) {
13497                 currentMove = forwardMostMove = backwardMostMove = 1;
13498                 CopyBoard(boards[1], initial_position);
13499                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13500                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13501                 timeRemaining[0][1] = whiteTimeRemaining;
13502                 timeRemaining[1][1] = blackTimeRemaining;
13503                 if (commentList[0] != NULL) {
13504                     commentList[1] = commentList[0];
13505                     commentList[0] = NULL;
13506                 }
13507             } else {
13508                 currentMove = forwardMostMove = backwardMostMove = 0;
13509             }
13510         }
13511         yyboardindex = forwardMostMove;
13512         cm = (ChessMove) Myylex();
13513     }
13514
13515   if(!creatingBook) {
13516     if (first.pr == NoProc) {
13517         StartChessProgram(&first);
13518     }
13519     InitChessProgram(&first, FALSE);
13520     if(gameInfo.variant == VariantUnknown && *oldName) {
13521         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13522         gameInfo.variant = v;
13523     }
13524     SendToProgram("force\n", &first);
13525     if (startedFromSetupPosition) {
13526         SendBoard(&first, forwardMostMove);
13527     if (appData.debugMode) {
13528         fprintf(debugFP, "Load Game\n");
13529     }
13530         DisplayBothClocks();
13531     }
13532   }
13533
13534     /* [HGM] server: flag to write setup moves in broadcast file as one */
13535     loadFlag = appData.suppressLoadMoves;
13536
13537     while (cm == Comment) {
13538         char *p;
13539         if (appData.debugMode)
13540           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13541         p = yy_text;
13542         AppendComment(currentMove, p, FALSE);
13543         yyboardindex = forwardMostMove;
13544         cm = (ChessMove) Myylex();
13545     }
13546
13547     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13548         cm == WhiteWins || cm == BlackWins ||
13549         cm == GameIsDrawn || cm == GameUnfinished) {
13550         DisplayMessage("", _("No moves in game"));
13551         if (cmailMsgLoaded) {
13552             if (appData.debugMode)
13553               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13554             ClearHighlights();
13555             flipView = FALSE;
13556         }
13557         DrawPosition(FALSE, boards[currentMove]);
13558         DisplayBothClocks();
13559         gameMode = EditGame;
13560         ModeHighlight();
13561         gameFileFP = NULL;
13562         cmailOldMove = 0;
13563         return TRUE;
13564     }
13565
13566     // [HGM] PV info: routine tests if comment empty
13567     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13568         DisplayComment(currentMove - 1, commentList[currentMove]);
13569     }
13570     if (!matchMode && appData.timeDelay != 0)
13571       DrawPosition(FALSE, boards[currentMove]);
13572
13573     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13574       programStats.ok_to_send = 1;
13575     }
13576
13577     /* if the first token after the PGN tags is a move
13578      * and not move number 1, retrieve it from the parser
13579      */
13580     if (cm != MoveNumberOne)
13581         LoadGameOneMove(cm);
13582
13583     /* load the remaining moves from the file */
13584     while (LoadGameOneMove(EndOfFile)) {
13585       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13586       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13587     }
13588
13589     /* rewind to the start of the game */
13590     currentMove = backwardMostMove;
13591
13592     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13593
13594     if (oldGameMode == AnalyzeFile) {
13595       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13596       AnalyzeFileEvent();
13597     } else
13598     if (oldGameMode == AnalyzeMode) {
13599       AnalyzeFileEvent();
13600     }
13601
13602     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13603         long int w, b; // [HGM] adjourn: restore saved clock times
13604         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13605         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13606             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13607             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13608         }
13609     }
13610
13611     if(creatingBook) return TRUE;
13612     if (!matchMode && pos > 0) {
13613         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13614     } else
13615     if (matchMode || appData.timeDelay == 0) {
13616       ToEndEvent();
13617     } else if (appData.timeDelay > 0) {
13618       AutoPlayGameLoop();
13619     }
13620
13621     if (appData.debugMode)
13622         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13623
13624     loadFlag = 0; /* [HGM] true game starts */
13625     return TRUE;
13626 }
13627
13628 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13629 int
13630 ReloadPosition (int offset)
13631 {
13632     int positionNumber = lastLoadPositionNumber + offset;
13633     if (lastLoadPositionFP == NULL) {
13634         DisplayError(_("No position has been loaded yet"), 0);
13635         return FALSE;
13636     }
13637     if (positionNumber <= 0) {
13638         DisplayError(_("Can't back up any further"), 0);
13639         return FALSE;
13640     }
13641     return LoadPosition(lastLoadPositionFP, positionNumber,
13642                         lastLoadPositionTitle);
13643 }
13644
13645 /* Load the nth position from the given file */
13646 int
13647 LoadPositionFromFile (char *filename, int n, char *title)
13648 {
13649     FILE *f;
13650     char buf[MSG_SIZ];
13651
13652     if (strcmp(filename, "-") == 0) {
13653         return LoadPosition(stdin, n, "stdin");
13654     } else {
13655         f = fopen(filename, "rb");
13656         if (f == NULL) {
13657             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13658             DisplayError(buf, errno);
13659             return FALSE;
13660         } else {
13661             return LoadPosition(f, n, title);
13662         }
13663     }
13664 }
13665
13666 /* Load the nth position from the given open file, and close it */
13667 int
13668 LoadPosition (FILE *f, int positionNumber, char *title)
13669 {
13670     char *p, line[MSG_SIZ];
13671     Board initial_position;
13672     int i, j, fenMode, pn;
13673
13674     if (gameMode == Training )
13675         SetTrainingModeOff();
13676
13677     if (gameMode != BeginningOfGame) {
13678         Reset(FALSE, TRUE);
13679     }
13680     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13681         fclose(lastLoadPositionFP);
13682     }
13683     if (positionNumber == 0) positionNumber = 1;
13684     lastLoadPositionFP = f;
13685     lastLoadPositionNumber = positionNumber;
13686     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13687     if (first.pr == NoProc && !appData.noChessProgram) {
13688       StartChessProgram(&first);
13689       InitChessProgram(&first, FALSE);
13690     }
13691     pn = positionNumber;
13692     if (positionNumber < 0) {
13693         /* Negative position number means to seek to that byte offset */
13694         if (fseek(f, -positionNumber, 0) == -1) {
13695             DisplayError(_("Can't seek on position file"), 0);
13696             return FALSE;
13697         };
13698         pn = 1;
13699     } else {
13700         if (fseek(f, 0, 0) == -1) {
13701             if (f == lastLoadPositionFP ?
13702                 positionNumber == lastLoadPositionNumber + 1 :
13703                 positionNumber == 1) {
13704                 pn = 1;
13705             } else {
13706                 DisplayError(_("Can't seek on position file"), 0);
13707                 return FALSE;
13708             }
13709         }
13710     }
13711     /* See if this file is FEN or old-style xboard */
13712     if (fgets(line, MSG_SIZ, f) == NULL) {
13713         DisplayError(_("Position not found in file"), 0);
13714         return FALSE;
13715     }
13716     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13717     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13718
13719     if (pn >= 2) {
13720         if (fenMode || line[0] == '#') pn--;
13721         while (pn > 0) {
13722             /* skip positions before number pn */
13723             if (fgets(line, MSG_SIZ, f) == NULL) {
13724                 Reset(TRUE, TRUE);
13725                 DisplayError(_("Position not found in file"), 0);
13726                 return FALSE;
13727             }
13728             if (fenMode || line[0] == '#') pn--;
13729         }
13730     }
13731
13732     if (fenMode) {
13733         char *p;
13734         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13735             DisplayError(_("Bad FEN position in file"), 0);
13736             return FALSE;
13737         }
13738         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13739             sscanf(p+4, "%[^;]", bestMove);
13740         } else *bestMove = NULLCHAR;
13741         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13742             sscanf(p+4, "%[^;]", avoidMove);
13743         } else *avoidMove = NULLCHAR;
13744     } else {
13745         (void) fgets(line, MSG_SIZ, f);
13746         (void) fgets(line, MSG_SIZ, f);
13747
13748         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13749             (void) fgets(line, MSG_SIZ, f);
13750             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13751                 if (*p == ' ')
13752                   continue;
13753                 initial_position[i][j++] = CharToPiece(*p);
13754             }
13755         }
13756
13757         blackPlaysFirst = FALSE;
13758         if (!feof(f)) {
13759             (void) fgets(line, MSG_SIZ, f);
13760             if (strncmp(line, "black", strlen("black"))==0)
13761               blackPlaysFirst = TRUE;
13762         }
13763     }
13764     startedFromSetupPosition = TRUE;
13765
13766     CopyBoard(boards[0], initial_position);
13767     if (blackPlaysFirst) {
13768         currentMove = forwardMostMove = backwardMostMove = 1;
13769         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13770         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13771         CopyBoard(boards[1], initial_position);
13772         DisplayMessage("", _("Black to play"));
13773     } else {
13774         currentMove = forwardMostMove = backwardMostMove = 0;
13775         DisplayMessage("", _("White to play"));
13776     }
13777     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13778     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13779         SendToProgram("force\n", &first);
13780         SendBoard(&first, forwardMostMove);
13781     }
13782     if (appData.debugMode) {
13783 int i, j;
13784   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13785   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13786         fprintf(debugFP, "Load Position\n");
13787     }
13788
13789     if (positionNumber > 1) {
13790       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13791         DisplayTitle(line);
13792     } else {
13793         DisplayTitle(title);
13794     }
13795     gameMode = EditGame;
13796     ModeHighlight();
13797     ResetClocks();
13798     timeRemaining[0][1] = whiteTimeRemaining;
13799     timeRemaining[1][1] = blackTimeRemaining;
13800     DrawPosition(FALSE, boards[currentMove]);
13801
13802     return TRUE;
13803 }
13804
13805
13806 void
13807 CopyPlayerNameIntoFileName (char **dest, char *src)
13808 {
13809     while (*src != NULLCHAR && *src != ',') {
13810         if (*src == ' ') {
13811             *(*dest)++ = '_';
13812             src++;
13813         } else {
13814             *(*dest)++ = *src++;
13815         }
13816     }
13817 }
13818
13819 char *
13820 DefaultFileName (char *ext)
13821 {
13822     static char def[MSG_SIZ];
13823     char *p;
13824
13825     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13826         p = def;
13827         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13828         *p++ = '-';
13829         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13830         *p++ = '.';
13831         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13832     } else {
13833         def[0] = NULLCHAR;
13834     }
13835     return def;
13836 }
13837
13838 /* Save the current game to the given file */
13839 int
13840 SaveGameToFile (char *filename, int append)
13841 {
13842     FILE *f;
13843     char buf[MSG_SIZ];
13844     int result, i, t,tot=0;
13845
13846     if (strcmp(filename, "-") == 0) {
13847         return SaveGame(stdout, 0, NULL);
13848     } else {
13849         for(i=0; i<10; i++) { // upto 10 tries
13850              f = fopen(filename, append ? "a" : "w");
13851              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13852              if(f || errno != 13) break;
13853              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13854              tot += t;
13855         }
13856         if (f == NULL) {
13857             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13858             DisplayError(buf, errno);
13859             return FALSE;
13860         } else {
13861             safeStrCpy(buf, lastMsg, MSG_SIZ);
13862             DisplayMessage(_("Waiting for access to save file"), "");
13863             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13864             DisplayMessage(_("Saving game"), "");
13865             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13866             result = SaveGame(f, 0, NULL);
13867             DisplayMessage(buf, "");
13868             return result;
13869         }
13870     }
13871 }
13872
13873 char *
13874 SavePart (char *str)
13875 {
13876     static char buf[MSG_SIZ];
13877     char *p;
13878
13879     p = strchr(str, ' ');
13880     if (p == NULL) return str;
13881     strncpy(buf, str, p - str);
13882     buf[p - str] = NULLCHAR;
13883     return buf;
13884 }
13885
13886 #define PGN_MAX_LINE 75
13887
13888 #define PGN_SIDE_WHITE  0
13889 #define PGN_SIDE_BLACK  1
13890
13891 static int
13892 FindFirstMoveOutOfBook (int side)
13893 {
13894     int result = -1;
13895
13896     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13897         int index = backwardMostMove;
13898         int has_book_hit = 0;
13899
13900         if( (index % 2) != side ) {
13901             index++;
13902         }
13903
13904         while( index < forwardMostMove ) {
13905             /* Check to see if engine is in book */
13906             int depth = pvInfoList[index].depth;
13907             int score = pvInfoList[index].score;
13908             int in_book = 0;
13909
13910             if( depth <= 2 ) {
13911                 in_book = 1;
13912             }
13913             else if( score == 0 && depth == 63 ) {
13914                 in_book = 1; /* Zappa */
13915             }
13916             else if( score == 2 && depth == 99 ) {
13917                 in_book = 1; /* Abrok */
13918             }
13919
13920             has_book_hit += in_book;
13921
13922             if( ! in_book ) {
13923                 result = index;
13924
13925                 break;
13926             }
13927
13928             index += 2;
13929         }
13930     }
13931
13932     return result;
13933 }
13934
13935 void
13936 GetOutOfBookInfo (char * buf)
13937 {
13938     int oob[2];
13939     int i;
13940     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13941
13942     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13943     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13944
13945     *buf = '\0';
13946
13947     if( oob[0] >= 0 || oob[1] >= 0 ) {
13948         for( i=0; i<2; i++ ) {
13949             int idx = oob[i];
13950
13951             if( idx >= 0 ) {
13952                 if( i > 0 && oob[0] >= 0 ) {
13953                     strcat( buf, "   " );
13954                 }
13955
13956                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13957                 sprintf( buf+strlen(buf), "%s%.2f",
13958                     pvInfoList[idx].score >= 0 ? "+" : "",
13959                     pvInfoList[idx].score / 100.0 );
13960             }
13961         }
13962     }
13963 }
13964
13965 /* Save game in PGN style */
13966 static void
13967 SaveGamePGN2 (FILE *f)
13968 {
13969     int i, offset, linelen, newblock;
13970 //    char *movetext;
13971     char numtext[32];
13972     int movelen, numlen, blank;
13973     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13974
13975     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13976
13977     PrintPGNTags(f, &gameInfo);
13978
13979     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13980
13981     if (backwardMostMove > 0 || startedFromSetupPosition) {
13982         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13983         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13984         fprintf(f, "\n{--------------\n");
13985         PrintPosition(f, backwardMostMove);
13986         fprintf(f, "--------------}\n");
13987         free(fen);
13988     }
13989     else {
13990         /* [AS] Out of book annotation */
13991         if( appData.saveOutOfBookInfo ) {
13992             char buf[64];
13993
13994             GetOutOfBookInfo( buf );
13995
13996             if( buf[0] != '\0' ) {
13997                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13998             }
13999         }
14000
14001         fprintf(f, "\n");
14002     }
14003
14004     i = backwardMostMove;
14005     linelen = 0;
14006     newblock = TRUE;
14007
14008     while (i < forwardMostMove) {
14009         /* Print comments preceding this move */
14010         if (commentList[i] != NULL) {
14011             if (linelen > 0) fprintf(f, "\n");
14012             fprintf(f, "%s", commentList[i]);
14013             linelen = 0;
14014             newblock = TRUE;
14015         }
14016
14017         /* Format move number */
14018         if ((i % 2) == 0)
14019           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
14020         else
14021           if (newblock)
14022             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
14023           else
14024             numtext[0] = NULLCHAR;
14025
14026         numlen = strlen(numtext);
14027         newblock = FALSE;
14028
14029         /* Print move number */
14030         blank = linelen > 0 && numlen > 0;
14031         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
14032             fprintf(f, "\n");
14033             linelen = 0;
14034             blank = 0;
14035         }
14036         if (blank) {
14037             fprintf(f, " ");
14038             linelen++;
14039         }
14040         fprintf(f, "%s", numtext);
14041         linelen += numlen;
14042
14043         /* Get move */
14044         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
14045         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
14046
14047         /* Print move */
14048         blank = linelen > 0 && movelen > 0;
14049         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14050             fprintf(f, "\n");
14051             linelen = 0;
14052             blank = 0;
14053         }
14054         if (blank) {
14055             fprintf(f, " ");
14056             linelen++;
14057         }
14058         fprintf(f, "%s", move_buffer);
14059         linelen += movelen;
14060
14061         /* [AS] Add PV info if present */
14062         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
14063             /* [HGM] add time */
14064             char buf[MSG_SIZ]; int seconds;
14065
14066             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
14067
14068             if( seconds <= 0)
14069               buf[0] = 0;
14070             else
14071               if( seconds < 30 )
14072                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
14073               else
14074                 {
14075                   seconds = (seconds + 4)/10; // round to full seconds
14076                   if( seconds < 60 )
14077                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14078                   else
14079                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14080                 }
14081
14082             if(appData.cumulativeTimePGN) {
14083                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14084             }
14085
14086             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14087                       pvInfoList[i].score >= 0 ? "+" : "",
14088                       pvInfoList[i].score / 100.0,
14089                       pvInfoList[i].depth,
14090                       buf );
14091
14092             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14093
14094             /* Print score/depth */
14095             blank = linelen > 0 && movelen > 0;
14096             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14097                 fprintf(f, "\n");
14098                 linelen = 0;
14099                 blank = 0;
14100             }
14101             if (blank) {
14102                 fprintf(f, " ");
14103                 linelen++;
14104             }
14105             fprintf(f, "%s", move_buffer);
14106             linelen += movelen;
14107         }
14108
14109         i++;
14110     }
14111
14112     /* Start a new line */
14113     if (linelen > 0) fprintf(f, "\n");
14114
14115     /* Print comments after last move */
14116     if (commentList[i] != NULL) {
14117         fprintf(f, "%s\n", commentList[i]);
14118     }
14119
14120     /* Print result */
14121     if (gameInfo.resultDetails != NULL &&
14122         gameInfo.resultDetails[0] != NULLCHAR) {
14123         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14124         if(gameInfo.result == GameUnfinished && appData.clockMode &&
14125            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14126             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14127         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14128     } else {
14129         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14130     }
14131 }
14132
14133 /* Save game in PGN style and close the file */
14134 int
14135 SaveGamePGN (FILE *f)
14136 {
14137     SaveGamePGN2(f);
14138     fclose(f);
14139     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14140     return TRUE;
14141 }
14142
14143 /* Save game in old style and close the file */
14144 int
14145 SaveGameOldStyle (FILE *f)
14146 {
14147     int i, offset;
14148     time_t tm;
14149
14150     tm = time((time_t *) NULL);
14151
14152     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14153     PrintOpponents(f);
14154
14155     if (backwardMostMove > 0 || startedFromSetupPosition) {
14156         fprintf(f, "\n[--------------\n");
14157         PrintPosition(f, backwardMostMove);
14158         fprintf(f, "--------------]\n");
14159     } else {
14160         fprintf(f, "\n");
14161     }
14162
14163     i = backwardMostMove;
14164     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14165
14166     while (i < forwardMostMove) {
14167         if (commentList[i] != NULL) {
14168             fprintf(f, "[%s]\n", commentList[i]);
14169         }
14170
14171         if ((i % 2) == 1) {
14172             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14173             i++;
14174         } else {
14175             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14176             i++;
14177             if (commentList[i] != NULL) {
14178                 fprintf(f, "\n");
14179                 continue;
14180             }
14181             if (i >= forwardMostMove) {
14182                 fprintf(f, "\n");
14183                 break;
14184             }
14185             fprintf(f, "%s\n", parseList[i]);
14186             i++;
14187         }
14188     }
14189
14190     if (commentList[i] != NULL) {
14191         fprintf(f, "[%s]\n", commentList[i]);
14192     }
14193
14194     /* This isn't really the old style, but it's close enough */
14195     if (gameInfo.resultDetails != NULL &&
14196         gameInfo.resultDetails[0] != NULLCHAR) {
14197         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14198                 gameInfo.resultDetails);
14199     } else {
14200         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14201     }
14202
14203     fclose(f);
14204     return TRUE;
14205 }
14206
14207 /* Save the current game to open file f and close the file */
14208 int
14209 SaveGame (FILE *f, int dummy, char *dummy2)
14210 {
14211     if (gameMode == EditPosition) EditPositionDone(TRUE);
14212     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14213     if (appData.oldSaveStyle)
14214       return SaveGameOldStyle(f);
14215     else
14216       return SaveGamePGN(f);
14217 }
14218
14219 /* Save the current position to the given file */
14220 int
14221 SavePositionToFile (char *filename)
14222 {
14223     FILE *f;
14224     char buf[MSG_SIZ];
14225
14226     if (strcmp(filename, "-") == 0) {
14227         return SavePosition(stdout, 0, NULL);
14228     } else {
14229         f = fopen(filename, "a");
14230         if (f == NULL) {
14231             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14232             DisplayError(buf, errno);
14233             return FALSE;
14234         } else {
14235             safeStrCpy(buf, lastMsg, MSG_SIZ);
14236             DisplayMessage(_("Waiting for access to save file"), "");
14237             flock(fileno(f), LOCK_EX); // [HGM] lock
14238             DisplayMessage(_("Saving position"), "");
14239             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14240             SavePosition(f, 0, NULL);
14241             DisplayMessage(buf, "");
14242             return TRUE;
14243         }
14244     }
14245 }
14246
14247 /* Save the current position to the given open file and close the file */
14248 int
14249 SavePosition (FILE *f, int dummy, char *dummy2)
14250 {
14251     time_t tm;
14252     char *fen;
14253
14254     if (gameMode == EditPosition) EditPositionDone(TRUE);
14255     if (appData.oldSaveStyle) {
14256         tm = time((time_t *) NULL);
14257
14258         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14259         PrintOpponents(f);
14260         fprintf(f, "[--------------\n");
14261         PrintPosition(f, currentMove);
14262         fprintf(f, "--------------]\n");
14263     } else {
14264         fen = PositionToFEN(currentMove, NULL, 1);
14265         fprintf(f, "%s\n", fen);
14266         free(fen);
14267     }
14268     fclose(f);
14269     return TRUE;
14270 }
14271
14272 void
14273 ReloadCmailMsgEvent (int unregister)
14274 {
14275 #if !WIN32
14276     static char *inFilename = NULL;
14277     static char *outFilename;
14278     int i;
14279     struct stat inbuf, outbuf;
14280     int status;
14281
14282     /* Any registered moves are unregistered if unregister is set, */
14283     /* i.e. invoked by the signal handler */
14284     if (unregister) {
14285         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14286             cmailMoveRegistered[i] = FALSE;
14287             if (cmailCommentList[i] != NULL) {
14288                 free(cmailCommentList[i]);
14289                 cmailCommentList[i] = NULL;
14290             }
14291         }
14292         nCmailMovesRegistered = 0;
14293     }
14294
14295     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14296         cmailResult[i] = CMAIL_NOT_RESULT;
14297     }
14298     nCmailResults = 0;
14299
14300     if (inFilename == NULL) {
14301         /* Because the filenames are static they only get malloced once  */
14302         /* and they never get freed                                      */
14303         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14304         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14305
14306         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14307         sprintf(outFilename, "%s.out", appData.cmailGameName);
14308     }
14309
14310     status = stat(outFilename, &outbuf);
14311     if (status < 0) {
14312         cmailMailedMove = FALSE;
14313     } else {
14314         status = stat(inFilename, &inbuf);
14315         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14316     }
14317
14318     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14319        counts the games, notes how each one terminated, etc.
14320
14321        It would be nice to remove this kludge and instead gather all
14322        the information while building the game list.  (And to keep it
14323        in the game list nodes instead of having a bunch of fixed-size
14324        parallel arrays.)  Note this will require getting each game's
14325        termination from the PGN tags, as the game list builder does
14326        not process the game moves.  --mann
14327        */
14328     cmailMsgLoaded = TRUE;
14329     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14330
14331     /* Load first game in the file or popup game menu */
14332     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14333
14334 #endif /* !WIN32 */
14335     return;
14336 }
14337
14338 int
14339 RegisterMove ()
14340 {
14341     FILE *f;
14342     char string[MSG_SIZ];
14343
14344     if (   cmailMailedMove
14345         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14346         return TRUE;            /* Allow free viewing  */
14347     }
14348
14349     /* Unregister move to ensure that we don't leave RegisterMove        */
14350     /* with the move registered when the conditions for registering no   */
14351     /* longer hold                                                       */
14352     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14353         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14354         nCmailMovesRegistered --;
14355
14356         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14357           {
14358               free(cmailCommentList[lastLoadGameNumber - 1]);
14359               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14360           }
14361     }
14362
14363     if (cmailOldMove == -1) {
14364         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14365         return FALSE;
14366     }
14367
14368     if (currentMove > cmailOldMove + 1) {
14369         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14370         return FALSE;
14371     }
14372
14373     if (currentMove < cmailOldMove) {
14374         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14375         return FALSE;
14376     }
14377
14378     if (forwardMostMove > currentMove) {
14379         /* Silently truncate extra moves */
14380         TruncateGame();
14381     }
14382
14383     if (   (currentMove == cmailOldMove + 1)
14384         || (   (currentMove == cmailOldMove)
14385             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14386                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14387         if (gameInfo.result != GameUnfinished) {
14388             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14389         }
14390
14391         if (commentList[currentMove] != NULL) {
14392             cmailCommentList[lastLoadGameNumber - 1]
14393               = StrSave(commentList[currentMove]);
14394         }
14395         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14396
14397         if (appData.debugMode)
14398           fprintf(debugFP, "Saving %s for game %d\n",
14399                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14400
14401         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14402
14403         f = fopen(string, "w");
14404         if (appData.oldSaveStyle) {
14405             SaveGameOldStyle(f); /* also closes the file */
14406
14407             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14408             f = fopen(string, "w");
14409             SavePosition(f, 0, NULL); /* also closes the file */
14410         } else {
14411             fprintf(f, "{--------------\n");
14412             PrintPosition(f, currentMove);
14413             fprintf(f, "--------------}\n\n");
14414
14415             SaveGame(f, 0, NULL); /* also closes the file*/
14416         }
14417
14418         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14419         nCmailMovesRegistered ++;
14420     } else if (nCmailGames == 1) {
14421         DisplayError(_("You have not made a move yet"), 0);
14422         return FALSE;
14423     }
14424
14425     return TRUE;
14426 }
14427
14428 void
14429 MailMoveEvent ()
14430 {
14431 #if !WIN32
14432     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14433     FILE *commandOutput;
14434     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14435     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14436     int nBuffers;
14437     int i;
14438     int archived;
14439     char *arcDir;
14440
14441     if (! cmailMsgLoaded) {
14442         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14443         return;
14444     }
14445
14446     if (nCmailGames == nCmailResults) {
14447         DisplayError(_("No unfinished games"), 0);
14448         return;
14449     }
14450
14451 #if CMAIL_PROHIBIT_REMAIL
14452     if (cmailMailedMove) {
14453       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);
14454         DisplayError(msg, 0);
14455         return;
14456     }
14457 #endif
14458
14459     if (! (cmailMailedMove || RegisterMove())) return;
14460
14461     if (   cmailMailedMove
14462         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14463       snprintf(string, MSG_SIZ, partCommandString,
14464                appData.debugMode ? " -v" : "", appData.cmailGameName);
14465         commandOutput = popen(string, "r");
14466
14467         if (commandOutput == NULL) {
14468             DisplayError(_("Failed to invoke cmail"), 0);
14469         } else {
14470             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14471                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14472             }
14473             if (nBuffers > 1) {
14474                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14475                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14476                 nBytes = MSG_SIZ - 1;
14477             } else {
14478                 (void) memcpy(msg, buffer, nBytes);
14479             }
14480             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14481
14482             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14483                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14484
14485                 archived = TRUE;
14486                 for (i = 0; i < nCmailGames; i ++) {
14487                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14488                         archived = FALSE;
14489                     }
14490                 }
14491                 if (   archived
14492                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14493                         != NULL)) {
14494                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14495                            arcDir,
14496                            appData.cmailGameName,
14497                            gameInfo.date);
14498                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14499                     cmailMsgLoaded = FALSE;
14500                 }
14501             }
14502
14503             DisplayInformation(msg);
14504             pclose(commandOutput);
14505         }
14506     } else {
14507         if ((*cmailMsg) != '\0') {
14508             DisplayInformation(cmailMsg);
14509         }
14510     }
14511
14512     return;
14513 #endif /* !WIN32 */
14514 }
14515
14516 char *
14517 CmailMsg ()
14518 {
14519 #if WIN32
14520     return NULL;
14521 #else
14522     int  prependComma = 0;
14523     char number[5];
14524     char string[MSG_SIZ];       /* Space for game-list */
14525     int  i;
14526
14527     if (!cmailMsgLoaded) return "";
14528
14529     if (cmailMailedMove) {
14530       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14531     } else {
14532         /* Create a list of games left */
14533       snprintf(string, MSG_SIZ, "[");
14534         for (i = 0; i < nCmailGames; i ++) {
14535             if (! (   cmailMoveRegistered[i]
14536                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14537                 if (prependComma) {
14538                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14539                 } else {
14540                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14541                     prependComma = 1;
14542                 }
14543
14544                 strcat(string, number);
14545             }
14546         }
14547         strcat(string, "]");
14548
14549         if (nCmailMovesRegistered + nCmailResults == 0) {
14550             switch (nCmailGames) {
14551               case 1:
14552                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14553                 break;
14554
14555               case 2:
14556                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14557                 break;
14558
14559               default:
14560                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14561                          nCmailGames);
14562                 break;
14563             }
14564         } else {
14565             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14566               case 1:
14567                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14568                          string);
14569                 break;
14570
14571               case 0:
14572                 if (nCmailResults == nCmailGames) {
14573                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14574                 } else {
14575                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14576                 }
14577                 break;
14578
14579               default:
14580                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14581                          string);
14582             }
14583         }
14584     }
14585     return cmailMsg;
14586 #endif /* WIN32 */
14587 }
14588
14589 void
14590 ResetGameEvent ()
14591 {
14592     if (gameMode == Training)
14593       SetTrainingModeOff();
14594
14595     Reset(TRUE, TRUE);
14596     cmailMsgLoaded = FALSE;
14597     if (appData.icsActive) {
14598       SendToICS(ics_prefix);
14599       SendToICS("refresh\n");
14600     }
14601 }
14602
14603 void
14604 ExitEvent (int status)
14605 {
14606     exiting++;
14607     if (exiting > 2) {
14608       /* Give up on clean exit */
14609       exit(status);
14610     }
14611     if (exiting > 1) {
14612       /* Keep trying for clean exit */
14613       return;
14614     }
14615
14616     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14617     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14618
14619     if (telnetISR != NULL) {
14620       RemoveInputSource(telnetISR);
14621     }
14622     if (icsPR != NoProc) {
14623       DestroyChildProcess(icsPR, TRUE);
14624     }
14625
14626     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14627     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14628
14629     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14630     /* make sure this other one finishes before killing it!                  */
14631     if(endingGame) { int count = 0;
14632         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14633         while(endingGame && count++ < 10) DoSleep(1);
14634         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14635     }
14636
14637     /* Kill off chess programs */
14638     if (first.pr != NoProc) {
14639         ExitAnalyzeMode();
14640
14641         DoSleep( appData.delayBeforeQuit );
14642         SendToProgram("quit\n", &first);
14643         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14644     }
14645     if (second.pr != NoProc) {
14646         DoSleep( appData.delayBeforeQuit );
14647         SendToProgram("quit\n", &second);
14648         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14649     }
14650     if (first.isr != NULL) {
14651         RemoveInputSource(first.isr);
14652     }
14653     if (second.isr != NULL) {
14654         RemoveInputSource(second.isr);
14655     }
14656
14657     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14658     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14659
14660     ShutDownFrontEnd();
14661     exit(status);
14662 }
14663
14664 void
14665 PauseEngine (ChessProgramState *cps)
14666 {
14667     SendToProgram("pause\n", cps);
14668     cps->pause = 2;
14669 }
14670
14671 void
14672 UnPauseEngine (ChessProgramState *cps)
14673 {
14674     SendToProgram("resume\n", cps);
14675     cps->pause = 1;
14676 }
14677
14678 void
14679 PauseEvent ()
14680 {
14681     if (appData.debugMode)
14682         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14683     if (pausing) {
14684         pausing = FALSE;
14685         ModeHighlight();
14686         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14687             StartClocks();
14688             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14689                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14690                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14691             }
14692             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14693             HandleMachineMove(stashedInputMove, stalledEngine);
14694             stalledEngine = NULL;
14695             return;
14696         }
14697         if (gameMode == MachinePlaysWhite ||
14698             gameMode == TwoMachinesPlay   ||
14699             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14700             if(first.pause)  UnPauseEngine(&first);
14701             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14702             if(second.pause) UnPauseEngine(&second);
14703             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14704             StartClocks();
14705         } else {
14706             DisplayBothClocks();
14707         }
14708         if (gameMode == PlayFromGameFile) {
14709             if (appData.timeDelay >= 0)
14710                 AutoPlayGameLoop();
14711         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14712             Reset(FALSE, TRUE);
14713             SendToICS(ics_prefix);
14714             SendToICS("refresh\n");
14715         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14716             ForwardInner(forwardMostMove);
14717         }
14718         pauseExamInvalid = FALSE;
14719     } else {
14720         switch (gameMode) {
14721           default:
14722             return;
14723           case IcsExamining:
14724             pauseExamForwardMostMove = forwardMostMove;
14725             pauseExamInvalid = FALSE;
14726             /* fall through */
14727           case IcsObserving:
14728           case IcsPlayingWhite:
14729           case IcsPlayingBlack:
14730             pausing = TRUE;
14731             ModeHighlight();
14732             return;
14733           case PlayFromGameFile:
14734             (void) StopLoadGameTimer();
14735             pausing = TRUE;
14736             ModeHighlight();
14737             break;
14738           case BeginningOfGame:
14739             if (appData.icsActive) return;
14740             /* else fall through */
14741           case MachinePlaysWhite:
14742           case MachinePlaysBlack:
14743           case TwoMachinesPlay:
14744             if (forwardMostMove == 0)
14745               return;           /* don't pause if no one has moved */
14746             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14747                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14748                 if(onMove->pause) {           // thinking engine can be paused
14749                     PauseEngine(onMove);      // do it
14750                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14751                         PauseEngine(onMove->other);
14752                     else
14753                         SendToProgram("easy\n", onMove->other);
14754                     StopClocks();
14755                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14756             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14757                 if(first.pause) {
14758                     PauseEngine(&first);
14759                     StopClocks();
14760                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14761             } else { // human on move, pause pondering by either method
14762                 if(first.pause)
14763                     PauseEngine(&first);
14764                 else if(appData.ponderNextMove)
14765                     SendToProgram("easy\n", &first);
14766                 StopClocks();
14767             }
14768             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14769           case AnalyzeMode:
14770             pausing = TRUE;
14771             ModeHighlight();
14772             break;
14773         }
14774     }
14775 }
14776
14777 void
14778 EditCommentEvent ()
14779 {
14780     char title[MSG_SIZ];
14781
14782     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14783       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14784     } else {
14785       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14786                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14787                parseList[currentMove - 1]);
14788     }
14789
14790     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14791 }
14792
14793
14794 void
14795 EditTagsEvent ()
14796 {
14797     char *tags = PGNTags(&gameInfo);
14798     bookUp = FALSE;
14799     EditTagsPopUp(tags, NULL);
14800     free(tags);
14801 }
14802
14803 void
14804 StartSecond ()
14805 {
14806     if(WaitForEngine(&second, StartSecond)) return;
14807     InitChessProgram(&second, FALSE);
14808     FeedMovesToProgram(&second, currentMove);
14809
14810     SendToProgram("analyze\n", &second);
14811     second.analyzing = TRUE;
14812     ThawUI();
14813 }
14814
14815 void
14816 ToggleSecond ()
14817 {
14818   if(second.analyzing) {
14819     SendToProgram("exit\n", &second);
14820     second.analyzing = FALSE;
14821   } else {
14822     StartSecond();
14823   }
14824 }
14825
14826 /* Toggle ShowThinking */
14827 void
14828 ToggleShowThinking()
14829 {
14830   appData.showThinking = !appData.showThinking;
14831   ShowThinkingEvent();
14832 }
14833
14834 int
14835 AnalyzeModeEvent ()
14836 {
14837     char buf[MSG_SIZ];
14838
14839     if (!first.analysisSupport) {
14840       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14841       DisplayError(buf, 0);
14842       return 0;
14843     }
14844     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14845     if (appData.icsActive) {
14846         if (gameMode != IcsObserving) {
14847           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14848             DisplayError(buf, 0);
14849             /* secure check */
14850             if (appData.icsEngineAnalyze) {
14851                 if (appData.debugMode)
14852                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14853                 ExitAnalyzeMode();
14854                 ModeHighlight();
14855             }
14856             return 0;
14857         }
14858         /* if enable, user wants to disable icsEngineAnalyze */
14859         if (appData.icsEngineAnalyze) {
14860                 ExitAnalyzeMode();
14861                 ModeHighlight();
14862                 return 0;
14863         }
14864         appData.icsEngineAnalyze = TRUE;
14865         if (appData.debugMode)
14866             fprintf(debugFP, "ICS engine analyze starting... \n");
14867     }
14868
14869     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14870     if (appData.noChessProgram || gameMode == AnalyzeMode)
14871       return 0;
14872
14873     if (gameMode != AnalyzeFile) {
14874         if (!appData.icsEngineAnalyze) {
14875                EditGameEvent();
14876                if (gameMode != EditGame) return 0;
14877         }
14878         if (!appData.showThinking) ToggleShowThinking();
14879         ResurrectChessProgram();
14880         SendToProgram("analyze\n", &first);
14881         first.analyzing = TRUE;
14882         /*first.maybeThinking = TRUE;*/
14883         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14884         EngineOutputPopUp();
14885     }
14886     if (!appData.icsEngineAnalyze) {
14887         gameMode = AnalyzeMode;
14888         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14889     }
14890     pausing = FALSE;
14891     ModeHighlight();
14892     SetGameInfo();
14893
14894     StartAnalysisClock();
14895     GetTimeMark(&lastNodeCountTime);
14896     lastNodeCount = 0;
14897     return 1;
14898 }
14899
14900 void
14901 AnalyzeFileEvent ()
14902 {
14903     if (appData.noChessProgram || gameMode == AnalyzeFile)
14904       return;
14905
14906     if (!first.analysisSupport) {
14907       char buf[MSG_SIZ];
14908       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14909       DisplayError(buf, 0);
14910       return;
14911     }
14912
14913     if (gameMode != AnalyzeMode) {
14914         keepInfo = 1; // mere annotating should not alter PGN tags
14915         EditGameEvent();
14916         keepInfo = 0;
14917         if (gameMode != EditGame) return;
14918         if (!appData.showThinking) ToggleShowThinking();
14919         ResurrectChessProgram();
14920         SendToProgram("analyze\n", &first);
14921         first.analyzing = TRUE;
14922         /*first.maybeThinking = TRUE;*/
14923         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14924         EngineOutputPopUp();
14925     }
14926     gameMode = AnalyzeFile;
14927     pausing = FALSE;
14928     ModeHighlight();
14929
14930     StartAnalysisClock();
14931     GetTimeMark(&lastNodeCountTime);
14932     lastNodeCount = 0;
14933     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14934     AnalysisPeriodicEvent(1);
14935 }
14936
14937 void
14938 MachineWhiteEvent ()
14939 {
14940     char buf[MSG_SIZ];
14941     char *bookHit = NULL;
14942
14943     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14944       return;
14945
14946
14947     if (gameMode == PlayFromGameFile ||
14948         gameMode == TwoMachinesPlay  ||
14949         gameMode == Training         ||
14950         gameMode == AnalyzeMode      ||
14951         gameMode == EndOfGame)
14952         EditGameEvent();
14953
14954     if (gameMode == EditPosition)
14955         EditPositionDone(TRUE);
14956
14957     if (!WhiteOnMove(currentMove)) {
14958         DisplayError(_("It is not White's turn"), 0);
14959         return;
14960     }
14961
14962     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14963       ExitAnalyzeMode();
14964
14965     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14966         gameMode == AnalyzeFile)
14967         TruncateGame();
14968
14969     ResurrectChessProgram();    /* in case it isn't running */
14970     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14971         gameMode = MachinePlaysWhite;
14972         ResetClocks();
14973     } else
14974     gameMode = MachinePlaysWhite;
14975     pausing = FALSE;
14976     ModeHighlight();
14977     SetGameInfo();
14978     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14979     DisplayTitle(buf);
14980     if (first.sendName) {
14981       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14982       SendToProgram(buf, &first);
14983     }
14984     if (first.sendTime) {
14985       if (first.useColors) {
14986         SendToProgram("black\n", &first); /*gnu kludge*/
14987       }
14988       SendTimeRemaining(&first, TRUE);
14989     }
14990     if (first.useColors) {
14991       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14992     }
14993     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14994     SetMachineThinkingEnables();
14995     first.maybeThinking = TRUE;
14996     StartClocks();
14997     firstMove = FALSE;
14998
14999     if (appData.autoFlipView && !flipView) {
15000       flipView = !flipView;
15001       DrawPosition(FALSE, NULL);
15002       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
15003     }
15004
15005     if(bookHit) { // [HGM] book: simulate book reply
15006         static char bookMove[MSG_SIZ]; // a bit generous?
15007
15008         programStats.nodes = programStats.depth = programStats.time =
15009         programStats.score = programStats.got_only_move = 0;
15010         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15011
15012         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15013         strcat(bookMove, bookHit);
15014         savedMessage = bookMove; // args for deferred call
15015         savedState = &first;
15016         ScheduleDelayedEvent(DeferredBookMove, 1);
15017     }
15018 }
15019
15020 void
15021 MachineBlackEvent ()
15022 {
15023   char buf[MSG_SIZ];
15024   char *bookHit = NULL;
15025
15026     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
15027         return;
15028
15029
15030     if (gameMode == PlayFromGameFile ||
15031         gameMode == TwoMachinesPlay  ||
15032         gameMode == Training         ||
15033         gameMode == AnalyzeMode      ||
15034         gameMode == EndOfGame)
15035         EditGameEvent();
15036
15037     if (gameMode == EditPosition)
15038         EditPositionDone(TRUE);
15039
15040     if (WhiteOnMove(currentMove)) {
15041         DisplayError(_("It is not Black's turn"), 0);
15042         return;
15043     }
15044
15045     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
15046       ExitAnalyzeMode();
15047
15048     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15049         gameMode == AnalyzeFile)
15050         TruncateGame();
15051
15052     ResurrectChessProgram();    /* in case it isn't running */
15053     gameMode = MachinePlaysBlack;
15054     pausing = FALSE;
15055     ModeHighlight();
15056     SetGameInfo();
15057     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15058     DisplayTitle(buf);
15059     if (first.sendName) {
15060       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
15061       SendToProgram(buf, &first);
15062     }
15063     if (first.sendTime) {
15064       if (first.useColors) {
15065         SendToProgram("white\n", &first); /*gnu kludge*/
15066       }
15067       SendTimeRemaining(&first, FALSE);
15068     }
15069     if (first.useColors) {
15070       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
15071     }
15072     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15073     SetMachineThinkingEnables();
15074     first.maybeThinking = TRUE;
15075     StartClocks();
15076
15077     if (appData.autoFlipView && flipView) {
15078       flipView = !flipView;
15079       DrawPosition(FALSE, NULL);
15080       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
15081     }
15082     if(bookHit) { // [HGM] book: simulate book reply
15083         static char bookMove[MSG_SIZ]; // a bit generous?
15084
15085         programStats.nodes = programStats.depth = programStats.time =
15086         programStats.score = programStats.got_only_move = 0;
15087         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15088
15089         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15090         strcat(bookMove, bookHit);
15091         savedMessage = bookMove; // args for deferred call
15092         savedState = &first;
15093         ScheduleDelayedEvent(DeferredBookMove, 1);
15094     }
15095 }
15096
15097
15098 void
15099 DisplayTwoMachinesTitle ()
15100 {
15101     char buf[MSG_SIZ];
15102     if (appData.matchGames > 0) {
15103         if(appData.tourneyFile[0]) {
15104           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15105                    gameInfo.white, _("vs."), gameInfo.black,
15106                    nextGame+1, appData.matchGames+1,
15107                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15108         } else
15109         if (first.twoMachinesColor[0] == 'w') {
15110           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15111                    gameInfo.white, _("vs."),  gameInfo.black,
15112                    first.matchWins, second.matchWins,
15113                    matchGame - 1 - (first.matchWins + second.matchWins));
15114         } else {
15115           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15116                    gameInfo.white, _("vs."), gameInfo.black,
15117                    second.matchWins, first.matchWins,
15118                    matchGame - 1 - (first.matchWins + second.matchWins));
15119         }
15120     } else {
15121       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15122     }
15123     DisplayTitle(buf);
15124 }
15125
15126 void
15127 SettingsMenuIfReady ()
15128 {
15129   if (second.lastPing != second.lastPong) {
15130     DisplayMessage("", _("Waiting for second chess program"));
15131     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15132     return;
15133   }
15134   ThawUI();
15135   DisplayMessage("", "");
15136   SettingsPopUp(&second);
15137 }
15138
15139 int
15140 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15141 {
15142     char buf[MSG_SIZ];
15143     if (cps->pr == NoProc) {
15144         StartChessProgram(cps);
15145         if (cps->protocolVersion == 1) {
15146           retry();
15147           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15148         } else {
15149           /* kludge: allow timeout for initial "feature" command */
15150           if(retry != TwoMachinesEventIfReady) FreezeUI();
15151           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15152           DisplayMessage("", buf);
15153           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15154         }
15155         return 1;
15156     }
15157     return 0;
15158 }
15159
15160 void
15161 TwoMachinesEvent P((void))
15162 {
15163     int i, move = forwardMostMove;
15164     char buf[MSG_SIZ];
15165     ChessProgramState *onmove;
15166     char *bookHit = NULL;
15167     static int stalling = 0;
15168     TimeMark now;
15169     long wait;
15170
15171     if (appData.noChessProgram) return;
15172
15173     switch (gameMode) {
15174       case TwoMachinesPlay:
15175         return;
15176       case MachinePlaysWhite:
15177       case MachinePlaysBlack:
15178         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15179             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15180             return;
15181         }
15182         /* fall through */
15183       case BeginningOfGame:
15184       case PlayFromGameFile:
15185       case EndOfGame:
15186         EditGameEvent();
15187         if (gameMode != EditGame) return;
15188         break;
15189       case EditPosition:
15190         EditPositionDone(TRUE);
15191         break;
15192       case AnalyzeMode:
15193       case AnalyzeFile:
15194         ExitAnalyzeMode();
15195         break;
15196       case EditGame:
15197       default:
15198         break;
15199     }
15200
15201 //    forwardMostMove = currentMove;
15202     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15203     startingEngine = TRUE;
15204
15205     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15206
15207     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15208     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15209       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15210       return;
15211     }
15212   if(!appData.epd) {
15213     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15214
15215     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15216                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15217         startingEngine = matchMode = FALSE;
15218         DisplayError("second engine does not play this", 0);
15219         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15220         EditGameEvent(); // switch back to EditGame mode
15221         return;
15222     }
15223
15224     if(!stalling) {
15225       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15226       SendToProgram("force\n", &second);
15227       stalling = 1;
15228       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15229       return;
15230     }
15231   }
15232     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15233     if(appData.matchPause>10000 || appData.matchPause<10)
15234                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15235     wait = SubtractTimeMarks(&now, &pauseStart);
15236     if(wait < appData.matchPause) {
15237         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15238         return;
15239     }
15240     // we are now committed to starting the game
15241     stalling = 0;
15242     DisplayMessage("", "");
15243   if(!appData.epd) {
15244     if (startedFromSetupPosition) {
15245         SendBoard(&second, backwardMostMove);
15246     if (appData.debugMode) {
15247         fprintf(debugFP, "Two Machines\n");
15248     }
15249     }
15250     for (i = backwardMostMove; i < forwardMostMove; i++) {
15251         SendMoveToProgram(i, &second);
15252     }
15253   }
15254
15255     gameMode = TwoMachinesPlay;
15256     pausing = startingEngine = FALSE;
15257     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15258     SetGameInfo();
15259     DisplayTwoMachinesTitle();
15260     firstMove = TRUE;
15261     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15262         onmove = &first;
15263     } else {
15264         onmove = &second;
15265     }
15266     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15267     SendToProgram(first.computerString, &first);
15268     if (first.sendName) {
15269       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15270       SendToProgram(buf, &first);
15271     }
15272   if(!appData.epd) {
15273     SendToProgram(second.computerString, &second);
15274     if (second.sendName) {
15275       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15276       SendToProgram(buf, &second);
15277     }
15278   }
15279
15280     if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15281         ResetClocks();
15282         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15283         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15284     }
15285     if (onmove->sendTime) {
15286       if (onmove->useColors) {
15287         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15288       }
15289       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15290     }
15291     if (onmove->useColors) {
15292       SendToProgram(onmove->twoMachinesColor, onmove);
15293     }
15294     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15295 //    SendToProgram("go\n", onmove);
15296     onmove->maybeThinking = TRUE;
15297     SetMachineThinkingEnables();
15298
15299     StartClocks();
15300
15301     if(bookHit) { // [HGM] book: simulate book reply
15302         static char bookMove[MSG_SIZ]; // a bit generous?
15303
15304         programStats.nodes = programStats.depth = programStats.time =
15305         programStats.score = programStats.got_only_move = 0;
15306         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15307
15308         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15309         strcat(bookMove, bookHit);
15310         savedMessage = bookMove; // args for deferred call
15311         savedState = onmove;
15312         ScheduleDelayedEvent(DeferredBookMove, 1);
15313     }
15314 }
15315
15316 void
15317 TrainingEvent ()
15318 {
15319     if (gameMode == Training) {
15320       SetTrainingModeOff();
15321       gameMode = PlayFromGameFile;
15322       DisplayMessage("", _("Training mode off"));
15323     } else {
15324       gameMode = Training;
15325       animateTraining = appData.animate;
15326
15327       /* make sure we are not already at the end of the game */
15328       if (currentMove < forwardMostMove) {
15329         SetTrainingModeOn();
15330         DisplayMessage("", _("Training mode on"));
15331       } else {
15332         gameMode = PlayFromGameFile;
15333         DisplayError(_("Already at end of game"), 0);
15334       }
15335     }
15336     ModeHighlight();
15337 }
15338
15339 void
15340 IcsClientEvent ()
15341 {
15342     if (!appData.icsActive) return;
15343     switch (gameMode) {
15344       case IcsPlayingWhite:
15345       case IcsPlayingBlack:
15346       case IcsObserving:
15347       case IcsIdle:
15348       case BeginningOfGame:
15349       case IcsExamining:
15350         return;
15351
15352       case EditGame:
15353         break;
15354
15355       case EditPosition:
15356         EditPositionDone(TRUE);
15357         break;
15358
15359       case AnalyzeMode:
15360       case AnalyzeFile:
15361         ExitAnalyzeMode();
15362         break;
15363
15364       default:
15365         EditGameEvent();
15366         break;
15367     }
15368
15369     gameMode = IcsIdle;
15370     ModeHighlight();
15371     return;
15372 }
15373
15374 void
15375 EditGameEvent ()
15376 {
15377     int i;
15378
15379     switch (gameMode) {
15380       case Training:
15381         SetTrainingModeOff();
15382         break;
15383       case MachinePlaysWhite:
15384       case MachinePlaysBlack:
15385       case BeginningOfGame:
15386         SendToProgram("force\n", &first);
15387         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15388             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15389                 char buf[MSG_SIZ];
15390                 abortEngineThink = TRUE;
15391                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15392                 SendToProgram(buf, &first);
15393                 DisplayMessage("Aborting engine think", "");
15394                 FreezeUI();
15395             }
15396         }
15397         SetUserThinkingEnables();
15398         break;
15399       case PlayFromGameFile:
15400         (void) StopLoadGameTimer();
15401         if (gameFileFP != NULL) {
15402             gameFileFP = NULL;
15403         }
15404         break;
15405       case EditPosition:
15406         EditPositionDone(TRUE);
15407         break;
15408       case AnalyzeMode:
15409       case AnalyzeFile:
15410         ExitAnalyzeMode();
15411         SendToProgram("force\n", &first);
15412         break;
15413       case TwoMachinesPlay:
15414         GameEnds(EndOfFile, NULL, GE_PLAYER);
15415         ResurrectChessProgram();
15416         SetUserThinkingEnables();
15417         break;
15418       case EndOfGame:
15419         ResurrectChessProgram();
15420         break;
15421       case IcsPlayingBlack:
15422       case IcsPlayingWhite:
15423         DisplayError(_("Warning: You are still playing a game"), 0);
15424         break;
15425       case IcsObserving:
15426         DisplayError(_("Warning: You are still observing a game"), 0);
15427         break;
15428       case IcsExamining:
15429         DisplayError(_("Warning: You are still examining a game"), 0);
15430         break;
15431       case IcsIdle:
15432         break;
15433       case EditGame:
15434       default:
15435         return;
15436     }
15437
15438     pausing = FALSE;
15439     StopClocks();
15440     first.offeredDraw = second.offeredDraw = 0;
15441
15442     if (gameMode == PlayFromGameFile) {
15443         whiteTimeRemaining = timeRemaining[0][currentMove];
15444         blackTimeRemaining = timeRemaining[1][currentMove];
15445         DisplayTitle("");
15446     }
15447
15448     if (gameMode == MachinePlaysWhite ||
15449         gameMode == MachinePlaysBlack ||
15450         gameMode == TwoMachinesPlay ||
15451         gameMode == EndOfGame) {
15452         i = forwardMostMove;
15453         while (i > currentMove) {
15454             SendToProgram("undo\n", &first);
15455             i--;
15456         }
15457         if(!adjustedClock) {
15458         whiteTimeRemaining = timeRemaining[0][currentMove];
15459         blackTimeRemaining = timeRemaining[1][currentMove];
15460         DisplayBothClocks();
15461         }
15462         if (whiteFlag || blackFlag) {
15463             whiteFlag = blackFlag = 0;
15464         }
15465         DisplayTitle("");
15466     }
15467
15468     gameMode = EditGame;
15469     ModeHighlight();
15470     SetGameInfo();
15471 }
15472
15473 void
15474 EditPositionEvent ()
15475 {
15476     int i;
15477     if (gameMode == EditPosition) {
15478         EditGameEvent();
15479         return;
15480     }
15481
15482     EditGameEvent();
15483     if (gameMode != EditGame) return;
15484
15485     gameMode = EditPosition;
15486     ModeHighlight();
15487     SetGameInfo();
15488     CopyBoard(rightsBoard, nullBoard);
15489     if (currentMove > 0)
15490       CopyBoard(boards[0], boards[currentMove]);
15491     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15492       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15493
15494     blackPlaysFirst = !WhiteOnMove(currentMove);
15495     ResetClocks();
15496     currentMove = forwardMostMove = backwardMostMove = 0;
15497     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15498     DisplayMove(-1);
15499     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15500 }
15501
15502 void
15503 ExitAnalyzeMode ()
15504 {
15505     /* [DM] icsEngineAnalyze - possible call from other functions */
15506     if (appData.icsEngineAnalyze) {
15507         appData.icsEngineAnalyze = FALSE;
15508
15509         DisplayMessage("",_("Close ICS engine analyze..."));
15510     }
15511     if (first.analysisSupport && first.analyzing) {
15512       SendToBoth("exit\n");
15513       first.analyzing = second.analyzing = FALSE;
15514     }
15515     thinkOutput[0] = NULLCHAR;
15516 }
15517
15518 void
15519 EditPositionDone (Boolean fakeRights)
15520 {
15521     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15522
15523     startedFromSetupPosition = TRUE;
15524     InitChessProgram(&first, FALSE);
15525     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15526       int r, f;
15527       boards[0][EP_STATUS] = EP_NONE;
15528       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15529       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15530         if(rightsBoard[r][f]) {
15531           ChessSquare p = boards[0][r][f];
15532           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15533           else if(p == king) boards[0][CASTLING][2] = f;
15534           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15535           else rightsBoard[r][f] = 2; // mark for second pass
15536         }
15537       }
15538       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15539         if(rightsBoard[r][f] == 2) {
15540           ChessSquare p = boards[0][r][f];
15541           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15542           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15543         }
15544       }
15545     }
15546     SendToProgram("force\n", &first);
15547     if (blackPlaysFirst) {
15548         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15549         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15550         currentMove = forwardMostMove = backwardMostMove = 1;
15551         CopyBoard(boards[1], boards[0]);
15552     } else {
15553         currentMove = forwardMostMove = backwardMostMove = 0;
15554     }
15555     SendBoard(&first, forwardMostMove);
15556     if (appData.debugMode) {
15557         fprintf(debugFP, "EditPosDone\n");
15558     }
15559     DisplayTitle("");
15560     DisplayMessage("", "");
15561     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15562     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15563     gameMode = EditGame;
15564     ModeHighlight();
15565     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15566     ClearHighlights(); /* [AS] */
15567 }
15568
15569 /* Pause for `ms' milliseconds */
15570 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15571 void
15572 TimeDelay (long ms)
15573 {
15574     TimeMark m1, m2;
15575
15576     GetTimeMark(&m1);
15577     do {
15578         GetTimeMark(&m2);
15579     } while (SubtractTimeMarks(&m2, &m1) < ms);
15580 }
15581
15582 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15583 void
15584 SendMultiLineToICS (char *buf)
15585 {
15586     char temp[MSG_SIZ+1], *p;
15587     int len;
15588
15589     len = strlen(buf);
15590     if (len > MSG_SIZ)
15591       len = MSG_SIZ;
15592
15593     strncpy(temp, buf, len);
15594     temp[len] = 0;
15595
15596     p = temp;
15597     while (*p) {
15598         if (*p == '\n' || *p == '\r')
15599           *p = ' ';
15600         ++p;
15601     }
15602
15603     strcat(temp, "\n");
15604     SendToICS(temp);
15605     SendToPlayer(temp, strlen(temp));
15606 }
15607
15608 void
15609 SetWhiteToPlayEvent ()
15610 {
15611     if (gameMode == EditPosition) {
15612         blackPlaysFirst = FALSE;
15613         DisplayBothClocks();    /* works because currentMove is 0 */
15614     } else if (gameMode == IcsExamining) {
15615         SendToICS(ics_prefix);
15616         SendToICS("tomove white\n");
15617     }
15618 }
15619
15620 void
15621 SetBlackToPlayEvent ()
15622 {
15623     if (gameMode == EditPosition) {
15624         blackPlaysFirst = TRUE;
15625         currentMove = 1;        /* kludge */
15626         DisplayBothClocks();
15627         currentMove = 0;
15628     } else if (gameMode == IcsExamining) {
15629         SendToICS(ics_prefix);
15630         SendToICS("tomove black\n");
15631     }
15632 }
15633
15634 void
15635 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15636 {
15637     char buf[MSG_SIZ];
15638     ChessSquare piece = boards[0][y][x];
15639     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15640     static int lastVariant;
15641     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15642
15643     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15644
15645     switch (selection) {
15646       case ClearBoard:
15647         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15648         MarkTargetSquares(1);
15649         CopyBoard(currentBoard, boards[0]);
15650         CopyBoard(menuBoard, initialPosition);
15651         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15652             SendToICS(ics_prefix);
15653             SendToICS("bsetup clear\n");
15654         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15655             SendToICS(ics_prefix);
15656             SendToICS("clearboard\n");
15657         } else {
15658             int nonEmpty = 0;
15659             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15660                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15661                 for (y = 0; y < BOARD_HEIGHT; y++) {
15662                     if (gameMode == IcsExamining) {
15663                         if (boards[currentMove][y][x] != EmptySquare) {
15664                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15665                                     AAA + x, ONE + y);
15666                             SendToICS(buf);
15667                         }
15668                     } else if(boards[0][y][x] != DarkSquare) {
15669                         if(boards[0][y][x] != p) nonEmpty++;
15670                         boards[0][y][x] = p;
15671                     }
15672                 }
15673             }
15674             CopyBoard(rightsBoard, nullBoard);
15675             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15676                 int r, i;
15677                 for(r = 0; r < BOARD_HEIGHT; r++) {
15678                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15679                     ChessSquare p = menuBoard[r][x];
15680                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15681                   }
15682                 }
15683                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15684                 DisplayMessage("Clicking clock again restores position", "");
15685                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15686                 if(!nonEmpty) { // asked to clear an empty board
15687                     CopyBoard(boards[0], menuBoard);
15688                 } else
15689                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15690                     CopyBoard(boards[0], initialPosition);
15691                 } else
15692                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15693                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15694                     CopyBoard(boards[0], erasedBoard);
15695                 } else
15696                     CopyBoard(erasedBoard, currentBoard);
15697
15698                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15699                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15700             }
15701         }
15702         if (gameMode == EditPosition) {
15703             DrawPosition(FALSE, boards[0]);
15704         }
15705         break;
15706
15707       case WhitePlay:
15708         SetWhiteToPlayEvent();
15709         break;
15710
15711       case BlackPlay:
15712         SetBlackToPlayEvent();
15713         break;
15714
15715       case EmptySquare:
15716         if (gameMode == IcsExamining) {
15717             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15718             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15719             SendToICS(buf);
15720         } else {
15721             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15722                 if(x == BOARD_LEFT-2) {
15723                     if(y < handSize-1-gameInfo.holdingsSize) break;
15724                     boards[0][y][1] = 0;
15725                 } else
15726                 if(x == BOARD_RGHT+1) {
15727                     if(y >= gameInfo.holdingsSize) break;
15728                     boards[0][y][BOARD_WIDTH-2] = 0;
15729                 } else break;
15730             }
15731             boards[0][y][x] = EmptySquare;
15732             DrawPosition(FALSE, boards[0]);
15733         }
15734         break;
15735
15736       case PromotePiece:
15737         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15738            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15739             selection = (ChessSquare) (PROMOTED(piece));
15740         } else if(piece == EmptySquare) selection = WhiteSilver;
15741         else selection = (ChessSquare)((int)piece - 1);
15742         goto defaultlabel;
15743
15744       case DemotePiece:
15745         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15746            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15747             selection = (ChessSquare) (DEMOTED(piece));
15748         } else if(piece == EmptySquare) selection = BlackSilver;
15749         else selection = (ChessSquare)((int)piece + 1);
15750         goto defaultlabel;
15751
15752       case WhiteQueen:
15753       case BlackQueen:
15754         if(gameInfo.variant == VariantShatranj ||
15755            gameInfo.variant == VariantXiangqi  ||
15756            gameInfo.variant == VariantCourier  ||
15757            gameInfo.variant == VariantASEAN    ||
15758            gameInfo.variant == VariantMakruk     )
15759             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15760         goto defaultlabel;
15761
15762       case WhiteRook:
15763         baseRank = 0;
15764       case BlackRook:
15765         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15766         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15767         goto defaultlabel;
15768
15769       case WhiteKing:
15770         baseRank = 0;
15771       case BlackKing:
15772         if(gameInfo.variant == VariantXiangqi)
15773             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15774         if(gameInfo.variant == VariantKnightmate)
15775             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15776         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15777       default:
15778         defaultlabel:
15779         if (gameMode == IcsExamining) {
15780             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15781             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15782                      PieceToChar(selection), AAA + x, ONE + y);
15783             SendToICS(buf);
15784         } else {
15785             rightsBoard[y][x] = hasRights;
15786             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15787                 int n;
15788                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15789                     n = PieceToNumber(selection - BlackPawn);
15790                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15791                     boards[0][handSize-1-n][0] = selection;
15792                     boards[0][handSize-1-n][1]++;
15793                 } else
15794                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15795                     n = PieceToNumber(selection);
15796                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15797                     boards[0][n][BOARD_WIDTH-1] = selection;
15798                     boards[0][n][BOARD_WIDTH-2]++;
15799                 }
15800             } else
15801             boards[0][y][x] = selection;
15802             DrawPosition(TRUE, boards[0]);
15803             ClearHighlights();
15804             fromX = fromY = -1;
15805         }
15806         break;
15807     }
15808 }
15809
15810
15811 void
15812 DropMenuEvent (ChessSquare selection, int x, int y)
15813 {
15814     ChessMove moveType;
15815
15816     switch (gameMode) {
15817       case IcsPlayingWhite:
15818       case MachinePlaysBlack:
15819         if (!WhiteOnMove(currentMove)) {
15820             DisplayMoveError(_("It is Black's turn"));
15821             return;
15822         }
15823         moveType = WhiteDrop;
15824         break;
15825       case IcsPlayingBlack:
15826       case MachinePlaysWhite:
15827         if (WhiteOnMove(currentMove)) {
15828             DisplayMoveError(_("It is White's turn"));
15829             return;
15830         }
15831         moveType = BlackDrop;
15832         break;
15833       case EditGame:
15834         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15835         break;
15836       default:
15837         return;
15838     }
15839
15840     if (moveType == BlackDrop && selection < BlackPawn) {
15841       selection = (ChessSquare) ((int) selection
15842                                  + (int) BlackPawn - (int) WhitePawn);
15843     }
15844     if (boards[currentMove][y][x] != EmptySquare) {
15845         DisplayMoveError(_("That square is occupied"));
15846         return;
15847     }
15848
15849     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15850 }
15851
15852 void
15853 AcceptEvent ()
15854 {
15855     /* Accept a pending offer of any kind from opponent */
15856
15857     if (appData.icsActive) {
15858         SendToICS(ics_prefix);
15859         SendToICS("accept\n");
15860     } else if (cmailMsgLoaded) {
15861         if (currentMove == cmailOldMove &&
15862             commentList[cmailOldMove] != NULL &&
15863             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15864                    "Black offers a draw" : "White offers a draw")) {
15865             TruncateGame();
15866             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15867             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15868         } else {
15869             DisplayError(_("There is no pending offer on this move"), 0);
15870             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15871         }
15872     } else {
15873         /* Not used for offers from chess program */
15874     }
15875 }
15876
15877 void
15878 DeclineEvent ()
15879 {
15880     /* Decline a pending offer of any kind from opponent */
15881
15882     if (appData.icsActive) {
15883         SendToICS(ics_prefix);
15884         SendToICS("decline\n");
15885     } else if (cmailMsgLoaded) {
15886         if (currentMove == cmailOldMove &&
15887             commentList[cmailOldMove] != NULL &&
15888             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15889                    "Black offers a draw" : "White offers a draw")) {
15890 #ifdef NOTDEF
15891             AppendComment(cmailOldMove, "Draw declined", TRUE);
15892             DisplayComment(cmailOldMove - 1, "Draw declined");
15893 #endif /*NOTDEF*/
15894         } else {
15895             DisplayError(_("There is no pending offer on this move"), 0);
15896         }
15897     } else {
15898         /* Not used for offers from chess program */
15899     }
15900 }
15901
15902 void
15903 RematchEvent ()
15904 {
15905     /* Issue ICS rematch command */
15906     if (appData.icsActive) {
15907         SendToICS(ics_prefix);
15908         SendToICS("rematch\n");
15909     }
15910 }
15911
15912 void
15913 CallFlagEvent ()
15914 {
15915     /* Call your opponent's flag (claim a win on time) */
15916     if (appData.icsActive) {
15917         SendToICS(ics_prefix);
15918         SendToICS("flag\n");
15919     } else {
15920         switch (gameMode) {
15921           default:
15922             return;
15923           case MachinePlaysWhite:
15924             if (whiteFlag) {
15925                 if (blackFlag)
15926                   GameEnds(GameIsDrawn, "Both players ran out of time",
15927                            GE_PLAYER);
15928                 else
15929                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15930             } else {
15931                 DisplayError(_("Your opponent is not out of time"), 0);
15932             }
15933             break;
15934           case MachinePlaysBlack:
15935             if (blackFlag) {
15936                 if (whiteFlag)
15937                   GameEnds(GameIsDrawn, "Both players ran out of time",
15938                            GE_PLAYER);
15939                 else
15940                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15941             } else {
15942                 DisplayError(_("Your opponent is not out of time"), 0);
15943             }
15944             break;
15945         }
15946     }
15947 }
15948
15949 void
15950 ClockClick (int which)
15951 {       // [HGM] code moved to back-end from winboard.c
15952         if(which) { // black clock
15953           if (gameMode == EditPosition || gameMode == IcsExamining) {
15954             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15955             SetBlackToPlayEvent();
15956           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15957                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15958           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15959           } else if (shiftKey) {
15960             AdjustClock(which, -1);
15961           } else if (gameMode == IcsPlayingWhite ||
15962                      gameMode == MachinePlaysBlack) {
15963             CallFlagEvent();
15964           }
15965         } else { // white clock
15966           if (gameMode == EditPosition || gameMode == IcsExamining) {
15967             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15968             SetWhiteToPlayEvent();
15969           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15970                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15971           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15972           } else if (shiftKey) {
15973             AdjustClock(which, -1);
15974           } else if (gameMode == IcsPlayingBlack ||
15975                    gameMode == MachinePlaysWhite) {
15976             CallFlagEvent();
15977           }
15978         }
15979 }
15980
15981 void
15982 DrawEvent ()
15983 {
15984     /* Offer draw or accept pending draw offer from opponent */
15985
15986     if (appData.icsActive) {
15987         /* Note: tournament rules require draw offers to be
15988            made after you make your move but before you punch
15989            your clock.  Currently ICS doesn't let you do that;
15990            instead, you immediately punch your clock after making
15991            a move, but you can offer a draw at any time. */
15992
15993         SendToICS(ics_prefix);
15994         SendToICS("draw\n");
15995         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15996     } else if (cmailMsgLoaded) {
15997         if (currentMove == cmailOldMove &&
15998             commentList[cmailOldMove] != NULL &&
15999             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
16000                    "Black offers a draw" : "White offers a draw")) {
16001             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
16002             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
16003         } else if (currentMove == cmailOldMove + 1) {
16004             char *offer = WhiteOnMove(cmailOldMove) ?
16005               "White offers a draw" : "Black offers a draw";
16006             AppendComment(currentMove, offer, TRUE);
16007             DisplayComment(currentMove - 1, offer);
16008             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
16009         } else {
16010             DisplayError(_("You must make your move before offering a draw"), 0);
16011             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
16012         }
16013     } else if (first.offeredDraw) {
16014         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
16015     } else {
16016         if (first.sendDrawOffers) {
16017             SendToProgram("draw\n", &first);
16018             userOfferedDraw = TRUE;
16019         }
16020     }
16021 }
16022
16023 void
16024 AdjournEvent ()
16025 {
16026     /* Offer Adjourn or accept pending Adjourn offer from opponent */
16027
16028     if (appData.icsActive) {
16029         SendToICS(ics_prefix);
16030         SendToICS("adjourn\n");
16031     } else {
16032         /* Currently GNU Chess doesn't offer or accept Adjourns */
16033     }
16034 }
16035
16036
16037 void
16038 AbortEvent ()
16039 {
16040     /* Offer Abort or accept pending Abort offer from opponent */
16041
16042     if (appData.icsActive) {
16043         SendToICS(ics_prefix);
16044         SendToICS("abort\n");
16045     } else {
16046         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
16047     }
16048 }
16049
16050 void
16051 ResignEvent ()
16052 {
16053     /* Resign.  You can do this even if it's not your turn. */
16054
16055     if (appData.icsActive) {
16056         SendToICS(ics_prefix);
16057         SendToICS("resign\n");
16058     } else {
16059         switch (gameMode) {
16060           case MachinePlaysWhite:
16061             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16062             break;
16063           case MachinePlaysBlack:
16064             GameEnds(BlackWins, "White resigns", GE_PLAYER);
16065             break;
16066           case EditGame:
16067             if (cmailMsgLoaded) {
16068                 TruncateGame();
16069                 if (WhiteOnMove(cmailOldMove)) {
16070                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
16071                 } else {
16072                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16073                 }
16074                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
16075             }
16076             break;
16077           default:
16078             break;
16079         }
16080     }
16081 }
16082
16083
16084 void
16085 StopObservingEvent ()
16086 {
16087     /* Stop observing current games */
16088     SendToICS(ics_prefix);
16089     SendToICS("unobserve\n");
16090 }
16091
16092 void
16093 StopExaminingEvent ()
16094 {
16095     /* Stop observing current game */
16096     SendToICS(ics_prefix);
16097     SendToICS("unexamine\n");
16098 }
16099
16100 void
16101 ForwardInner (int target)
16102 {
16103     int limit; int oldSeekGraphUp = seekGraphUp;
16104
16105     if (appData.debugMode)
16106         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16107                 target, currentMove, forwardMostMove);
16108
16109     if (gameMode == EditPosition)
16110       return;
16111
16112     seekGraphUp = FALSE;
16113     MarkTargetSquares(1);
16114     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16115
16116     if (gameMode == PlayFromGameFile && !pausing)
16117       PauseEvent();
16118
16119     if (gameMode == IcsExamining && pausing)
16120       limit = pauseExamForwardMostMove;
16121     else
16122       limit = forwardMostMove;
16123
16124     if (target > limit) target = limit;
16125
16126     if (target > 0 && moveList[target - 1][0]) {
16127         int fromX, fromY, toX, toY;
16128         toX = moveList[target - 1][2] - AAA;
16129         toY = moveList[target - 1][3] - ONE;
16130         if (moveList[target - 1][1] == '@') {
16131             if (appData.highlightLastMove) {
16132                 SetHighlights(-1, -1, toX, toY);
16133             }
16134         } else {
16135             fromX = moveList[target - 1][0] - AAA;
16136             fromY = moveList[target - 1][1] - ONE;
16137             if (target == currentMove + 1) {
16138                 if(moveList[target - 1][4] == ';') { // multi-leg
16139                     killX = moveList[target - 1][5] - AAA;
16140                     killY = moveList[target - 1][6] - ONE;
16141                 }
16142                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16143                 killX = killY = -1;
16144             }
16145             if (appData.highlightLastMove) {
16146                 SetHighlights(fromX, fromY, toX, toY);
16147             }
16148         }
16149     }
16150     if (gameMode == EditGame || gameMode == AnalyzeMode ||
16151         gameMode == Training || gameMode == PlayFromGameFile ||
16152         gameMode == AnalyzeFile) {
16153         while (currentMove < target) {
16154             if(second.analyzing) SendMoveToProgram(currentMove, &second);
16155             SendMoveToProgram(currentMove++, &first);
16156         }
16157     } else {
16158         currentMove = target;
16159     }
16160
16161     if (gameMode == EditGame || gameMode == EndOfGame) {
16162         whiteTimeRemaining = timeRemaining[0][currentMove];
16163         blackTimeRemaining = timeRemaining[1][currentMove];
16164     }
16165     DisplayBothClocks();
16166     DisplayMove(currentMove - 1);
16167     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16168     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16169     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16170         DisplayComment(currentMove - 1, commentList[currentMove]);
16171     }
16172     ClearMap(); // [HGM] exclude: invalidate map
16173 }
16174
16175
16176 void
16177 ForwardEvent ()
16178 {
16179     if (gameMode == IcsExamining && !pausing) {
16180         SendToICS(ics_prefix);
16181         SendToICS("forward\n");
16182     } else {
16183         ForwardInner(currentMove + 1);
16184     }
16185 }
16186
16187 void
16188 ToEndEvent ()
16189 {
16190     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16191         /* to optimze, we temporarily turn off analysis mode while we feed
16192          * the remaining moves to the engine. Otherwise we get analysis output
16193          * after each move.
16194          */
16195         if (first.analysisSupport) {
16196           SendToProgram("exit\nforce\n", &first);
16197           first.analyzing = FALSE;
16198         }
16199     }
16200
16201     if (gameMode == IcsExamining && !pausing) {
16202         SendToICS(ics_prefix);
16203         SendToICS("forward 999999\n");
16204     } else {
16205         ForwardInner(forwardMostMove);
16206     }
16207
16208     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16209         /* we have fed all the moves, so reactivate analysis mode */
16210         SendToProgram("analyze\n", &first);
16211         first.analyzing = TRUE;
16212         /*first.maybeThinking = TRUE;*/
16213         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16214     }
16215 }
16216
16217 void
16218 BackwardInner (int target)
16219 {
16220     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16221
16222     if (appData.debugMode)
16223         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16224                 target, currentMove, forwardMostMove);
16225
16226     if (gameMode == EditPosition) return;
16227     seekGraphUp = FALSE;
16228     MarkTargetSquares(1);
16229     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16230     if (currentMove <= backwardMostMove) {
16231         ClearHighlights();
16232         DrawPosition(full_redraw, boards[currentMove]);
16233         return;
16234     }
16235     if (gameMode == PlayFromGameFile && !pausing)
16236       PauseEvent();
16237
16238     if (moveList[target][0]) {
16239         int fromX, fromY, toX, toY;
16240         toX = moveList[target][2] - AAA;
16241         toY = moveList[target][3] - ONE;
16242         if (moveList[target][1] == '@') {
16243             if (appData.highlightLastMove) {
16244                 SetHighlights(-1, -1, toX, toY);
16245             }
16246         } else {
16247             fromX = moveList[target][0] - AAA;
16248             fromY = moveList[target][1] - ONE;
16249             if (target == currentMove - 1) {
16250                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16251             }
16252             if (appData.highlightLastMove) {
16253                 SetHighlights(fromX, fromY, toX, toY);
16254             }
16255         }
16256     }
16257     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16258         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16259         while (currentMove > target) {
16260             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16261                 // null move cannot be undone. Reload program with move history before it.
16262                 int i;
16263                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16264                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16265                 }
16266                 SendBoard(&first, i);
16267               if(second.analyzing) SendBoard(&second, i);
16268                 for(currentMove=i; currentMove<target; currentMove++) {
16269                     SendMoveToProgram(currentMove, &first);
16270                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16271                 }
16272                 break;
16273             }
16274             SendToBoth("undo\n");
16275             currentMove--;
16276         }
16277     } else {
16278         currentMove = target;
16279     }
16280
16281     if (gameMode == EditGame || gameMode == EndOfGame) {
16282         whiteTimeRemaining = timeRemaining[0][currentMove];
16283         blackTimeRemaining = timeRemaining[1][currentMove];
16284     }
16285     DisplayBothClocks();
16286     DisplayMove(currentMove - 1);
16287     DrawPosition(full_redraw, boards[currentMove]);
16288     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16289     // [HGM] PV info: routine tests if comment empty
16290     DisplayComment(currentMove - 1, commentList[currentMove]);
16291     ClearMap(); // [HGM] exclude: invalidate map
16292 }
16293
16294 void
16295 BackwardEvent ()
16296 {
16297     if (gameMode == IcsExamining && !pausing) {
16298         SendToICS(ics_prefix);
16299         SendToICS("backward\n");
16300     } else {
16301         BackwardInner(currentMove - 1);
16302     }
16303 }
16304
16305 void
16306 ToStartEvent ()
16307 {
16308     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16309         /* to optimize, we temporarily turn off analysis mode while we undo
16310          * all the moves. Otherwise we get analysis output after each undo.
16311          */
16312         if (first.analysisSupport) {
16313           SendToProgram("exit\nforce\n", &first);
16314           first.analyzing = FALSE;
16315         }
16316     }
16317
16318     if (gameMode == IcsExamining && !pausing) {
16319         SendToICS(ics_prefix);
16320         SendToICS("backward 999999\n");
16321     } else {
16322         BackwardInner(backwardMostMove);
16323     }
16324
16325     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16326         /* we have fed all the moves, so reactivate analysis mode */
16327         SendToProgram("analyze\n", &first);
16328         first.analyzing = TRUE;
16329         /*first.maybeThinking = TRUE;*/
16330         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16331     }
16332 }
16333
16334 void
16335 ToNrEvent (int to)
16336 {
16337   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16338   if (to >= forwardMostMove) to = forwardMostMove;
16339   if (to <= backwardMostMove) to = backwardMostMove;
16340   if (to < currentMove) {
16341     BackwardInner(to);
16342   } else {
16343     ForwardInner(to);
16344   }
16345 }
16346
16347 void
16348 RevertEvent (Boolean annotate)
16349 {
16350     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16351         return;
16352     }
16353     if (gameMode != IcsExamining) {
16354         DisplayError(_("You are not examining a game"), 0);
16355         return;
16356     }
16357     if (pausing) {
16358         DisplayError(_("You can't revert while pausing"), 0);
16359         return;
16360     }
16361     SendToICS(ics_prefix);
16362     SendToICS("revert\n");
16363 }
16364
16365 void
16366 RetractMoveEvent ()
16367 {
16368     switch (gameMode) {
16369       case MachinePlaysWhite:
16370       case MachinePlaysBlack:
16371         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16372             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16373             return;
16374         }
16375         if (forwardMostMove < 2) return;
16376         currentMove = forwardMostMove = forwardMostMove - 2;
16377         whiteTimeRemaining = timeRemaining[0][currentMove];
16378         blackTimeRemaining = timeRemaining[1][currentMove];
16379         DisplayBothClocks();
16380         DisplayMove(currentMove - 1);
16381         ClearHighlights();/*!! could figure this out*/
16382         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16383         SendToProgram("remove\n", &first);
16384         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16385         break;
16386
16387       case BeginningOfGame:
16388       default:
16389         break;
16390
16391       case IcsPlayingWhite:
16392       case IcsPlayingBlack:
16393         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16394             SendToICS(ics_prefix);
16395             SendToICS("takeback 2\n");
16396         } else {
16397             SendToICS(ics_prefix);
16398             SendToICS("takeback 1\n");
16399         }
16400         break;
16401     }
16402 }
16403
16404 void
16405 MoveNowEvent ()
16406 {
16407     ChessProgramState *cps;
16408
16409     switch (gameMode) {
16410       case MachinePlaysWhite:
16411         if (!WhiteOnMove(forwardMostMove)) {
16412             DisplayError(_("It is your turn"), 0);
16413             return;
16414         }
16415         cps = &first;
16416         break;
16417       case MachinePlaysBlack:
16418         if (WhiteOnMove(forwardMostMove)) {
16419             DisplayError(_("It is your turn"), 0);
16420             return;
16421         }
16422         cps = &first;
16423         break;
16424       case TwoMachinesPlay:
16425         if (WhiteOnMove(forwardMostMove) ==
16426             (first.twoMachinesColor[0] == 'w')) {
16427             cps = &first;
16428         } else {
16429             cps = &second;
16430         }
16431         break;
16432       case BeginningOfGame:
16433       default:
16434         return;
16435     }
16436     SendToProgram("?\n", cps);
16437 }
16438
16439 void
16440 TruncateGameEvent ()
16441 {
16442     EditGameEvent();
16443     if (gameMode != EditGame) return;
16444     TruncateGame();
16445 }
16446
16447 void
16448 TruncateGame ()
16449 {
16450     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16451     if (forwardMostMove > currentMove) {
16452         if (gameInfo.resultDetails != NULL) {
16453             free(gameInfo.resultDetails);
16454             gameInfo.resultDetails = NULL;
16455             gameInfo.result = GameUnfinished;
16456         }
16457         forwardMostMove = currentMove;
16458         HistorySet(parseList, backwardMostMove, forwardMostMove,
16459                    currentMove-1);
16460     }
16461 }
16462
16463 void
16464 HintEvent ()
16465 {
16466     if (appData.noChessProgram) return;
16467     switch (gameMode) {
16468       case MachinePlaysWhite:
16469         if (WhiteOnMove(forwardMostMove)) {
16470             DisplayError(_("Wait until your turn."), 0);
16471             return;
16472         }
16473         break;
16474       case BeginningOfGame:
16475       case MachinePlaysBlack:
16476         if (!WhiteOnMove(forwardMostMove)) {
16477             DisplayError(_("Wait until your turn."), 0);
16478             return;
16479         }
16480         break;
16481       default:
16482         DisplayError(_("No hint available"), 0);
16483         return;
16484     }
16485     SendToProgram("hint\n", &first);
16486     hintRequested = TRUE;
16487 }
16488
16489 int
16490 SaveSelected (FILE *g, int dummy, char *dummy2)
16491 {
16492     ListGame * lg = (ListGame *) gameList.head;
16493     int nItem, cnt=0;
16494     FILE *f;
16495
16496     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16497         DisplayError(_("Game list not loaded or empty"), 0);
16498         return 0;
16499     }
16500
16501     creatingBook = TRUE; // suppresses stuff during load game
16502
16503     /* Get list size */
16504     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16505         if(lg->position >= 0) { // selected?
16506             LoadGame(f, nItem, "", TRUE);
16507             SaveGamePGN2(g); // leaves g open
16508             cnt++; DoEvents();
16509         }
16510         lg = (ListGame *) lg->node.succ;
16511     }
16512
16513     fclose(g);
16514     creatingBook = FALSE;
16515
16516     return cnt;
16517 }
16518
16519 void
16520 CreateBookEvent ()
16521 {
16522     ListGame * lg = (ListGame *) gameList.head;
16523     FILE *f, *g;
16524     int nItem;
16525     static int secondTime = FALSE;
16526
16527     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16528         DisplayError(_("Game list not loaded or empty"), 0);
16529         return;
16530     }
16531
16532     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16533         fclose(g);
16534         secondTime++;
16535         DisplayNote(_("Book file exists! Try again for overwrite."));
16536         return;
16537     }
16538
16539     creatingBook = TRUE;
16540     secondTime = FALSE;
16541
16542     /* Get list size */
16543     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16544         if(lg->position >= 0) {
16545             LoadGame(f, nItem, "", TRUE);
16546             AddGameToBook(TRUE);
16547             DoEvents();
16548         }
16549         lg = (ListGame *) lg->node.succ;
16550     }
16551
16552     creatingBook = FALSE;
16553     FlushBook();
16554 }
16555
16556 void
16557 BookEvent ()
16558 {
16559     if (appData.noChessProgram) return;
16560     switch (gameMode) {
16561       case MachinePlaysWhite:
16562         if (WhiteOnMove(forwardMostMove)) {
16563             DisplayError(_("Wait until your turn."), 0);
16564             return;
16565         }
16566         break;
16567       case BeginningOfGame:
16568       case MachinePlaysBlack:
16569         if (!WhiteOnMove(forwardMostMove)) {
16570             DisplayError(_("Wait until your turn."), 0);
16571             return;
16572         }
16573         break;
16574       case EditPosition:
16575         EditPositionDone(TRUE);
16576         break;
16577       case TwoMachinesPlay:
16578         return;
16579       default:
16580         break;
16581     }
16582     SendToProgram("bk\n", &first);
16583     bookOutput[0] = NULLCHAR;
16584     bookRequested = TRUE;
16585 }
16586
16587 void
16588 AboutGameEvent ()
16589 {
16590     char *tags = PGNTags(&gameInfo);
16591     TagsPopUp(tags, CmailMsg());
16592     free(tags);
16593 }
16594
16595 /* end button procedures */
16596
16597 void
16598 PrintPosition (FILE *fp, int move)
16599 {
16600     int i, j;
16601
16602     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16603         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16604             char c = PieceToChar(boards[move][i][j]);
16605             fputc(c == '?' ? '.' : c, fp);
16606             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16607         }
16608     }
16609     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16610       fprintf(fp, "white to play\n");
16611     else
16612       fprintf(fp, "black to play\n");
16613 }
16614
16615 void
16616 PrintOpponents (FILE *fp)
16617 {
16618     if (gameInfo.white != NULL) {
16619         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16620     } else {
16621         fprintf(fp, "\n");
16622     }
16623 }
16624
16625 /* Find last component of program's own name, using some heuristics */
16626 void
16627 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16628 {
16629     char *p, *q, c;
16630     int local = (strcmp(host, "localhost") == 0);
16631     while (!local && (p = strchr(prog, ';')) != NULL) {
16632         p++;
16633         while (*p == ' ') p++;
16634         prog = p;
16635     }
16636     if (*prog == '"' || *prog == '\'') {
16637         q = strchr(prog + 1, *prog);
16638     } else {
16639         q = strchr(prog, ' ');
16640     }
16641     if (q == NULL) q = prog + strlen(prog);
16642     p = q;
16643     while (p >= prog && *p != '/' && *p != '\\') p--;
16644     p++;
16645     if(p == prog && *p == '"') p++;
16646     c = *q; *q = 0;
16647     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16648     memcpy(buf, p, q - p);
16649     buf[q - p] = NULLCHAR;
16650     if (!local) {
16651         strcat(buf, "@");
16652         strcat(buf, host);
16653     }
16654 }
16655
16656 char *
16657 TimeControlTagValue ()
16658 {
16659     char buf[MSG_SIZ];
16660     if (!appData.clockMode) {
16661       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16662     } else if (movesPerSession > 0) {
16663       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16664     } else if (timeIncrement == 0) {
16665       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16666     } else {
16667       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16668     }
16669     return StrSave(buf);
16670 }
16671
16672 void
16673 SetGameInfo ()
16674 {
16675     /* This routine is used only for certain modes */
16676     VariantClass v = gameInfo.variant;
16677     ChessMove r = GameUnfinished;
16678     char *p = NULL;
16679
16680     if(keepInfo) return;
16681
16682     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16683         r = gameInfo.result;
16684         p = gameInfo.resultDetails;
16685         gameInfo.resultDetails = NULL;
16686     }
16687     ClearGameInfo(&gameInfo);
16688     gameInfo.variant = v;
16689
16690     switch (gameMode) {
16691       case MachinePlaysWhite:
16692         gameInfo.event = StrSave( appData.pgnEventHeader );
16693         gameInfo.site = StrSave(HostName());
16694         gameInfo.date = PGNDate();
16695         gameInfo.round = StrSave("-");
16696         gameInfo.white = StrSave(first.tidy);
16697         gameInfo.black = StrSave(UserName());
16698         gameInfo.timeControl = TimeControlTagValue();
16699         break;
16700
16701       case MachinePlaysBlack:
16702         gameInfo.event = StrSave( appData.pgnEventHeader );
16703         gameInfo.site = StrSave(HostName());
16704         gameInfo.date = PGNDate();
16705         gameInfo.round = StrSave("-");
16706         gameInfo.white = StrSave(UserName());
16707         gameInfo.black = StrSave(first.tidy);
16708         gameInfo.timeControl = TimeControlTagValue();
16709         break;
16710
16711       case TwoMachinesPlay:
16712         gameInfo.event = StrSave( appData.pgnEventHeader );
16713         gameInfo.site = StrSave(HostName());
16714         gameInfo.date = PGNDate();
16715         if (roundNr > 0) {
16716             char buf[MSG_SIZ];
16717             snprintf(buf, MSG_SIZ, "%d", roundNr);
16718             gameInfo.round = StrSave(buf);
16719         } else {
16720             gameInfo.round = StrSave("-");
16721         }
16722         if (first.twoMachinesColor[0] == 'w') {
16723             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16724             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16725         } else {
16726             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16727             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16728         }
16729         gameInfo.timeControl = TimeControlTagValue();
16730         break;
16731
16732       case EditGame:
16733         gameInfo.event = StrSave("Edited game");
16734         gameInfo.site = StrSave(HostName());
16735         gameInfo.date = PGNDate();
16736         gameInfo.round = StrSave("-");
16737         gameInfo.white = StrSave("-");
16738         gameInfo.black = StrSave("-");
16739         gameInfo.result = r;
16740         gameInfo.resultDetails = p;
16741         break;
16742
16743       case EditPosition:
16744         gameInfo.event = StrSave("Edited position");
16745         gameInfo.site = StrSave(HostName());
16746         gameInfo.date = PGNDate();
16747         gameInfo.round = StrSave("-");
16748         gameInfo.white = StrSave("-");
16749         gameInfo.black = StrSave("-");
16750         break;
16751
16752       case IcsPlayingWhite:
16753       case IcsPlayingBlack:
16754       case IcsObserving:
16755       case IcsExamining:
16756         break;
16757
16758       case PlayFromGameFile:
16759         gameInfo.event = StrSave("Game from non-PGN file");
16760         gameInfo.site = StrSave(HostName());
16761         gameInfo.date = PGNDate();
16762         gameInfo.round = StrSave("-");
16763         gameInfo.white = StrSave("?");
16764         gameInfo.black = StrSave("?");
16765         break;
16766
16767       default:
16768         break;
16769     }
16770 }
16771
16772 void
16773 ReplaceComment (int index, char *text)
16774 {
16775     int len;
16776     char *p;
16777     float score;
16778
16779     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16780        pvInfoList[index-1].depth == len &&
16781        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16782        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16783     while (*text == '\n') text++;
16784     len = strlen(text);
16785     while (len > 0 && text[len - 1] == '\n') len--;
16786
16787     if (commentList[index] != NULL)
16788       free(commentList[index]);
16789
16790     if (len == 0) {
16791         commentList[index] = NULL;
16792         return;
16793     }
16794   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16795       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16796       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16797     commentList[index] = (char *) malloc(len + 2);
16798     strncpy(commentList[index], text, len);
16799     commentList[index][len] = '\n';
16800     commentList[index][len + 1] = NULLCHAR;
16801   } else {
16802     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16803     char *p;
16804     commentList[index] = (char *) malloc(len + 7);
16805     safeStrCpy(commentList[index], "{\n", 3);
16806     safeStrCpy(commentList[index]+2, text, len+1);
16807     commentList[index][len+2] = NULLCHAR;
16808     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16809     strcat(commentList[index], "\n}\n");
16810   }
16811 }
16812
16813 void
16814 CrushCRs (char *text)
16815 {
16816   char *p = text;
16817   char *q = text;
16818   char ch;
16819
16820   do {
16821     ch = *p++;
16822     if (ch == '\r') continue;
16823     *q++ = ch;
16824   } while (ch != '\0');
16825 }
16826
16827 void
16828 AppendComment (int index, char *text, Boolean addBraces)
16829 /* addBraces  tells if we should add {} */
16830 {
16831     int oldlen, len;
16832     char *old;
16833
16834 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16835     if(addBraces == 3) addBraces = 0; else // force appending literally
16836     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16837
16838     CrushCRs(text);
16839     while (*text == '\n') text++;
16840     len = strlen(text);
16841     while (len > 0 && text[len - 1] == '\n') len--;
16842     text[len] = NULLCHAR;
16843
16844     if (len == 0) return;
16845
16846     if (commentList[index] != NULL) {
16847       Boolean addClosingBrace = addBraces;
16848         old = commentList[index];
16849         oldlen = strlen(old);
16850         while(commentList[index][oldlen-1] ==  '\n')
16851           commentList[index][--oldlen] = NULLCHAR;
16852         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16853         safeStrCpy(commentList[index], old, oldlen + len + 6);
16854         free(old);
16855         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16856         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16857           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16858           while (*text == '\n') { text++; len--; }
16859           commentList[index][--oldlen] = NULLCHAR;
16860       }
16861         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16862         else          strcat(commentList[index], "\n");
16863         strcat(commentList[index], text);
16864         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16865         else          strcat(commentList[index], "\n");
16866     } else {
16867         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16868         if(addBraces)
16869           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16870         else commentList[index][0] = NULLCHAR;
16871         strcat(commentList[index], text);
16872         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16873         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16874     }
16875 }
16876
16877 static char *
16878 FindStr (char * text, char * sub_text)
16879 {
16880     char * result = strstr( text, sub_text );
16881
16882     if( result != NULL ) {
16883         result += strlen( sub_text );
16884     }
16885
16886     return result;
16887 }
16888
16889 /* [AS] Try to extract PV info from PGN comment */
16890 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16891 char *
16892 GetInfoFromComment (int index, char * text)
16893 {
16894     char * sep = text, *p;
16895
16896     if( text != NULL && index > 0 ) {
16897         int score = 0;
16898         int depth = 0;
16899         int time = -1, sec = 0, deci;
16900         char * s_eval = FindStr( text, "[%eval " );
16901         char * s_emt = FindStr( text, "[%emt " );
16902 #if 0
16903         if( s_eval != NULL || s_emt != NULL ) {
16904 #else
16905         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16906 #endif
16907             /* New style */
16908             char delim;
16909
16910             if( s_eval != NULL ) {
16911                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16912                     return text;
16913                 }
16914
16915                 if( delim != ']' ) {
16916                     return text;
16917                 }
16918             }
16919
16920             if( s_emt != NULL ) {
16921             }
16922                 return text;
16923         }
16924         else {
16925             /* We expect something like: [+|-]nnn.nn/dd */
16926             int score_lo = 0;
16927
16928             if(*text != '{') return text; // [HGM] braces: must be normal comment
16929
16930             sep = strchr( text, '/' );
16931             if( sep == NULL || sep < (text+4) ) {
16932                 return text;
16933             }
16934
16935             p = text;
16936             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16937             if(p[1] == '(') { // comment starts with PV
16938                p = strchr(p, ')'); // locate end of PV
16939                if(p == NULL || sep < p+5) return text;
16940                // at this point we have something like "{(.*) +0.23/6 ..."
16941                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16942                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16943                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16944             }
16945             time = -1; sec = -1; deci = -1;
16946             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16947                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16948                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16949                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16950                 return text;
16951             }
16952
16953             if( score_lo < 0 || score_lo >= 100 ) {
16954                 return text;
16955             }
16956
16957             if(sec >= 0) time = 600*time + 10*sec; else
16958             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16959
16960             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16961
16962             /* [HGM] PV time: now locate end of PV info */
16963             while( *++sep >= '0' && *sep <= '9'); // strip depth
16964             if(time >= 0)
16965             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16966             if(sec >= 0)
16967             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16968             if(deci >= 0)
16969             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16970             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16971         }
16972
16973         if( depth <= 0 ) {
16974             return text;
16975         }
16976
16977         if( time < 0 ) {
16978             time = -1;
16979         }
16980
16981         pvInfoList[index-1].depth = depth;
16982         pvInfoList[index-1].score = score;
16983         pvInfoList[index-1].time  = 10*time; // centi-sec
16984         if(*sep == '}') *sep = 0; else *--sep = '{';
16985         if(p != text) {
16986             while(*p++ = *sep++)
16987                                 ;
16988             sep = text;
16989         } // squeeze out space between PV and comment, and return both
16990     }
16991     return sep;
16992 }
16993
16994 void
16995 SendToProgram (char *message, ChessProgramState *cps)
16996 {
16997     int count, outCount, error;
16998     char buf[MSG_SIZ];
16999
17000     if (cps->pr == NoProc) return;
17001     Attention(cps);
17002
17003     if (appData.debugMode) {
17004         TimeMark now;
17005         GetTimeMark(&now);
17006         fprintf(debugFP, "%ld >%-6s: %s",
17007                 SubtractTimeMarks(&now, &programStartTime),
17008                 cps->which, message);
17009         if(serverFP)
17010             fprintf(serverFP, "%ld >%-6s: %s",
17011                 SubtractTimeMarks(&now, &programStartTime),
17012                 cps->which, message), fflush(serverFP);
17013     }
17014
17015     count = strlen(message);
17016     outCount = OutputToProcess(cps->pr, message, count, &error);
17017     if (outCount < count && !exiting
17018                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
17019       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
17020       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
17021         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17022             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17023                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17024                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17025                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17026             } else {
17027                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17028                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17029                 gameInfo.result = res;
17030             }
17031             gameInfo.resultDetails = StrSave(buf);
17032         }
17033         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17034         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17035     }
17036 }
17037
17038 void
17039 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
17040 {
17041     char *end_str;
17042     char buf[MSG_SIZ];
17043     ChessProgramState *cps = (ChessProgramState *)closure;
17044
17045     if (isr != cps->isr) return; /* Killed intentionally */
17046     if (count <= 0) {
17047         if (count == 0) {
17048             RemoveInputSource(cps->isr);
17049             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
17050                     _(cps->which), cps->program);
17051             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
17052             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17053                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17054                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17055                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17056                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17057                 } else {
17058                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17059                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17060                     gameInfo.result = res;
17061                 }
17062                 gameInfo.resultDetails = StrSave(buf);
17063             }
17064             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17065             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
17066         } else {
17067             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
17068                     _(cps->which), cps->program);
17069             RemoveInputSource(cps->isr);
17070
17071             /* [AS] Program is misbehaving badly... kill it */
17072             if( count == -2 ) {
17073                 DestroyChildProcess( cps->pr, 9 );
17074                 cps->pr = NoProc;
17075             }
17076
17077             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17078         }
17079         return;
17080     }
17081
17082     if ((end_str = strchr(message, '\r')) != NULL)
17083       *end_str = NULLCHAR;
17084     if ((end_str = strchr(message, '\n')) != NULL)
17085       *end_str = NULLCHAR;
17086
17087     if (appData.debugMode) {
17088         TimeMark now; int print = 1;
17089         char *quote = ""; char c; int i;
17090
17091         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17092                 char start = message[0];
17093                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17094                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17095                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
17096                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17097                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17098                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
17099                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17100                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
17101                    sscanf(message, "hint: %c", &c)!=1 &&
17102                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
17103                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17104                     print = (appData.engineComments >= 2);
17105                 }
17106                 message[0] = start; // restore original message
17107         }
17108         if(print) {
17109                 GetTimeMark(&now);
17110                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17111                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17112                         quote,
17113                         message);
17114                 if(serverFP)
17115                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
17116                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17117                         quote,
17118                         message), fflush(serverFP);
17119         }
17120     }
17121
17122     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17123     if (appData.icsEngineAnalyze) {
17124         if (strstr(message, "whisper") != NULL ||
17125              strstr(message, "kibitz") != NULL ||
17126             strstr(message, "tellics") != NULL) return;
17127     }
17128
17129     HandleMachineMove(message, cps);
17130 }
17131
17132
17133 void
17134 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17135 {
17136     char buf[MSG_SIZ];
17137     int seconds;
17138
17139     if( timeControl_2 > 0 ) {
17140         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17141             tc = timeControl_2;
17142         }
17143     }
17144     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17145     inc /= cps->timeOdds;
17146     st  /= cps->timeOdds;
17147
17148     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17149
17150     if (st > 0) {
17151       /* Set exact time per move, normally using st command */
17152       if (cps->stKludge) {
17153         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17154         seconds = st % 60;
17155         if (seconds == 0) {
17156           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17157         } else {
17158           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17159         }
17160       } else {
17161         snprintf(buf, MSG_SIZ, "st %d\n", st);
17162       }
17163     } else {
17164       /* Set conventional or incremental time control, using level command */
17165       if (seconds == 0) {
17166         /* Note old gnuchess bug -- minutes:seconds used to not work.
17167            Fixed in later versions, but still avoid :seconds
17168            when seconds is 0. */
17169         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17170       } else {
17171         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17172                  seconds, inc/1000.);
17173       }
17174     }
17175     SendToProgram(buf, cps);
17176
17177     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17178     /* Orthogonally, limit search to given depth */
17179     if (sd > 0) {
17180       if (cps->sdKludge) {
17181         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17182       } else {
17183         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17184       }
17185       SendToProgram(buf, cps);
17186     }
17187
17188     if(cps->nps >= 0) { /* [HGM] nps */
17189         if(cps->supportsNPS == FALSE)
17190           cps->nps = -1; // don't use if engine explicitly says not supported!
17191         else {
17192           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17193           SendToProgram(buf, cps);
17194         }
17195     }
17196 }
17197
17198 ChessProgramState *
17199 WhitePlayer ()
17200 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17201 {
17202     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17203        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17204         return &second;
17205     return &first;
17206 }
17207
17208 void
17209 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17210 {
17211     char message[MSG_SIZ];
17212     long time, otime;
17213
17214     /* Note: this routine must be called when the clocks are stopped
17215        or when they have *just* been set or switched; otherwise
17216        it will be off by the time since the current tick started.
17217     */
17218     if (machineWhite) {
17219         time = whiteTimeRemaining / 10;
17220         otime = blackTimeRemaining / 10;
17221     } else {
17222         time = blackTimeRemaining / 10;
17223         otime = whiteTimeRemaining / 10;
17224     }
17225     /* [HGM] translate opponent's time by time-odds factor */
17226     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17227
17228     if (time <= 0) time = 1;
17229     if (otime <= 0) otime = 1;
17230
17231     snprintf(message, MSG_SIZ, "time %ld\n", time);
17232     SendToProgram(message, cps);
17233
17234     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17235     SendToProgram(message, cps);
17236 }
17237
17238 char *
17239 EngineDefinedVariant (ChessProgramState *cps, int n)
17240 {   // return name of n-th unknown variant that engine supports
17241     static char buf[MSG_SIZ];
17242     char *p, *s = cps->variants;
17243     if(!s) return NULL;
17244     do { // parse string from variants feature
17245       VariantClass v;
17246         p = strchr(s, ',');
17247         if(p) *p = NULLCHAR;
17248       v = StringToVariant(s);
17249       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17250         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17251             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17252                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17253                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17254                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17255             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17256         }
17257         if(p) *p++ = ',';
17258         if(n < 0) return buf;
17259     } while(s = p);
17260     return NULL;
17261 }
17262
17263 int
17264 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17265 {
17266   char buf[MSG_SIZ];
17267   int len = strlen(name);
17268   int val;
17269
17270   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17271     (*p) += len + 1;
17272     sscanf(*p, "%d", &val);
17273     *loc = (val != 0);
17274     while (**p && **p != ' ')
17275       (*p)++;
17276     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17277     SendToProgram(buf, cps);
17278     return TRUE;
17279   }
17280   return FALSE;
17281 }
17282
17283 int
17284 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17285 {
17286   char buf[MSG_SIZ];
17287   int len = strlen(name);
17288   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17289     (*p) += len + 1;
17290     sscanf(*p, "%d", loc);
17291     while (**p && **p != ' ') (*p)++;
17292     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17293     SendToProgram(buf, cps);
17294     return TRUE;
17295   }
17296   return FALSE;
17297 }
17298
17299 int
17300 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17301 {
17302   char buf[MSG_SIZ];
17303   int len = strlen(name);
17304   if (strncmp((*p), name, len) == 0
17305       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17306     (*p) += len + 2;
17307     len = strlen(*p) + 1; if(len < MSG_SIZ && !strcmp(name, "option")) len = MSG_SIZ; // make sure string options have enough space to change their value
17308     FREE(*loc); *loc = malloc(len);
17309     strncpy(*loc, *p, len);
17310     sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17311     while (**p && **p != '\"') (*p)++;
17312     if (**p == '\"') (*p)++;
17313     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17314     SendToProgram(buf, cps);
17315     return TRUE;
17316   }
17317   return FALSE;
17318 }
17319
17320 int
17321 ParseOption (Option *opt, ChessProgramState *cps)
17322 // [HGM] options: process the string that defines an engine option, and determine
17323 // name, type, default value, and allowed value range
17324 {
17325         char *p, *q, buf[MSG_SIZ];
17326         int n, min = (-1)<<31, max = 1<<31, def;
17327
17328         opt->target = &opt->value;   // OK for spin/slider and checkbox
17329         if(p = strstr(opt->name, " -spin ")) {
17330             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17331             if(max < min) max = min; // enforce consistency
17332             if(def < min) def = min;
17333             if(def > max) def = max;
17334             opt->value = def;
17335             opt->min = min;
17336             opt->max = max;
17337             opt->type = Spin;
17338         } else if((p = strstr(opt->name, " -slider "))) {
17339             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17340             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17341             if(max < min) max = min; // enforce consistency
17342             if(def < min) def = min;
17343             if(def > max) def = max;
17344             opt->value = def;
17345             opt->min = min;
17346             opt->max = max;
17347             opt->type = Spin; // Slider;
17348         } else if((p = strstr(opt->name, " -string "))) {
17349             opt->textValue = p+9;
17350             opt->type = TextBox;
17351             opt->target = &opt->textValue;
17352         } else if((p = strstr(opt->name, " -file "))) {
17353             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17354             opt->target = opt->textValue = p+7;
17355             opt->type = FileName; // FileName;
17356             opt->target = &opt->textValue;
17357         } else if((p = strstr(opt->name, " -path "))) {
17358             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17359             opt->target = opt->textValue = p+7;
17360             opt->type = PathName; // PathName;
17361             opt->target = &opt->textValue;
17362         } else if(p = strstr(opt->name, " -check ")) {
17363             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17364             opt->value = (def != 0);
17365             opt->type = CheckBox;
17366         } else if(p = strstr(opt->name, " -combo ")) {
17367             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17368             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17369             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17370             opt->value = n = 0;
17371             while(q = StrStr(q, " /// ")) {
17372                 n++; *q = 0;    // count choices, and null-terminate each of them
17373                 q += 5;
17374                 if(*q == '*') { // remember default, which is marked with * prefix
17375                     q++;
17376                     opt->value = n;
17377                 }
17378                 cps->comboList[cps->comboCnt++] = q;
17379             }
17380             cps->comboList[cps->comboCnt++] = NULL;
17381             opt->max = n + 1;
17382             opt->type = ComboBox;
17383         } else if(p = strstr(opt->name, " -button")) {
17384             opt->type = Button;
17385         } else if(p = strstr(opt->name, " -save")) {
17386             opt->type = SaveButton;
17387         } else return FALSE;
17388         *p = 0; // terminate option name
17389         *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17390         if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17391         // now look if the command-line options define a setting for this engine option.
17392         if(cps->optionSettings && cps->optionSettings[0])
17393             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17394         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17395           snprintf(buf, MSG_SIZ, "option %s", p);
17396                 if(p = strstr(buf, ",")) *p = 0;
17397                 if(q = strchr(buf, '=')) switch(opt->type) {
17398                     case ComboBox:
17399                         for(n=0; n<opt->max; n++)
17400                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17401                         break;
17402                     case TextBox:
17403                     case FileName:
17404                     case PathName:
17405                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17406                         break;
17407                     case Spin:
17408                     case CheckBox:
17409                         opt->value = atoi(q+1);
17410                     default:
17411                         break;
17412                 }
17413                 strcat(buf, "\n");
17414                 SendToProgram(buf, cps);
17415         }
17416         return TRUE;
17417 }
17418
17419 void
17420 FeatureDone (ChessProgramState *cps, int val)
17421 {
17422   DelayedEventCallback cb = GetDelayedEvent();
17423   if ((cb == InitBackEnd3 && cps == &first) ||
17424       (cb == SettingsMenuIfReady && cps == &second) ||
17425       (cb == LoadEngine) || (cb == StartSecond) ||
17426       (cb == TwoMachinesEventIfReady)) {
17427     CancelDelayedEvent();
17428     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17429   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17430   cps->initDone = val;
17431   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17432 }
17433
17434 /* Parse feature command from engine */
17435 void
17436 ParseFeatures (char *args, ChessProgramState *cps)
17437 {
17438   char *p = args;
17439   char *q = NULL;
17440   int val;
17441   char buf[MSG_SIZ];
17442
17443   for (;;) {
17444     while (*p == ' ') p++;
17445     if (*p == NULLCHAR) return;
17446
17447     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17448     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17449     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17450     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17451     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17452     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17453     if (BoolFeature(&p, "reuse", &val, cps)) {
17454       /* Engine can disable reuse, but can't enable it if user said no */
17455       if (!val) cps->reuse = FALSE;
17456       continue;
17457     }
17458     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17459     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17460       if (gameMode == TwoMachinesPlay) {
17461         DisplayTwoMachinesTitle();
17462       } else {
17463         DisplayTitle("");
17464       }
17465       continue;
17466     }
17467     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17468     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17469     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17470     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17471     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17472     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17473     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17474     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17475     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17476     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17477     if (IntFeature(&p, "done", &val, cps)) {
17478       FeatureDone(cps, val);
17479       continue;
17480     }
17481     /* Added by Tord: */
17482     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17483     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17484     /* End of additions by Tord */
17485
17486     /* [HGM] added features: */
17487     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17488     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17489     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17490     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17491     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17492     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17493     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17494     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17495         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17496         if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17497         FREE(cps->option[cps->nrOptions].name);
17498         cps->option[cps->nrOptions].name = q; q = NULL;
17499         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17500           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17501             SendToProgram(buf, cps);
17502             continue;
17503         }
17504         if(cps->nrOptions >= MAX_OPTIONS) {
17505             cps->nrOptions--;
17506             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17507             DisplayError(buf, 0);
17508         }
17509         continue;
17510     }
17511     /* End of additions by HGM */
17512
17513     /* unknown feature: complain and skip */
17514     q = p;
17515     while (*q && *q != '=') q++;
17516     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17517     SendToProgram(buf, cps);
17518     p = q;
17519     if (*p == '=') {
17520       p++;
17521       if (*p == '\"') {
17522         p++;
17523         while (*p && *p != '\"') p++;
17524         if (*p == '\"') p++;
17525       } else {
17526         while (*p && *p != ' ') p++;
17527       }
17528     }
17529   }
17530
17531 }
17532
17533 void
17534 PeriodicUpdatesEvent (int newState)
17535 {
17536     if (newState == appData.periodicUpdates)
17537       return;
17538
17539     appData.periodicUpdates=newState;
17540
17541     /* Display type changes, so update it now */
17542 //    DisplayAnalysis();
17543
17544     /* Get the ball rolling again... */
17545     if (newState) {
17546         AnalysisPeriodicEvent(1);
17547         StartAnalysisClock();
17548     }
17549 }
17550
17551 void
17552 PonderNextMoveEvent (int newState)
17553 {
17554     if (newState == appData.ponderNextMove) return;
17555     if (gameMode == EditPosition) EditPositionDone(TRUE);
17556     if (newState) {
17557         SendToProgram("hard\n", &first);
17558         if (gameMode == TwoMachinesPlay) {
17559             SendToProgram("hard\n", &second);
17560         }
17561     } else {
17562         SendToProgram("easy\n", &first);
17563         thinkOutput[0] = NULLCHAR;
17564         if (gameMode == TwoMachinesPlay) {
17565             SendToProgram("easy\n", &second);
17566         }
17567     }
17568     appData.ponderNextMove = newState;
17569 }
17570
17571 void
17572 NewSettingEvent (int option, int *feature, char *command, int value)
17573 {
17574     char buf[MSG_SIZ];
17575
17576     if (gameMode == EditPosition) EditPositionDone(TRUE);
17577     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17578     if(feature == NULL || *feature) SendToProgram(buf, &first);
17579     if (gameMode == TwoMachinesPlay) {
17580         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17581     }
17582 }
17583
17584 void
17585 ShowThinkingEvent ()
17586 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17587 {
17588     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17589     int newState = appData.showThinking
17590         // [HGM] thinking: other features now need thinking output as well
17591         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17592
17593     if (oldState == newState) return;
17594     oldState = newState;
17595     if (gameMode == EditPosition) EditPositionDone(TRUE);
17596     if (oldState) {
17597         SendToProgram("post\n", &first);
17598         if (gameMode == TwoMachinesPlay) {
17599             SendToProgram("post\n", &second);
17600         }
17601     } else {
17602         SendToProgram("nopost\n", &first);
17603         thinkOutput[0] = NULLCHAR;
17604         if (gameMode == TwoMachinesPlay) {
17605             SendToProgram("nopost\n", &second);
17606         }
17607     }
17608 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17609 }
17610
17611 void
17612 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17613 {
17614   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17615   if (pr == NoProc) return;
17616   AskQuestion(title, question, replyPrefix, pr);
17617 }
17618
17619 void
17620 TypeInEvent (char firstChar)
17621 {
17622     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17623         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17624         gameMode == AnalyzeMode || gameMode == EditGame ||
17625         gameMode == EditPosition || gameMode == IcsExamining ||
17626         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17627         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17628                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17629                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17630         gameMode == Training) PopUpMoveDialog(firstChar);
17631 }
17632
17633 void
17634 TypeInDoneEvent (char *move)
17635 {
17636         Board board;
17637         int n, fromX, fromY, toX, toY;
17638         char promoChar;
17639         ChessMove moveType;
17640
17641         // [HGM] FENedit
17642         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17643                 EditPositionPasteFEN(move);
17644                 return;
17645         }
17646         // [HGM] movenum: allow move number to be typed in any mode
17647         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17648           ToNrEvent(2*n-1);
17649           return;
17650         }
17651         // undocumented kludge: allow command-line option to be typed in!
17652         // (potentially fatal, and does not implement the effect of the option.)
17653         // should only be used for options that are values on which future decisions will be made,
17654         // and definitely not on options that would be used during initialization.
17655         if(strstr(move, "!!! -") == move) {
17656             ParseArgsFromString(move+4);
17657             return;
17658         }
17659
17660       if (gameMode != EditGame && currentMove != forwardMostMove &&
17661         gameMode != Training) {
17662         DisplayMoveError(_("Displayed move is not current"));
17663       } else {
17664         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17665           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17666         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17667         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17668           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17669           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17670         } else {
17671           DisplayMoveError(_("Could not parse move"));
17672         }
17673       }
17674 }
17675
17676 void
17677 DisplayMove (int moveNumber)
17678 {
17679     char message[MSG_SIZ];
17680     char res[MSG_SIZ];
17681     char cpThinkOutput[MSG_SIZ];
17682
17683     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17684
17685     if (moveNumber == forwardMostMove - 1 ||
17686         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17687
17688         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17689
17690         if (strchr(cpThinkOutput, '\n')) {
17691             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17692         }
17693     } else {
17694         *cpThinkOutput = NULLCHAR;
17695     }
17696
17697     /* [AS] Hide thinking from human user */
17698     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17699         *cpThinkOutput = NULLCHAR;
17700         if( thinkOutput[0] != NULLCHAR ) {
17701             int i;
17702
17703             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17704                 cpThinkOutput[i] = '.';
17705             }
17706             cpThinkOutput[i] = NULLCHAR;
17707             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17708         }
17709     }
17710
17711     if (moveNumber == forwardMostMove - 1 &&
17712         gameInfo.resultDetails != NULL) {
17713         if (gameInfo.resultDetails[0] == NULLCHAR) {
17714           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17715         } else {
17716           snprintf(res, MSG_SIZ, " {%s} %s",
17717                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17718         }
17719     } else {
17720         res[0] = NULLCHAR;
17721     }
17722
17723     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17724         DisplayMessage(res, cpThinkOutput);
17725     } else {
17726       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17727                 WhiteOnMove(moveNumber) ? " " : ".. ",
17728                 parseList[moveNumber], res);
17729         DisplayMessage(message, cpThinkOutput);
17730     }
17731 }
17732
17733 void
17734 DisplayComment (int moveNumber, char *text)
17735 {
17736     char title[MSG_SIZ];
17737
17738     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17739       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17740     } else {
17741       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17742               WhiteOnMove(moveNumber) ? " " : ".. ",
17743               parseList[moveNumber]);
17744     }
17745     if (text != NULL && (appData.autoDisplayComment || commentUp))
17746         CommentPopUp(title, text);
17747 }
17748
17749 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17750  * might be busy thinking or pondering.  It can be omitted if your
17751  * gnuchess is configured to stop thinking immediately on any user
17752  * input.  However, that gnuchess feature depends on the FIONREAD
17753  * ioctl, which does not work properly on some flavors of Unix.
17754  */
17755 void
17756 Attention (ChessProgramState *cps)
17757 {
17758 #if ATTENTION
17759     if (!cps->useSigint) return;
17760     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17761     switch (gameMode) {
17762       case MachinePlaysWhite:
17763       case MachinePlaysBlack:
17764       case TwoMachinesPlay:
17765       case IcsPlayingWhite:
17766       case IcsPlayingBlack:
17767       case AnalyzeMode:
17768       case AnalyzeFile:
17769         /* Skip if we know it isn't thinking */
17770         if (!cps->maybeThinking) return;
17771         if (appData.debugMode)
17772           fprintf(debugFP, "Interrupting %s\n", cps->which);
17773         InterruptChildProcess(cps->pr);
17774         cps->maybeThinking = FALSE;
17775         break;
17776       default:
17777         break;
17778     }
17779 #endif /*ATTENTION*/
17780 }
17781
17782 int
17783 CheckFlags ()
17784 {
17785     if (whiteTimeRemaining <= 0) {
17786         if (!whiteFlag) {
17787             whiteFlag = TRUE;
17788             if (appData.icsActive) {
17789                 if (appData.autoCallFlag &&
17790                     gameMode == IcsPlayingBlack && !blackFlag) {
17791                   SendToICS(ics_prefix);
17792                   SendToICS("flag\n");
17793                 }
17794             } else {
17795                 if (blackFlag) {
17796                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17797                 } else {
17798                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17799                     if (appData.autoCallFlag) {
17800                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17801                         return TRUE;
17802                     }
17803                 }
17804             }
17805         }
17806     }
17807     if (blackTimeRemaining <= 0) {
17808         if (!blackFlag) {
17809             blackFlag = TRUE;
17810             if (appData.icsActive) {
17811                 if (appData.autoCallFlag &&
17812                     gameMode == IcsPlayingWhite && !whiteFlag) {
17813                   SendToICS(ics_prefix);
17814                   SendToICS("flag\n");
17815                 }
17816             } else {
17817                 if (whiteFlag) {
17818                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17819                 } else {
17820                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17821                     if (appData.autoCallFlag) {
17822                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17823                         return TRUE;
17824                     }
17825                 }
17826             }
17827         }
17828     }
17829     return FALSE;
17830 }
17831
17832 void
17833 CheckTimeControl ()
17834 {
17835     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17836         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17837
17838     /*
17839      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17840      */
17841     if ( !WhiteOnMove(forwardMostMove) ) {
17842         /* White made time control */
17843         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17844         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17845         /* [HGM] time odds: correct new time quota for time odds! */
17846                                             / WhitePlayer()->timeOdds;
17847         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17848     } else {
17849         lastBlack -= blackTimeRemaining;
17850         /* Black made time control */
17851         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17852                                             / WhitePlayer()->other->timeOdds;
17853         lastWhite = whiteTimeRemaining;
17854     }
17855 }
17856
17857 void
17858 DisplayBothClocks ()
17859 {
17860     int wom = gameMode == EditPosition ?
17861       !blackPlaysFirst : WhiteOnMove(currentMove);
17862     DisplayWhiteClock(whiteTimeRemaining, wom);
17863     DisplayBlackClock(blackTimeRemaining, !wom);
17864 }
17865
17866
17867 /* Timekeeping seems to be a portability nightmare.  I think everyone
17868    has ftime(), but I'm really not sure, so I'm including some ifdefs
17869    to use other calls if you don't.  Clocks will be less accurate if
17870    you have neither ftime nor gettimeofday.
17871 */
17872
17873 /* VS 2008 requires the #include outside of the function */
17874 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17875 #include <sys/timeb.h>
17876 #endif
17877
17878 /* Get the current time as a TimeMark */
17879 void
17880 GetTimeMark (TimeMark *tm)
17881 {
17882 #if HAVE_GETTIMEOFDAY
17883
17884     struct timeval timeVal;
17885     struct timezone timeZone;
17886
17887     gettimeofday(&timeVal, &timeZone);
17888     tm->sec = (long) timeVal.tv_sec;
17889     tm->ms = (int) (timeVal.tv_usec / 1000L);
17890
17891 #else /*!HAVE_GETTIMEOFDAY*/
17892 #if HAVE_FTIME
17893
17894 // include <sys/timeb.h> / moved to just above start of function
17895     struct timeb timeB;
17896
17897     ftime(&timeB);
17898     tm->sec = (long) timeB.time;
17899     tm->ms = (int) timeB.millitm;
17900
17901 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17902     tm->sec = (long) time(NULL);
17903     tm->ms = 0;
17904 #endif
17905 #endif
17906 }
17907
17908 /* Return the difference in milliseconds between two
17909    time marks.  We assume the difference will fit in a long!
17910 */
17911 long
17912 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17913 {
17914     return 1000L*(tm2->sec - tm1->sec) +
17915            (long) (tm2->ms - tm1->ms);
17916 }
17917
17918
17919 /*
17920  * Code to manage the game clocks.
17921  *
17922  * In tournament play, black starts the clock and then white makes a move.
17923  * We give the human user a slight advantage if he is playing white---the
17924  * clocks don't run until he makes his first move, so it takes zero time.
17925  * Also, we don't account for network lag, so we could get out of sync
17926  * with GNU Chess's clock -- but then, referees are always right.
17927  */
17928
17929 static TimeMark tickStartTM;
17930 static long intendedTickLength;
17931
17932 long
17933 NextTickLength (long timeRemaining)
17934 {
17935     long nominalTickLength, nextTickLength;
17936
17937     if (timeRemaining > 0L && timeRemaining <= 10000L)
17938       nominalTickLength = 100L;
17939     else
17940       nominalTickLength = 1000L;
17941     nextTickLength = timeRemaining % nominalTickLength;
17942     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17943
17944     return nextTickLength;
17945 }
17946
17947 /* Adjust clock one minute up or down */
17948 void
17949 AdjustClock (Boolean which, int dir)
17950 {
17951     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17952     if(which) blackTimeRemaining += 60000*dir;
17953     else      whiteTimeRemaining += 60000*dir;
17954     DisplayBothClocks();
17955     adjustedClock = TRUE;
17956 }
17957
17958 /* Stop clocks and reset to a fresh time control */
17959 void
17960 ResetClocks ()
17961 {
17962     (void) StopClockTimer();
17963     if (appData.icsActive) {
17964         whiteTimeRemaining = blackTimeRemaining = 0;
17965     } else if (searchTime) {
17966         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17967         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17968     } else { /* [HGM] correct new time quote for time odds */
17969         whiteTC = blackTC = fullTimeControlString;
17970         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17971         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17972     }
17973     if (whiteFlag || blackFlag) {
17974         DisplayTitle("");
17975         whiteFlag = blackFlag = FALSE;
17976     }
17977     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17978     DisplayBothClocks();
17979     adjustedClock = FALSE;
17980 }
17981
17982 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17983
17984 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
17985
17986 /* Decrement running clock by amount of time that has passed */
17987 void
17988 DecrementClocks ()
17989 {
17990     long tRemaining;
17991     long lastTickLength, fudge;
17992     TimeMark now;
17993
17994     if (!appData.clockMode) return;
17995     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17996
17997     GetTimeMark(&now);
17998
17999     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18000
18001     /* Fudge if we woke up a little too soon */
18002     fudge = intendedTickLength - lastTickLength;
18003     if (fudge < 0 || fudge > FUDGE) fudge = 0;
18004
18005     if (WhiteOnMove(forwardMostMove)) {
18006         if(whiteNPS >= 0) lastTickLength = 0;
18007          tRemaining = whiteTimeRemaining -= lastTickLength;
18008         if( tRemaining < 0 && !appData.icsActive) {
18009             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
18010             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
18011                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
18012                 lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
18013             }
18014         }
18015         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
18016         DisplayWhiteClock(whiteTimeRemaining - fudge,
18017                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18018         timeSuffix = 0;
18019     } else {
18020         if(blackNPS >= 0) lastTickLength = 0;
18021          tRemaining = blackTimeRemaining -= lastTickLength;
18022         if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
18023             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
18024             if(suddenDeath) {
18025                 blackStartMove = forwardMostMove;
18026                 lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
18027             }
18028         }
18029         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
18030         DisplayBlackClock(blackTimeRemaining - fudge,
18031                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18032         timeSuffix = 0;
18033     }
18034     if (CheckFlags()) return;
18035
18036     if(twoBoards) { // count down secondary board's clocks as well
18037         activePartnerTime -= lastTickLength;
18038         partnerUp = 1;
18039         if(activePartner == 'W')
18040             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
18041         else
18042             DisplayBlackClock(activePartnerTime, TRUE);
18043         partnerUp = 0;
18044     }
18045
18046     tickStartTM = now;
18047     intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
18048     StartClockTimer(intendedTickLength);
18049
18050     /* if the time remaining has fallen below the alarm threshold, sound the
18051      * alarm. if the alarm has sounded and (due to a takeback or time control
18052      * with increment) the time remaining has increased to a level above the
18053      * threshold, reset the alarm so it can sound again.
18054      */
18055
18056     if (appData.icsActive && appData.icsAlarm) {
18057
18058         /* make sure we are dealing with the user's clock */
18059         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
18060                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
18061            )) return;
18062
18063         if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
18064             alarmSounded = FALSE;
18065         } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
18066             PlayAlarmSound();
18067             alarmSounded = TRUE;
18068         }
18069     }
18070 }
18071
18072
18073 /* A player has just moved, so stop the previously running
18074    clock and (if in clock mode) start the other one.
18075    We redisplay both clocks in case we're in ICS mode, because
18076    ICS gives us an update to both clocks after every move.
18077    Note that this routine is called *after* forwardMostMove
18078    is updated, so the last fractional tick must be subtracted
18079    from the color that is *not* on move now.
18080 */
18081 void
18082 SwitchClocks (int newMoveNr)
18083 {
18084     long lastTickLength;
18085     TimeMark now;
18086     int flagged = FALSE;
18087
18088     GetTimeMark(&now);
18089
18090     if (StopClockTimer() && appData.clockMode) {
18091         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18092         if (!WhiteOnMove(forwardMostMove)) {
18093             if(blackNPS >= 0) lastTickLength = 0;
18094             blackTimeRemaining -= lastTickLength;
18095            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18096 //         if(pvInfoList[forwardMostMove].time == -1)
18097                  pvInfoList[forwardMostMove].time =               // use GUI time
18098                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18099         } else {
18100            if(whiteNPS >= 0) lastTickLength = 0;
18101            whiteTimeRemaining -= lastTickLength;
18102            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18103 //         if(pvInfoList[forwardMostMove].time == -1)
18104                  pvInfoList[forwardMostMove].time =
18105                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18106         }
18107         flagged = CheckFlags();
18108     }
18109     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18110     CheckTimeControl();
18111
18112     if (flagged || !appData.clockMode) return;
18113
18114     switch (gameMode) {
18115       case MachinePlaysBlack:
18116       case MachinePlaysWhite:
18117       case BeginningOfGame:
18118         if (pausing) return;
18119         break;
18120
18121       case EditGame:
18122       case PlayFromGameFile:
18123       case IcsExamining:
18124         return;
18125
18126       default:
18127         break;
18128     }
18129
18130     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18131         if(WhiteOnMove(forwardMostMove))
18132              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18133         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18134     }
18135
18136     tickStartTM = now;
18137     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18138       whiteTimeRemaining : blackTimeRemaining);
18139     StartClockTimer(intendedTickLength);
18140 }
18141
18142
18143 /* Stop both clocks */
18144 void
18145 StopClocks ()
18146 {
18147     long lastTickLength;
18148     TimeMark now;
18149
18150     if (!StopClockTimer()) return;
18151     if (!appData.clockMode) return;
18152
18153     GetTimeMark(&now);
18154
18155     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18156     if (WhiteOnMove(forwardMostMove)) {
18157         if(whiteNPS >= 0) lastTickLength = 0;
18158         whiteTimeRemaining -= lastTickLength;
18159         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18160     } else {
18161         if(blackNPS >= 0) lastTickLength = 0;
18162         blackTimeRemaining -= lastTickLength;
18163         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18164     }
18165     CheckFlags();
18166 }
18167
18168 /* Start clock of player on move.  Time may have been reset, so
18169    if clock is already running, stop and restart it. */
18170 void
18171 StartClocks ()
18172 {
18173     (void) StopClockTimer(); /* in case it was running already */
18174     DisplayBothClocks();
18175     if (CheckFlags()) return;
18176
18177     if (!appData.clockMode) return;
18178     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18179
18180     GetTimeMark(&tickStartTM);
18181     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18182       whiteTimeRemaining : blackTimeRemaining);
18183
18184    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18185     whiteNPS = blackNPS = -1;
18186     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18187        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18188         whiteNPS = first.nps;
18189     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18190        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18191         blackNPS = first.nps;
18192     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18193         whiteNPS = second.nps;
18194     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18195         blackNPS = second.nps;
18196     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18197
18198     StartClockTimer(intendedTickLength);
18199 }
18200
18201 char *
18202 TimeString (long ms)
18203 {
18204     long second, minute, hour, day;
18205     char *sign = "";
18206     static char buf[40], moveTime[8];
18207
18208     if (ms > 0 && ms <= 9900) {
18209       /* convert milliseconds to tenths, rounding up */
18210       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18211
18212       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18213       return buf;
18214     }
18215
18216     /* convert milliseconds to seconds, rounding up */
18217     /* use floating point to avoid strangeness of integer division
18218        with negative dividends on many machines */
18219     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18220
18221     if (second < 0) {
18222         sign = "-";
18223         second = -second;
18224     }
18225
18226     day = second / (60 * 60 * 24);
18227     second = second % (60 * 60 * 24);
18228     hour = second / (60 * 60);
18229     second = second % (60 * 60);
18230     minute = second / 60;
18231     second = second % 60;
18232
18233     if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18234     else *moveTime = NULLCHAR;
18235
18236     if (day > 0)
18237       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18238               sign, day, hour, minute, second, moveTime);
18239     else if (hour > 0)
18240       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18241     else
18242       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18243
18244     return buf;
18245 }
18246
18247
18248 /*
18249  * This is necessary because some C libraries aren't ANSI C compliant yet.
18250  */
18251 char *
18252 StrStr (char *string, char *match)
18253 {
18254     int i, length;
18255
18256     length = strlen(match);
18257
18258     for (i = strlen(string) - length; i >= 0; i--, string++)
18259       if (!strncmp(match, string, length))
18260         return string;
18261
18262     return NULL;
18263 }
18264
18265 char *
18266 StrCaseStr (char *string, char *match)
18267 {
18268     int i, j, length;
18269
18270     length = strlen(match);
18271
18272     for (i = strlen(string) - length; i >= 0; i--, string++) {
18273         for (j = 0; j < length; j++) {
18274             if (ToLower(match[j]) != ToLower(string[j]))
18275               break;
18276         }
18277         if (j == length) return string;
18278     }
18279
18280     return NULL;
18281 }
18282
18283 #ifndef _amigados
18284 int
18285 StrCaseCmp (char *s1, char *s2)
18286 {
18287     char c1, c2;
18288
18289     for (;;) {
18290         c1 = ToLower(*s1++);
18291         c2 = ToLower(*s2++);
18292         if (c1 > c2) return 1;
18293         if (c1 < c2) return -1;
18294         if (c1 == NULLCHAR) return 0;
18295     }
18296 }
18297
18298
18299 int
18300 ToLower (int c)
18301 {
18302     return isupper(c) ? tolower(c) : c;
18303 }
18304
18305
18306 int
18307 ToUpper (int c)
18308 {
18309     return islower(c) ? toupper(c) : c;
18310 }
18311 #endif /* !_amigados    */
18312
18313 char *
18314 StrSave (char *s)
18315 {
18316   char *ret;
18317
18318   if ((ret = (char *) malloc(strlen(s) + 1)))
18319     {
18320       safeStrCpy(ret, s, strlen(s)+1);
18321     }
18322   return ret;
18323 }
18324
18325 char *
18326 StrSavePtr (char *s, char **savePtr)
18327 {
18328     if (*savePtr) {
18329         free(*savePtr);
18330     }
18331     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18332       safeStrCpy(*savePtr, s, strlen(s)+1);
18333     }
18334     return(*savePtr);
18335 }
18336
18337 char *
18338 PGNDate ()
18339 {
18340     time_t clock;
18341     struct tm *tm;
18342     char buf[MSG_SIZ];
18343
18344     clock = time((time_t *)NULL);
18345     tm = localtime(&clock);
18346     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18347             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18348     return StrSave(buf);
18349 }
18350
18351
18352 char *
18353 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18354 {
18355     int i, j, fromX, fromY, toX, toY;
18356     int whiteToPlay, haveRights = nrCastlingRights;
18357     char buf[MSG_SIZ];
18358     char *p, *q;
18359     int emptycount;
18360     ChessSquare piece;
18361
18362     whiteToPlay = (gameMode == EditPosition) ?
18363       !blackPlaysFirst : (move % 2 == 0);
18364     p = buf;
18365
18366     /* Piece placement data */
18367     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18368         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18369         emptycount = 0;
18370         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18371             if (boards[move][i][j] == EmptySquare) {
18372                 emptycount++;
18373             } else { ChessSquare piece = boards[move][i][j];
18374                 if (emptycount > 0) {
18375                     if(emptycount<10) /* [HGM] can be >= 10 */
18376                         *p++ = '0' + emptycount;
18377                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18378                     emptycount = 0;
18379                 }
18380                 if(PieceToChar(piece) == '+') {
18381                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18382                     *p++ = '+';
18383                     piece = (ChessSquare)(CHUDEMOTED(piece));
18384                 }
18385                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18386                 if(*p = PieceSuffix(piece)) p++;
18387                 if(p[-1] == '~') {
18388                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18389                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18390                     *p++ = '~';
18391                 }
18392             }
18393         }
18394         if (emptycount > 0) {
18395             if(emptycount<10) /* [HGM] can be >= 10 */
18396                 *p++ = '0' + emptycount;
18397             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18398             emptycount = 0;
18399         }
18400         *p++ = '/';
18401     }
18402     *(p - 1) = ' ';
18403
18404     /* [HGM] print Crazyhouse or Shogi holdings */
18405     if( gameInfo.holdingsWidth ) {
18406         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18407         q = p;
18408         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18409             piece = boards[move][i][BOARD_WIDTH-1];
18410             if( piece != EmptySquare )
18411               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18412                   *p++ = PieceToChar(piece);
18413         }
18414         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18415             piece = boards[move][handSize-i-1][0];
18416             if( piece != EmptySquare )
18417               for(j=0; j<(int) boards[move][handSize-i-1][1]; j++)
18418                   *p++ = PieceToChar(piece);
18419         }
18420
18421         if( q == p ) *p++ = '-';
18422         *p++ = ']';
18423         *p++ = ' ';
18424     }
18425
18426     /* Active color */
18427     *p++ = whiteToPlay ? 'w' : 'b';
18428     *p++ = ' ';
18429
18430   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18431     haveRights = 0; q = p;
18432     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18433       piece = boards[move][0][i];
18434       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18435         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18436       }
18437     }
18438     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18439       piece = boards[move][BOARD_HEIGHT-1][i];
18440       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18441         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18442       }
18443     }
18444     if(p == q) *p++ = '-';
18445     *p++ = ' ';
18446   }
18447
18448   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18449     while(*p++ = *q++)
18450                       ;
18451     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18452   } else {
18453   if(haveRights) {
18454      int handW=0, handB=0;
18455      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18456         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18457         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18458      }
18459      q = p;
18460      if(appData.fischerCastling) {
18461         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18462            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18463                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18464         } else {
18465        /* [HGM] write directly from rights */
18466            if(boards[move][CASTLING][2] != NoRights &&
18467               boards[move][CASTLING][0] != NoRights   )
18468                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18469            if(boards[move][CASTLING][2] != NoRights &&
18470               boards[move][CASTLING][1] != NoRights   )
18471                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18472         }
18473         if(handB) {
18474            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18475                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18476         } else {
18477            if(boards[move][CASTLING][5] != NoRights &&
18478               boards[move][CASTLING][3] != NoRights   )
18479                 *p++ = boards[move][CASTLING][3] + AAA;
18480            if(boards[move][CASTLING][5] != NoRights &&
18481               boards[move][CASTLING][4] != NoRights   )
18482                 *p++ = boards[move][CASTLING][4] + AAA;
18483         }
18484      } else {
18485
18486         /* [HGM] write true castling rights */
18487         if( nrCastlingRights == 6 ) {
18488             int q, k=0;
18489             if(boards[move][CASTLING][0] != NoRights &&
18490                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18491             q = (boards[move][CASTLING][1] != NoRights &&
18492                  boards[move][CASTLING][2] != NoRights  );
18493             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18494                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18495                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18496                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18497             }
18498             if(q) *p++ = 'Q';
18499             k = 0;
18500             if(boards[move][CASTLING][3] != NoRights &&
18501                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18502             q = (boards[move][CASTLING][4] != NoRights &&
18503                  boards[move][CASTLING][5] != NoRights  );
18504             if(handB) {
18505                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18506                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18507                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18508             }
18509             if(q) *p++ = 'q';
18510         }
18511      }
18512      if (q == p) *p++ = '-'; /* No castling rights */
18513      *p++ = ' ';
18514   }
18515
18516   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18517      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18518      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18519     /* En passant target square */
18520     if (move > backwardMostMove) {
18521         fromX = moveList[move - 1][0] - AAA;
18522         fromY = moveList[move - 1][1] - ONE;
18523         toX = moveList[move - 1][2] - AAA;
18524         toY = moveList[move - 1][3] - ONE;
18525         if ((whiteToPlay ? toY < fromY - 1 : toY > fromY + 1) &&
18526             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) ) {
18527             /* 2-square pawn move just happened */
18528             *p++ = (3*toX + 5*fromX + 4)/8 + AAA;
18529             *p++ = (3*toY + 5*fromY + 4)/8 + ONE;
18530             if(gameInfo.variant == VariantBerolina) {
18531                 *p++ = toX + AAA;
18532                 *p++ = toY + ONE;
18533             }
18534         } else {
18535             *p++ = '-';
18536         }
18537     } else if(move == backwardMostMove) {
18538         // [HGM] perhaps we should always do it like this, and forget the above?
18539         if((signed char)boards[move][EP_STATUS] >= 0) {
18540             *p++ = boards[move][EP_STATUS] + AAA;
18541             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18542         } else {
18543             *p++ = '-';
18544         }
18545     } else {
18546         *p++ = '-';
18547     }
18548     *p++ = ' ';
18549   }
18550   }
18551
18552     i = boards[move][CHECK_COUNT];
18553     if(i) {
18554         sprintf(p, "%d+%d ", i&255, i>>8);
18555         while(*p) p++;
18556     }
18557
18558     if(moveCounts)
18559     {   int i = 0, j=move;
18560
18561         /* [HGM] find reversible plies */
18562         if (appData.debugMode) { int k;
18563             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18564             for(k=backwardMostMove; k<=forwardMostMove; k++)
18565                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18566
18567         }
18568
18569         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18570         if( j == backwardMostMove ) i += initialRulePlies;
18571         sprintf(p, "%d ", i);
18572         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18573
18574         /* Fullmove number */
18575         sprintf(p, "%d", (move / 2) + 1);
18576     } else *--p = NULLCHAR;
18577
18578     return StrSave(buf);
18579 }
18580
18581 Boolean
18582 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18583 {
18584     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18585     char *p, c;
18586     int emptycount, virgin[BOARD_FILES];
18587     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18588
18589     p = fen;
18590
18591     for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18592
18593     /* Piece placement data */
18594     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18595         j = 0;
18596         for (;;) {
18597             if (*p == '/' || *p == ' ' || *p == '[' ) {
18598                 if(j > w) w = j;
18599                 emptycount = gameInfo.boardWidth - j;
18600                 while (emptycount--)
18601                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18602                 if (*p == '/') p++;
18603                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18604                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18605                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18606                     }
18607                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18608                 }
18609                 break;
18610 #if(BOARD_FILES >= 10)*0
18611             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18612                 p++; emptycount=10;
18613                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18614                 while (emptycount--)
18615                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18616 #endif
18617             } else if (*p == '*') {
18618                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18619             } else if (isdigit(*p)) {
18620                 emptycount = *p++ - '0';
18621                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18622                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18623                 while (emptycount--)
18624                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18625             } else if (*p == '<') {
18626                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18627                 else if (i != 0 || !shuffle) return FALSE;
18628                 p++;
18629             } else if (shuffle && *p == '>') {
18630                 p++; // for now ignore closing shuffle range, and assume rank-end
18631             } else if (*p == '?') {
18632                 if (j >= gameInfo.boardWidth) return FALSE;
18633                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18634                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18635             } else if (*p == '+' || isalpha(*p)) {
18636                 char *q, *s = SUFFIXES;
18637                 if (j >= gameInfo.boardWidth) return FALSE;
18638                 if(*p=='+') {
18639                     char c = *++p;
18640                     if(q = strchr(s, p[1])) p++;
18641                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18642                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18643                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18644                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18645                 } else {
18646                     char c = *p++;
18647                     if(q = strchr(s, *p)) p++;
18648                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18649                 }
18650
18651                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18652                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18653                     piece = (ChessSquare) (PROMOTED(piece));
18654                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18655                     p++;
18656                 }
18657                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18658                 if(piece == king) wKingRank = i;
18659                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18660             } else {
18661                 return FALSE;
18662             }
18663         }
18664     }
18665     while (*p == '/' || *p == ' ') p++;
18666
18667     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18668
18669     /* [HGM] by default clear Crazyhouse holdings, if present */
18670     if(gameInfo.holdingsWidth) {
18671        for(i=0; i<handSize; i++) {
18672            board[i][0]             = EmptySquare; /* black holdings */
18673            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18674            board[i][1]             = (ChessSquare) 0; /* black counts */
18675            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18676        }
18677     }
18678
18679     /* [HGM] look for Crazyhouse holdings here */
18680     while(*p==' ') p++;
18681     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18682         int swap=0, wcnt=0, bcnt=0;
18683         if(*p == '[') p++;
18684         if(*p == '<') swap++, p++;
18685         if(*p == '-' ) p++; /* empty holdings */ else {
18686             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18687             /* if we would allow FEN reading to set board size, we would   */
18688             /* have to add holdings and shift the board read so far here   */
18689             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18690                 p++;
18691                 if((int) piece >= (int) BlackPawn ) {
18692                     i = (int)piece - (int)BlackPawn;
18693                     i = PieceToNumber((ChessSquare)i);
18694                     if( i >= gameInfo.holdingsSize ) return FALSE;
18695                     board[handSize-1-i][0] = piece; /* black holdings */
18696                     board[handSize-1-i][1]++;       /* black counts   */
18697                     bcnt++;
18698                 } else {
18699                     i = (int)piece - (int)WhitePawn;
18700                     i = PieceToNumber((ChessSquare)i);
18701                     if( i >= gameInfo.holdingsSize ) return FALSE;
18702                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18703                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18704                     wcnt++;
18705                 }
18706             }
18707             if(subst) { // substitute back-rank question marks by holdings pieces
18708                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18709                     int k, m, n = bcnt + 1;
18710                     if(board[0][j] == ClearBoard) {
18711                         if(!wcnt) return FALSE;
18712                         n = rand() % wcnt;
18713                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18714                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18715                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18716                             break;
18717                         }
18718                     }
18719                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18720                         if(!bcnt) return FALSE;
18721                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18722                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[handSize-1-k][1]) < 0) {
18723                             board[BOARD_HEIGHT-1][j] = board[handSize-1-k][0]; bcnt--;
18724                             if(--board[handSize-1-k][1] == 0) board[handSize-1-k][0] = EmptySquare;
18725                             break;
18726                         }
18727                     }
18728                 }
18729                 subst = 0;
18730             }
18731         }
18732         if(*p == ']') p++;
18733     }
18734
18735     if(subst) return FALSE; // substitution requested, but no holdings
18736
18737     while(*p == ' ') p++;
18738
18739     /* Active color */
18740     c = *p++;
18741     if(appData.colorNickNames) {
18742       if( c == appData.colorNickNames[0] ) c = 'w'; else
18743       if( c == appData.colorNickNames[1] ) c = 'b';
18744     }
18745     switch (c) {
18746       case 'w':
18747         *blackPlaysFirst = FALSE;
18748         break;
18749       case 'b':
18750         *blackPlaysFirst = TRUE;
18751         break;
18752       default:
18753         return FALSE;
18754     }
18755
18756     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18757     /* return the extra info in global variiables             */
18758
18759     while(*p==' ') p++;
18760
18761     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18762         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18763         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18764     }
18765
18766     /* set defaults in case FEN is incomplete */
18767     board[EP_STATUS] = EP_UNKNOWN;
18768     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18769     for(i=0; i<nrCastlingRights; i++ ) {
18770         board[CASTLING][i] =
18771             appData.fischerCastling ? NoRights : initialRights[i];
18772     }   /* assume possible unless obviously impossible */
18773     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18774     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18775     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18776                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18777     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18778     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18779     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18780                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18781     FENrulePlies = 0;
18782
18783     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18784       char *q = p;
18785       int w=0, b=0;
18786       while(isalpha(*p)) {
18787         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18788         if(islower(*p)) b |= 1 << (*p++ - 'a');
18789       }
18790       if(*p == '-') p++;
18791       if(p != q) {
18792         board[TOUCHED_W] = ~w;
18793         board[TOUCHED_B] = ~b;
18794         while(*p == ' ') p++;
18795       }
18796     } else
18797
18798     if(nrCastlingRights) {
18799       int fischer = 0;
18800       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18801       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18802           /* castling indicator present, so default becomes no castlings */
18803           for(i=0; i<nrCastlingRights; i++ ) {
18804                  board[CASTLING][i] = NoRights;
18805           }
18806       }
18807       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18808              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18809              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18810              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18811         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18812
18813         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18814             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18815             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18816         }
18817         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18818             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18819         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18820                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18821         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18822                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18823         switch(c) {
18824           case'K':
18825               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18826               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18827               board[CASTLING][2] = whiteKingFile;
18828               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18829               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18830               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18831               break;
18832           case'Q':
18833               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18834               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18835               board[CASTLING][2] = whiteKingFile;
18836               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18837               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18838               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18839               break;
18840           case'k':
18841               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18842               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18843               board[CASTLING][5] = blackKingFile;
18844               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18845               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18846               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18847               break;
18848           case'q':
18849               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18850               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18851               board[CASTLING][5] = blackKingFile;
18852               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18853               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18854               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18855           case '-':
18856               break;
18857           default: /* FRC castlings */
18858               if(c >= 'a') { /* black rights */
18859                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18860                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18861                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18862                   if(i == BOARD_RGHT) break;
18863                   board[CASTLING][5] = i;
18864                   c -= AAA;
18865                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18866                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18867                   if(c > i)
18868                       board[CASTLING][3] = c;
18869                   else
18870                       board[CASTLING][4] = c;
18871               } else { /* white rights */
18872                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18873                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18874                     if(board[0][i] == WhiteKing) break;
18875                   if(i == BOARD_RGHT) break;
18876                   board[CASTLING][2] = i;
18877                   c -= AAA - 'a' + 'A';
18878                   if(board[0][c] >= WhiteKing) break;
18879                   if(c > i)
18880                       board[CASTLING][0] = c;
18881                   else
18882                       board[CASTLING][1] = c;
18883               }
18884         }
18885       }
18886       for(i=0; i<nrCastlingRights; i++)
18887         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18888       if(gameInfo.variant == VariantSChess)
18889         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18890       if(fischer && shuffle) appData.fischerCastling = TRUE;
18891     if (appData.debugMode) {
18892         fprintf(debugFP, "FEN castling rights:");
18893         for(i=0; i<nrCastlingRights; i++)
18894         fprintf(debugFP, " %d", board[CASTLING][i]);
18895         fprintf(debugFP, "\n");
18896     }
18897
18898       while(*p==' ') p++;
18899     }
18900
18901     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18902
18903     /* read e.p. field in games that know e.p. capture */
18904     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18905        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18906        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18907       if(*p=='-') {
18908         p++; board[EP_STATUS] = EP_NONE;
18909       } else {
18910          int d, r, c = *p - AAA;
18911
18912          if(c >= BOARD_LEFT && c < BOARD_RGHT) {
18913              p++;
18914              board[EP_STATUS] = board[EP_FILE] = c; r = 0;
18915              if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18916              d = (r < BOARD_HEIGHT << 1 ? 1 : -1); // assume double-push (P next to e.p. square nearer center)
18917              if(board[r+d][c] == EmptySquare) d *= 2; // but if no Pawn there, triple push
18918              board[LAST_TO] = 256*(r + d) + c;
18919              c = *p++ - AAA;
18920              if(c >= BOARD_LEFT && c < BOARD_RGHT) { // mover explicitly mentioned
18921                  if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18922                  board[LAST_TO] = 256*r + c;
18923                  if(!(board[EP_RANK]-r & 1)) board[EP_RANK] |= 128;
18924              }
18925          }
18926       }
18927     }
18928
18929     while(*p == ' ') p++;
18930
18931     board[CHECK_COUNT] = 0; // [HGM] 3check: check-count field
18932     if(sscanf(p, "%d+%d", &i, &j) == 2) {
18933         board[CHECK_COUNT] = i + 256*j;
18934         while(*p && *p != ' ') p++;
18935     }
18936
18937     c = sscanf(p, "%d%*d +%d+%d", &i, &j, &k);
18938     if(c > 0) {
18939         FENrulePlies = i; /* 50-move ply counter */
18940         /* (The move number is still ignored)    */
18941         if(c == 3 && !board[CHECK_COUNT]) board[CHECK_COUNT] = (3 - j) + 256*(3 - k); // SCIDB-style check count
18942     }
18943
18944     return TRUE;
18945 }
18946
18947 void
18948 EditPositionPasteFEN (char *fen)
18949 {
18950   if (fen != NULL) {
18951     Board initial_position;
18952
18953     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18954       DisplayError(_("Bad FEN position in clipboard"), 0);
18955       return ;
18956     } else {
18957       int savedBlackPlaysFirst = blackPlaysFirst;
18958       EditPositionEvent();
18959       blackPlaysFirst = savedBlackPlaysFirst;
18960       CopyBoard(boards[0], initial_position);
18961       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18962       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18963       DisplayBothClocks();
18964       DrawPosition(FALSE, boards[currentMove]);
18965     }
18966   }
18967 }
18968
18969 static char cseq[12] = "\\   ";
18970
18971 Boolean
18972 set_cont_sequence (char *new_seq)
18973 {
18974     int len;
18975     Boolean ret;
18976
18977     // handle bad attempts to set the sequence
18978         if (!new_seq)
18979                 return 0; // acceptable error - no debug
18980
18981     len = strlen(new_seq);
18982     ret = (len > 0) && (len < sizeof(cseq));
18983     if (ret)
18984       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18985     else if (appData.debugMode)
18986       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18987     return ret;
18988 }
18989
18990 /*
18991     reformat a source message so words don't cross the width boundary.  internal
18992     newlines are not removed.  returns the wrapped size (no null character unless
18993     included in source message).  If dest is NULL, only calculate the size required
18994     for the dest buffer.  lp argument indicats line position upon entry, and it's
18995     passed back upon exit.
18996 */
18997 int
18998 wrap (char *dest, char *src, int count, int width, int *lp)
18999 {
19000     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
19001
19002     cseq_len = strlen(cseq);
19003     old_line = line = *lp;
19004     ansi = len = clen = 0;
19005
19006     for (i=0; i < count; i++)
19007     {
19008         if (src[i] == '\033')
19009             ansi = 1;
19010
19011         // if we hit the width, back up
19012         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
19013         {
19014             // store i & len in case the word is too long
19015             old_i = i, old_len = len;
19016
19017             // find the end of the last word
19018             while (i && src[i] != ' ' && src[i] != '\n')
19019             {
19020                 i--;
19021                 len--;
19022             }
19023
19024             // word too long?  restore i & len before splitting it
19025             if ((old_i-i+clen) >= width)
19026             {
19027                 i = old_i;
19028                 len = old_len;
19029             }
19030
19031             // extra space?
19032             if (i && src[i-1] == ' ')
19033                 len--;
19034
19035             if (src[i] != ' ' && src[i] != '\n')
19036             {
19037                 i--;
19038                 if (len)
19039                     len--;
19040             }
19041
19042             // now append the newline and continuation sequence
19043             if (dest)
19044                 dest[len] = '\n';
19045             len++;
19046             if (dest)
19047                 strncpy(dest+len, cseq, cseq_len);
19048             len += cseq_len;
19049             line = cseq_len;
19050             clen = cseq_len;
19051             continue;
19052         }
19053
19054         if (dest)
19055             dest[len] = src[i];
19056         len++;
19057         if (!ansi)
19058             line++;
19059         if (src[i] == '\n')
19060             line = 0;
19061         if (src[i] == 'm')
19062             ansi = 0;
19063     }
19064     if (dest && appData.debugMode)
19065     {
19066         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
19067             count, width, line, len, *lp);
19068         show_bytes(debugFP, src, count);
19069         fprintf(debugFP, "\ndest: ");
19070         show_bytes(debugFP, dest, len);
19071         fprintf(debugFP, "\n");
19072     }
19073     *lp = dest ? line : old_line;
19074
19075     return len;
19076 }
19077
19078 // [HGM] vari: routines for shelving variations
19079 Boolean modeRestore = FALSE;
19080
19081 void
19082 PushInner (int firstMove, int lastMove)
19083 {
19084         int i, j, nrMoves = lastMove - firstMove;
19085
19086         // push current tail of game on stack
19087         savedResult[storedGames] = gameInfo.result;
19088         savedDetails[storedGames] = gameInfo.resultDetails;
19089         gameInfo.resultDetails = NULL;
19090         savedFirst[storedGames] = firstMove;
19091         savedLast [storedGames] = lastMove;
19092         savedFramePtr[storedGames] = framePtr;
19093         framePtr -= nrMoves; // reserve space for the boards
19094         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
19095             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
19096             for(j=0; j<MOVE_LEN; j++)
19097                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
19098             for(j=0; j<2*MOVE_LEN; j++)
19099                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
19100             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
19101             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
19102             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
19103             pvInfoList[firstMove+i-1].depth = 0;
19104             commentList[framePtr+i] = commentList[firstMove+i];
19105             commentList[firstMove+i] = NULL;
19106         }
19107
19108         storedGames++;
19109         forwardMostMove = firstMove; // truncate game so we can start variation
19110 }
19111
19112 void
19113 PushTail (int firstMove, int lastMove)
19114 {
19115         if(appData.icsActive) { // only in local mode
19116                 forwardMostMove = currentMove; // mimic old ICS behavior
19117                 return;
19118         }
19119         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19120
19121         PushInner(firstMove, lastMove);
19122         if(storedGames == 1) GreyRevert(FALSE);
19123         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19124 }
19125
19126 void
19127 PopInner (Boolean annotate)
19128 {
19129         int i, j, nrMoves;
19130         char buf[8000], moveBuf[20];
19131
19132         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19133         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19134         nrMoves = savedLast[storedGames] - currentMove;
19135         if(annotate) {
19136                 int cnt = 10;
19137                 if(!WhiteOnMove(currentMove))
19138                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19139                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19140                 for(i=currentMove; i<forwardMostMove; i++) {
19141                         if(WhiteOnMove(i))
19142                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19143                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19144                         strcat(buf, moveBuf);
19145                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19146                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19147                 }
19148                 strcat(buf, ")");
19149         }
19150         for(i=1; i<=nrMoves; i++) { // copy last variation back
19151             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19152             for(j=0; j<MOVE_LEN; j++)
19153                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19154             for(j=0; j<2*MOVE_LEN; j++)
19155                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19156             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19157             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19158             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19159             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19160             commentList[currentMove+i] = commentList[framePtr+i];
19161             commentList[framePtr+i] = NULL;
19162         }
19163         if(annotate) AppendComment(currentMove+1, buf, FALSE);
19164         framePtr = savedFramePtr[storedGames];
19165         gameInfo.result = savedResult[storedGames];
19166         if(gameInfo.resultDetails != NULL) {
19167             free(gameInfo.resultDetails);
19168       }
19169         gameInfo.resultDetails = savedDetails[storedGames];
19170         forwardMostMove = currentMove + nrMoves;
19171 }
19172
19173 Boolean
19174 PopTail (Boolean annotate)
19175 {
19176         if(appData.icsActive) return FALSE; // only in local mode
19177         if(!storedGames) return FALSE; // sanity
19178         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19179
19180         PopInner(annotate);
19181         if(currentMove < forwardMostMove) ForwardEvent(); else
19182         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19183
19184         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19185         return TRUE;
19186 }
19187
19188 void
19189 CleanupTail ()
19190 {       // remove all shelved variations
19191         int i;
19192         for(i=0; i<storedGames; i++) {
19193             if(savedDetails[i])
19194                 free(savedDetails[i]);
19195             savedDetails[i] = NULL;
19196         }
19197         for(i=framePtr; i<MAX_MOVES; i++) {
19198                 if(commentList[i]) free(commentList[i]);
19199                 commentList[i] = NULL;
19200         }
19201         framePtr = MAX_MOVES-1;
19202         storedGames = 0;
19203 }
19204
19205 void
19206 LoadVariation (int index, char *text)
19207 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19208         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19209         int level = 0, move;
19210
19211         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19212         // first find outermost bracketing variation
19213         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19214             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19215                 if(*p == '{') wait = '}'; else
19216                 if(*p == '[') wait = ']'; else
19217                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19218                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19219             }
19220             if(*p == wait) wait = NULLCHAR; // closing ]} found
19221             p++;
19222         }
19223         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19224         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19225         end[1] = NULLCHAR; // clip off comment beyond variation
19226         ToNrEvent(currentMove-1);
19227         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19228         // kludge: use ParsePV() to append variation to game
19229         move = currentMove;
19230         ParsePV(start, TRUE, TRUE);
19231         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19232         ClearPremoveHighlights();
19233         CommentPopDown();
19234         ToNrEvent(currentMove+1);
19235 }
19236
19237 int transparency[2];
19238
19239 void
19240 LoadTheme ()
19241 {
19242 #define BUF_SIZ (2*MSG_SIZ)
19243     char *p, *q, buf[BUF_SIZ];
19244     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19245         snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19246         ParseArgsFromString(buf);
19247         ActivateTheme(TRUE); // also redo colors
19248         return;
19249     }
19250     p = nickName;
19251     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19252     {
19253         int len;
19254         q = appData.themeNames;
19255         snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19256       if(appData.useBitmaps) {
19257         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19258                 Shorten(appData.liteBackTextureFile));
19259         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19260                 Shorten(appData.darkBackTextureFile),
19261                 appData.liteBackTextureMode,
19262                 appData.darkBackTextureMode );
19263       } else {
19264         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19265       }
19266       if(!appData.useBitmaps || transparency[0]) {
19267         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19268       }
19269       if(!appData.useBitmaps || transparency[1]) {
19270         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19271       }
19272       if(appData.useBorder) {
19273         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19274                 appData.border);
19275       } else {
19276         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19277       }
19278       if(appData.useFont) {
19279         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19280                 appData.renderPiecesWithFont,
19281                 appData.fontToPieceTable,
19282                 Col2Text(9),    // appData.fontBackColorWhite
19283                 Col2Text(10) ); // appData.fontForeColorBlack
19284       } else {
19285         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19286         if(appData.pieceDirectory[0]) {
19287           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19288           if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19289             snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19290         }
19291         if(!appData.pieceDirectory[0] || !appData.trueColors)
19292           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19293                 Col2Text(0),   // whitePieceColor
19294                 Col2Text(1) ); // blackPieceColor
19295       }
19296       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19297                 Col2Text(4),   // highlightSquareColor
19298                 Col2Text(5) ); // premoveHighlightColor
19299         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19300         if(insert != q) insert[-1] = NULLCHAR;
19301         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19302         if(q)   free(q);
19303     }
19304     ActivateTheme(FALSE);
19305 }